From f90a836ef471bb75728527d0c7cd5c331d5fd384 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 14:52:00 -0700 Subject: [PATCH 001/416] 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 0994a99ef9c875791e5d6801446514ade0e8b39f Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 14:56:03 -0700 Subject: [PATCH 002/416] 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 54e9bba192332441bc29592d0df41e7a635a92c4 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 15:06:29 -0700 Subject: [PATCH 003/416] 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 c0e79accd5bdbe306f2af817d8982ad8eaaaf3ff Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 15:15:58 -0700 Subject: [PATCH 004/416] improve `tests/e2e_tests/test_commit_reveal.py` --- tests/e2e_tests/test_commit_reveal.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 61f6bdeeed..5d45182604 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -63,7 +63,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" ) @@ -111,7 +111,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 @@ -215,7 +215,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle ] # Ensure no weights are available as of now - assert subtensor.weights(netuid=alice_subnet_netuid, mechid=mechid) == [] + assert subtensor.subnets.weights(netuid=alice_subnet_netuid, mechid=mechid) == [] logging.console.success("No weights are available before next epoch.") # 5 is safety drand offset @@ -273,7 +273,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 ): """ @@ -313,7 +313,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" ) @@ -360,7 +360,7 @@ 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 @@ -464,7 +464,7 @@ async def test_async_commit_and_reveal_weights_cr4( # Ensure no weights are available as of now assert ( - await async_subtensor.weights(netuid=alice_subnet_netuid, mechid=mechid) + await async_subtensor.subnets.weights(netuid=alice_subnet_netuid, mechid=mechid) == [] ) logging.console.success("No weights are available before next epoch.") From 6fa6c5d0dbf466f5ec4e610b1a00c6e49ac3d492 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 27 Aug 2025 16:08:56 -0700 Subject: [PATCH 005/416] add async version of `tests/e2e_tests/utils/chain_interactions.py` functions --- tests/e2e_tests/utils/chain_interactions.py | 84 +++++++++++++++------ 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index 7fee568d4d..282778fe8d 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -36,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, @@ -53,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", @@ -60,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, @@ -100,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. @@ -193,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) @@ -247,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, @@ -322,16 +365,13 @@ async def async_sudo_set_admin_utils( return await response.is_success, await response.error_message -async def root_set_subtensor_hyperparameter_values( +def root_set_subtensor_hyperparameter_values( substrate: "SubstrateInterface", wallet: "Wallet", call_function: str, call_params: dict, - return_error_message: bool = False, ) -> tuple[bool, Optional[dict]]: - """ - Sets liquid alpha values using AdminUtils. Mimics setting hyperparams - """ + """Sets liquid alpha values using AdminUtils. Mimics setting hyperparams.""" call = substrate.compose_call( call_module="SubtensorModule", call_function=call_function, From 5e657519e42048b4615a32bf956f1c4e888e226e Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 00:33:30 -0700 Subject: [PATCH 006/416] 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 932a0c95d6..9ea40a9c17 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 2fa6ce6e8ad7f27cee9a222d6bdaba5ef8149a5c Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 00:43:50 -0700 Subject: [PATCH 007/416] add async tests in `tests/e2e_tests/test_commit_weights.py` --- tests/e2e_tests/test_commit_weights.py | 400 +++++++++++++++++++++++-- 1 file changed, 372 insertions(+), 28 deletions(-) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 4de52ecf45..b14b7e4e87 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -1,6 +1,7 @@ import numpy as np import pytest import retry +import time from bittensor.core.extrinsics.sudo import ( sudo_set_mechanism_count_extrinsic, @@ -10,6 +11,9 @@ 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, @@ -33,18 +37,20 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa Raises: AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing test_commit_and_reveal_weights") # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( "Failed to set admin freeze window to 0" ) - netuid = subtensor.get_total_subnets() # 2 - set_tempo = 50 if subtensor.is_fast_blocks() else 20 - print("Testing test_commit_and_reveal_weights") + netuid = subtensor.subnets.get_total_subnets() # 2 + set_tempo = 50 if subtensor.chain.is_fast_blocks() else 20 # 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" @@ -62,13 +68,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" ) @@ -84,9 +93,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( @@ -113,7 +123,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( wallet=alice_wallet, netuid=netuid, mechid=mechid, @@ -138,7 +148,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" ) @@ -146,7 +156,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( wallet=alice_wallet, netuid=netuid, mechid=mechid, @@ -169,7 +179,167 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa 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 @@ -187,6 +357,7 @@ 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") # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( @@ -199,7 +370,6 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall # 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" @@ -254,20 +424,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]" @@ -327,3 +483,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 bd8acaffd5c94abae8563ae3b94ab20e1d6332a4 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 01:09:21 -0700 Subject: [PATCH 008/416] 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 1d45da057bb68a0ed2e0a5dfcd78ac47f9ff7111 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 02:07:50 -0700 Subject: [PATCH 009/416] 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 d717017382..a8b3b74ca8 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 3561ff6c456f8c6a3bedfef15aab449ee624866a Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 02:11:19 -0700 Subject: [PATCH 010/416] 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 282778fe8d..4d32193de6 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 beefd07c087213123357717c994d0a119bb4d081 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 02:11:34 -0700 Subject: [PATCH 011/416] 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 4826691ffe5d92e2866b7468be00ddea86224e39 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 02:46:45 -0700 Subject: [PATCH 012/416] 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 50b1350892d335026cbb10fc590e9275239f1fdd Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 14:48:03 -0700 Subject: [PATCH 013/416] 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 31b877610153b5f1fe171d57b2e387acd77f030d Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 14:48:44 -0700 Subject: [PATCH 014/416] add async e2e tests in `tests/e2e_tests/test_hotkeys.py` --- tests/e2e_tests/test_hotkeys.py | 562 +++++++++++++++++++++++++++----- 1 file changed, 489 insertions(+), 73 deletions(-) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 36d1bbfc4d..6f4ab75833 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -15,11 +15,17 @@ sudo_set_admin_freeze_window_extrinsic, ) 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): @@ -28,9 +34,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." ) @@ -40,34 +48,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 @@ -76,7 +84,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) @@ -88,6 +153,8 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w - Clear children list """ + logging.console.info("Testing [green]test_children[/green].") + # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0) @@ -104,19 +171,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] @@ -125,8 +192,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] @@ -134,18 +201,18 @@ 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, @@ -160,19 +227,20 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w 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, ) @@ -181,9 +249,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=[ ( @@ -195,9 +263,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=[ ( @@ -210,9 +278,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=[ ( @@ -228,9 +296,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=[ ( @@ -245,7 +313,7 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w raise_error=True, ) - success, error = subtensor.set_children( + success, message = subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, @@ -256,15 +324,17 @@ 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( + success, children, error = subtensor.wallets.get_children( hotkey=alice_wallet.hotkey.ss58_address, block=set_children_block, netuid=dave_subnet_netuid, @@ -275,17 +345,21 @@ 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( + 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( + success, children, error = subtensor.wallets.get_children( hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -294,27 +368,32 @@ 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( + 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( + 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( + subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, @@ -325,25 +404,32 @@ async def test_children(local_chain, subtensor, alice_wallet, bob_wallet, dave_w # wait for rate limit to expire + 1 block to ensure that the rate limit is expired subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT + 1) - subtensor.set_children( + 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( + 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 + 1) - success, children, error = subtensor.get_children( + success, children, error = subtensor.wallets.get_children( hotkey=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -355,8 +441,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, @@ -364,9 +450,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=[ ( @@ -378,3 +464,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(SubnetNotExists): + 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 9fb638f67c004dfabfc8131cbf38c511127c8d4a Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:01:57 -0700 Subject: [PATCH 015/416] 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 dbfa4bfc73bab535e85c1b4d30be011bb05509e1 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:02:53 -0700 Subject: [PATCH 016/416] `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 ae483110ded29797550db17ae0853f7c7ad7b8fa Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:04:01 -0700 Subject: [PATCH 017/416] apply related changes to test utils --- tests/e2e_tests/utils/e2e_test_utils.py | 39 +++++++++++++------------ 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index 744663d08c..d61084b4e1 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.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 3ebbccd960af14a2c15ac44778724e77ad621538 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:06:08 -0700 Subject: [PATCH 018/416] apply related changes to `chain_interactions.py` --- tests/e2e_tests/utils/chain_interactions.py | 37 ++++++++++----------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index 4d32193de6..ac4afe8e45 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -14,8 +14,7 @@ # 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 ( AsyncSubstrateInterface, AsyncExtrinsicReceipt, @@ -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 ac55e895b05178ca9db9175ebf4444d315cf27e7 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:08:08 -0700 Subject: [PATCH 019/416] apply SubtensorApi changes to updated tess --- tests/e2e_tests/test_commit_weights.py | 58 +++++++++---------- tests/e2e_tests/test_commitment.py | 40 ++++++------- .../test_cross_subtensor_compatibility.py | 11 ++-- tests/e2e_tests/test_delegate.py | 18 +++--- 4 files changed, 64 insertions(+), 63 deletions(-) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index b14b7e4e87..83a9f5099c 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -53,7 +53,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" assert sudo_set_mechanism_count_extrinsic( subtensor, alice_wallet, netuid, TESTED_SUB_SUBNETS @@ -137,7 +137,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa assert success is True, message storage_index = get_mechid_storage_index(netuid, mechid) - weight_commits = subtensor.query_module( + weight_commits = subtensor.queries.query_module( module="SubtensorModule", name="WeightCommits", params=[storage_index, alice_wallet.hotkey.ss58_address], @@ -207,7 +207,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" ) @@ -280,7 +280,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], @@ -312,7 +312,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 @@ -364,17 +364,17 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall "Failed to set admin freeze window to 0" ) - subnet_tempo = 50 if subtensor.is_fast_blocks() else 20 - netuid = subtensor.get_total_subnets() # 2 + subnet_tempo = 50 if subtensor.chain.is_fast_blocks() else 20 + 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( @@ -396,13 +396,13 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall netuid=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" ) @@ -417,9 +417,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) @@ -433,7 +433,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_, @@ -452,7 +452,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( @@ -462,14 +462,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], @@ -503,8 +503,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( @@ -512,12 +512,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" ) @@ -541,15 +541,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" ) @@ -564,9 +564,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) @@ -656,7 +656,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 886fd1e1ff3dc2db8bccac6cffa4af02ae7f5d34 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:27:56 -0700 Subject: [PATCH 020/416] 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 d61084b4e1..296f75fb0a 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 d9733d8fdbbcb7c5920c50d57e837a1c3a398880 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:38:01 -0700 Subject: [PATCH 021/416] async test for `tests/e2e_tests/test_incentive.py` --- tests/e2e_tests/test_incentive.py | 197 ++++++++++++++++++++++++++++-- 1 file changed, 186 insertions(+), 11 deletions(-) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index cf1289b977..7b6febd100 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -4,9 +4,11 @@ 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, ) -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 @@ -22,9 +24,9 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): Raises: AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing [blue]test_incentive[/blue]") alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - # turn off admin freeze window limit for testing assert ( sudo_set_admin_utils( @@ -37,10 +39,10 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): ), "Failed to set admin freeze window to 0" # 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" ) @@ -59,12 +61,12 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): 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" ) @@ -73,7 +75,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 1 ) # 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 @@ -82,7 +84,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): 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 @@ -90,7 +92,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): 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( substrate=subtensor.substrate, wallet=alice_wallet, @@ -145,7 +147,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): 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 @@ -166,7 +168,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): 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 == [ ( @@ -187,3 +189,176 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): 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 1fbbe7c353a676cd3aefb140476fd7eef89921f8 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 28 Aug 2025 20:38:18 -0700 Subject: [PATCH 022/416] 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 83a9f5099c..8ef7fe97c7 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -371,7 +371,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" @@ -396,10 +398,13 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall netuid=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, ( @@ -417,7 +422,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 @@ -452,7 +458,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 7b6febd100..ef4c28cd80 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -8,7 +8,10 @@ async_wait_epoch, sudo_set_admin_utils, ) -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, +) @pytest.mark.asyncio @@ -39,7 +42,9 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): ), "Failed to set admin freeze window to 0" # 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), ( @@ -211,7 +216,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), ( @@ -230,23 +237,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 @@ -302,9 +313,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 0aa56aa6c5226244af3c580f4660d19bc42eb457 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:10:23 -0700 Subject: [PATCH 023/416] 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 ef4c28cd80..90e316375f 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -255,9 +255,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 7b4c885844747433a92abc55ae71abb4d58076b8 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:34:29 -0700 Subject: [PATCH 024/416] 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 1a1e69f4a9eff6b30350b3a1a08d5d11dd558e0c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:37:20 -0700 Subject: [PATCH 025/416] async test for `tests/e2e_tests/test_liquid_alpha.py` --- tests/e2e_tests/test_liquid_alpha.py | 266 +++++++++++++++++++++++---- 1 file changed, 228 insertions(+), 38 deletions(-) diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index f505b093ec..d46e3603d3 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -1,11 +1,15 @@ +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, 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 def liquid_alpha_call_params(netuid: int, alpha_values: str): @@ -17,7 +21,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` @@ -30,11 +34,13 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ + logging.console.info("Testing test_liquid_alpha_enabled") + # turn off admin freeze window limit for testing assert ( sudo_set_admin_utils( - local_chain, - alice_wallet, + substrate=subtensor.substrate, + wallet=alice_wallet, call_function="sudo_set_admin_freeze_window", call_params={"window": 0}, )[0] @@ -43,33 +49,34 @@ def test_liquid_alpha(local_chain, subtensor, alice_wallet): 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), ( "Subnet failed to start." ) # 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 - assert subtensor.add_stake( + assert subtensor.staking.add_stake( wallet=alice_wallet, netuid=netuid, amount=Balance.from_tao(10_000), - ), "Failed to stake" + ), "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." @@ -77,8 +84,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, @@ -88,10 +95,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." @@ -99,16 +110,16 @@ 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, ( - "Failed to set alpha high." + ), "Unable to set alpha_values" + assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_high == 54099, ( + "Failed to set alpha high" ) - assert subtensor.get_subnet_hyperparameters(netuid).alpha_low == 26001, ( - "Failed to set alpha low." + assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_low == 26001, ( + "Failed to set alpha low" ) # Testing alpha high upper and lower bounds @@ -129,8 +140,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, @@ -144,8 +155,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, @@ -159,8 +170,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, @@ -172,8 +183,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, @@ -185,25 +196,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, + ) + 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, ) - logging.console.info("✅ Passed test_liquid_alpha") + + 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 05746a028e9c4994f9e398105d0c29de62230592 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:37:32 -0700 Subject: [PATCH 026/416] 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 90e316375f..ef4c28cd80 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -255,9 +255,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 d46e3603d3..818e9454f0 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -9,7 +9,10 @@ sudo_set_hyperparameter_values, sudo_set_admin_utils, ) -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): @@ -267,7 +270,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" @@ -291,9 +296,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" @@ -392,8 +399,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 11d82c8901814ba52dc04894637f7eddea02d650 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 11:38:57 -0700 Subject: [PATCH 027/416] 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 818e9454f0..f6f33edfe8 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -242,7 +242,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 @@ -402,4 +402,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 6015cf08a20402207747caf73fcbe86fb192f1b3 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 12:07:58 -0700 Subject: [PATCH 028/416] 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 4af96d663399f52e60b40f23055f9733b835f9a3 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 12:56:29 -0700 Subject: [PATCH 029/416] async test for `tests/e2e_tests/test_metagraph.py` --- tests/e2e_tests/test_metagraph.py | 787 ++++++++++++++++++++++++++++-- 1 file changed, 749 insertions(+), 38 deletions(-) diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index b1b6d5ccc9..48bd6d47d5 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, @@ -295,7 +473,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( @@ -381,14 +559,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 == [ @@ -417,32 +861,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): @@ -453,9 +899,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, @@ -465,7 +912,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 ) @@ -559,7 +1006,7 @@ 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, @@ -574,7 +1021,245 @@ 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=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, + wait_for_finalization=True, + ) + + fields = [ + 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=fields ) @@ -676,6 +1361,10 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): commitments=None, ) + logging.console.info( + "✅ Passed [blue]test_metagraph_info_with_indexes_async[/blue]" + ) + def test_blocks(subtensor): """ @@ -684,6 +1373,7 @@ def test_blocks(subtensor): - Get block hash - Wait for block """ + logging.console.info("Testing [blue]test_blocks[/blue]") get_current_block = subtensor.get_current_block() block = subtensor.block @@ -691,10 +1381,31 @@ def test_blocks(subtensor): # Several random tests fell during the block finalization period. Fast blocks of 0.25 seconds (very fast) assert get_current_block in [block, block + 1] - 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]") + +@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.get_current_block() in [block + 10, block + 11] + logging.console.info("✅ Passed [blue]test_blocks_async[/blue]") From 201a42423b0697cc96a9f0d2a8cea205f9e4b88e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 13:02:10 -0700 Subject: [PATCH 030/416] 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 747725cda017ad27a5513d6b9bb53ac7855849d8 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 13:14:21 -0700 Subject: [PATCH 031/416] 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 3639879ecd3d6371d89eb61edce500d050d2b619 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 13:32:00 -0700 Subject: [PATCH 032/416] 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 0bd10fc617ffc32331f0c9ee0985565014884bcf Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 13:53:22 -0700 Subject: [PATCH 033/416] 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 bd642bab1234181e300ba887df25d92d35aa8988 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 14:37:01 -0700 Subject: [PATCH 034/416] async test for `tests/e2e_tests/test_set_weights.py` --- tests/e2e_tests/test_set_weights.py | 273 ++++++++++++++++++++++++++-- 1 file changed, 256 insertions(+), 17 deletions(-) diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 47b50f8d64..06bb140a2b 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -6,18 +6,24 @@ sudo_set_mechanism_count_extrinsic, sudo_set_admin_freeze_window_extrinsic, ) +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. @@ -31,6 +37,7 @@ 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]") # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic( subtensor=subtensor, @@ -47,6 +54,7 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) ) print("Testing test_set_weights_uses_next_nonce") + subnet_tempo = 50 # Lower the network registration rate limit and cost sudo_set_admin_utils( @@ -63,19 +71,20 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) call_params={"interval": "1"}, # 1 block # reduce lock every block ) - # Try to register the subnets - for _ in netuids: - assert subtensor.register_subnet( + 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( substrate=subtensor.substrate, wallet=alice_wallet, @@ -92,16 +101,20 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) mech_count=2, ) - # 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( + 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 @@ -114,11 +127,11 @@ async def test_set_weights_uses_next_nonce(local_chain, subtensor, alice_wallet) 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" ) @@ -134,10 +147,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) @@ -155,7 +171,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_, mechid_): - success, message = subtensor.set_weights( + success, message = subtensor.extrinsics.set_weights( wallet=alice_wallet, netuid=netuid_, mechid=mechid_, @@ -195,3 +211,226 @@ def set_weights(netuid_, mechid_): assert alice_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 + ) + + 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_async[/blue]" + ) From 54df9355b2cfc8e40fccb0a2cdc8d2ff70ee26b5 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 14:52:45 -0700 Subject: [PATCH 035/416] 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 9113a3f44727d54110b272336c87b3225f0e5085 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 14:53:01 -0700 Subject: [PATCH 036/416] 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 8ef7fe97c7..fc00d98bea 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -491,7 +491,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 @@ -678,4 +678,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 97c4594b8b0fa995402bd9cb1ed96f0c3b1c17af Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 16:16:33 -0700 Subject: [PATCH 037/416] 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 713c1ac2de..9c2733822c 100644 --- a/bittensor/core/subtensor_api/staking.py +++ b/bittensor/core/subtensor_api/staking.py @@ -25,6 +25,8 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_unstake_fee = subtensor.get_unstake_fee self.move_stake = subtensor.move_stake self.set_auto_stake = subtensor.set_auto_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 6c2c88fa96a15b6ab83a18f63ce2bb237a3288c3 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 16:50:47 -0700 Subject: [PATCH 038/416] async tests for `tests/e2e_tests/test_staking.py` --- tests/e2e_tests/test_staking.py | 1587 ++++++++++++++++++++++++++----- 1 file changed, 1357 insertions(+), 230 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 928276ca7f..fa6fecfb69 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,76 +530,227 @@ 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 """ + logging.console.info("Testing [blue]test_batch_operations_async[/blue]") - # turn off admin freeze window limit for testing - assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - )[0] - is True - ), "Failed to set admin freeze window to 0" + netuids = [ + 2, + 3, + ] - 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) + for _ in netuids: + await async_subtensor.subnets.register_subnet( + wallet=alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) - # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) + # 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) - # 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 netuid in netuids: + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + 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, + ) - subtensor.extrinsics.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" - initial_stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + balances = await async_subtensor.wallets.get_balances( + alice_wallet.coldkey.ss58_address, + bob_wallet.coldkey.ss58_address, ) - 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) + 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 + ), + } - # 1. Strict params - should fail + 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]") + + + # turn off admin freeze window limit for testing + assert ( + sudo_set_admin_utils( + substrate=subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_admin_freeze_window", + call_params={"window": 0}, + )[0] + is True + ), "Failed to set admin freeze window to 0" + + 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, @@ -513,7 +863,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, @@ -524,7 +874,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, @@ -539,69 +889,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, @@ -610,42 +966,241 @@ 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, - 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, - rate_tolerance=0.3, # 30% + rate_tolerance=0.005, # 0.5% allow_partial_stake=True, ) assert success is True - # Verify stake was moved - origin_stake = subtensor.get_stake( # TODO this seems unused - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=origin_netuid, + 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, ) - dest_stake = subtensor.get_stake( - alice_wallet.coldkey.ss58_address, - alice_wallet.hotkey.ss58_address, - netuid=dest_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, + 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 = 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" @@ -655,23 +1210,311 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): ) -def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): +@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, + ) + 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, + ) + 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_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.") + + +@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, @@ -680,7 +1523,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( @@ -695,29 +1540,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, @@ -728,14 +1575,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), @@ -758,7 +1607,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 [] ) @@ -766,14 +1615,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, @@ -783,19 +1632,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, @@ -806,7 +1656,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, @@ -815,7 +1665,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): @@ -824,23 +1674,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, @@ -849,7 +1701,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( @@ -866,23 +1720,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, @@ -893,7 +1749,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 = ( [ @@ -912,13 +1770,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( @@ -938,6 +1798,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. @@ -953,32 +1950,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, @@ -986,30 +1977,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, @@ -1017,11 +2001,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, @@ -1030,7 +2017,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, @@ -1041,7 +2028,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: @@ -1049,7 +2038,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, @@ -1060,7 +2049,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, @@ -1070,11 +2059,149 @@ 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" + ) + def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet): """Tests auto staking logic.""" From 51488dc56d1bd5c133566023ff74fe3fdeeee7a2 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 16:53:01 -0700 Subject: [PATCH 039/416] 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 8c1670b0104d7567fd72084ac896cee6c4ed9328 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 17:04:13 -0700 Subject: [PATCH 040/416] async tests for `tests/e2e_tests/test_subtensor_functions.py` --- tests/e2e_tests/test_subtensor_functions.py | 284 ++++++++++++++++---- 1 file changed, 239 insertions(+), 45 deletions(-) diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 286b16e076..95de4d936b 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -2,13 +2,17 @@ import time import pytest +from bittensor.utils.btlogging import logging from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.utils.balance import Balance from tests.e2e_tests.utils.chain_interactions import ( + async_wait_epoch, wait_epoch, ) -from bittensor.core.extrinsics.utils import get_extrinsic_fee -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: @@ -41,47 +45,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" ) - # TODO: in SDKv10 replace this logic with using `ExtrinsicResponse.extrinsic_fee` - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": alice_wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - register_fee = get_extrinsic_fee(call, alice_wallet.hotkey, subtensor) - # 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() # TODO: in SDKv10 replace this logic with using `ExtrinsicResponse.extrinsic_fee` call = subtensor.substrate.compose_call( @@ -100,78 +94,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) - assert ( - alice_balance_post_sn + pre_subnet_creation_cost + register_fee - == initial_alice_balance - ), "Balance is the same even after registering a subnet" + alice_balance_post_sn = subtensor.wallets.get_balance( + alice_wallet.coldkeypub.ss58_address + ) + assert alice_balance_post_sn + pre_subnet_creation_cost + register_fee == 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", @@ -182,7 +179,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, ( @@ -209,12 +208,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] @@ -231,4 +425,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 8732652f23325bdaf17189384a843f026e33d10d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 17:32:46 -0700 Subject: [PATCH 041/416] 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 7a04b7784c309876b9ff7db55e352a5ba4bc03ff Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 17:55:40 -0700 Subject: [PATCH 042/416] 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 f79ac72558..df80bfbd13 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4768,6 +4768,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 9ea40a9c17..f187bae089 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, ) @@ -71,6 +73,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. @@ -86,6 +89,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]: @@ -104,6 +108,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 5866624777..168ebbf385 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3591,6 +3591,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 dbc13f3aa415def8c98457325f6d88988006c8b2 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 17:55:51 -0700 Subject: [PATCH 043/416] 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 23b55b8a16..4cda472103 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2935,6 +2935,7 @@ async def test_commit_weights_success(subtensor, fake_wallet, mocker): wait_for_finalization=False, period=16, mechid=0, + 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 d7411e02dc..4418f1bec2 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1988,6 +1988,7 @@ def test_commit_weights(subtensor, fake_wallet, mocker): wait_for_finalization=wait_for_finalization, period=16, mechid=0, + raise_error=True, ) assert result == expected_result From e22751602b47b01317f7fb6f221c6ea33329557b Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:06:56 -0700 Subject: [PATCH 044/416] 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 20bc8051cf8e8044389fd9bd9f87d374884e74f0 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:12:48 -0700 Subject: [PATCH 045/416] 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 3149ab2bf76dc846cc8de32d109119f891cfa481 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:26:31 -0700 Subject: [PATCH 046/416] 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 db587eefd405007af0385884a37ebd8e63d6616a Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:31:54 -0700 Subject: [PATCH 047/416] 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 a91dc1d2b0ce495a2ad03f5ce6f3f073adefff2d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:33:42 -0700 Subject: [PATCH 048/416] 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 8433723a0398c7339c59e292bafad71ac5400b5d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:40:57 -0700 Subject: [PATCH 049/416] 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 6c3c89a52de848249d9cab2cba5e40192435532f Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:44:53 -0700 Subject: [PATCH 050/416] 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 8af0245861c0e6b94e42bfb2b0b1d173e49f5520 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:49:23 -0700 Subject: [PATCH 051/416] 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 df0459c6ad1b069ded92a74d74c12048f5c95072 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 18:54:47 -0700 Subject: [PATCH 052/416] 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 52b1c2a14070320a79bdc038da740e168c628ff6 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 19:03:51 -0700 Subject: [PATCH 053/416] 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 0d0843c7bbfcc4a1fe9352d7cc4acffb4e3b124c Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 19:53:03 -0700 Subject: [PATCH 054/416] 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 f81422ef9ee4ec21b399cc3047ac49e192539676 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:05:37 -0700 Subject: [PATCH 055/416] 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 0bb3185087c678825beb78e2436102e5e01e7f17 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:07:34 -0700 Subject: [PATCH 056/416] 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 9f930fde2b62586deb2f44cbf5d64f18421f1aba Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:09:29 -0700 Subject: [PATCH 057/416] 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 421e46f84e8622ef42242637278a25d2f109cb01 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:16:32 -0700 Subject: [PATCH 058/416] 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 e8a62f76f83ed26fc857783147ad392f697e8da9 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:17:51 -0700 Subject: [PATCH 059/416] 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 b80dbacb468aea075e988801090de5daad4651b8 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:22:54 -0700 Subject: [PATCH 060/416] 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 1d6abfaf5ff941c60d1a818ecd781c0265d0fbcc Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:23:27 -0700 Subject: [PATCH 061/416] 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 8ed1910a39633369d62d9e459dee40d5435db297 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:34:43 -0700 Subject: [PATCH 062/416] 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 75b4dfef2fa287f5e9e7d7e3a02033d8342d4778 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:40:43 -0700 Subject: [PATCH 063/416] 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 cf20a473cc616e95cad8a54d139e80f34e3d7b59 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 20:46:05 -0700 Subject: [PATCH 064/416] 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 fb5b5bef1306be12ca970c2cb78fee215c7f11a5 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:03:39 -0700 Subject: [PATCH 065/416] 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 b1576c3d1ba3948c6b03fb768c358b6a7de653ef Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:15:05 -0700 Subject: [PATCH 066/416] 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 1bc9ee10b2b9057139aa7c58ff44ad7874e89aec Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:28:48 -0700 Subject: [PATCH 067/416] 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 62b5a8fe894c8ad34f4a1c505c3c2ab6c11d09ae Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:40:59 -0700 Subject: [PATCH 068/416] 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 c9a35c66dea7fe91241770e5cbaa23458656cc36 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:47:32 -0700 Subject: [PATCH 069/416] 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 19bc600978804c5af02d48a3ad261c3c929b7989 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 29 Aug 2025 21:49:41 -0700 Subject: [PATCH 070/416] 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 7b284b0512759efee8186ad0f7d8b26007e902e7 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 03:07:24 -0700 Subject: [PATCH 071/416] 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 df80bfbd13..0d72c1a493 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5098,6 +5098,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 f187bae089..d115162506 100644 --- a/bittensor/core/extrinsics/commit_weights.py +++ b/bittensor/core/extrinsics/commit_weights.py @@ -131,6 +131,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. @@ -179,6 +180,7 @@ def _do_reveal_weights( period=period, sign_with="hotkey", nonce_key="hotkey", + raise_error=raise_error, ) @@ -194,6 +196,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. @@ -212,6 +215,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]: @@ -233,6 +237,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 168ebbf385..2291113c52 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3922,6 +3922,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 31ade0a7b2e1f011eb0cbcc4a78a2a59d2d45f98 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 03:14:09 -0700 Subject: [PATCH 072/416] 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 4418f1bec2..8db736dea0 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, mechid=0, ) From efb7cbd8a11ba6dd3c84d02285c0384624e92f06 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 03:33:56 -0700 Subject: [PATCH 073/416] `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 0d72c1a493..41bb75b7e3 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 @@ -2674,24 +2673,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 090059209873e0301e67f8a382331c87c944f72c Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 03:46:34 -0700 Subject: [PATCH 074/416] 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 f17742f2109d10e699607ee3c6827d49a38b6111 Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 04:01:39 -0700 Subject: [PATCH 075/416] 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 ba69b6904d068bf6273623b3936e27ec7a5d260a Mon Sep 17 00:00:00 2001 From: Roman Date: Sat, 30 Aug 2025 04:05:48 -0700 Subject: [PATCH 076/416] 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 f412602893108c564a819e0482acbcf5fb0f459c Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 11:18:47 -0700 Subject: [PATCH 077/416] `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 41bb75b7e3..170e9a954e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4107,20 +4107,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 12f6122725682f8b33a37625437d0d62c2df2503 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 11:35:42 -0700 Subject: [PATCH 078/416] update tests --- tests/e2e_tests/test_commit_reveal.py | 8 ++++-- tests/e2e_tests/test_commit_weights.py | 28 +++++++++---------- .../test_cross_subtensor_compatibility.py | 2 +- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 5d45182604..5cc48dab2a 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -17,6 +17,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, @@ -274,7 +276,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) @@ -303,7 +305,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 @@ -325,7 +327,7 @@ async def test_commit_and_reveal_weights_cr4_async( # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( - substrate=local_chain, + substrate=async_subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_commit_reveal_weights_enabled", value=True, diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index fc00d98bea..57764ad1b1 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -24,7 +24,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) @@ -61,11 +61,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), ( @@ -83,8 +83,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"}, ) @@ -100,8 +100,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, @@ -343,7 +343,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. @@ -391,7 +391,7 @@ async def test_commit_weights_uses_next_nonce(local_chain, subtensor, alice_wall # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( - substrate=local_chain, + substrate=subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_commit_reveal_weights_enabled", value=True, @@ -413,8 +413,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 ea0248c05697a8a1e597b464e96bf9ccfd3ef29a Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 11:44:26 -0700 Subject: [PATCH 079/416] 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 170e9a954e..280b3ed785 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -825,15 +825,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 b755c833ea566650589c6621ca7e40ce97a2d768 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 13:40:07 -0700 Subject: [PATCH 080/416] 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 88c4f68616..2683171b95 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", "setuptools~=70.0.0", @@ -80,10 +80,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 6586c44c59485a5ea3e0a7df1f3e8f4101685721 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 13:40:29 -0700 Subject: [PATCH 081/416] bring back gather logic --- bittensor/core/async_subtensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 280b3ed785..4e204e1e5a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1776,7 +1776,7 @@ async def get_all_revealed_commitments( result[hotkey_ss58_address] = commitment_message return result - # TODO: deprecated in SDKv10 + # TODO: remove in SDKv10 async def get_current_weight_commit_info( self, netuid: int, @@ -1817,7 +1817,7 @@ async def get_current_weight_commit_info( commits = result.records[0][1] if result.records else [] return [WeightCommitInfo.from_vec_u8(commit) for commit in commits] - # TODO: deprecated in SDKv10 + # TODO: remove in SDKv10 async def get_current_weight_commit_info_v2( self, netuid: int, From aa3c693ef4c57643af76644df8e5c6acc9fdefa0 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 13:41:45 -0700 Subject: [PATCH 082/416] 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 5828fd04b832df1a227205a5e2a992c86f5ba3c4 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 14:48:29 -0700 Subject: [PATCH 083/416] `._do_commit_reveal_v3` is included in the main code `.commit_reveal_v3_extrinsic`, `.commit_reveal_v3_extrinsic` renamed to `.commit_reveal_extrinsic` --- .../core/extrinsics/asyncex/commit_reveal.py | 85 +++++------------- bittensor/core/extrinsics/commit_reveal.py | 86 +++++-------------- bittensor/core/subtensor.py | 4 +- 3 files changed, 45 insertions(+), 130 deletions(-) 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 44c6f16675..e867fca810 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -16,65 +16,8 @@ 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: deprecate in SDKv10 -def commit_reveal_v3_extrinsic( +# TODO: remove in SDKv10 +def commit_reveal_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, @@ -85,6 +28,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 +46,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 +75,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 2291113c52..b8024ea7a0 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1143,7 +1143,7 @@ def get_all_revealed_commitments( result[hotkey_ss58_address] = commitment_message return result - # TODO: deprecated in SDKv10 + # TODO: remove in SDKv10 def get_current_weight_commit_info( self, netuid: int, block: Optional[int] = None ) -> list[tuple[str, str, int]]: @@ -1177,7 +1177,7 @@ def get_current_weight_commit_info( commits = result.records[0][1] if result.records else [] return [WeightCommitInfo.from_vec_u8(commit) for commit in commits] - # TODO: deprecated in SDKv10 + # TODO: remove in SDKv10 def get_current_weight_commit_info_v2( self, netuid: int, block: Optional[int] = None ) -> list[tuple[str, int, str, int]]: From 81a6d7aaf128dc9f2404e43ace3a82a01e25ab21 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 14:49:24 -0700 Subject: [PATCH 084/416] update tests --- tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py | 8 ++++---- tests/unit_tests/extrinsics/test_commit_reveal.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 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, From 71800fbb736bd1a32b9e541992237402b51c6c09 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 15:00:35 -0700 Subject: [PATCH 085/416] 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 68673ac67ce2de8702c184a2025e360ee9cb0fbc Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 15:00:50 -0700 Subject: [PATCH 086/416] 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 98f53ccd03b9180fdd5611431f5fe5df55deab0b Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 15:28:04 -0700 Subject: [PATCH 087/416] `._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 | 71 ++----- migration.md | 3 +- .../extrinsics/asyncex/test_weights.py | 184 +++--------------- .../extrinsics/test_commit_weights.py | 53 ----- 5 files changed, 50 insertions(+), 327 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 d115162506..98b4764fdd 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,61 +64,13 @@ def _do_commit_weights( raise_error=raise_error, ) - -# TODO: deprecate in SDKv10 -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 48b49e16cc448788203d5615c9798250503119d7 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 16:11:34 -0700 Subject: [PATCH 088/416] 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 adc002ed4f..bfccfc8771 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, @@ -510,3 +510,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 4e59942c5796f52d3a198f6184c9562078963fd5 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 16:13:07 -0700 Subject: [PATCH 089/416] `._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 | 100 ++++--------------- 2 files changed, 36 insertions(+), 151 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 98b4764fdd..73631c3ed6 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,19 @@ 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( +# TODO: deprecate in SDKv10 +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 +87,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 +103,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 +120,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,68 +138,8 @@ def _do_reveal_weights( raise_error=raise_error, ) - -# TODO: deprecate in SDKv10 -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 8220ba63f2cfdf277a25ac36f695a0768c3c2eca Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 16:13:37 -0700 Subject: [PATCH 090/416] 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 a2d2ba2a752f1b332b1cfeb760a178714be97cf7 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:10:26 -0700 Subject: [PATCH 091/416] `._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 | 111 ++++----------- migration.md | 3 +- 3 files changed, 69 insertions(+), 186 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 b860620886..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,66 +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 - - -# TODO: deprecate in SDKv10 def set_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", @@ -122,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 75e2f81b712c5ed4094cf63a460ea196ed98394e Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:10:38 -0700 Subject: [PATCH 092/416] 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 758f84e3ac80d72b14d58ead5135997e2c05ef8a Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:15:49 -0700 Subject: [PATCH 093/416] 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 6b7a7e8f0565a3cc7c5cbb79f65c8c860bb710f0 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:19:07 -0700 Subject: [PATCH 094/416] 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 c653ca01a15f71cf709ca689f22137d54a857af9 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:27:43 -0700 Subject: [PATCH 095/416] `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 ------------------- migration.md | 3 +- .../unit_tests/extrinsics/test_set_weights.py | 2 +- 4 files changed, 87 insertions(+), 96 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 73631c3ed6..0b31752eff 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( @@ -143,3 +152,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/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 bff27e105c1daef774495082e50f9b6108d0c045 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:39:09 -0700 Subject: [PATCH 096/416] `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 | 1 + migration.md | 1 + .../extrinsics/test_commit_weights.py | 2 +- .../unit_tests/extrinsics/test_set_weights.py | 2 +- 6 files changed, 78 insertions(+), 75 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 0b31752eff..4ee6b11986 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: @@ -100,18 +100,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: @@ -164,28 +164,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) @@ -218,6 +220,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 b8024ea7a0..5b7b87d84d 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -74,6 +74,7 @@ get_metadata, serve_axon_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 9d3f9a862dbf6015b9587fc705b18a5aab5628a5 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 17:56:44 -0700 Subject: [PATCH 097/416] 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 4ee6b11986..09d509167f 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -224,7 +224,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 f17880eae3d7edb04e0577b1e93851ce78a88308 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 18:33:51 -0700 Subject: [PATCH 098/416] 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 0ada822094b5bc05467b9da392d7769fe8479c7c Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 19:19:27 -0700 Subject: [PATCH 099/416] `_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 e47a9e71e8b65af2e2e3db729399b0ad0ad3d3ac Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 2 Sep 2025 20:08:30 -0700 Subject: [PATCH 100/416] 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 6d9008708e0693da4ab2f7327254e9fb264a3d4b Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 10:31:00 -0700 Subject: [PATCH 101/416] `_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 50ce02b880d51af206b69e53794317a6f3635618 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 11:36:23 -0700 Subject: [PATCH 102/416] `._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 dd3267b93ecd1900ae604c6535770d06ef3f01e5 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 12:05:51 -0700 Subject: [PATCH 103/416] `._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 4e204e1e5a..ad82ebd2d2 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5747,7 +5747,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, @@ -5760,7 +5760,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`. @@ -5777,7 +5777,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 5b7b87d84d..194ece0e2e 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4552,7 +4552,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, @@ -4565,7 +4565,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``. @@ -4584,7 +4584,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 4cda472103..37783befd4 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 8db736dea0..0dceca20c6 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1314,18 +1314,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 d39d6c2c54d2539263ab26247cf05b7e32e73b99 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 12:19:22 -0700 Subject: [PATCH 104/416] 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 d4bbc14a5ccc9e68cd00c4b40b1a228d79dd1aa4 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 12:22:47 -0700 Subject: [PATCH 105/416] 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 29223540d1b8a2a45c454b2490b27c88097599a0 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 14:14:18 -0700 Subject: [PATCH 106/416] obsolete extrinsic `set_root_weights_extrinsic` removed. Also related subtensor calls `subtensor.set_root_weights_extrinsic` removed too. --- bittensor/core/async_subtensor.py | 46 +--- bittensor/core/extrinsics/asyncex/root.py | 119 +-------- bittensor/core/extrinsics/root.py | 119 +-------- bittensor/core/subtensor.py | 47 +--- 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(+), 800 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ad82ebd2d2..3ecca22bcf 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -59,10 +59,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, @@ -5173,47 +5170,6 @@ async def root_register( period=period, ) - async def root_set_weights( - self, - wallet: "Wallet", - netuids: UIDs, - weights: Weights, - 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_auto_stake( 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 194ece0e2e..65aefda200 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -64,10 +64,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, @@ -3999,48 +3996,6 @@ def root_set_pending_childkey_cooldown( period=period, ) - def root_set_weights( - self, - wallet: "Wallet", - netuids: UIDs, - 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_auto_stake( 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 5a6cc4bfd6..7b4d07fd59 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -160,7 +160,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_auto_stake = subtensor._subtensor.set_auto_stake subtensor.set_children = subtensor._subtensor.set_children 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 37783befd4..c31c3ec11b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2854,48 +2854,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=np.ndarray) - mocked_np_array_weights = mocker.Mock(autospec=np.ndarray) - mocker.patch.object( - 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 3afedcfec954a56d2051feaacfcb6be981dbdc47 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 14:00:23 -0700 Subject: [PATCH 107/416] `set_children_extrinsic` and `root_set_pending_childkey_cooldown_extrinsic` --- bittensor/core/async_subtensor.py | 40 +++++++------- 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, 134 insertions(+), 82 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 3ecca22bcf..e35441335c 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5105,25 +5105,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. """ @@ -5131,9 +5133,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 @@ -5216,29 +5219,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. @@ -5259,10 +5263,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 8cfca27f78..cde329ba8c 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 993016e6ca..46c4b5129c 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 65aefda200..d9c0d76745 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3965,25 +3965,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. """ @@ -3991,9 +3993,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_auto_stake( @@ -4042,30 +4045,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, @@ -4073,10 +4076,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( @@ -4093,7 +4096,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 a909aab9554e4d3960ca5d20a556009ee9c11e29 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 15:16:08 -0700 Subject: [PATCH 108/416] `.commit_reveal_extrinsic` and `subtensor.set_weights` + tests --- bittensor/core/async_subtensor.py | 43 ++++++++------- .../core/extrinsics/asyncex/commit_reveal.py | 34 ++++++------ bittensor/core/extrinsics/commit_reveal.py | 32 +++++++----- bittensor/core/subtensor.py | 52 ++++++++++--------- migration.md | 7 +-- .../extrinsics/asyncex/test_commit_reveal.py | 3 ++ .../extrinsics/test_commit_reveal.py | 3 ++ tests/unit_tests/test_subtensor.py | 3 +- 8 files changed, 100 insertions(+), 77 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e35441335c..8866b69f07 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5405,14 +5405,15 @@ async def set_weights( netuid: int, uids: UIDs, weights: Weights, + 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, mechid: int = 0, - commit_reveal_version: int = 4, ): """ Sets the weight vector for a neuron acting as a validator, specifying the weights assigned to subnet miners @@ -5423,25 +5424,26 @@ async def set_weights( miners. Parameters: - wallet: The wallet associated with the neuron setting the weights. + 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. + 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. + 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. - max_retries: The number of maximum attempts to set weights. - block_time: The number of seconds for block duration. - 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. - mechid: The subnet mechanism unique identifier. - commit_reveal_version: The version of the commit-reveal in the chain. 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 the Yuma Consensus mechanism, where each validator's weight vector contributes to the overall weight matrix used to calculate emissions and maintain network consensus. @@ -5488,12 +5490,13 @@ async def _blocks_weight_limit() -> bool: mechid=mechid, 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, - commit_reveal_version=commit_reveal_version, ) 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 e867fca810..bd06e27f36 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -23,34 +23,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) @@ -97,6 +100,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 d9c0d76745..9e20ad8964 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4217,43 +4217,46 @@ def set_weights( netuid: int, uids: UIDs, weights: Weights, + 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, mechid: int = 0, - commit_reveal_version: int = 4, ) -> 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. Parameters: - wallet: The wallet associated with the neuron setting the weights. + 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. + 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. + 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. - max_retries: The number of maximum attempts to set weights. - block_time: The number of seconds for block duration. - 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. mechid: The subnet mechanism unique identifier. - commit_reveal_version: The version of the commit-reveal in the chain. 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 the Yuma Consensus mechanism, where each validator's weight vector contributes to - the overall weight matrix used to calculate emissions and maintain network consensus. + 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. Notes: See @@ -4290,12 +4293,13 @@ def _blocks_weight_limit() -> bool: mechid=mechid, 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, - commit_reveal_version=commit_reveal_version, ) 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 0dceca20c6..1ab2636891 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3183,12 +3183,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, - commit_reveal_version=4, + raise_error=False, mechid=0, ) assert result == mocked_commit_timelocked_mechanism_weights_extrinsic.return_value From 69657dce0804d762eb67426ff93461dc7984e6f4 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 15:24:32 -0700 Subject: [PATCH 109/416] `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 8866b69f07..d4633a2762 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5411,8 +5411,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, mechid: int = 0, ): """ diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 9e20ad8964..da1e21ea05 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4223,8 +4223,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, mechid: int = 0, ) -> tuple[bool, str]: """ From 1d36f0b38db082bf5ca1335a05ae87f7b96cc45e Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 15:27:44 -0700 Subject: [PATCH 110/416] 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 c31c3ec11b..2e517444e5 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, mechid=0, From 0bdb8c22282d5985438842f544c59e09453d8955 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 15:39:50 -0700 Subject: [PATCH 111/416] `.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 d4633a2762..04a2733a3b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4570,26 +4570,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]: @@ -4597,7 +4598,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, @@ -4607,9 +4608,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 da1e21ea05..a53f6962f7 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3396,26 +3396,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]: @@ -3423,7 +3424,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, @@ -3433,9 +3434,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 2e517444e5..bc5989cd48 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3674,8 +3674,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 1ab2636891..b1de1dc692 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3949,8 +3949,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 74a92987f08ccd7281545bff6731a8146d15588b Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 16:10:55 -0700 Subject: [PATCH 112/416] `.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 04a2733a3b..da47e3b069 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4780,24 +4780,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]: @@ -4831,7 +4832,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, @@ -4840,9 +4841,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( @@ -4993,23 +4995,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]: @@ -5018,7 +5021,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( @@ -5027,9 +5030,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( @@ -5676,21 +5680,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]: @@ -5704,9 +5710,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 a53f6962f7..7c9c54da16 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3608,24 +3608,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]: @@ -3659,7 +3660,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, @@ -3668,9 +3669,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( @@ -3822,23 +3824,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]: @@ -3847,7 +3850,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( @@ -3856,9 +3859,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( @@ -4480,21 +4484,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]: @@ -4508,9 +4514,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 bc5989cd48..ef6bab8b0b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3708,8 +3708,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 @@ -3739,8 +3740,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 @@ -3769,8 +3771,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 b1de1dc692..d6a5f12c9e 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3982,8 +3982,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 @@ -4012,8 +4013,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 @@ -4041,8 +4043,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 39119d7ecbcc25a49409201c038897c208a077f1 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 17:13:00 -0700 Subject: [PATCH 113/416] `.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 da47e3b069..e2e9083b8b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5766,25 +5766,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. @@ -5798,9 +5800,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 7c9c54da16..735d355db5 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4572,28 +4572,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( @@ -4604,9 +4606,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 2573e2171db62e7284efeb765725682c3e9b8bc1 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 17:35:46 -0700 Subject: [PATCH 114/416] `.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 e2e9083b8b..cb842db407 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5618,36 +5618,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. @@ -5667,12 +5668,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 735d355db5..6f5082b906 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4421,40 +4421,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: @@ -4471,12 +4471,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 d6a5f12c9e..b50ae6710e 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3059,6 +3059,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 @@ -3104,6 +3105,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 ea6efdd13b8682609f59503e3c40eb147be561d1 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 17:55:31 -0700 Subject: [PATCH 115/416] `.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 cb842db407..4437d98160 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4850,32 +4850,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. @@ -4884,15 +4886,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 6f5082b906..3b15aca36e 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3678,49 +3678,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 93729c07e65b64aa715266ede7912712d58ff902 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:05:28 -0700 Subject: [PATCH 116/416] `.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 4437d98160..d780cd8533 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4655,26 +4655,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: @@ -4690,9 +4691,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 3b15aca36e..6314594d90 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3486,24 +3486,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. @@ -3522,9 +3522,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 ef6bab8b0b..9dc94a7d07 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 81e7cccbd2a1bd60b5a086ed5938099015becbc4 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:08:56 -0700 Subject: [PATCH 117/416] 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 fa6fecfb69..1b99a9e691 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -1395,9 +1395,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, @@ -1473,9 +1473,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, @@ -1566,9 +1566,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, @@ -1647,9 +1647,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 1e70780b2ed3acfb99d78068748eaca6316aa609 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:12:21 -0700 Subject: [PATCH 118/416] 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 d780cd8533..a645e61e18 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4622,6 +4622,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. @@ -4634,6 +4635,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. @@ -4649,6 +4653,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 6314594d90..3f477a0cbb 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3454,14 +3454,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 22f520a3f6bcfcbe3f3f3c0828bdbca86307168d Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:22:15 -0700 Subject: [PATCH 119/416] `.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 a645e61e18..0c370abde1 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4971,22 +4971,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. @@ -4994,9 +4994,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 3f477a0cbb..9ca6a5d737 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3794,22 +3794,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. @@ -3817,9 +3817,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 8d8b25744acb572a94aaa75a6cdae9373190630d Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:31:35 -0700 Subject: [PATCH 120/416] 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 0c370abde1..c42c48e70f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4973,7 +4973,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 9ca6a5d737..6900fc7336 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3796,7 +3796,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 6f4ab75833..61d37dca0e 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -37,7 +37,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." ) @@ -93,7 +93,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." ) @@ -171,7 +171,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." ) @@ -497,7 +497,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 ef4c28cd80..22dfdfc8ca 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -42,9 +42,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): ), "Failed to set admin freeze window to 0" # 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), ( @@ -216,7 +214,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 48bd6d47d5..7c18a971ea 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) @@ -637,7 +637,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 @@ -902,7 +902,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, @@ -1139,7 +1139,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 1b99a9e691..38a7a7bf36 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), ( @@ -706,7 +706,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), ( @@ -905,7 +905,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), ( @@ -1105,12 +1105,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" ) @@ -1225,12 +1225,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" ) @@ -1340,7 +1340,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" ) @@ -1372,7 +1372,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" ) @@ -1505,7 +1505,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" ) @@ -1541,7 +1541,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" ) @@ -1678,7 +1678,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" ) @@ -1727,7 +1727,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) @@ -1811,7 +1811,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" ) @@ -1862,7 +1862,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 @@ -1954,7 +1954,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" ) @@ -1978,7 +1978,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" ) @@ -2082,7 +2082,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" ) @@ -2108,7 +2108,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 95de4d936b..52b379f4de 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -70,7 +70,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" ) @@ -272,7 +272,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 0f06a91873d89f5213422a9da81ca6a0e3f703ee Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 18:48:25 -0700 Subject: [PATCH 121/416] 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 6900fc7336..2765269868 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3731,7 +3731,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, @@ -3944,7 +3944,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 5896df51b0..cdf677a2d1 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 56ddde2beea3e473b98ab4f69f800e1054c711d4 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:04:33 -0700 Subject: [PATCH 122/416] `.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 c42c48e70f..c7a43d1405 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4909,8 +4909,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, @@ -4920,33 +4918,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. @@ -4955,8 +4957,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, @@ -4966,6 +4966,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 2765269868..07da96847a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3731,8 +3731,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, @@ -3742,44 +3740,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, @@ -3789,6 +3788,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 9dc94a7d07..586479c8e0 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 26d949fcaf15be07744d7e6ac83f4dbf152ccbc5 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:11:26 -0700 Subject: [PATCH 123/416] 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 7c18a971ea..a7f12fdd89 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), ( @@ -596,11 +596,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( @@ -862,11 +858,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 06bb140a2b..e42e747d0c 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -73,11 +73,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), ( @@ -252,11 +250,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 38a7a7bf36..18a7a37b5e 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 e3f3d24a971f060ecfdacbe461200bc1f07ff232 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:21:06 -0700 Subject: [PATCH 124/416] `.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 c7a43d1405..a27c8d233d 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5378,29 +5378,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, @@ -5414,9 +5415,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 07da96847a..6c60ad939b 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4183,29 +4183,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, @@ -4219,9 +4220,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 586479c8e0..2e59cc6b60 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2993,9 +2993,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 b50ae6710e..6ef639180d 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3249,9 +3249,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 faad50e3045fe6d1c62c889c95a7e57562c5c770 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:28:29 -0700 Subject: [PATCH 125/416] 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 61d37dca0e..bc0fd762c7 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -63,8 +63,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, ) @@ -122,7 +122,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 a7f12fdd89..9221cee51c 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -562,8 +562,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) @@ -822,8 +820,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( @@ -999,10 +995,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 = [ @@ -1237,10 +1231,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 18a7a37b5e..981ade38da 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: @@ -1115,14 +1103,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 @@ -1235,14 +1219,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 @@ -1374,15 +1354,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( @@ -1545,15 +1521,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( @@ -1678,10 +1650,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( @@ -1724,10 +1694,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( @@ -1813,10 +1781,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( @@ -1861,10 +1827,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( @@ -1957,15 +1921,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 @@ -1981,15 +1941,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. @@ -2087,15 +2043,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 @@ -2111,15 +2063,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 dcc1614d57309be5f762ffd4ab054542e145ea96 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:36:31 -0700 Subject: [PATCH 126/416] 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 a27c8d233d..3e04c42bc4 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5378,7 +5378,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 6c60ad939b..378b1539a7 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4183,7 +4183,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 6bce6d52b8e3464ea185bab6746faf92caad4034 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 19:39:33 -0700 Subject: [PATCH 127/416] =?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 2e59cc6b60..9d66090305 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2993,7 +2993,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 6ef639180d..9dad1672b6 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3249,7 +3249,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 d8bd3670400670ab5490a51dc389be3ba56516e1 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 21:21:43 -0700 Subject: [PATCH 128/416] `.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 3e04c42bc4..db64d0568b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4669,7 +4669,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 @@ -4687,9 +4687,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( @@ -5163,34 +5164,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_auto_stake( 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 378b1539a7..dd01de956d 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3495,7 +3495,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 @@ -3513,9 +3513,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( @@ -3946,32 +3947,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 423f05471b52932cd9f2f286f72bf0ee02f9da2e Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 21:58:10 -0700 Subject: [PATCH 129/416] `.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 db64d0568b..6daeb8705a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5558,27 +5558,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. @@ -5587,10 +5591,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 dd01de956d..df0dce486d 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4352,41 +4352,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 9dad1672b6..d4b670e7a0 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 @@ -1227,7 +1226,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 @@ -1235,10 +1237,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 @@ -1458,128 +1461,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 47ff1224465a08074e3237cf585803040f9f5b99 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 22:08:27 -0700 Subject: [PATCH 130/416] 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 6daeb8705a..767788dc00 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5561,7 +5561,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 df0dce486d..f64df4b6fb 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4355,7 +4355,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 c4121502e3b26f953aaf2ed967c28216f1fd4a7e Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 22:47:13 -0700 Subject: [PATCH 131/416] `.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 767788dc00..ece1e149e1 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -946,45 +946,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, @@ -4038,23 +3999,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. """ @@ -4073,6 +4040,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( @@ -5598,6 +5568,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 f64df4b6fb..e3b504953a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -541,35 +541,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]: @@ -2961,20 +2932,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. @@ -2995,6 +2973,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]: @@ -4392,6 +4373,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 7b4d07fd59..b0a2f29f16 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 d4b670e7a0..d11a006a31 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1267,7 +1267,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( @@ -1277,6 +1277,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 e6e561ebc367763043b71a0a691e9e5c22508f64 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 23:33:11 -0700 Subject: [PATCH 132/416] `.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 ece1e149e1..5be9e6c5db 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4479,36 +4479,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. @@ -4524,12 +4526,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( @@ -4590,9 +4593,10 @@ async def add_stake_multiple( hotkey_ss58s: list[str], netuids: UIDs, 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. @@ -4603,11 +4607,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. @@ -4621,9 +4626,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 ab5d35af63..4928350844 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -21,39 +21,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. @@ -159,7 +159,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, @@ -168,8 +168,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 @@ -200,14 +201,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( @@ -223,30 +224,30 @@ async def add_stake_multiple_extrinsic( netuids: UIDs, 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 @@ -363,11 +364,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 642a0fa826..be442741d9 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -16,15 +16,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. @@ -33,24 +35,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. @@ -68,7 +69,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 @@ -161,6 +163,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. @@ -190,14 +193,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( @@ -211,32 +214,33 @@ def add_stake_multiple_extrinsic( wallet: "Wallet", hotkey_ss58s: list[str], netuids: UIDs, + 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 ): @@ -284,9 +288,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: @@ -345,6 +350,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 e3b504953a..0751dd9a9f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3315,44 +3315,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( @@ -3361,12 +3362,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( @@ -3427,30 +3429,32 @@ def add_stake_multiple( hotkey_ss58s: list[str], netuids: UIDs, 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, @@ -3458,9 +3462,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 d5dd4b0094..8260590968 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -53,6 +53,7 @@ def test_add_stake_extrinsic(mocker): sign_with="coldkey", use_nonce=True, period=None, + raise_error=False, ) @@ -135,12 +136,13 @@ 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 d11a006a31..7ebcbab319 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2749,6 +2749,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 @@ -2789,6 +2790,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 @@ -2823,6 +2825,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 73fbdeca7e6e9945a6dc7f4903f32712da6eab5e Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 3 Sep 2025 23:44:16 -0700 Subject: [PATCH 133/416] `.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 5be9e6c5db..2cf5603719 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5629,22 +5629,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]: @@ -5655,9 +5657,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 0751dd9a9f..02ac9a5b51 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4434,24 +4434,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]: @@ -4462,9 +4462,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 9d66090305..39b80546c7 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3103,6 +3103,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 7ebcbab319..fefb680784 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3240,6 +3240,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 13c3c6326695ab1ac05fa1e9e787f98deb3ad234 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 00:14:13 -0700 Subject: [PATCH 134/416] 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 dd2ec9ca4a81140eb536e6592971a35304d8116b Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 00:54:12 -0700 Subject: [PATCH 135/416] `.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 2cf5603719..2f6ead8dcc 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5271,30 +5271,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. @@ -5309,7 +5309,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) @@ -5322,33 +5321,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 02ac9a5b51..cef505e6ec 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4099,19 +4099,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. @@ -4126,7 +4127,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) @@ -4139,33 +4139,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 f4952e9242301da6630113c840295dbdd7bb8e12 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 01:50:10 -0700 Subject: [PATCH 136/416] 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 6f84cd6818db9a9732617b6b7f40e1f680ad9db9 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 03:19:23 -0700 Subject: [PATCH 137/416] `.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 2f6ead8dcc..a43d72b1af 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5769,25 +5769,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`. """ @@ -5799,10 +5802,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 cef505e6ec..5292c04139 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4574,27 +4574,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`. @@ -4607,10 +4608,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 39b80546c7..cec70113c4 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 fefb680784..ebaaad5035 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1338,6 +1338,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 16296111633a517942db2e12162f155edeeb5ba7 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 14:03:13 -0700 Subject: [PATCH 138/416] `.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 a43d72b1af..256f5e9b1e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5860,40 +5860,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. @@ -5908,13 +5907,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( @@ -5924,7 +5923,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 3bbc40a146..c078023e02 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -18,54 +18,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]" ) @@ -80,24 +70,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 @@ -105,7 +84,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) @@ -118,7 +97,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], " @@ -137,7 +116,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" @@ -160,9 +139,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 @@ -192,14 +172,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 bc546e36d8..6bef65468c 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -17,54 +17,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]" ) @@ -77,24 +67,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 @@ -102,7 +82,7 @@ def unstake_extrinsic( call_params = { "hotkey": hotkey_ss58, "netuid": netuid, - "amount_unstaked": unstaking_balance.rao, + "amount_unstaked": amount.rao, } if safe_staking: @@ -116,7 +96,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], " @@ -135,7 +115,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" @@ -159,6 +139,7 @@ def unstake_extrinsic( sign_with="coldkey", use_nonce=True, period=period, + raise_error=raise_error, ) if success: # If we successfully unstaked. @@ -189,14 +170,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 5292c04139..08b09fe1d4 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4666,61 +4666,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( @@ -4730,7 +4730,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 981ade38da..9e5d5bda61 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, ) @@ -805,11 +809,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, @@ -832,11 +834,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, @@ -856,11 +856,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, @@ -1007,11 +1005,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, @@ -1034,11 +1030,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, @@ -1058,11 +1052,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, @@ -1991,8 +1983,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 @@ -2002,8 +1992,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. @@ -2082,8 +2070,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, @@ -2091,8 +2079,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, @@ -2112,11 +2100,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 @@ -2124,11 +2110,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 ebaaad5035..0e5a53edca 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2835,6 +2835,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") @@ -2842,6 +2843,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, @@ -2855,16 +2857,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 @@ -2873,6 +2875,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") @@ -2880,6 +2883,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, @@ -2893,16 +2897,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 5f7a2873e29a06b2552cfef635163aa4a87c8591 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 14:18:32 -0700 Subject: [PATCH 139/416] `.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 256f5e9b1e..41a12112c4 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5922,23 +5922,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]: @@ -5995,9 +5997,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 c078023e02..45f6796b16 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -194,24 +194,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]: @@ -250,6 +252,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 6bef65468c..a254238b87 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -193,24 +193,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]: @@ -248,6 +250,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 08b09fe1d4..99ce556789 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4729,23 +4729,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]: @@ -4801,9 +4803,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 cec70113c4..a3cc5484ec 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3455,8 +3455,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 0e5a53edca..a67db1174f 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3699,8 +3699,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 1f9ba6b8d217e065e67a04632b4cb3a4d63fe359 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:00:15 -0700 Subject: [PATCH 140/416] `.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 41a12112c4..e1a3fb3f9e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -6006,29 +6006,31 @@ async def unstake_all( async def unstake_multiple( self, wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + 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. @@ -6039,13 +6041,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 45f6796b16..e6775f1832 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -259,36 +259,34 @@ async def unstake_all_extrinsic( async def unstake_multiple_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + 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`.") @@ -396,6 +394,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 a254238b87..213a7930a5 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -259,36 +259,34 @@ def unstake_all_extrinsic( def unstake_multiple_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + 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`.") @@ -389,9 +387,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 99ce556789..00aaaa00db 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4812,46 +4812,47 @@ def unstake_all( def unstake_multiple( self, wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + 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 a67db1174f..fcbd0791ce 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3033,6 +3033,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 01c96e434f9f55d3be8c011ac7b449c25647f449 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:12:36 -0700 Subject: [PATCH 141/416] `.unstake_multiple_extrinsic` and `subtensor.unstake_multiple` + tests --- bittensor/core/async_subtensor.py | 32 ++++++++++---------- bittensor/core/extrinsics/asyncex/weights.py | 14 ++++----- bittensor/core/extrinsics/weights.py | 14 ++++----- bittensor/core/subtensor.py | 29 +++++++++--------- migration.md | 1 + 5 files changed, 46 insertions(+), 44 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e1a3fb3f9e..964c3d797e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4687,31 +4687,31 @@ async def commit_weights( uids: UIDs, weights: Weights, 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, mechid: int = 0, ) -> 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. - mechid: The subnet mechanism unique identifier. + 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]: @@ -4747,7 +4747,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 09d509167f..2663861f50 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 00aaaa00db..0ee1284bb1 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3523,10 +3523,11 @@ def commit_weights( uids: UIDs, weights: Weights, 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, mechid: int = 0, ) -> tuple[bool, str]: """ @@ -3537,24 +3538,24 @@ def commit_weights( 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: Array/list of neuron UIDs for which weights are being committed. - weights: Array/list of weight values corresponding to each UID. + 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. - 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. 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. - mechid: The subnet mechanism unique identifier. + 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 @@ -3576,10 +3577,10 @@ def commit_weights( uids=uids, weights=weights, salt=salt, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, period=period, raise_error=True, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) 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 80ce2ccb19f2dc67a8517eef25062c15b1a3cd1a Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:27:47 -0700 Subject: [PATCH 142/416] `.reveal_weights_extrinsic` and `subtensor.reveal_weights` --- bittensor/core/async_subtensor.py | 7 ++++--- bittensor/core/extrinsics/asyncex/weights.py | 12 ++++++------ bittensor/core/extrinsics/weights.py | 12 ++++++------ bittensor/core/subtensor.py | 7 ++++--- migration.md | 1 + 5 files changed, 21 insertions(+), 18 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 964c3d797e..512372d878 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5035,10 +5035,11 @@ async def reveal_weights( weights: Weights, salt: Salt, 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, mechid: int = 0, ) -> tuple[bool, str]: """ @@ -5085,10 +5086,10 @@ async def reveal_weights( weights=weights, salt=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 2663861f50..5675c88198 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -90,16 +90,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. @@ -107,12 +107,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 0ee1284bb1..b4b175ebd5 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3867,11 +3867,12 @@ def reveal_weights( uids: UIDs, weights: Weights, salt: Salt, + 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, mechid: int = 0, ) -> tuple[bool, str]: """ @@ -3921,7 +3922,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 bd7e5a7e2739bbca60db60bba237a6148cb795e5 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:30:38 -0700 Subject: [PATCH 143/416] 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 519898ba60b34859cc739cea8ad9bb6af9f7027a Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 15:45:10 -0700 Subject: [PATCH 144/416] `.set_weights_extrinsic` and `subtensor.set_weights` --- bittensor/core/async_subtensor.py | 6 +- 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 | 1 + 9 files changed, 103 insertions(+), 102 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 512372d878..abc853c2c8 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5474,7 +5474,8 @@ 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_timelocked_mechanism_weights_extrinsic( subtensor=self, @@ -5514,9 +5515,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 bd06e27f36..88077d4183 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -55,63 +55,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 5675c88198..5aa68543e7 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -161,28 +161,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 b4b175ebd5..0768a01b16 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4286,22 +4286,26 @@ 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_timelocked_mechanism_weights_extrinsic( - subtensor=self, - wallet=wallet, - netuid=netuid, - mechid=mechid, - 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_timelocked_mechanism_weights_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + mechid=mechid, + 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 `set_mechanism_weights_extrinsic` @@ -4320,9 +4324,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 a3cc5484ec..686945c4c3 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2806,6 +2806,7 @@ async def test_set_weights_success(subtensor, fake_wallet, mocker): weights=fake_weights, period=8, mechid=0, + 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 fcbd0791ce..70e2b333aa 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1207,6 +1207,7 @@ def test_set_weights(subtensor, mocker, fake_wallet): wait_for_finalization=fake_wait_for_finalization, period=8, mechid=0, + raise_error=True, ) assert result == expected_result From 8d5f7ff9de6e482483b7f655ab4e09dd9c2d275e Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 16:33:35 -0700 Subject: [PATCH 145/416] 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 abc853c2c8..5b84eda1bd 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5867,7 +5867,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, @@ -5888,7 +5888,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. @@ -5912,7 +5912,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 e6775f1832..cb202a1509 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -23,7 +23,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, @@ -39,7 +39,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 @@ -86,7 +86,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 213a7930a5..dce3e62331 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -22,7 +22,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, @@ -39,7 +39,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. @@ -85,7 +85,7 @@ def unstake_extrinsic( "amount_unstaked": amount.rao, } - if safe_staking: + if safe_unstaking: pool = subtensor.subnet(netuid=netuid) base_price = pool.price.tao @@ -171,7 +171,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 0768a01b16..632ede6620 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4678,7 +4678,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, @@ -4698,7 +4698,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. @@ -4723,7 +4723,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 9e5d5bda61..df30c85c9f 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -812,7 +812,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, ) @@ -837,7 +837,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, ) @@ -859,7 +859,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, ) @@ -1008,7 +1008,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, ) @@ -1033,7 +1033,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, ) @@ -1055,7 +1055,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 70e2b333aa..8eecc742fb 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2849,7 +2849,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, ) @@ -2861,7 +2861,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, @@ -2872,8 +2872,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 @@ -2889,7 +2889,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, ) @@ -2901,7 +2901,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 4d24962adb12400576011f3108c88e7c6984120a Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 16:43:57 -0700 Subject: [PATCH 146/416] 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 5b84eda1bd..1570a5b80d 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5666,7 +5666,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, @@ -5684,7 +5684,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. @@ -5716,7 +5716,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 cb202a1509..edda662b56 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -173,7 +173,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 632ede6620..26d45ba1df 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4476,7 +4476,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, @@ -4494,7 +4494,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. @@ -4526,7 +4526,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 df30c85c9f..7c2f274dd1 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -1132,7 +1132,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, ) @@ -1158,7 +1158,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, ) @@ -1248,7 +1248,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, ) @@ -1274,7 +1274,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 8eecc742fb..1f27470313 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2933,7 +2933,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, ) @@ -2948,7 +2948,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, @@ -2979,7 +2979,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, ) @@ -2994,7 +2994,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 10580588b537916a8533b346e12303d22bedc2ad Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 16:49:01 -0700 Subject: [PATCH 147/416] 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 f887b4c1bbd148166efbe49de56835d43b95babf Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 17:30:36 -0700 Subject: [PATCH 148/416] `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 f1af83b15ed1e3988205bc8fb6067565cc5a9970 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 18:23:35 -0700 Subject: [PATCH 149/416] 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 0ac29e5f03a079cd4eecabd5321ed4d839fb27b0 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 20:02:09 -0700 Subject: [PATCH 150/416] Changes in `.add_stake_extrinsic` and `subtensor.add_stake`: see migration.md --- bittensor/core/async_subtensor.py | 9 +- bittensor/core/extrinsics/asyncex/staking.py | 215 ++++++++----------- 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, 248 insertions(+), 353 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 1570a5b80d..a9211c9d30 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4476,9 +4476,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, @@ -4494,9 +4494,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 4928350844..8fd0ff7009 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -17,10 +17,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, @@ -37,11 +36,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``. @@ -64,15 +61,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 @@ -86,135 +78,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, - } - - 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) + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_staked": amount.rao, + } - 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]" - ) + if safe_staking: + pool = await subtensor.subnet(netuid=netuid) + base_price = pool.price.tao - 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 = await 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 = 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, + + 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: # 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 be442741d9..e7c83f0dc9 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -16,10 +16,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, @@ -30,16 +29,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``. @@ -62,15 +60,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 @@ -82,131 +75,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, - } - - 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) + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_staked": amount.rao, + } - 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]" - ) + if safe_staking: + pool = subtensor.subnet(netuid=netuid) + base_price = pool.price.tao - 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, + } ) - 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_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]" + ) + 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]") + + 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 - 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 def add_stake_multiple_extrinsic( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 26d45ba1df..deceedd1e1 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3312,9 +3312,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, @@ -3330,9 +3330,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 f6f33edfe8..20b56a6d87 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -74,6 +74,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" @@ -266,6 +267,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 9221cee51c..f30cb43f1b 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 e42e747d0c..23bac7a970 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -108,11 +108,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 @@ -279,11 +277,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 7c2f274dd1..6fcf1ae229 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, ) @@ -737,11 +733,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, @@ -761,11 +755,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, @@ -788,11 +780,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, @@ -933,11 +923,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, @@ -957,11 +945,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, @@ -984,11 +970,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, @@ -1105,11 +1089,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 @@ -1221,11 +1203,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 @@ -1313,11 +1293,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) @@ -1411,11 +1389,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, ) @@ -1476,11 +1452,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( @@ -1583,8 +1557,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, ) @@ -1647,12 +1619,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( @@ -1778,12 +1748,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( @@ -1953,8 +1921,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( @@ -1962,8 +1928,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}" @@ -2073,8 +2037,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( @@ -2082,8 +2044,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 1f27470313..a9b722cce1 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2720,7 +2720,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" @@ -2729,6 +2730,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, @@ -2743,8 +2745,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, @@ -2759,8 +2761,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( @@ -2770,6 +2773,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, @@ -2784,8 +2788,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 c191b07a135b5b5e3173395722a13e5362f18e40 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 21:26:13 -0700 Subject: [PATCH 151/416] 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 a9211c9d30..df8e491817 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4591,7 +4591,7 @@ async def add_stake_multiple( wallet: "Wallet", hotkey_ss58s: list[str], netuids: UIDs, - amounts: Optional[list[Balance]] = None, + amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -4601,11 +4601,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. @@ -4622,8 +4622,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 8fd0ff7009..ef32256ca7 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -195,23 +195,22 @@ async def add_stake_multiple_extrinsic( wallet: "Wallet", hotkey_ss58s: list[str], netuids: UIDs, - 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. @@ -222,35 +221,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]" @@ -269,11 +269,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. @@ -295,28 +293,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( @@ -324,7 +311,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, }, ) @@ -343,12 +330,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]") @@ -374,10 +357,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 @@ -386,7 +365,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 e7c83f0dc9..52383d5ac1 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -189,23 +189,22 @@ def add_stake_multiple_extrinsic( wallet: "Wallet", hotkey_ss58s: list[str], netuids: UIDs, - 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. @@ -216,36 +215,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]" @@ -263,10 +262,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: @@ -287,32 +285,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, }, ) @@ -328,16 +319,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]") @@ -360,10 +347,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 @@ -372,7 +355,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 deceedd1e1..aa8d91456b 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3427,7 +3427,7 @@ def add_stake_multiple( wallet: "Wallet", hotkey_ss58s: list[str], netuids: UIDs, - amounts: Optional[list[Balance]] = None, + amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -3437,11 +3437,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. @@ -3458,8 +3458,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 6fcf1ae229..e58a08d09d 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 9d89d93f35a19ba7eae260259a17011cdcf65de5 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 21:58:45 -0700 Subject: [PATCH 152/416] 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 6e9e22aca058bca5894e284bde4e5997c93665b4 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 23:13:28 -0700 Subject: [PATCH 153/416] `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 957e8b2750b9a3f9cef86b3ecaba45a6e4d2ef31 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 23:14:32 -0700 Subject: [PATCH 154/416] 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 edda662b56..5a42e58b45 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -288,29 +288,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) @@ -321,10 +311,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 dce3e62331..01834a33b7 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -288,23 +288,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 ): @@ -319,10 +311,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]" @@ -378,7 +389,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, @@ -390,7 +401,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: @@ -414,7 +425,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 d2d9e21d77092439f96fafa73a84d9ad8bdea553 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 4 Sep 2025 23:21:41 -0700 Subject: [PATCH 155/416] 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 feeb80559cb7e9c66a89db725f6992b8d66757ef Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 00:55:54 -0700 Subject: [PATCH 156/416] 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 9ee942042f4a622e80a342c76902cf4d95157867 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:23:40 -0700 Subject: [PATCH 157/416] 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 e8b3bd6889a92da6b93919e835f466b28c6abdff Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:28:38 -0700 Subject: [PATCH 158/416] 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 ae30e726e0c415baf621a2a4e5eea6865696b5d9 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:36:10 -0700 Subject: [PATCH 159/416] 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 2846f2e8f126b60e5277baa74a4faa333b456deb Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:45:04 -0700 Subject: [PATCH 160/416] 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 edd94e690667aa726fe00f07a6af9690c19dea5d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:51:29 -0700 Subject: [PATCH 161/416] 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 0ecea383cb8baab39f892fc8ed4c29130337af5e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 01:58:43 -0700 Subject: [PATCH 162/416] 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 cb76cf6e8af6ae7ab789273b46b4c9b88893aaa7 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 02:03:18 -0700 Subject: [PATCH 163/416] 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 751afdbe5fb8de85e04a52501a52125bfb72786a Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 02:07:44 -0700 Subject: [PATCH 164/416] 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 a4365bb9646ae4eff2d18cdf0c93f716489d4da0 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:05:52 -0700 Subject: [PATCH 165/416] 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 5702e276c40921c6354f58d2d49a5a531565ece6 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:10:26 -0700 Subject: [PATCH 166/416] 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 6815388022e7306f7811414fa1f4bfa9caf6181d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:12:47 -0700 Subject: [PATCH 167/416] 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 49f5a94002afdf658d82512362283e566a2ffc68 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:15:36 -0700 Subject: [PATCH 168/416] 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 5b1f50882a60c502d08974fc25d9c38b2d649442 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:30:22 -0700 Subject: [PATCH 169/416] 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 e0a2d9bbb8aad71b945e1b9fdb57318b8ad9a2be Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:36:44 -0700 Subject: [PATCH 170/416] 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 4fa42a28c6c601aeb6ceea9be69af4bc4a8d9ed1 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 05:41:49 -0700 Subject: [PATCH 171/416] 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 7c55fe481ebee910735d2a5e8c5b06ca1ecf220f Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 11:19:49 -0700 Subject: [PATCH 172/416] 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 28620d5bf1d44a8fb0209a072650812c43301351 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 11:36:36 -0700 Subject: [PATCH 173/416] 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 452515471ae39581945d577c09645b98829858de Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 11:55:50 -0700 Subject: [PATCH 174/416] 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 ebe80e4c3add1941adff86e70e9ad5030aa54ac3 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 12:04:15 -0700 Subject: [PATCH 175/416] 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 a2f3b08e7cc376c394f73d7c7ea9a51f9c37b25e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 12:11:56 -0700 Subject: [PATCH 176/416] 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 1b19ea6ff85dd487ac11665ed07e9fb0096f29f5 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 16:34:42 -0700 Subject: [PATCH 177/416] update migration.md --- migration.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migration.md b/migration.md index 3e4b0a7a1a..22b0a56574 100644 --- a/migration.md +++ b/migration.md @@ -112,8 +112,8 @@ rename this variable in documentation. ## 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. +2. Implement Crowdloan logic. Issue: https://github.com/opentensor/bittensor/issues/3017 +3. Implement Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 ## Testing 1. When running tests via Docker, ensure no lingering processes occupy required ports before launch. From 13d5d47dcffeeaf45407878812c552a95a068d80 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 16:35:03 -0700 Subject: [PATCH 178/416] add `bittensor.core.types.ExtrinsicResponse` --- bittensor/core/types.py | 72 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 3 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index eb1e84478f..eb4ed2c5ac 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -1,6 +1,9 @@ import argparse from abc import ABC -from typing import TypedDict, Optional, Union +from dataclasses import dataclass +from typing import Any, TypedDict, Optional, Union + +from scalecodec.types import GenericExtrinsic import numpy as np from numpy.typing import NDArray @@ -8,8 +11,7 @@ from bittensor.core import settings from bittensor.core.chain_data import NeuronInfo, NeuronInfoLite from bittensor.core.config import Config -from bittensor.utils import determine_chain_endpoint_and_network -from bittensor.utils import networking, Certificate +from bittensor.utils import determine_chain_endpoint_and_network, networking, Certificate from bittensor.utils.btlogging import logging # Type annotations for UIDs and weights. @@ -270,3 +272,67 @@ class PrometheusServeCallParams(TypedDict): class ParamWithTypes(TypedDict): name: str # Name of the parameter. type: str # ScaleType string of the parameter. + + +@dataclass +class ExtrinsicResponse: + """ + A standardized response container for handling the extrinsic results submissions and related operations in the SDK. + + This class is designed to give developers a consistent way to represent the outcome of an extrinsic call — whether + it succeeded or failed — along with useful metadata for debugging, logging, or higher-level business logic. + + Attributes: + success: Indicates if the extrinsic execution was successful. + message: A status or informational message returned from the execution (e.g., "Successfully registered subnet"). + error: Captures the underlying exception if the extrinsic failed, otherwise `None`. + data: Arbitrary data returned from the extrinsic, such as decoded events, or extra context. + extrinsic_function: The name of the SDK extrinsic function that was executed (e.g. "register_subnet_extrinsic"). + extrinsic: The raw extrinsic object used in the call, if available. + + Example: + import bittensor as bt + + subtensor = bt.SubtensorApi("local") + wallet = bt.Wallet("alice") + + response = subtensor.subnets.register_subnet(alice_wallet) + print(response) + + ExtrinsicResponse: + success: True + message: Successfully registered subnet + error: None + extrinsic_function: register_subnet_extrinsic + extrinsic: {'account_id': '0xd43593c715fdd31c... + + success, message = response + print(success, message) + + True Successfully registered subnet + """ + + success: bool = True + message: str = None + error: Optional[Exception] = None + data: Optional[Any] = None + extrinsic_function: Optional[str] = None + extrinsic: Optional[GenericExtrinsic] = None + + def __iter__(self): + yield self.success + yield self.message + + def __str__(self): + return str( + f"ExtrinsicResponse:" + f"\n\tsuccess: {self.success}" + f"\n\tmessage: {self.message}" + f"\n\terror: {self.error}" + f"\n\textrinsic_function: {self.extrinsic_function}" + f"\n\textrinsic: {self.extrinsic}" + f"\n\tdata: {self.data}" + ) + + def __repr__(self): + return repr((self.success, self.message)) From 59e6653ee1b7736b12538016b1ca795221dfeded Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 16:36:17 -0700 Subject: [PATCH 179/416] update `sign_and_send_extrinsic` with ExtrinsicResponse --- bittensor/core/async_subtensor.py | 144 +++++++++++++--------------- bittensor/core/subtensor.py | 151 ++++++++++++++---------------- 2 files changed, 135 insertions(+), 160 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index df8e491817..7e635ad61b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -90,6 +90,7 @@ ) from bittensor.core.metagraph import AsyncMetagraph from bittensor.core.settings import version_as_int, TYPE_REGISTRY +from bittensor.core.types import ExtrinsicResponse, ParamWithTypes, SubtensorMixin from bittensor.core.types import ( ParamWithTypes, Salt, @@ -4401,30 +4402,32 @@ async def sign_and_send_extrinsic( period: Optional[int] = None, nonce_key: str = "hotkey", raise_error: bool = False, - ) -> tuple[bool, str]: + calling_function: Optional[str] = None, + ) -> ExtrinsicResponse: """ Helper method to sign and submit an extrinsic call to chain. - Arguments: + Parameters: call: a prepared Call object wallet: the wallet whose coldkey will be used to sign the extrinsic wait_for_inclusion: whether to wait until the extrinsic call is included on the chain wait_for_finalization: whether to wait until the extrinsic call is finalized on the chain sign_with: the wallet's keypair to use for the signing. Options are "coldkey", "hotkey", "coldkeypub" use_nonce: unique identifier for the transaction related with hot/coldkey. - 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. nonce_key: the type on nonce to use. Options are "hotkey" or "coldkey". - nonce_key: the type on nonce to use. Options are "hotkey", "coldkey", or "coldkeypub". - raise_error: raises a relevant exception rather than returning ``False`` if unsuccessful. + raise_error: raises the relevant exception rather than returning `False` if unsuccessful. + calling_function: the name of the calling function. Returns: - (success, error message) + ExtrinsicResponse: The result object of the extrinsic execution. Raises: SubstrateRequestException: Substrate request exception. """ + extrinsic_response = ExtrinsicResponse(extrinsic_function=calling_function) possible_keys = ("coldkey", "hotkey", "coldkeypub") if sign_with not in possible_keys: raise AttributeError( @@ -4444,32 +4447,43 @@ async def sign_and_send_extrinsic( if period is not None: extrinsic_data["era"] = {"period": period} - extrinsic = await self.substrate.create_signed_extrinsic(**extrinsic_data) + extrinsic_response.extrinsic = await self.substrate.create_signed_extrinsic( + **extrinsic_data + ) try: response = await self.substrate.submit_extrinsic( - extrinsic, + extrinsic=extrinsic_response.extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - message = "Not waiting for finalization or inclusion." - logging.debug(f"{message}. Extrinsic: {extrinsic}") - return True, message + extrinsic_response.message = ( + "Not waiting for finalization or inclusion." + ) + return extrinsic_response if await response.is_success: - return True, "" + extrinsic_response.message = "" + return extrinsic_response - if raise_error: - raise ChainError.from_error(await response.error_message) + response_error_message = await response.error_message - return False, format_error_message(await response.error_message) + if raise_error: + raise ChainError.from_error(response_error_message) + extrinsic_response.success = False + extrinsic_response.message = format_error_message(response_error_message) + extrinsic_response.error = response_error_message + return extrinsic_response - except SubstrateRequestException as e: + except SubstrateRequestException as error: if raise_error: raise - return False, format_error_message(e) + extrinsic_response.success = False + extrinsic_response.message = format_error_message(error) + extrinsic_response.error = error + return extrinsic_response # Extrinsics ======================================================================================================= @@ -4512,7 +4526,7 @@ async def add_stake( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - bool: ``True`` if the staking is successful, ``False`` otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. 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 @@ -4565,9 +4579,7 @@ async def add_liquidity( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call ``toggle_user_liquidity`` method to enable/disable user liquidity. @@ -4614,7 +4626,7 @@ async def add_stake_multiple( 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. + ExtrinsicResponse: The result object of the extrinsic execution. This function is essential for managing stakes across multiple neurons, reflecting the dynamic and collaborative nature of the Bittensor network. @@ -4639,7 +4651,7 @@ async def burned_register( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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. @@ -4655,7 +4667,7 @@ async def burned_register( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - bool: ``True`` if the registration is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ async with self: if netuid == 0: @@ -4713,9 +4725,7 @@ async def commit_weights( 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. + ExtrinsicResponse: The result object of the extrinsic execution. This function allows subnet validators to create a tamper-proof record of their weight vector at a specific point in time, creating a foundation of transparency and accountability for the Bittensor network. @@ -4784,9 +4794,7 @@ async def modify_liquidity( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. Example: import bittensor as bt @@ -4863,7 +4871,7 @@ async def move_stake( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - success: True if the stake movement was successful. + ExtrinsicResponse: The result object of the extrinsic execution. """ amount = check_and_convert_to_balance(amount) return await move_stake_extrinsic( @@ -4924,7 +4932,7 @@ async def register( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: ``True`` if the registration is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. This function facilitates the entry of new neurons into the network, supporting the decentralized growth and scalability of the Bittensor ecosystem. @@ -4954,7 +4962,7 @@ async def register_subnet( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Registers a new subnetwork on the Bittensor network. @@ -4968,7 +4976,7 @@ async def register_subnet( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ return await register_subnet_extrinsic( subtensor=self, @@ -5005,9 +5013,7 @@ async def remove_liquidity( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: - Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity` @@ -5061,9 +5067,7 @@ async def reveal_weights( mechid: The subnet mechanism unique identifier. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. This function allows neurons to reveal their previously committed weight distribution, ensuring transparency and accountability within the Bittensor network. @@ -5106,7 +5110,7 @@ async def root_set_pending_childkey_cooldown( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Sets the pending childkey cooldown. Parameters: @@ -5120,9 +5124,7 @@ async def root_set_pending_childkey_cooldown( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: This operation can only be successfully performed if your wallet has root privileges. """ @@ -5136,7 +5138,6 @@ async def root_set_pending_childkey_cooldown( wait_for_finalization=wait_for_finalization, ) - # TODO: remove `block_hash` argument async def root_register( self, wallet: "Wallet", @@ -5144,7 +5145,7 @@ async def root_register( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Register neuron by recycling some TAO. @@ -5158,7 +5159,7 @@ async def root_register( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - bool: ``True`` if the registration is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ return await root_register_extrinsic( @@ -5220,7 +5221,7 @@ async def set_children( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ Allows a coldkey to set children-keys. @@ -5237,9 +5238,7 @@ async def set_children( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Raises: DuplicateChild: There are duplicates in the list of children. @@ -5292,9 +5291,7 @@ async def set_delegate_take( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Raises: DelegateTakeTooHigh: Delegate take is too high. @@ -5369,9 +5366,7 @@ async def set_subnet_identity( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ return await set_subnet_identity_extrinsic( subtensor=self, @@ -5433,9 +5428,7 @@ async def set_weights( 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. + ExtrinsicResponse: The result object of the extrinsic execution. 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. @@ -5553,7 +5546,7 @@ async def serve_axon( 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. + ExtrinsicResponse: The result object of the extrinsic execution. By registering an Axon, the neuron becomes an active part of the network's distributed computing infrastructure, contributing to the collective intelligence of Bittensor. @@ -5597,7 +5590,7 @@ async def set_commitment( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: `True` if the commitment was successful, `False` otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. Example: # Commit some data to subnet 1 @@ -5644,9 +5637,7 @@ async def start_call( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ return await start_call_extrinsic( subtensor=self, @@ -5698,7 +5689,7 @@ async def swap_stake( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: True if the extrinsic was successful. + ExtrinsicResponse: The result object of the extrinsic execution. 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: @@ -5748,9 +5739,7 @@ async def toggle_user_liquidity( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: The call can be executed successfully by the subnet owner only. """ @@ -5794,7 +5783,7 @@ async def transfer( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - `True` if the transferring was successful, otherwise `False`. + ExtrinsicResponse: The result object of the extrinsic execution. """ if amount is not None: amount = check_and_convert_to_balance(amount) @@ -5842,7 +5831,7 @@ async def transfer_stake( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - success: True if the transfer was successful. + ExtrinsicResponse: The result object of the extrinsic execution. """ amount = check_and_convert_to_balance(amount) return await transfer_stake_extrinsic( @@ -5897,7 +5886,7 @@ async def unstake( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - bool: `True` if the unstaking process is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. This function supports flexible stake management, allowing neurons to adjust their network participation and potential reward accruals. @@ -5945,10 +5934,7 @@ async def unstake_all( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - tuple[bool, str]: - A tuple containing: - - `True` and a success message if the unstake operation succeeded; - - `False` and an error message otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. Example: # If you would like to unstake all stakes in all subnets safely, use default `rate_tolerance` or pass your @@ -6035,7 +6021,7 @@ async def unstake_multiple( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - bool: `True` if the batch unstaking is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. This function allows for strategic reallocation or withdrawal of stakes, aligning with the dynamic stake management aspect of the Bittensor network. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index aa8d91456b..92aedbe1a1 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -94,6 +94,7 @@ SS58_FORMAT, TYPE_REGISTRY, ) +from bittensor.core.types import ExtrinsicResponse, ParamWithTypes, SubtensorMixin from bittensor.core.types import ( ParamWithTypes, Salt, @@ -3237,29 +3238,32 @@ def sign_and_send_extrinsic( period: Optional[int] = None, nonce_key: str = "hotkey", raise_error: bool = False, - ) -> tuple[bool, str]: + calling_function: Optional[str] = None, + ) -> ExtrinsicResponse: """ Helper method to sign and submit an extrinsic call to chain. - Arguments: - call (scalecodec.types.GenericCall): a prepared Call object - wallet (bittensor_wallet.Wallet): the wallet whose coldkey will be used to sign the extrinsic - wait_for_inclusion (bool): whether to wait until the extrinsic call is included on the chain - wait_for_finalization (bool): whether to wait until the extrinsic call is finalized on the chain - sign_with (str): the wallet's keypair to use for the signing. Options are "coldkey", "hotkey", "coldkeypub" - use_nonce (bool): unique identifier for the transaction related with hot/coldkey. - 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: + call: a prepared Call object + wallet: the wallet whose coldkey will be used to sign the extrinsic + wait_for_inclusion: whether to wait until the extrinsic call is included on the chain + wait_for_finalization: whether to wait until the extrinsic call is finalized on the chain + sign_with: the wallet's keypair to use for the signing. Options are "coldkey", "hotkey", "coldkeypub" + use_nonce: unique identifier for the transaction related with hot/coldkey. + 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. nonce_key: the type on nonce to use. Options are "hotkey" or "coldkey". raise_error: raises the relevant exception rather than returning `False` if unsuccessful. + calling_function: the name of the calling function. Returns: - (success, error message) + ExtrinsicResponse: The result object of the extrinsic execution. Raises: SubstrateRequestException: Substrate request exception. """ + extrinsic_response = ExtrinsicResponse(extrinsic_function=calling_function) possible_keys = ("coldkey", "hotkey", "coldkeypub") if sign_with not in possible_keys: raise AttributeError( @@ -3280,32 +3284,44 @@ def sign_and_send_extrinsic( if period is not None: extrinsic_data["era"] = {"period": period} - extrinsic = self.substrate.create_signed_extrinsic(**extrinsic_data) + extrinsic_response.extrinsic = self.substrate.create_signed_extrinsic( + **extrinsic_data + ) try: response = self.substrate.submit_extrinsic( - extrinsic, + extrinsic=extrinsic_response.extrinsic, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - message = "Not waiting for finalization or inclusion." - logging.debug(f"{message}. Extrinsic: {extrinsic}") - return True, message + extrinsic_response.message = ( + "Not waiting for finalization or inclusion." + ) + return extrinsic_response if response.is_success: - return True, "" + extrinsic_response.message = "" + return extrinsic_response + + response_error_message = response.error_message if raise_error: - raise ChainError.from_error(response.error_message) + raise ChainError.from_error(response_error_message) - return False, format_error_message(response.error_message) + extrinsic_response.success = False + extrinsic_response.message = format_error_message(response_error_message) + extrinsic_response.error = response_error_message + return extrinsic_response - except SubstrateRequestException as e: + except SubstrateRequestException as error: if raise_error: raise - return False, format_error_message(e) + extrinsic_response.success = False + extrinsic_response.message = format_error_message(error) + extrinsic_response.error = error + return extrinsic_response # Extrinsics ======================================================================================================= @@ -3348,7 +3364,7 @@ def add_stake( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - bool: ``True`` if the staking is successful, ``False`` otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. 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 @@ -3401,9 +3417,7 @@ def add_liquidity( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity` method to enable/disable user liquidity. @@ -3450,7 +3464,7 @@ def add_stake_multiple( 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. + ExtrinsicResponse: The result object of the extrinsic execution. This function is essential for managing stakes across multiple neurons, reflecting the dynamic and collaborative nature of the Bittensor network. @@ -3475,7 +3489,7 @@ def burned_register( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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. @@ -3491,7 +3505,7 @@ def burned_register( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - bool: ``True`` if the registration is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ if netuid == 0: @@ -3549,9 +3563,7 @@ def commit_weights( 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. + ExtrinsicResponse: The result object of the extrinsic execution. 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. @@ -3617,9 +3629,7 @@ def modify_liquidity( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. Example: import bittensor as bt @@ -3696,7 +3706,7 @@ def move_stake( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - success: True if the stake movement was successful. + ExtrinsicResponse: The result object of the extrinsic execution. """ amount = check_and_convert_to_balance(amount) return move_stake_extrinsic( @@ -3757,7 +3767,7 @@ def register( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: ``True`` if the registration is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. This function facilitates the entry of new neurons into the network, supporting the decentralized growth and scalability of the Bittensor ecosystem. @@ -3787,7 +3797,7 @@ def register_subnet( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Registers a new subnetwork on the Bittensor network. @@ -3801,7 +3811,7 @@ def register_subnet( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ return register_subnet_extrinsic( subtensor=self, @@ -3838,9 +3848,7 @@ def remove_liquidity( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: - Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity` @@ -3894,9 +3902,7 @@ def reveal_weights( mechid: The subnet mechanism unique identifier. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. This function allows neurons to reveal their previously committed weight distribution, ensuring transparency and accountability within the Bittensor network. @@ -3938,7 +3944,7 @@ def root_register( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Register neuron by recycling some TAO. @@ -3952,7 +3958,7 @@ def root_register( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - bool: ``True`` if the registration is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ return root_register_extrinsic( @@ -3972,7 +3978,7 @@ def root_set_pending_childkey_cooldown( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Sets the pending childkey cooldown. Parameters: @@ -3986,9 +3992,7 @@ def root_set_pending_childkey_cooldown( wait_for_finalization (bool): 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: This operation can only be successfully performed if your wallet has root privileges. """ @@ -4052,7 +4056,7 @@ def set_children( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ Allows a coldkey to set children-keys. @@ -4069,9 +4073,7 @@ def set_children( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ return set_children_extrinsic( subtensor=self, @@ -4111,9 +4113,7 @@ def set_delegate_take( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Raises: DelegateTakeTooHigh: Delegate take is too high. @@ -4188,9 +4188,7 @@ def set_subnet_identity( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ return set_subnet_identity_extrinsic( subtensor=self, @@ -4250,9 +4248,7 @@ def set_weights( mechid: The subnet mechanism unique identifier. 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. + ExtrinsicResponse: The result object of the extrinsic execution. 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. @@ -4362,7 +4358,7 @@ def serve_axon( 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. + ExtrinsicResponse: The result object of the extrinsic execution. By registering an Axon, the neuron becomes an active part of the network's distributed computing infrastructure, contributing to the collective intelligence of Bittensor. @@ -4407,7 +4403,7 @@ def set_commitment( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: `True` if the commitment was successful, `False` otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. Example: # Commit some data to subnet 1 @@ -4454,9 +4450,7 @@ def start_call( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ return start_call_extrinsic( subtensor=self, @@ -4508,7 +4502,7 @@ def swap_stake( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: True if the extrinsic was successful. + ExtrinsicResponse: The result object of the extrinsic execution. 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: @@ -4558,9 +4552,7 @@ def toggle_user_liquidity( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: The call can be executed successfully by the subnet owner only. """ @@ -4604,7 +4596,7 @@ def transfer( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - `True` if the transferring was successful, otherwise `False`. + ExtrinsicResponse: The result object of the extrinsic execution. """ if amount is not None: amount = check_and_convert_to_balance(amount) @@ -4652,7 +4644,7 @@ def transfer_stake( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - success: True if the transfer was successful. + ExtrinsicResponse: The result object of the extrinsic execution. """ amount = check_and_convert_to_balance(amount) return transfer_stake_extrinsic( @@ -4707,7 +4699,7 @@ def unstake( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - bool: ``True`` if the unstaking process is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. 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 @@ -4756,10 +4748,7 @@ def unstake_all( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - tuple[bool, str]: - A tuple containing: - - `True` and a success message if the unstake operation succeeded; - - `False` and an error message otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. Example: # If you would like to unstake all stakes in all subnets safely: @@ -4845,7 +4834,7 @@ def unstake_multiple( wait_for_finalization: Whether to wait for finalization of the extrinsic. Returns: - bool: ``True`` if the batch unstaking is successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. This function allows for strategic reallocation or withdrawal of stakes, aligning with the dynamic stake management aspect of the Bittensor network. From bc648ebec58f4cfb8f971a00c08be7403e79aa6e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 16:36:44 -0700 Subject: [PATCH 180/416] improve `set_children_extrinsic` + `root_set_pending_childkey_cooldown_extrinsic` --- bittensor/core/extrinsics/asyncex/children.py | 54 +++++++++---------- bittensor/core/extrinsics/children.py | 51 +++++++++--------- 2 files changed, 54 insertions(+), 51 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index cde329ba8c..c08a7f5510 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Optional -from bittensor.utils import float_to_u64, unlock_key +from bittensor.utils import float_to_u64, unlock_key, get_function_name +from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -16,7 +17,7 @@ async def set_children_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Allows a coldkey to set children-keys. @@ -34,9 +35,7 @@ async def set_children_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Raises: DuplicateChild: There are duplicates in the list of children. @@ -54,7 +53,9 @@ async def set_children_extrinsic( unlock = unlock_key(wallet, raise_error=raise_error) if not unlock.success: - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) async with subtensor.substrate as substrate: call = await substrate.compose_call( @@ -73,22 +74,24 @@ async def set_children_extrinsic( }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) if not wait_for_finalization and not wait_for_inclusion: - return True, message + return response - if success: - return True, "Success with `set_children_extrinsic` response." + if response.success: + response.message = f"Success with {response.extrinsic_function} response." + return response - return True, message + return response async def root_set_pending_childkey_cooldown_extrinsic( @@ -99,7 +102,7 @@ async def root_set_pending_childkey_cooldown_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Allows a root coldkey to set children-keys. @@ -115,14 +118,12 @@ async def root_set_pending_childkey_cooldown_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ - unlock = unlock_key(wallet) - - if not unlock.success: - return False, unlock.message + if not (unlock := unlock_key(wallet)).success: + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) async with subtensor.substrate as substrate: call = await substrate.compose_call( @@ -137,22 +138,21 @@ async def root_set_pending_childkey_cooldown_extrinsic( call_params={"call": call}, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) if not wait_for_finalization and not wait_for_inclusion: - return True, message + return response - if success: - return ( - True, - "Success with `root_set_pending_childkey_cooldown_extrinsic` response.", - ) + if response.success: + response.message = f"Success with {response.extrinsic_function} response." + return response - return True, message + return response diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index 46c4b5129c..3440d5aa3b 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -1,5 +1,7 @@ from typing import TYPE_CHECKING, Optional -from bittensor.utils import float_to_u64, unlock_key + +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import float_to_u64, unlock_key, get_function_name if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -16,7 +18,7 @@ def set_children_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -): +) -> "ExtrinsicResponse": """ Allows a coldkey to set children-keys. @@ -34,9 +36,7 @@ def set_children_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Raises: DuplicateChild: There are duplicates in the list of children. @@ -54,7 +54,9 @@ def set_children_extrinsic( unlock = unlock_key(wallet, raise_error=raise_error) if not unlock.success: - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -72,22 +74,24 @@ def set_children_extrinsic( }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) if not wait_for_finalization and not wait_for_inclusion: - return True, message + return response - if success: - return True, "Success with `set_children_extrinsic` response." + if response.success: + response.message = f"Success with {response.extrinsic_function} response." + return response - return True, message + return response def root_set_pending_childkey_cooldown_extrinsic( @@ -98,7 +102,7 @@ def root_set_pending_childkey_cooldown_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Allows a root coldkey to set children-keys. @@ -114,14 +118,14 @@ def root_set_pending_childkey_cooldown_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ unlock = unlock_key(wallet) if not unlock.success: - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -135,22 +139,21 @@ def root_set_pending_childkey_cooldown_extrinsic( call_params={"call": call}, ) - success, message = subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) if not wait_for_finalization and not wait_for_inclusion: - return True, message + return response - if success: - return ( - True, - "Success with `root_set_pending_childkey_cooldown_extrinsic` response.", - ) + if response.success: + response.message = f"Success with {response.extrinsic_function} response." + return response - return True, message + return response From f02b097344f0a67e080692d6a0e12dda52905aa4 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 17:13:02 -0700 Subject: [PATCH 181/416] improve `ExtrinsicResponse` --- bittensor/core/types.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index eb4ed2c5ac..2ba7e29505 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -336,3 +336,16 @@ def __str__(self): def __repr__(self): return repr((self.success, self.message)) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, tuple): + return (self.success, self.message) == other + if isinstance(other, ExtrinsicResponse): + return ( + self.success == other.success + and self.message == other.message + and self.error == other.error + and self.extrinsic_function == other.extrinsic_function + and self.extrinsic == other.extrinsic + ) + return super().__eq__(other) From 1fc2795a56bf9ef6e8637648460be3855b7ef8de Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 17:15:35 -0700 Subject: [PATCH 182/416] fix test_children.py --- tests/unit_tests/extrinsics/asyncex/test_children.py | 7 +++++-- tests/unit_tests/extrinsics/test_children.py | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_children.py b/tests/unit_tests/extrinsics/asyncex/test_children.py index 48c0940170..36f227a84f 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_children.py +++ b/tests/unit_tests/extrinsics/asyncex/test_children.py @@ -1,6 +1,7 @@ import pytest from bittensor.core.extrinsics.asyncex import children +from bittensor.core.types import ExtrinsicResponse @pytest.mark.asyncio @@ -19,7 +20,7 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): substrate = subtensor.substrate.__aenter__.return_value substrate.compose_call = mocker.AsyncMock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(True, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -54,6 +55,7 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + calling_function="set_children_extrinsic", ) assert success is True @@ -71,7 +73,7 @@ async def test_root_set_pending_childkey_cooldown_extrinsic( substrate = subtensor.substrate.__aenter__.return_value substrate.compose_call = mocker.AsyncMock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(True, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -90,6 +92,7 @@ async def test_root_set_pending_childkey_cooldown_extrinsic( raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + calling_function="root_set_pending_childkey_cooldown_extrinsic", ) 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 12bb96f09b..6895e64bdc 100644 --- a/tests/unit_tests/extrinsics/test_children.py +++ b/tests/unit_tests/extrinsics/test_children.py @@ -1,4 +1,5 @@ from bittensor.core.extrinsics import children +from bittensor.core.types import ExtrinsicResponse def test_set_children_extrinsic(subtensor, mocker, fake_wallet): @@ -15,7 +16,7 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): subtensor.substrate.compose_call = mocker.Mock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(True, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -50,6 +51,7 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + calling_function="set_children_extrinsic", ) assert success is True @@ -63,7 +65,7 @@ def test_root_set_pending_childkey_cooldown_extrinsic(subtensor, mocker, fake_wa subtensor.substrate.compose_call = mocker.Mock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(True, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -82,6 +84,7 @@ def test_root_set_pending_childkey_cooldown_extrinsic(subtensor, mocker, fake_wa raise_error=False, wait_for_inclusion=True, wait_for_finalization=False, + calling_function="root_set_pending_childkey_cooldown_extrinsic", ) assert success is True assert "Success" in message From 2df5a68cfd66dcdb4a8897d15d8eac10e5181dba Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 17:41:42 -0700 Subject: [PATCH 183/416] update `commit_reveal_extrinsic` + tests --- bittensor/core/async_subtensor.py | 21 ++-- bittensor/core/extrinsics/asyncex/children.py | 3 +- .../core/extrinsics/asyncex/commit_reveal.py | 117 +++++++++--------- bittensor/core/extrinsics/commit_reveal.py | 27 ++-- bittensor/core/subtensor.py | 29 +++-- .../extrinsics/asyncex/test_commit_reveal.py | 43 ++++--- .../extrinsics/test_commit_reveal.py | 16 ++- tests/unit_tests/test_async_subtensor.py | 15 ++- tests/unit_tests/test_subtensor.py | 6 +- tests/unit_tests/test_subtensor_extended.py | 12 +- 10 files changed, 165 insertions(+), 124 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 7e635ad61b..88969a4436 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5401,7 +5401,7 @@ async def set_weights( wait_for_inclusion: bool = True, wait_for_finalization: bool = True, mechid: int = 0, - ): + ) -> ExtrinsicResponse: """ Sets the weight vector for a neuron acting as a validator, specifying the weights assigned to subnet miners based on their performance evaluation. @@ -5445,14 +5445,15 @@ async def _blocks_weight_limit() -> bool: return bslu > wrl retries = 0 - success = False - message = "No attempt made. Perhaps it is too soon to set weights!" + response = ExtrinsicResponse( + False, "No attempt made. Perhaps it is too soon to set weights!" + ) if ( uid := await self.get_uid_for_hotkey_on_subnet( wallet.hotkey.ss58_address, netuid ) ) is None: - return ( + return ExtrinsicResponse( False, f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}", ) @@ -5462,14 +5463,14 @@ async def _blocks_weight_limit() -> bool: while ( retries < max_retries - and success is False + and response.success is False and await _blocks_weight_limit() ): logging.info( f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) - success, message = await commit_timelocked_mechanism_weights_extrinsic( + response = await commit_timelocked_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -5485,13 +5486,13 @@ async def _blocks_weight_limit() -> bool: wait_for_finalization=wait_for_finalization, ) retries += 1 - return success, message + return response else: # go with `set_mechanism_weights_extrinsic` while ( retries < max_retries - and success is False + and response.success is False and await _blocks_weight_limit() ): try: @@ -5499,7 +5500,7 @@ async def _blocks_weight_limit() -> bool: f"Setting weights for subnet #[blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) - success, message = await set_mechanism_weights_extrinsic( + response = await set_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -5516,7 +5517,7 @@ async def _blocks_weight_limit() -> bool: logging.error(f"Error setting weights: {e}") retries += 1 - return success, message + return response async def serve_axon( self, diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index c08a7f5510..92204f957e 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING, Optional -from bittensor.utils import float_to_u64, unlock_key, get_function_name + from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import float_to_u64, unlock_key, get_function_name if TYPE_CHECKING: from bittensor_wallet import Wallet diff --git a/bittensor/core/extrinsics/asyncex/commit_reveal.py b/bittensor/core/extrinsics/asyncex/commit_reveal.py index fd1bd00f53..efce0825c3 100644 --- a/bittensor/core/extrinsics/asyncex/commit_reveal.py +++ b/bittensor/core/extrinsics/asyncex/commit_reveal.py @@ -7,6 +7,8 @@ from numpy.typing import NDArray from bittensor.core.settings import version_as_int +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import get_function_name, unlock_key from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import convert_and_normalize_weights_and_uids @@ -29,7 +31,7 @@ async def commit_reveal_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Commits and reveals weights for a given subtensor and wallet with provided uids and weights. @@ -50,67 +52,68 @@ async def commit_reveal_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ - try: - uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - - current_block = await subtensor.substrate.get_block(None) - subnet_hyperparameters = await subtensor.get_subnet_hyperparameters( - netuid, block_hash=current_block["header"]["hash"] + if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() ) - 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["header"]["number"], - netuid=netuid, - subnet_reveal_period_epochs=subnet_reveal_period_epochs, - block_time=block_time, - hotkey=wallet.hotkey.public_key, - ) + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - 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]..." - ) + current_block = await subtensor.substrate.get_block(None) + subnet_hyperparameters = await subtensor.get_subnet_hyperparameters( + netuid, block_hash=current_block["header"]["hash"] + ) + tempo = subnet_hyperparameters.tempo + subnet_reveal_period_epochs = subnet_hyperparameters.commit_reveal_period - 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, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - sign_with="hotkey", - period=period, - raise_error=raise_error, - ) + # 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["header"]["number"], + netuid=netuid, + subnet_reveal_period_epochs=subnet_reveal_period_epochs, + block_time=block_time, + hotkey=wallet.hotkey.public_key, + ) - if not success: - logging.error(message) - return False, message + 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.success( - f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." - ) - return True, f"reveal_round:{reveal_round}" + 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, + }, + ) + response = 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, + raise_error=raise_error, + calling_function=get_function_name(), + ) + + if not response.success: + logging.error(response.message) + return response - 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]." + ) + response.message = f"reveal_round:{reveal_round}" + response.data = reveal_round + return response diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py index 88077d4183..49655b6000 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -1,7 +1,8 @@ """This module provides sync functionality for commit reveal in the Bittensor network.""" from typing import Union, TYPE_CHECKING, Optional - +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import get_function_name, unlock_key import numpy as np from bittensor_drand import get_encrypted_commit from numpy.typing import NDArray @@ -30,7 +31,7 @@ def commit_reveal_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Commits and reveals weights for a given subtensor and wallet with provided uids and weights. @@ -51,10 +52,13 @@ def commit_reveal_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ + if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) current_block = subtensor.get_current_block() @@ -92,7 +96,7 @@ def commit_reveal_extrinsic( "commit_reveal_version": commit_reveal_version, }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -100,13 +104,16 @@ def commit_reveal_extrinsic( sign_with="hotkey", period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if not success: - logging.error(message) - return False, message + if not response.success: + logging.error(response.message) + return response logging.success( f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." ) - return True, f"reveal_round:{reveal_round}" + response.message = f"reveal_round:{reveal_round}" + response.data = reveal_round + return response diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 92aedbe1a1..eb431e4090 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4223,7 +4223,7 @@ def set_weights( wait_for_inclusion: bool = True, wait_for_finalization: bool = True, mechid: int = 0, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ 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 @@ -4263,12 +4263,13 @@ def _blocks_weight_limit() -> bool: return bslu > wrl retries = 0 - success = False - message = "No attempt made. Perhaps it is too soon to commit weights!" + response = ExtrinsicResponse( + False, "No attempt made. Perhaps it is too soon to set weights!" + ) if ( uid := self.get_uid_for_hotkey_on_subnet(wallet.hotkey.ss58_address, netuid) ) is None: - return ( + return ExtrinsicResponse( False, f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}", ) @@ -4276,13 +4277,17 @@ def _blocks_weight_limit() -> bool: if self.commit_reveal_enabled(netuid=netuid): # go with `commit_timelocked_mechanism_weights_extrinsic` extrinsic - while retries < max_retries and success is False and _blocks_weight_limit(): + while ( + retries < max_retries + and response.success is False + and _blocks_weight_limit() + ): logging.info( f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) try: - success, message = commit_timelocked_mechanism_weights_extrinsic( + response = commit_timelocked_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -4301,17 +4306,21 @@ def _blocks_weight_limit() -> bool: logging.error(f"Error setting weights: {e}") retries += 1 - return success, message + return response else: # go with `set_mechanism_weights_extrinsic` - while retries < max_retries and success is False and _blocks_weight_limit(): + while ( + retries < max_retries + and response.success is False + and _blocks_weight_limit() + ): try: logging.info( f"Setting weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) - success, message = set_mechanism_weights_extrinsic( + response = set_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -4328,7 +4337,7 @@ def _blocks_weight_limit() -> bool: logging.error(f"Error setting weights: {e}") retries += 1 - return success, message + return response def serve_axon( self, diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py index 7e46e436be..b255efcbe6 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py @@ -1,9 +1,10 @@ -from bittensor.core import async_subtensor as subtensor_module -from bittensor.core.chain_data import SubnetHyperparameters -from bittensor.core.extrinsics.asyncex import commit_reveal as async_commit_reveal +import numpy as np import pytest import torch -import numpy as np + +from bittensor.core.chain_data import SubnetHyperparameters +from bittensor.core.extrinsics.asyncex import commit_reveal as async_commit_reveal +from bittensor.core.types import ExtrinsicResponse @pytest.fixture @@ -73,7 +74,9 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( ) 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") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) mock_block = mocker.patch.object( subtensor.substrate, @@ -122,6 +125,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( sign_with="hotkey", period=None, raise_error=False, + calling_function="commit_reveal_extrinsic", ) @@ -145,7 +149,9 @@ async def test_commit_reveal_v3_extrinsic_success_with_numpy( ) 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") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) mocker.patch.object(subtensor.substrate, "get_block_number", return_value=1) mocker.patch.object( @@ -178,6 +184,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_numpy( sign_with="hotkey", period=None, raise_error=False, + calling_function="commit_reveal_extrinsic", ) @@ -206,7 +213,9 @@ async def test_commit_reveal_v3_extrinsic_response_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, "Failed") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(False, "Failed"), ) mocker.patch.object(subtensor.substrate, "get_block_number", return_value=1) mocker.patch.object( @@ -237,6 +246,7 @@ async def test_commit_reveal_v3_extrinsic_response_false( sign_with="hotkey", period=None, raise_error=False, + calling_function="commit_reveal_extrinsic", ) @@ -255,14 +265,11 @@ async def test_commit_reveal_v3_extrinsic_exception(mocker, subtensor, fake_wall ) # Call - success, message = await async_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 + with pytest.raises(Exception): + await async_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/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index 92dc926f51..d1f2de6c1d 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -5,6 +5,7 @@ from bittensor.core import subtensor as subtensor_module from bittensor.core.chain_data import SubnetHyperparameters from bittensor.core.extrinsics import commit_reveal +from bittensor.core.types import ExtrinsicResponse @pytest.fixture @@ -74,7 +75,9 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( ) 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") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) mock_block = mocker.patch.object(subtensor, "get_current_block", return_value=1) mock_hyperparams = mocker.patch.object( @@ -120,6 +123,7 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( sign_with="hotkey", period=None, raise_error=False, + calling_function="commit_reveal_extrinsic", ) @@ -142,7 +146,9 @@ def test_commit_reveal_v3_extrinsic_success_with_numpy( ) 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") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) mocker.patch.object(subtensor, "get_current_block", return_value=1) mocker.patch.object( @@ -175,6 +181,7 @@ def test_commit_reveal_v3_extrinsic_success_with_numpy( sign_with="hotkey", period=None, raise_error=False, + calling_function="commit_reveal_extrinsic", ) @@ -202,7 +209,9 @@ def test_commit_reveal_v3_extrinsic_response_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, "Failed") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(False, "Failed"), ) mocker.patch.object(subtensor, "get_current_block", return_value=1) mocker.patch.object( @@ -233,6 +242,7 @@ def test_commit_reveal_v3_extrinsic_response_false( sign_with="hotkey", period=None, raise_error=False, + calling_function="commit_reveal_extrinsic", ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 686945c4c3..3e556d0738 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -15,6 +15,7 @@ StakeInfo, SelectiveMetagraphIndex, ) +from bittensor.core.types import ExtrinsicResponse from bittensor.utils import U64_MAX from bittensor.utils.balance import Balance from tests.helpers.helpers import assert_submit_signed_extrinsic @@ -180,7 +181,7 @@ async def test_burned_register(mock_substrate, subtensor, fake_wallet, mocker): return_value=Balance(1), ) - success = await subtensor.burned_register( + success, _ = await subtensor.burned_register( fake_wallet, netuid=1, ) @@ -223,7 +224,7 @@ async def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, m return_value=False, ) - success = await subtensor.burned_register( + success, _ = await subtensor.burned_register( fake_wallet, netuid=0, ) @@ -1730,7 +1731,7 @@ async def fake_is_success(): call=fake_call, keypair=fake_wallet.coldkey ) mocked_submit_extrinsic.assert_called_once_with( - fake_extrinsic, + extrinsic=fake_extrinsic, wait_for_inclusion=True, wait_for_finalization=True, ) @@ -1781,7 +1782,7 @@ async def fake_error_message(): call=fake_call, keypair=fake_wallet.coldkey ) mocked_submit_extrinsic.assert_called_once_with( - fake_extrinsic, + extrinsic=fake_extrinsic, wait_for_inclusion=True, wait_for_finalization=True, ) @@ -1818,7 +1819,7 @@ async def test_sign_and_send_extrinsic_success_without_inclusion_finalization( ) mocked_submit_extrinsic.assert_awaited_once() mocked_submit_extrinsic.assert_called_once_with( - fake_extrinsic, + extrinsic=fake_extrinsic, wait_for_inclusion=False, wait_for_finalization=False, ) @@ -2773,7 +2774,9 @@ async def test_set_weights_success(subtensor, fake_wallet, mocker): mocked_weights_rate_limit = mocker.AsyncMock(return_value=1) subtensor.weights_rate_limit = mocked_weights_rate_limit - mocked_set_weights_extrinsic = mocker.AsyncMock(return_value=(True, "Success")) + mocked_set_weights_extrinsic = mocker.AsyncMock( + return_value=ExtrinsicResponse(True, "Success") + ) mocker.patch.object( async_subtensor, "set_mechanism_weights_extrinsic", mocked_set_weights_extrinsic ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index a9b722cce1..2260ebd724 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2,7 +2,7 @@ import unittest.mock as mock import datetime from unittest.mock import MagicMock - +from bittensor.core.types import ExtrinsicResponse import pytest from bittensor_wallet import Wallet from async_substrate_interface import sync_substrate @@ -1159,7 +1159,7 @@ def test_set_weights(subtensor, mocker, fake_wallet): fake_wait_for_finalization = False fake_max_retries = 5 - expected_result = (True, None) + expected_result = ExtrinsicResponse(True, None) mocked_get_uid_for_hotkey_on_subnet = mocker.MagicMock() subtensor.get_uid_for_hotkey_on_subnet = mocked_get_uid_for_hotkey_on_subnet @@ -3058,7 +3058,7 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): mocked_commit_timelocked_mechanism_weights_extrinsic = mocker.patch.object( subtensor_module, "commit_timelocked_mechanism_weights_extrinsic" ) - mocked_commit_timelocked_mechanism_weights_extrinsic.return_value = ( + mocked_commit_timelocked_mechanism_weights_extrinsic.return_value = ExtrinsicResponse( True, "Weights committed successfully", ) diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 01a365bd31..4142e89b51 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -177,7 +177,7 @@ def test_burned_register(mock_substrate, subtensor, fake_wallet, mocker): ) mocker.patch.object(subtensor, "get_balance") - success = subtensor.burned_register( + success, _ = subtensor.burned_register( fake_wallet, netuid=1, ) @@ -216,7 +216,7 @@ def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, mocker) return_value=False, ) - success = subtensor.burned_register( + success, _ = subtensor.burned_register( fake_wallet, netuid=0, ) @@ -1208,7 +1208,7 @@ def test_register_subnet(mock_substrate, subtensor, fake_wallet, mocker, success result = subtensor.register_subnet(fake_wallet) - assert result is success + assert result.success is success assert_submit_signed_extrinsic( substrate=mock_substrate, @@ -1228,7 +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 @@ -1244,7 +1244,7 @@ def test_root_register(mock_substrate, subtensor, fake_wallet, mocker): subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=False ) - success = subtensor.root_register(fake_wallet) + success, _ = subtensor.root_register(fake_wallet) assert success is True @@ -1281,7 +1281,7 @@ def test_root_register_is_already_registered( subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=True ) - success = subtensor.root_register(fake_wallet) + success, _ = subtensor.root_register(fake_wallet) assert success is True From 55bd7721ce7fa99eebc5953ea12111a93f5ac415 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 17:56:46 -0700 Subject: [PATCH 184/416] update migration.md --- migration.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/migration.md b/migration.md index 22b0a56574..9688d5cc19 100644 --- a/migration.md +++ b/migration.md @@ -223,4 +223,8 @@ wait_for_finalization: bool = False, - 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 +- [x] `.set_weights_extrinsic` and `subtensor.set_weights` + +All extrinsics and related subtensor calls now return an object of class `bittensor.core.types.ExtrinsicResponse` +Additional changes in extrinsics: + - with `commit_reveal_extrinsic` or `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the numeric `reveal_round`. \ No newline at end of file From 24762048de9238a1665edff6583967d595c8d15e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 19:14:18 -0700 Subject: [PATCH 185/416] update `commit_weights_extrinsic` + `reveal_weights_extrinsic` + `set_weights_extrinsic` --- bittensor/core/async_subtensor.py | 47 +++++++------ bittensor/core/extrinsics/asyncex/weights.py | 70 +++++++++++-------- bittensor/core/extrinsics/weights.py | 65 ++++++++++------- bittensor/core/subtensor.py | 47 +++++++------ .../extrinsics/asyncex/test_weights.py | 22 ++++-- .../extrinsics/test_commit_weights.py | 11 +-- .../unit_tests/extrinsics/test_set_weights.py | 3 +- tests/unit_tests/test_async_subtensor.py | 2 +- tests/unit_tests/test_subtensor.py | 9 +-- 9 files changed, 161 insertions(+), 115 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 88969a4436..4510ed2bc0 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4704,7 +4704,7 @@ async def commit_weights( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, mechid: int = 0, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ 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. @@ -4734,8 +4734,9 @@ async def commit_weights( See also: , """ retries = 0 - success = False - message = "No attempt made. Perhaps it is too soon to commit weights!" + response = ExtrinsicResponse( + False, "No attempt made. Perhaps it is too soon to commit weights!" + ) logging.info( f"Committing weights with params: " @@ -4743,9 +4744,9 @@ async def commit_weights( f"version_key=[blue]{version_key}[/blue]" ) - while retries < max_retries and success is False: + while retries < max_retries and response.success is False: try: - success, message = await commit_mechanism_weights_extrinsic( + response = await commit_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -4758,13 +4759,12 @@ async def commit_weights( period=period, raise_error=raise_error, ) - if success: - break - except Exception as e: - logging.error(f"Error committing weights: {e}") + except Exception as error: + response.error = error if not response.error else response.error + logging.error(f"Error committing weights: {error}") retries += 1 - return success, message + return response async def modify_liquidity( self, @@ -5046,7 +5046,7 @@ async def reveal_weights( wait_for_finalization: bool = False, max_retries: int = 5, mechid: int = 0, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ 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. @@ -5075,12 +5075,13 @@ async def reveal_weights( See also: , """ retries = 0 - success = False - message = "No attempt made. Perhaps it is too soon to reveal weights!" + response = ExtrinsicResponse( + False, "No attempt made. Perhaps it is too soon to reveal weights!" + ) - while retries < max_retries and success is False: + while retries < max_retries and response.success is False: try: - success, message = await reveal_mechanism_weights_extrinsic( + response = await reveal_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -5094,13 +5095,12 @@ async def reveal_weights( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - if success: - break - except Exception as e: - logging.error(f"Error revealing weights: {e}") + except Exception as error: + response.error = error if not response.error else response.error + logging.error(f"Error revealing weights: {error}") retries += 1 - return success, message + return response async def root_set_pending_childkey_cooldown( self, @@ -5513,9 +5513,10 @@ async def _blocks_weight_limit() -> bool: 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 + except Exception as error: + response.error = error if not response.error else response.error + logging.error(f"Error setting weights: {error}") + retries += 1 return response diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 53aa650f2d..a841ca4c5b 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -1,12 +1,12 @@ """This module provides async functionality for working with weights in the Bittensor network.""" from typing import Union, TYPE_CHECKING, Optional - +from bittensor.core.types import ExtrinsicResponse 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 import get_function_name, unlock_key from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -28,7 +28,7 @@ async def commit_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ 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. @@ -46,13 +46,16 @@ async def commit_weights_extrinsic( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper error handling and user interaction when required. """ + if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="commit_weights", @@ -61,7 +64,7 @@ async def commit_weights_extrinsic( "commit_hash": commit_hash, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -71,13 +74,14 @@ async def commit_weights_extrinsic( nonce_key="hotkey", sign_with="hotkey", raise_error=raise_error, + calling_function=get_function_name(), ) - if success: - logging.info(message) + if response.success: + logging.info(response.message) else: - logging.error(f"{get_function_name}: {message}") - return success, message + logging.error(f"{get_function_name}: {response.message}") + return response async def reveal_weights_extrinsic( @@ -92,7 +96,7 @@ async def reveal_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ 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. @@ -113,13 +117,16 @@ async def reveal_weights_extrinsic( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper - error handling and user interaction when required. + error handling and user interaction when required. """ + if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="reveal_weights", @@ -131,7 +138,7 @@ async def reveal_weights_extrinsic( "version_key": version_key, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -141,13 +148,14 @@ async def reveal_weights_extrinsic( nonce_key="hotkey", use_nonce=True, raise_error=raise_error, + calling_function=get_function_name(), ) - if success: - logging.info(message) + if response.success: + logging.info(response.message) else: - logging.error(f"{get_function_name}: {message}") - return success, message + logging.error(f"{get_function_name}: {response.message}") + return response async def set_weights_extrinsic( @@ -161,7 +169,7 @@ async def set_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Sets the given weights and values on chain for a given wallet hotkey account. @@ -180,10 +188,13 @@ async def set_weights_extrinsic( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ + if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + # Convert types. uids, weights = convert_uids_and_weights(uids, weights) @@ -206,7 +217,7 @@ async def set_weights_extrinsic( "version_key": version_key, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -216,11 +227,12 @@ async def set_weights_extrinsic( nonce_key="hotkey", sign_with="hotkey", raise_error=raise_error, + calling_function=get_function_name(), ) - if success: + if response.success: logging.info("Successfully set weights and Finalized.") else: - logging.error(f"{get_function_name}: {message}") + logging.error(f"{get_function_name}: {response.message}") - return success, message + return response diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index 5aa68543e7..933b0f236f 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -6,7 +6,8 @@ from numpy.typing import NDArray from bittensor.core.settings import version_as_int -from bittensor.utils import get_function_name +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import get_function_name, unlock_key from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -28,7 +29,7 @@ def commit_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ 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. @@ -46,13 +47,15 @@ def commit_weights_extrinsic( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper error handling and user interaction when required. """ + if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -62,7 +65,7 @@ def commit_weights_extrinsic( "commit_hash": commit_hash, }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -72,13 +75,14 @@ def commit_weights_extrinsic( sign_with="hotkey", nonce_key="hotkey", raise_error=raise_error, + calling_function=get_function_name(), ) - if success: - logging.info(message) + if response.success: + logging.info(response.message) else: - logging.error(f"{get_function_name()}: {message}") - return success, message + logging.error(f"{get_function_name()}: {response.message}") + return response # TODO: deprecate in SDKv10 @@ -94,7 +98,7 @@ def reveal_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ 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. @@ -115,13 +119,15 @@ def reveal_weights_extrinsic( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper error handling and user interaction when required. """ + if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -135,7 +141,7 @@ def reveal_weights_extrinsic( }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -145,13 +151,14 @@ def reveal_weights_extrinsic( sign_with="hotkey", nonce_key="hotkey", raise_error=raise_error, + calling_function=get_function_name(), ) - if success: - logging.info(message) + if response.success: + logging.info(response.message) else: - logging.error(f"{get_function_name()}: {message}") - return success, message + logging.error(f"{get_function_name()}: {response.message}") + return response def set_weights_extrinsic( @@ -165,7 +172,7 @@ def set_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Sets the given weights and values on a chain for a wallet hotkey account. @@ -184,10 +191,13 @@ def set_weights_extrinsic( wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Returns: - tuple[bool, str]: - `True` if the weight commitment is successful, `False` otherwise. - `msg` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ + if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + # Convert types. uids, weights = convert_uids_and_weights(uids, weights) @@ -210,7 +220,7 @@ def set_weights_extrinsic( "version_key": version_key, }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -220,10 +230,11 @@ def set_weights_extrinsic( nonce_key="hotkey", sign_with="hotkey", raise_error=raise_error, + calling_function=get_function_name(), ) - if success: + if response.success: logging.info("Successfully set weights and Finalized.") else: - logging.error(f"{get_function_name}: {message}") - return success, message + logging.error(f"{get_function_name}: {response.message}") + return response diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index eb431e4090..b909b436bd 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3542,7 +3542,7 @@ def commit_weights( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, mechid: int = 0, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ 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. @@ -3569,8 +3569,9 @@ def commit_weights( time, enhancing transparency and accountability within the Bittensor network. """ retries = 0 - success = False - message = "No attempt made. Perhaps it is too soon to commit weights!" + response = ExtrinsicResponse( + False, "No attempt made. Perhaps it is too soon to commit weights!" + ) logging.info( f"Committing weights with params: " @@ -3578,9 +3579,9 @@ def commit_weights( f"version_key=[blue]{version_key}[/blue]" ) - while retries < max_retries and success is False: + while retries < max_retries and response.success is False: try: - success, message = commit_mechanism_weights_extrinsic( + response = commit_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -3596,10 +3597,11 @@ def commit_weights( if success: break except Exception as e: + response.error = error if not response.error else response.error logging.error(f"Error committing weights: {e}") retries += 1 - return success, message + return response def modify_liquidity( self, @@ -3881,7 +3883,7 @@ def reveal_weights( wait_for_inclusion: bool = False, wait_for_finalization: bool = False, mechid: int = 0, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ 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. @@ -3910,12 +3912,13 @@ def reveal_weights( See also: , """ retries = 0 - success = False - message = "No attempt made. Perhaps it is too soon to reveal weights!" + response = ExtrinsicResponse( + False, "No attempt made. Perhaps it is too soon to reveal weights!" + ) - while retries < max_retries and success is False: + while retries < max_retries and response.success is False: try: - success, message = reveal_mechanism_weights_extrinsic( + response = reveal_mechanism_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -3929,13 +3932,12 @@ def reveal_weights( period=period, raise_error=raise_error, ) - if success: - break - except Exception as e: - logging.error(f"Error revealing weights: {e}") + except Exception as error: + response.error = error if not response.error else response.error + logging.error(f"Error revealing weights: {error}") retries += 1 - return success, message + return response def root_register( self, @@ -4302,8 +4304,10 @@ def _blocks_weight_limit() -> bool: wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - except Exception as e: - logging.error(f"Error setting weights: {e}") + + except Exception as error: + response.error = error if not response.error else response.error + logging.error(f"Error setting weights: {error}") retries += 1 return response @@ -4333,9 +4337,10 @@ def _blocks_weight_limit() -> bool: 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 + except Exception as error: + response.error = error if not response.error else response.error + logging.error(f"Error setting weights: {error}") + retries += 1 return response diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index dc1bbdc8f0..73ad9a38f2 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -2,6 +2,7 @@ from bittensor.core import async_subtensor from bittensor.core.extrinsics.asyncex import weights as async_weights from bittensor.core.settings import version_as_int +from bittensor.core.types import ExtrinsicResponse @pytest.mark.asyncio @@ -27,7 +28,7 @@ async def test_set_weights_extrinsic_success_with_finalization( 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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -66,6 +67,7 @@ async def test_set_weights_extrinsic_success_with_finalization( nonce_key="hotkey", sign_with="hotkey", raise_error=False, + calling_function="set_weights_extrinsic", ) assert result is True assert message == "" @@ -94,7 +96,9 @@ async def test_set_weights_extrinsic_no_waiting(subtensor, fake_wallet, mocker): mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", - return_value=(True, "Not waiting for finalization or inclusion."), + return_value=ExtrinsicResponse( + True, "Not waiting for finalization or inclusion." + ), ) # Call @@ -133,6 +137,7 @@ async def test_set_weights_extrinsic_no_waiting(subtensor, fake_wallet, mocker): nonce_key="hotkey", sign_with="hotkey", raise_error=False, + calling_function="set_weights_extrinsic", ) assert result is True assert message == "Not waiting for finalization or inclusion." @@ -159,7 +164,9 @@ async def test_set_weights_extrinsic_failure(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=(False, "Test error message") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(False, "Test error message"), ) # Call @@ -198,6 +205,7 @@ async def test_set_weights_extrinsic_failure(subtensor, fake_wallet, mocker): nonce_key="hotkey", sign_with="hotkey", raise_error=False, + calling_function="set_weights_extrinsic", ) assert result is False assert message == "Test error message" @@ -249,7 +257,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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -278,6 +286,7 @@ async def test_commit_weights_extrinsic_success(subtensor, fake_wallet, mocker): raise_error=False, nonce_key="hotkey", sign_with="hotkey", + calling_function="commit_weights_extrinsic", ) assert result is True assert message == "" @@ -292,7 +301,9 @@ async def test_commit_weights_extrinsic_failure(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=(False, "Commit failed.") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(False, "Commit failed."), ) # Call @@ -321,6 +332,7 @@ async def test_commit_weights_extrinsic_failure(subtensor, fake_wallet, mocker): raise_error=False, nonce_key="hotkey", sign_with="hotkey", + calling_function="commit_weights_extrinsic", ) 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 1757cf6e33..c54ba78fa8 100644 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ b/tests/unit_tests/extrinsics/test_commit_weights.py @@ -4,13 +4,14 @@ commit_weights_extrinsic, reveal_weights_extrinsic, ) +from bittensor.core.types import ExtrinsicResponse @pytest.mark.parametrize( "sign_and_send_return", [ - (True, "Success"), - (False, "Failure"), + ExtrinsicResponse(True, "Success"), + ExtrinsicResponse(False, "Failure"), ], ids=["success", "failure"], ) @@ -52,6 +53,7 @@ def test_commit_weights_extrinsic(subtensor, fake_wallet, mocker, sign_and_send_ raise_error=False, sign_with="hotkey", use_nonce=True, + calling_function="commit_weights_extrinsic", ) assert result == sign_and_send_return @@ -59,8 +61,8 @@ def test_commit_weights_extrinsic(subtensor, fake_wallet, mocker, sign_and_send_ @pytest.mark.parametrize( "sign_and_send_return", [ - (True, "Success"), - (False, "Failure"), + ExtrinsicResponse(True, "Success"), + ExtrinsicResponse(False, "Failure"), ], ids=["success", "failure"], ) @@ -115,5 +117,6 @@ def test_reveal_weights_extrinsic(subtensor, fake_wallet, mocker, sign_and_send_ raise_error=False, sign_with="hotkey", use_nonce=True, + calling_function="reveal_weights_extrinsic", ) assert result == sign_and_send_return diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py index 5690571ef9..382a36d5c7 100644 --- a/tests/unit_tests/extrinsics/test_set_weights.py +++ b/tests/unit_tests/extrinsics/test_set_weights.py @@ -5,6 +5,7 @@ from bittensor.core.extrinsics.weights import ( set_weights_extrinsic, ) +from bittensor.core.types import ExtrinsicResponse from bittensor.core.subtensor import Subtensor @@ -74,7 +75,7 @@ def test_set_weights_extrinsic( patch.object( mock_subtensor, "sign_and_send_extrinsic", - return_value=(expected_success, expected_message), + return_value=ExtrinsicResponse(expected_success, expected_message), ), ): result, message = set_weights_extrinsic( diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 3e556d0738..4cf26e8cb6 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2870,7 +2870,7 @@ async def test_commit_weights_success(subtensor, fake_wallet, mocker): fake_weights = [100, 200, 300] max_retries = 3 - mocked_commit_weights_extrinsic = mocker.AsyncMock(return_value=(True, "Success")) + mocked_commit_weights_extrinsic = mocker.AsyncMock(return_value=ExtrinsicResponse(True, "Success")) mocker.patch.object( async_subtensor, "commit_mechanism_weights_extrinsic", diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 2260ebd724..080a2737a5 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -25,6 +25,7 @@ determine_chain_endpoint_and_network, ) from bittensor.utils.balance import Balance +from bittensor.core.types import ExtrinsicResponse U16_MAX = 65535 U64_MAX = 18446744073709551615 @@ -1842,7 +1843,7 @@ def test_commit_weights(subtensor, fake_wallet, mocker): wait_for_finalization = False max_retries = 5 - expected_result = (True, None) + expected_result = ExtrinsicResponse(True, None) mocked_commit_weights_extrinsic = mocker.patch.object( subtensor_module, "commit_mechanism_weights_extrinsic", @@ -1886,7 +1887,7 @@ def test_reveal_weights(subtensor, fake_wallet, mocker): uids = [1, 2, 3, 4] weights = [0.1, 0.2, 0.3, 0.4] salt = [4, 2, 2, 1] - expected_result = (True, None) + expected_result = ExtrinsicResponse(True, None) mocked_extrinsic = mocker.patch.object( subtensor_module, "reveal_mechanism_weights_extrinsic", @@ -1930,7 +1931,7 @@ def test_reveal_weights_false(subtensor, fake_wallet, mocker): weights = [0.1, 0.2, 0.3, 0.4] salt = [4, 2, 2, 1] - expected_result = ( + expected_result = ExtrinsicResponse( False, "No attempt made. Perhaps it is too soon to reveal weights!", ) @@ -1950,7 +1951,7 @@ def test_reveal_weights_false(subtensor, fake_wallet, mocker): ) # Assertion - assert result == expected_result + assert result == mocked_extrinsic.return_value assert mocked_extrinsic.call_count == 5 From 2ec519989d36b07e43381322986a1259a4502ca8 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 19:30:43 -0700 Subject: [PATCH 186/416] update `add_liquidity_extrinsic` + `modify_liquidity_extrinsic` + `remove_liquidity_extrinsic` + `toggle_user_liquidity_extrinsic` --- bittensor/core/async_subtensor.py | 8 ++-- .../core/extrinsics/asyncex/liquidity.py | 31 ++++++++---- bittensor/core/extrinsics/liquidity.py | 47 ++++++++++--------- bittensor/core/subtensor.py | 8 ++-- .../extrinsics/asyncex/test_liquidity.py | 4 ++ tests/unit_tests/extrinsics/test_liquidity.py | 4 ++ 6 files changed, 64 insertions(+), 38 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 4510ed2bc0..eeb3fb6602 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4560,7 +4560,7 @@ async def add_liquidity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ Adds liquidity to the specified price range. @@ -4777,7 +4777,7 @@ async def modify_liquidity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Modifies liquidity in liquidity position by adding or removing liquidity from it. Parameters: @@ -4997,7 +4997,7 @@ async def remove_liquidity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Remove liquidity and credit balances back to wallet's hotkey stake. Parameters: @@ -5726,7 +5726,7 @@ async def toggle_user_liquidity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Allow to toggle user liquidity for specified subnet. Parameters: diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index fc98f46631..eb5cc8f5e4 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -1,6 +1,7 @@ from typing import Optional, TYPE_CHECKING -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import unlock_key, get_function_name from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import price_to_tick @@ -22,7 +23,7 @@ async def add_liquidity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Adds liquidity to the specified price range. @@ -51,7 +52,9 @@ async def add_liquidity_extrinsic( """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) tick_low = price_to_tick(price_low.tao) tick_high = price_to_tick(price_high.tao) @@ -76,6 +79,7 @@ async def add_liquidity_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) @@ -90,7 +94,7 @@ async def modify_liquidity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Modifies liquidity in liquidity position by adding or removing liquidity from it. Parameters: @@ -117,7 +121,9 @@ async def modify_liquidity_extrinsic( """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = await subtensor.substrate.compose_call( call_module="Swap", @@ -138,6 +144,7 @@ async def modify_liquidity_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) @@ -151,7 +158,7 @@ async def remove_liquidity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Remove liquidity and credit balances back to wallet's hotkey stake. Parameters: @@ -177,7 +184,9 @@ async def remove_liquidity_extrinsic( """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = await subtensor.substrate.compose_call( call_module="Swap", @@ -197,6 +206,7 @@ async def remove_liquidity_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) @@ -209,7 +219,7 @@ async def toggle_user_liquidity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Allow to toggle user liquidity for specified subnet. Parameters: @@ -231,7 +241,9 @@ async def toggle_user_liquidity_extrinsic( """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = await subtensor.substrate.compose_call( call_module="Swap", @@ -246,4 +258,5 @@ async def toggle_user_liquidity_extrinsic( wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) diff --git a/bittensor/core/extrinsics/liquidity.py b/bittensor/core/extrinsics/liquidity.py index dbb974082a..56c1f6d13e 100644 --- a/bittensor/core/extrinsics/liquidity.py +++ b/bittensor/core/extrinsics/liquidity.py @@ -1,6 +1,7 @@ from typing import Optional, TYPE_CHECKING -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import unlock_key, get_function_name from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import price_to_tick @@ -22,7 +23,7 @@ def add_liquidity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Adds liquidity to the specified price range. @@ -42,16 +43,16 @@ def add_liquidity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. 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) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) tick_low = price_to_tick(price_low.tao) tick_high = price_to_tick(price_high.tao) @@ -76,6 +77,7 @@ def add_liquidity_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) @@ -90,7 +92,7 @@ def modify_liquidity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Modifies liquidity in liquidity position by adding or removing liquidity from it. Parameters: @@ -108,16 +110,16 @@ def modify_liquidity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: Modifying 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) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="Swap", @@ -138,6 +140,7 @@ def modify_liquidity_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) @@ -151,7 +154,7 @@ def remove_liquidity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Remove liquidity and credit balances back to wallet's hotkey stake. Parameters: @@ -168,16 +171,16 @@ def remove_liquidity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. 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) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="Swap", @@ -197,6 +200,7 @@ def remove_liquidity_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) @@ -209,7 +213,7 @@ def toggle_user_liquidity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Allow to toggle user liquidity for specified subnet. Parameters: @@ -225,13 +229,13 @@ def toggle_user_liquidity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="Swap", @@ -246,4 +250,5 @@ def toggle_user_liquidity_extrinsic( wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b909b436bd..0e01dcca1b 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3398,7 +3398,7 @@ def add_liquidity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ Adds liquidity to the specified price range. @@ -3614,7 +3614,7 @@ def modify_liquidity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Modifies liquidity in liquidity position by adding or removing liquidity from it. Parameters: @@ -3834,7 +3834,7 @@ def remove_liquidity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Remove liquidity and credit balances back to wallet's hotkey stake. Parameters: @@ -4551,7 +4551,7 @@ def toggle_user_liquidity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Allow to toggle user liquidity for specified subnet. Parameters: diff --git a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py index c572a518a9..430c8b522f 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py +++ b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py @@ -47,6 +47,7 @@ async def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="add_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -92,6 +93,7 @@ async def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="modify_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -134,6 +136,7 @@ async def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="remove_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -174,5 +177,6 @@ async def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, + calling_function="toggle_user_liquidity_extrinsic", ) 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 ce60b34bd3..eda7580d34 100644 --- a/tests/unit_tests/extrinsics/test_liquidity.py +++ b/tests/unit_tests/extrinsics/test_liquidity.py @@ -45,6 +45,7 @@ def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="add_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -89,6 +90,7 @@ def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="modify_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -130,6 +132,7 @@ def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="remove_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -169,5 +172,6 @@ def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, + calling_function="toggle_user_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value From ad89f332465b985f90c1ebc551cfcb339e683024 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 5 Sep 2025 19:31:28 -0700 Subject: [PATCH 187/416] add unlock logging error message --- bittensor/core/extrinsics/asyncex/children.py | 3 +++ bittensor/core/extrinsics/asyncex/commit_reveal.py | 1 + bittensor/core/extrinsics/asyncex/take.py | 6 +++--- bittensor/core/extrinsics/asyncex/weights.py | 3 +++ bittensor/core/extrinsics/children.py | 5 +++-- bittensor/core/extrinsics/commit_reveal.py | 1 + bittensor/core/extrinsics/take.py | 5 +++-- bittensor/core/extrinsics/weights.py | 3 +++ tests/unit_tests/extrinsics/test_root.py | 9 +++++---- 9 files changed, 25 insertions(+), 11 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 92204f957e..b652d2b881 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -2,6 +2,7 @@ from bittensor.core.types import ExtrinsicResponse from bittensor.utils import float_to_u64, unlock_key, get_function_name +from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -54,6 +55,7 @@ async def set_children_extrinsic( unlock = unlock_key(wallet, raise_error=raise_error) if not unlock.success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) @@ -122,6 +124,7 @@ async def root_set_pending_childkey_cooldown_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet)).success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) diff --git a/bittensor/core/extrinsics/asyncex/commit_reveal.py b/bittensor/core/extrinsics/asyncex/commit_reveal.py index efce0825c3..85bb840a81 100644 --- a/bittensor/core/extrinsics/asyncex/commit_reveal.py +++ b/bittensor/core/extrinsics/asyncex/commit_reveal.py @@ -55,6 +55,7 @@ async def commit_reveal_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) diff --git a/bittensor/core/extrinsics/asyncex/take.py b/bittensor/core/extrinsics/asyncex/take.py index 3840821c08..a4f4a6e6e4 100644 --- a/bittensor/core/extrinsics/asyncex/take.py +++ b/bittensor/core/extrinsics/asyncex/take.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING, Optional from bittensor_wallet.bittensor_wallet import Wallet - +from bittensor.utils.btlogging import logging from bittensor.utils import unlock_key if TYPE_CHECKING: @@ -39,8 +39,8 @@ async def increase_take_extrinsic( """ unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: + logging.error(unlock.message) return False, unlock.message call = await subtensor.substrate.compose_call( @@ -93,8 +93,8 @@ async def decrease_take_extrinsic( - False and an error message if the submission fails or the wallet cannot be unlocked. """ unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: + logging.error(unlock.message) return False, unlock.message call = await subtensor.substrate.compose_call( diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index a841ca4c5b..49db1361b8 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -52,6 +52,7 @@ async def commit_weights_extrinsic( error handling and user interaction when required. """ if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) @@ -123,6 +124,7 @@ async def reveal_weights_extrinsic( error handling and user interaction when required. """ if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) @@ -191,6 +193,7 @@ async def set_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index 3440d5aa3b..7d13e69a30 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -2,6 +2,7 @@ from bittensor.core.types import ExtrinsicResponse from bittensor.utils import float_to_u64, unlock_key, get_function_name +from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -52,8 +53,8 @@ def set_children_extrinsic( bittensor_wallet.errors.PasswordError: Decryption failed or wrong password for decryption provided. """ unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) @@ -121,8 +122,8 @@ def root_set_pending_childkey_cooldown_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ unlock = unlock_key(wallet) - if not unlock.success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py index 49655b6000..af1ede6f89 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -55,6 +55,7 @@ def commit_reveal_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) diff --git a/bittensor/core/extrinsics/take.py b/bittensor/core/extrinsics/take.py index 3effd7699b..8f5af392f8 100644 --- a/bittensor/core/extrinsics/take.py +++ b/bittensor/core/extrinsics/take.py @@ -3,6 +3,7 @@ from bittensor_wallet.bittensor_wallet import Wallet from bittensor.utils import unlock_key +from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor.core.subtensor import Subtensor @@ -38,8 +39,8 @@ def increase_take_extrinsic( - False and an error message if the submission fails or the wallet cannot be unlocked. """ unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: + logging.error(unlock.message) return False, unlock.message call = subtensor.substrate.compose_call( @@ -92,8 +93,8 @@ def decrease_take_extrinsic( - False and an error message if the submission fails or the wallet cannot be unlocked. """ unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: + logging.error(unlock.message) return False, unlock.message call = subtensor.substrate.compose_call( diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index 933b0f236f..db5de001c9 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -53,6 +53,7 @@ def commit_weights_extrinsic( error handling and user interaction when required. """ if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) @@ -125,6 +126,7 @@ def reveal_weights_extrinsic( error handling and user interaction when required. """ if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) @@ -194,6 +196,7 @@ def set_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() ) diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index 4a4c11e8e8..193be0f3e1 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -1,6 +1,7 @@ import pytest -from bittensor.core.subtensor import Subtensor from bittensor.core.extrinsics import root +from bittensor.core.subtensor import Subtensor +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance @@ -69,7 +70,7 @@ def test_root_register_extrinsic( mocked_sign_and_send_extrinsic = mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic", - return_value=(registration_success, "Error registering"), + return_value=ExtrinsicResponse(registration_success, "Error registering"), ) mocker.patch.object( mock_subtensor.substrate, @@ -90,7 +91,7 @@ def test_root_register_extrinsic( wait_for_finalization=wait_for_finalization, ) # Assert - assert result == expected_result + assert result.success == expected_result if not hotkey_registered[0]: mock_subtensor.substrate.compose_call.assert_called_once_with( @@ -119,7 +120,7 @@ def test_root_register_extrinsic_insufficient_balance( return_value=Balance(0), ) - success = root.root_register_extrinsic( + success, _ = root.root_register_extrinsic( subtensor=mock_subtensor, wallet=mock_wallet, ) From 120015b4c98d77ad42b248bd8055bf3dc6258b24 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 01:54:57 -0700 Subject: [PATCH 188/416] support indexing for success and message --- bittensor/core/types.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 2ba7e29505..9011b85e78 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -349,3 +349,14 @@ def __eq__(self, other: Any) -> bool: and self.extrinsic == other.extrinsic ) return super().__eq__(other) + + def __getitem__(self, index: int) -> Any: + if index == 0: + return self.success + elif index == 1: + return self.message + else: + raise IndexError("ExtrinsicResponse only supports indices 0 (success) and 1 (message).") + + def __len__(self): + return 2 From 329e364fa12d71a18fdc5eccbf6c9bc593292f8d Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 09:45:06 -0700 Subject: [PATCH 189/416] set `response.is_success="Success"` for all extrinsic in positive case --- 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 eeb3fb6602..06ad16a0f6 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4464,7 +4464,7 @@ async def sign_and_send_extrinsic( return extrinsic_response if await response.is_success: - extrinsic_response.message = "" + extrinsic_response.message = "Success" return extrinsic_response response_error_message = await response.error_message diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 0e01dcca1b..318167f104 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3301,7 +3301,7 @@ def sign_and_send_extrinsic( return extrinsic_response if response.is_success: - extrinsic_response.message = "" + extrinsic_response.message = "Success" return extrinsic_response response_error_message = response.error_message From 8148270c3d8bbc4aefd57f623907b10e07adefbb Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 09:46:28 -0700 Subject: [PATCH 190/416] update migration.md --- migration.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/migration.md b/migration.md index 9688d5cc19..2d04c5cdc4 100644 --- a/migration.md +++ b/migration.md @@ -227,4 +227,5 @@ wait_for_finalization: bool = False, All extrinsics and related subtensor calls now return an object of class `bittensor.core.types.ExtrinsicResponse` Additional changes in extrinsics: - - with `commit_reveal_extrinsic` or `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the numeric `reveal_round`. \ No newline at end of file + - with `commit_reveal_extrinsic` or `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the numeric `reveal_round`. + - in positive case, all extrinsics return `response.message = "Success"` From dde8f129f3c1c49dfc991d1e690f0e704ea0181d Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 12:31:18 -0700 Subject: [PATCH 191/416] update return in success case for children extrinsics --- bittensor/core/extrinsics/asyncex/children.py | 2 -- bittensor/core/extrinsics/children.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index b652d2b881..5ed14b7929 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -91,7 +91,6 @@ async def set_children_extrinsic( return response if response.success: - response.message = f"Success with {response.extrinsic_function} response." return response return response @@ -156,7 +155,6 @@ async def root_set_pending_childkey_cooldown_extrinsic( return response if response.success: - response.message = f"Success with {response.extrinsic_function} response." return response return response diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index 7d13e69a30..f18a91ee52 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -89,7 +89,6 @@ def set_children_extrinsic( return response if response.success: - response.message = f"Success with {response.extrinsic_function} response." return response return response @@ -154,7 +153,6 @@ def root_set_pending_childkey_cooldown_extrinsic( return response if response.success: - response.message = f"Success with {response.extrinsic_function} response." return response return response From 33e05f5ad8aca1c25d5b9075438c8d13ed71073e Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 12:31:52 -0700 Subject: [PATCH 192/416] update return in success case for `commit_reveal_extrinsic` extrinsics + data keep dict with `reveal_round` --- bittensor/core/extrinsics/asyncex/commit_reveal.py | 13 ++++++------- bittensor/core/extrinsics/commit_reveal.py | 13 ++++++------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/commit_reveal.py b/bittensor/core/extrinsics/asyncex/commit_reveal.py index 85bb840a81..88da6b9736 100644 --- a/bittensor/core/extrinsics/asyncex/commit_reveal.py +++ b/bittensor/core/extrinsics/asyncex/commit_reveal.py @@ -108,13 +108,12 @@ async def commit_reveal_extrinsic( calling_function=get_function_name(), ) - if not response.success: - logging.error(response.message) + if response.success: + logging.success( + f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." + ) + response.data = {"reveal_round": reveal_round} return response - logging.success( - f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." - ) - response.message = f"reveal_round:{reveal_round}" - response.data = reveal_round + logging.error(response.message) return response diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py index af1ede6f89..4f8ab2224e 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -108,13 +108,12 @@ def commit_reveal_extrinsic( calling_function=get_function_name(), ) - if not response.success: - logging.error(response.message) + if response.success: + logging.success( + f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." + ) + response.data = {"reveal_round": reveal_round} return response - logging.success( - f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." - ) - response.message = f"reveal_round:{reveal_round}" - response.data = reveal_round + logging.error(response.message) return response From 4b615112b1740a8ebda66a3e295f8762ef34b8df Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 12:32:00 -0700 Subject: [PATCH 193/416] update migration.md --- migration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migration.md b/migration.md index 2d04c5cdc4..f7053cada2 100644 --- a/migration.md +++ b/migration.md @@ -227,5 +227,5 @@ wait_for_finalization: bool = False, All extrinsics and related subtensor calls now return an object of class `bittensor.core.types.ExtrinsicResponse` Additional changes in extrinsics: - - with `commit_reveal_extrinsic` or `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the numeric `reveal_round`. + - with `commit_reveal_extrinsic` or `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the `{"reveal_round": reveal_round}`. - in positive case, all extrinsics return `response.message = "Success"` From 4feed5fb6e41bd3ed6acc4324ab607aed1d13446 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 12:41:40 -0700 Subject: [PATCH 194/416] update `test_commit_and_reveal_weights_cr4` --- tests/e2e_tests/test_commit_reveal.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 5cc48dab2a..24369067ca 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -174,7 +174,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # commit_block is the block when weights were committed on the chain (transaction block) expected_commit_block = subtensor.block + 1 # Commit weights - success, message = subtensor.extrinsics.set_weights( + response = subtensor.extrinsics.set_weights( wallet=alice_wallet, netuid=alice_subnet_netuid, mechid=mechid, @@ -187,11 +187,11 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle ) # Assert committing was a success - assert success is True, message - assert bool(re.match(r"reveal_round:\d+", message)) + assert response.success is True, response.message + assert response.data.get("reveal_round") is not None # Parse expected reveal_round - expected_reveal_round = int(message.split(":")[1]) + expected_reveal_round = response.data.get("reveal_round") logging.console.success( f"Successfully set weights: uids {weight_uids}, weights {weight_vals}, reveal_round: {expected_reveal_round}" ) @@ -425,7 +425,7 @@ async def test_commit_and_reveal_weights_cr4_async( # 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( + response = await async_subtensor.extrinsics.set_weights( wallet=alice_wallet, netuid=alice_subnet_netuid, mechid=mechid, @@ -438,11 +438,11 @@ async def test_commit_and_reveal_weights_cr4_async( ) # Assert committing was a success - assert success is True, message - assert bool(re.match(r"reveal_round:\d+", message)) + assert response.success is True, response.message + assert response.data.get("reveal_round") is not None # Parse expected reveal_round - expected_reveal_round = int(message.split(":")[1]) + expected_reveal_round = response.data.get("reveal_round") logging.console.success( f"Successfully set weights: uids {weight_uids}, weights {weight_vals}, reveal_round: {expected_reveal_round}" ) From 075979cdd1717f98e41585e3b573ebd88b852e68 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 12:42:29 -0700 Subject: [PATCH 195/416] improve `ExtrinsicResponse` --- bittensor/core/types.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 9011b85e78..2a1210ad57 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -356,7 +356,9 @@ def __getitem__(self, index: int) -> Any: elif index == 1: return self.message else: - raise IndexError("ExtrinsicResponse only supports indices 0 (success) and 1 (message).") + raise IndexError( + "ExtrinsicResponse only supports indices 0 (success) and 1 (message)." + ) def __len__(self): return 2 From 12115ba1b570a659712a8c2ec5bdcc43908f2557 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 12:54:51 -0700 Subject: [PATCH 196/416] update `liquidity` docstrings --- bittensor/core/extrinsics/asyncex/liquidity.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index eb5cc8f5e4..c2826b83e2 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -43,9 +43,7 @@ async def add_liquidity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. @@ -112,9 +110,7 @@ async def modify_liquidity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: Modifying is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. @@ -175,9 +171,7 @@ async def remove_liquidity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. Note: Adding is allowed even when user liquidity is enabled in specified subnet. Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. @@ -235,9 +229,7 @@ async def toggle_user_liquidity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) From 5674a3d26db6b1381f5405f425cffc241e94ce97 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 14:20:08 -0700 Subject: [PATCH 197/416] update `test_commit_reveal_v3_extrinsic*` --- tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py | 4 ++-- tests/unit_tests/extrinsics/test_commit_reveal.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py index b255efcbe6..4c52b6b457 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py @@ -102,7 +102,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( # Asserts assert success is True - assert message == "reveal_round:1" + assert message == "Success" mocked_convert_weights_and_uids_for_emit.assert_called_once_with( fake_uids, fake_weights ) @@ -173,7 +173,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_numpy( # Asserts assert success is True - assert message == "reveal_round:0" + assert message == "Success" mock_convert.assert_called_once_with(fake_uids, fake_weights) mock_encode_drand.assert_called_once() mocked_sign_and_send_extrinsic.assert_awaited_once_with( diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index d1f2de6c1d..c3ddc17383 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -100,7 +100,7 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( # Asserts assert success is True - assert message == "reveal_round:1" + assert message == "Success" mocked_convert_weights_and_uids_for_emit.assert_called_once_with( fake_uids, fake_weights ) @@ -170,7 +170,7 @@ def test_commit_reveal_v3_extrinsic_success_with_numpy( # Asserts assert success is True - assert message == "reveal_round:0" + assert message == "Success" mock_convert.assert_called_once_with(fake_uids, fake_weights) mock_encode_drand.assert_called_once() mocked_sign_and_send_extrinsic.assert_called_once_with( From e1061b1af7b38f7146b3013ecfc8b337d9692853 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 14:20:43 -0700 Subject: [PATCH 198/416] update `children_extrinsic*` --- tests/unit_tests/extrinsics/asyncex/test_children.py | 8 ++++++-- tests/unit_tests/extrinsics/test_children.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_children.py b/tests/unit_tests/extrinsics/asyncex/test_children.py index 36f227a84f..b89b4dc0ec 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_children.py +++ b/tests/unit_tests/extrinsics/asyncex/test_children.py @@ -20,7 +20,9 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): substrate = subtensor.substrate.__aenter__.return_value substrate.compose_call = mocker.AsyncMock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) # Call @@ -73,7 +75,9 @@ async def test_root_set_pending_childkey_cooldown_extrinsic( substrate = subtensor.substrate.__aenter__.return_value substrate.compose_call = mocker.AsyncMock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) # Call diff --git a/tests/unit_tests/extrinsics/test_children.py b/tests/unit_tests/extrinsics/test_children.py index 6895e64bdc..1b0710abb5 100644 --- a/tests/unit_tests/extrinsics/test_children.py +++ b/tests/unit_tests/extrinsics/test_children.py @@ -16,7 +16,9 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): subtensor.substrate.compose_call = mocker.Mock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) # Call @@ -65,7 +67,9 @@ def test_root_set_pending_childkey_cooldown_extrinsic(subtensor, mocker, fake_wa subtensor.substrate.compose_call = mocker.Mock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) # Call From 79600f711caf0b171a14224393c6e0c285993155 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 14:27:42 -0700 Subject: [PATCH 199/416] update `transfer_stake_extrinsic`, `swap_stake_extrinsic` and `move_stake_extrinsic` + related tests --- bittensor/core/async_subtensor.py | 6 +- .../core/extrinsics/asyncex/move_stake.py | 417 +++++++++--------- bittensor/core/extrinsics/move_stake.py | 411 +++++++++-------- bittensor/core/subtensor.py | 6 +- tests/e2e_tests/test_staking.py | 84 ++-- tests/unit_tests/test_async_subtensor.py | 2 +- tests/unit_tests/test_subtensor_extended.py | 118 ++--- 7 files changed, 493 insertions(+), 551 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 06ad16a0f6..e716018193 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4851,7 +4851,7 @@ async def move_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Moves stake to a different hotkey and/or subnet. @@ -5665,7 +5665,7 @@ async def swap_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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. @@ -5814,7 +5814,7 @@ async def transfer_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Transfers stake from one subnet to another while changing the coldkey owner. diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index a401350251..525f780b0a 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -1,6 +1,8 @@ import asyncio from typing import TYPE_CHECKING, Optional +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import get_function_name from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -49,7 +51,7 @@ async def transfer_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Transfers stake from one coldkey to another in the Bittensor network. @@ -69,7 +71,7 @@ async def transfer_stake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the transfer was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ amount.set_unit(netuid=origin_netuid) @@ -85,71 +87,65 @@ async def transfer_stake_extrinsic( destination_netuid=destination_netuid, ) if stake_in_origin < amount: - logging.error( - f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {hotkey_ss58}. " - f"Stake: {stake_in_origin}, amount: {amount}" - ) - return False + message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." + logging.error(f":cross_mark: [red]Failed[/red]: {message}") + return ExtrinsicResponse(False, message) + + logging.info( + f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " + f"[blue]{destination_coldkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " + f"[yellow]{destination_netuid}[/yellow]" + ) + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="transfer_stake", + call_params={ + "destination_coldkey": destination_coldkey_ss58, + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, + ) + + response = 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, + calling_function=get_function_name(), + ) - try: + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + ) logging.info( - f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " - f"[blue]{destination_coldkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " - f"[yellow]{destination_netuid}[/yellow]" + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": destination_coldkey_ss58, - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, + logging.info( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" ) - 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, - ) + return response - if success: - if not wait_for_finalization and not wait_for_inclusion: - return True - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = await _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=destination_coldkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" - ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" - ) - - return True - else: - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return False - - except Exception as e: - logging.error(f":cross_mark: [red]Failed[/red]: {str(e)}") - return False + logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + return response async def swap_stake_extrinsic( @@ -166,7 +162,7 @@ async def swap_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Swaps stake from one subnet to another for a given hotkey in the Bittensor network. @@ -189,7 +185,7 @@ async def swap_stake_extrinsic( Returns: - success (bool): True if the swap was successful. + ExtrinsicResponse: The result object of the extrinsic execution. """ amount.set_unit(netuid=origin_netuid) @@ -203,102 +199,98 @@ async def swap_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, ) + if stake_in_origin < amount: - logging.error( - f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {hotkey_ss58}. " - f"Stake: {stake_in_origin}, amount: {amount}" + message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." + logging.error(f":cross_mark: [red]Failed[/red]: {message}") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) + + call_params = { + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + } + + if safe_swapping: + origin_pool, destination_pool = await asyncio.gather( + subtensor.subnet(netuid=origin_netuid), + subtensor.subnet(netuid=destination_netuid), ) - return False + swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao + swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - try: - call_params = { - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - } - - if safe_swapping: - origin_pool, destination_pool = await asyncio.gather( - subtensor.subnet(netuid=origin_netuid), - subtensor.subnet(netuid=destination_netuid), - ) - swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao - swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - - logging.info( - f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]\n" - f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " - f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" - ) - call_params.update( - { - "limit_price": swap_rate_ratio_with_tolerance, - "allow_partial": allow_partial_stake, - } - ) - call_function = "swap_stake_limit" - else: - logging.info( - f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]" - ) - call_function = "swap_stake" - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, + logging.info( + f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]\n" + f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " + f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" + ) + call_params.update( + { + "limit_price": swap_rate_ratio_with_tolerance, + "allow_partial": allow_partial_stake, + } + ) + call_function = "swap_stake_limit" + else: + logging.info( + f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]" ) + call_function = "swap_stake" + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, + ) - success, err_msg = 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, + response = 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, + calling_function=get_function_name(), + ) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + ) + logging.info( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.info( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" ) - if success: - if not wait_for_finalization and not wait_for_inclusion: - return True - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = await _get_stake_in_origin_and_dest( - subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" - ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" - ) - - return True - else: - 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." - ) - else: - logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}") - return False - - except Exception as e: - logging.error(f":cross_mark: [red]Failed[/red]: {str(e)}") - return False + return response + + if safe_swapping and "Custom error: 8" in response.message: + logging.error( + ":cross_mark: [red]Failed[/red]: Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." + ) + else: + logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + + return response async def move_stake_extrinsic( @@ -314,7 +306,7 @@ async def move_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Moves stake from one hotkey to another within subnets in the Bittensor network. @@ -335,13 +327,14 @@ async def move_stake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: True if the move was successful. Otherwise, False. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not amount and not move_all_stake: - logging.error( - ":cross_mark: [red]Failed[/red]: Please specify an `amount` or `move_all_stake` argument to move stake." + message = ( + "Please specify an `amount` or `move_all_stake` argument to move stake." ) - return False + logging.error(f":cross_mark: [red]Failed[/red]: {message}") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) # Check sufficient stake stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( @@ -357,69 +350,63 @@ async def move_stake_extrinsic( amount = stake_in_origin elif stake_in_origin < amount: - logging.error( - f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {origin_hotkey_ss58}. " - f"Stake: {stake_in_origin}, amount: {amount}" - ) - return False + message = f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." + logging.error(f":cross_mark: [red]Failed[/red]: {message}") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) amount.set_unit(netuid=origin_netuid) - try: + logging.info( + 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]" + ) + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="move_stake", + call_params={ + "origin_hotkey": origin_hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_hotkey": destination_hotkey_ss58, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, + ) + + response = 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, + calling_function=get_function_name(), + ) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + subtensor=subtensor, + 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, + destination_netuid=destination_netuid, + ) logging.info( - 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]" + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": origin_hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey_ss58, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, + logging.info( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" ) - 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, - ) + return response - if success: - if not wait_for_finalization and not wait_for_inclusion: - return True - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = await _get_stake_in_origin_and_dest( - subtensor=subtensor, - 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, - destination_netuid=destination_netuid, - ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" - ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" - ) - - return True - else: - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return False - - except Exception as e: - logging.error(f":cross_mark: [red]Failed[/red]: {str(e)}") - return False + logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + return response diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index b3a9d54e95..fc5fe29bf3 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -1,7 +1,9 @@ from typing import Optional, TYPE_CHECKING +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging +from bittensor.utils import get_function_name if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -46,7 +48,7 @@ def transfer_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Transfers stake from one subnet to another while changing the coldkey owner. @@ -66,7 +68,7 @@ def transfer_stake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success (bool): True if the transfer was successful. + ExtrinsicResponse: The result object of the extrinsic execution. """ amount.set_unit(netuid=origin_netuid) @@ -82,71 +84,65 @@ def transfer_stake_extrinsic( destination_coldkey_ss58=destination_coldkey_ss58, ) if stake_in_origin < amount: - logging.error( - f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {hotkey_ss58}. " - f"Stake: {stake_in_origin}, amount: {amount}" - ) - return False + message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." + logging.error(f":cross_mark: [red]Failed[/red]: {message}") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) + + logging.info( + f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey [" + f"blue]{destination_coldkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " + f"[yellow]{destination_netuid}[/yellow]" + ) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="transfer_stake", + call_params={ + "destination_coldkey": destination_coldkey_ss58, + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, + ) + + response = 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, + calling_function=get_function_name(), + ) - try: + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, + ) logging.info( - f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey [" - f"blue]{destination_coldkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " - f"[yellow]{destination_netuid}[/yellow]" + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": destination_coldkey_ss58, - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, + logging.info( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_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, - period=period, - raise_error=raise_error, - ) + return response - if success: - if not wait_for_finalization and not wait_for_inclusion: - return True - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=destination_coldkey_ss58, - ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" - ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" - ) - - return True - else: - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return False - - except Exception as e: - logging.error(f":cross_mark: [red]Failed[/red]: {str(e)}") - return False + logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + return response def swap_stake_extrinsic( @@ -163,7 +159,7 @@ def swap_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. @@ -186,7 +182,7 @@ def swap_stake_extrinsic( Returns: - success (bool): True if the swap was successful. + ExtrinsicResponse: The result object of the extrinsic execution. """ amount.set_unit(netuid=origin_netuid) @@ -201,100 +197,96 @@ def swap_stake_extrinsic( origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) + if stake_in_origin < amount: - logging.error( - f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {hotkey_ss58}. " - f"Stake: {stake_in_origin}, amount: {amount}" + message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." + logging.error(f":cross_mark: [red]Failed[/red]: {message}") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) + + call_params = { + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + } + + 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 + swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) + + logging.info( + f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]\n" + f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " + f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" + ) + call_params.update( + { + "limit_price": swap_rate_ratio_with_tolerance, + "allow_partial": allow_partial_stake, + } ) - return False + call_function = "swap_stake_limit" + else: + logging.info( + f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]" + ) + call_function = "swap_stake" - try: - call_params = { - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - } - - 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 - swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - - logging.info( - f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]\n" - f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " - f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" - ) - call_params.update( - { - "limit_price": swap_rate_ratio_with_tolerance, - "allow_partial": allow_partial_stake, - } - ) - call_function = "swap_stake_limit" - else: - logging.info( - f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]" - ) - call_function = "swap_stake" - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, + ) + + response = 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, + calling_function=get_function_name(), + ) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + ) + logging.info( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" ) + logging.info( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + return response - success, err_msg = 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 safe_swapping and "Custom error: 8" in response.message: + logging.error( + ":cross_mark: [red]Failed[/red]: Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." ) + else: + logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - if success: - if not wait_for_finalization and not wait_for_inclusion: - return True - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" - ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" - ) - - return True - else: - 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." - ) - else: - logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}") - return False - - except Exception as e: - logging.error(f":cross_mark: [red]Failed[/red]: {str(e)}") - return False + return response def move_stake_extrinsic( @@ -310,7 +302,7 @@ def move_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Moves stake to a different hotkey and/or subnet while keeping the same coldkey owner. @@ -331,13 +323,14 @@ def move_stake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: True if the move was successful. Otherwise, False. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not amount and not move_all_stake: - logging.error( - ":cross_mark: [red]Failed[/red]: Please specify an `amount` or `move_all_stake` argument to move stake." + message = ( + "Please specify an `amount` or `move_all_stake` argument to move stake." ) - return False + logging.error(f":cross_mark: [red]Failed[/red]: {message}") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) # Check sufficient stake stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( @@ -353,68 +346,62 @@ def move_stake_extrinsic( amount = stake_in_origin elif stake_in_origin < amount: - logging.error( - f":cross_mark: [red]Failed[/red]: Insufficient stake in origin hotkey: {origin_hotkey_ss58}. " - f"Stake: {stake_in_origin}, amount: {amount}" - ) - return False + message = f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." + logging.error(f":cross_mark: [red]Failed[/red]: {message}") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) amount.set_unit(netuid=origin_netuid) - try: + logging.info( + 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_ss58, + "origin_netuid": origin_netuid, + "destination_hotkey": destination_hotkey_ss58, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, + ) + + response = 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, + calling_function=get_function_name(), + ) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = _get_stake_in_origin_and_dest( + subtensor=subtensor, + 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, + ) logging.info( - 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]" + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": origin_hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey_ss58, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, + logging.info( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_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, - period=period, - raise_error=raise_error, - ) + return response - if success: - if not wait_for_finalization and not wait_for_inclusion: - return True - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = _get_stake_in_origin_and_dest( - subtensor=subtensor, - 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, - ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" - ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" - ) - - return True - else: - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return False - - except Exception as e: - logging.error(f":cross_mark: [red]Failed[/red]: {str(e)}") - return False + logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + return response diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 318167f104..d04fdb0c56 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3688,7 +3688,7 @@ def move_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Moves stake to a different hotkey and/or subnet. @@ -4490,7 +4490,7 @@ def swap_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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. @@ -4639,7 +4639,7 @@ def transfer_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Transfers stake from one subnet to another while changing the coldkey owner. diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index e58a08d09d..c67169e3b2 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) + assert subtensor.subnets.register_subnet(alice_wallet).success # 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) + assert await async_subtensor.subnets.register_subnet(alice_wallet).success # Verify subnet created successfully assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -388,7 +388,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): ] for _ in netuids: - subtensor.subnets.register_subnet(alice_wallet) + assert subtensor.subnets.register_subnet(alice_wallet).success # make sure we passed start_call limit for both subnets for netuid in netuids: @@ -533,17 +533,19 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) ] for _ in netuids: - await async_subtensor.subnets.register_subnet(alice_wallet) + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success # 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) for netuid in netuids: - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=netuid, + ) + ).success for netuid in netuids: stake = await async_subtensor.staking.get_stake( @@ -1061,12 +1063,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) + assert subtensor.subnets.register_subnet(bob_wallet).success assert subtensor.subnets.subnet_exists(origin_netuid), ( "Subnet wasn't created successfully" ) dest_netuid = 3 - assert subtensor.subnets.register_subnet(bob_wallet) + assert subtensor.subnets.register_subnet(bob_wallet).success assert subtensor.subnets.subnet_exists(dest_netuid), ( "Subnet wasn't created successfully" ) @@ -1106,7 +1108,7 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): stake_swap_amount = Balance.from_tao(10_000) # 1. Try swap with strict threshold and big amount- should fail - success = subtensor.staking.swap_stake( + response = subtensor.staking.swap_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, origin_netuid=origin_netuid, @@ -1118,7 +1120,7 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): rate_tolerance=0.005, # 0.5% allow_partial_stake=False, ) - assert success is False + assert response.success is False # Verify no stake was moved dest_stake = subtensor.staking.get_stake( @@ -1132,7 +1134,7 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): # 2. Try swap with higher threshold and less amount - should succeed stake_swap_amount = Balance.from_tao(100) - success = subtensor.staking.swap_stake( + response = subtensor.staking.swap_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, origin_netuid=origin_netuid, @@ -1144,7 +1146,7 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): rate_tolerance=0.3, # 30% allow_partial_stake=True, ) - assert success is True + assert response.success is True # Verify stake was moved dest_stake = subtensor.staking.get_stake( @@ -1175,12 +1177,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) + assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success 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) + assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success assert await async_subtensor.subnets.subnet_exists(dest_netuid), ( "Subnet wasn't created successfully" ) @@ -1220,7 +1222,7 @@ async def test_safe_swap_stake_scenarios_async( 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( + response = await async_subtensor.staking.swap_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, origin_netuid=origin_netuid, @@ -1232,7 +1234,7 @@ async def test_safe_swap_stake_scenarios_async( rate_tolerance=0.005, # 0.5% allow_partial_stake=False, ) - assert success is False + assert response.success is False # Verify no stake was moved dest_stake = await async_subtensor.staking.get_stake( @@ -1246,7 +1248,7 @@ async def test_safe_swap_stake_scenarios_async( # 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( + response = await async_subtensor.staking.swap_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, origin_netuid=origin_netuid, @@ -1258,7 +1260,7 @@ async def test_safe_swap_stake_scenarios_async( rate_tolerance=0.3, # 30% allow_partial_stake=True, ) - assert success is True + assert response.success is True # Verify stake was moved dest_stake = await async_subtensor.staking.get_stake( @@ -1284,7 +1286,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) + assert subtensor.subnets.register_subnet(alice_wallet).success assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1314,7 +1316,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) + assert subtensor.subnets.register_subnet(bob_wallet).success assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1331,7 +1333,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): netuid=alice_subnet_netuid, ) - assert subtensor.staking.move_stake( + response = subtensor.staking.move_stake( wallet=alice_wallet, origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, origin_netuid=alice_subnet_netuid, @@ -1341,6 +1343,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): wait_for_finalization=True, wait_for_inclusion=True, ) + assert response.success is True stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) @@ -1407,7 +1410,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): subtensor.block + subtensor.subnets.tempo(netuid=bob_subnet_netuid) ) - assert subtensor.staking.move_stake( + response = subtensor.staking.move_stake( wallet=dave_wallet, origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, origin_netuid=bob_subnet_netuid, @@ -1417,6 +1420,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): wait_for_finalization=True, move_all_stake=True, ) + assert response.success is True dave_stake = subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, @@ -1441,7 +1445,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) + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1475,7 +1479,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) + assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1494,7 +1498,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ netuid=alice_subnet_netuid, ) - assert await async_subtensor.staking.move_stake( + response = await async_subtensor.staking.move_stake( wallet=alice_wallet, origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, origin_netuid=alice_subnet_netuid, @@ -1504,6 +1508,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ wait_for_finalization=True, wait_for_inclusion=True, ) + assert response.success is True stakes = await async_subtensor.staking.get_stake_for_coldkey( alice_wallet.coldkey.ss58_address @@ -1573,7 +1578,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ # let chain to process the transaction await async_subtensor.wait_for_block(block_ + tampo_) - assert await async_subtensor.staking.move_stake( + response = await async_subtensor.staking.move_stake( wallet=dave_wallet, origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, origin_netuid=bob_subnet_netuid, @@ -1583,6 +1588,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ wait_for_finalization=True, move_all_stake=True, ) + assert response.success is True dave_stake = await async_subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, @@ -1606,7 +1612,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) + assert subtensor.subnets.register_subnet(alice_wallet).success assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1651,7 +1657,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) + assert subtensor.subnets.register_subnet(dave_wallet).success assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) @@ -1660,7 +1666,7 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): netuid=dave_subnet_netuid, ) - assert subtensor.staking.transfer_stake( + response = subtensor.staking.transfer_stake( alice_wallet, destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, hotkey_ss58=alice_wallet.hotkey.ss58_address, @@ -1670,6 +1676,7 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): wait_for_inclusion=True, wait_for_finalization=True, ) + assert response.success is True alice_stakes = subtensor.staking.get_stake_for_coldkey( alice_wallet.coldkey.ss58_address @@ -1733,7 +1740,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) + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully" ) @@ -1780,7 +1787,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) + assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success assert await async_wait_to_start_call( async_subtensor, dave_wallet, dave_subnet_netuid @@ -1791,7 +1798,7 @@ async def test_transfer_stake_async( netuid=dave_subnet_netuid, ) - assert await async_subtensor.staking.transfer_stake( + response = await async_subtensor.staking.transfer_stake( alice_wallet, destination_coldkey_ss58=bob_wallet.coldkey.ss58_address, hotkey_ss58=alice_wallet.hotkey.ss58_address, @@ -1801,6 +1808,7 @@ async def test_transfer_stake_async( wait_for_inclusion=True, wait_for_finalization=True, ) + assert response.success is True alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( alice_wallet.coldkey.ss58_address @@ -1870,7 +1878,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) + assert subtensor.subnets.register_subnet(alice_wallet).success assert subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( "Subnet wasn't created successfully" ) @@ -1890,7 +1898,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) + assert subtensor.subnets.register_subnet(alice_wallet).success assert subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( "Subnet wasn't created successfully" ) @@ -1982,7 +1990,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) + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_2), ( "Subnet wasn't created successfully" ) @@ -2004,7 +2012,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) + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid_3), ( "Subnet wasn't created successfully" ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 4cf26e8cb6..e3931a3fdd 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -1735,7 +1735,7 @@ async def fake_is_success(): wait_for_inclusion=True, wait_for_finalization=True, ) - assert result == (True, "") + assert result == (True, "Success") @pytest.mark.asyncio diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 4142e89b51..f452362499 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -241,7 +241,7 @@ def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, mocker) ) -def test_get_all_commitments(mock_substrate, subtensor, mocker): +def test_get_all_commitments(mock_substrate, subtensor): mock_substrate.query_map.return_value = [ ( (tuple(bytearray(32)),), @@ -582,7 +582,7 @@ def test_get_neuron_certificate(mock_substrate, subtensor): ) -def test_get_stake_for_coldkey(mock_substrate, subtensor, mocker): +def test_get_stake_for_coldkey(mock_substrate, subtensor): mock_substrate.runtime_call.return_value.value = [ { "coldkey": tuple(bytearray(32)), @@ -688,14 +688,14 @@ def test_last_drand_round(mock_substrate, subtensor): @pytest.mark.parametrize( - "wait", + "wait,resp_message", ( - True, - False, + [True, "Success"], + [False, "Not waiting for finalization or inclusion."], ), ) -def test_move_stake(mock_substrate, subtensor, fake_wallet, wait): - success = subtensor.move_stake( +def test_move_stake(mock_substrate, subtensor, fake_wallet, wait, resp_message): + success, message = subtensor.move_stake( wallet=fake_wallet, origin_hotkey_ss58="origin_hotkey", origin_netuid=1, @@ -707,6 +707,7 @@ def test_move_stake(mock_substrate, subtensor, fake_wallet, wait): ) assert success is True + assert message == resp_message assert_submit_signed_extrinsic( mock_substrate, @@ -728,7 +729,7 @@ def test_move_stake(mock_substrate, subtensor, fake_wallet, wait): def test_move_stake_insufficient_stake(mock_substrate, subtensor, fake_wallet, mocker): mocker.patch.object(subtensor, "get_stake", return_value=Balance(0)) - success = subtensor.move_stake( + success, message = subtensor.move_stake( fake_wallet, origin_hotkey_ss58="origin_hotkey", origin_netuid=1, @@ -738,6 +739,7 @@ def test_move_stake_insufficient_stake(mock_substrate, subtensor, fake_wallet, m ) assert success is False + assert "Insufficient stake in origin hotkey" in message mock_substrate.submit_extrinsic.assert_not_called() @@ -748,7 +750,7 @@ def test_move_stake_error(mock_substrate, subtensor, fake_wallet, mocker): is_success=False, ) - success = subtensor.move_stake( + success, message = subtensor.move_stake( fake_wallet, origin_hotkey_ss58="origin_hotkey", origin_netuid=1, @@ -758,6 +760,10 @@ def test_move_stake_error(mock_substrate, subtensor, fake_wallet, mocker): ) assert success is False + assert ( + message + == "Subtensor returned `UnknownError(UnknownType)` error. This means: `Unknown Description`." + ) assert_submit_signed_extrinsic( mock_substrate, @@ -779,16 +785,16 @@ def test_move_stake_error(mock_substrate, subtensor, fake_wallet, mocker): def test_move_stake_exception(mock_substrate, subtensor, fake_wallet): mock_substrate.submit_extrinsic.side_effect = RuntimeError - success = subtensor.move_stake( - fake_wallet, - origin_hotkey_ss58="origin_hotkey", - origin_netuid=1, - destination_hotkey_ss58="destination_hotkey", - destination_netuid=2, - amount=Balance(1), - ) - - assert success is False + with pytest.raises(RuntimeError) as exc: + subtensor.move_stake( + fake_wallet, + origin_hotkey_ss58="origin_hotkey", + origin_netuid=1, + destination_hotkey_ss58="destination_hotkey", + destination_netuid=2, + amount=Balance(1), + raise_error=True, + ) assert_submit_signed_extrinsic( mock_substrate, @@ -917,7 +923,7 @@ def test_neurons_lite(mock_substrate, subtensor, mock_neuron_info): ) -def test_set_children(mock_substrate, subtensor, fake_wallet, mocker): +def test_set_children(mock_substrate, subtensor, fake_wallet): subtensor.set_children( fake_wallet, fake_wallet.hotkey.ss58_address, @@ -1064,7 +1070,7 @@ def test_swap_stake(mock_substrate, subtensor, fake_wallet, mocker): return_value=fake_wallet.coldkeypub.ss58_address, ) - result = subtensor.swap_stake( + success, message = subtensor.swap_stake( fake_wallet, fake_wallet.hotkey.ss58_address, origin_netuid=1, @@ -1072,7 +1078,8 @@ def test_swap_stake(mock_substrate, subtensor, fake_wallet, mocker): amount=Balance(999), ) - assert result is True + assert success is True + assert message == "Success" assert_submit_signed_extrinsic( mock_substrate, @@ -1343,13 +1350,15 @@ def test_sign_and_send_extrinsic_raises_error( @pytest.mark.parametrize( - "wait", + "wait,response_message", ( - True, - False, + [True, "Success"], + [False, "Not waiting for finalization or inclusion."], ), ) -def test_transfer_stake(mock_substrate, subtensor, fake_wallet, mocker, wait): +def test_transfer_stake( + mock_substrate, subtensor, fake_wallet, mocker, wait, response_message +): mocker.patch.object( subtensor, "get_hotkey_owner", @@ -1357,7 +1366,7 @@ def test_transfer_stake(mock_substrate, subtensor, fake_wallet, mocker, wait): return_value=fake_wallet.coldkeypub.ss58_address, ) - success = subtensor.transfer_stake( + success, message = subtensor.transfer_stake( fake_wallet, "dest", "hotkey_ss58", @@ -1369,6 +1378,7 @@ def test_transfer_stake(mock_substrate, subtensor, fake_wallet, mocker, wait): ) assert success is True + assert message == response_message assert_submit_signed_extrinsic( mock_substrate, @@ -1387,57 +1397,6 @@ def test_transfer_stake(mock_substrate, subtensor, fake_wallet, mocker, wait): ) -@pytest.mark.parametrize( - "side_effect", - ( - ( - unittest.mock.Mock( - error_message="ERROR", - is_success=False, - ), - ), - RuntimeError, - ), -) -def test_transfer_stake_error( - mock_substrate, subtensor, fake_wallet, mocker, side_effect -): - mocker.patch.object( - subtensor, - "get_hotkey_owner", - autospec=True, - return_value=fake_wallet.coldkeypub.ss58_address, - ) - mock_substrate.submit_extrinsic.return_value = side_effect - - success = subtensor.transfer_stake( - fake_wallet, - "dest", - "hotkey_ss58", - origin_netuid=1, - destination_netuid=1, - amount=Balance(1), - ) - - assert success is False - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": "dest", - "hotkey": "hotkey_ss58", - "origin_netuid": 1, - "destination_netuid": 1, - "alpha_amount": 1, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - def test_transfer_stake_insufficient_stake( mock_substrate, subtensor, fake_wallet, mocker ): @@ -1453,7 +1412,7 @@ def test_transfer_stake_insufficient_stake( "get_stake", return_value=Balance(0), ): - success = subtensor.transfer_stake( + success, message = subtensor.transfer_stake( fake_wallet, "dest", "hotkey_ss58", @@ -1463,6 +1422,7 @@ def test_transfer_stake_insufficient_stake( ) assert success is False + assert "Insufficient stake in origin hotkey" in message mock_substrate.submit_extrinsic.assert_not_called() From 3f8ae7aabbeb9fa29d1b41a0851e84e507bf6a9c Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 14:28:01 -0700 Subject: [PATCH 200/416] fix unit tests --- tests/e2e_tests/test_delegate.py | 34 +++++++++---------- tests/e2e_tests/test_hotkeys.py | 30 +++++++--------- tests/e2e_tests/test_liquidity.py | 24 ++++++------- .../extrinsics/asyncex/test_root.py | 22 ++++++------ .../extrinsics/test_registration.py | 6 ++-- 5 files changed, 55 insertions(+), 61 deletions(-) diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 04417f6615..49224e5273 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -54,7 +54,7 @@ def test_identity(subtensor, alice_wallet, bob_wallet): identities = subtensor.delegates.get_delegate_identities() assert alice_wallet.coldkey.ss58_address not in identities - success, error = set_identity( + success, message = set_identity( subtensor=subtensor, wallet=alice_wallet, name="Alice", @@ -62,8 +62,8 @@ def test_identity(subtensor, alice_wallet, bob_wallet): github_repo="https://github.com/opentensor/bittensor", description="Local Chain", ) - assert error == "" - assert success is True + assert success is True, message + assert message == "Success" identity = subtensor.neurons.query_identity(alice_wallet.coldkeypub.ss58_address) assert identity == ChainIdentity( @@ -118,7 +118,7 @@ async def test_identity_async(async_subtensor, alice_wallet, bob_wallet): identities = await async_subtensor.delegates.get_delegate_identities() assert alice_wallet.coldkey.ss58_address not in identities - success, error = await async_set_identity( + success, message = await async_set_identity( subtensor=async_subtensor, wallet=alice_wallet, name="Alice", @@ -127,8 +127,8 @@ async def test_identity_async(async_subtensor, alice_wallet, bob_wallet): description="Local Chain", ) - assert error == "" - assert success is True + assert success is True, message + assert message == "Success" identity = await async_subtensor.neurons.query_identity( alice_wallet.coldkeypub.ss58_address @@ -825,7 +825,7 @@ def test_get_vote_data(subtensor, alice_wallet): assert proposals.records == [] - success, error = propose( + success, message = propose( subtensor=subtensor, wallet=alice_wallet, proposal=subtensor.substrate.compose_call( @@ -840,8 +840,8 @@ def test_get_vote_data(subtensor, alice_wallet): duration=1_000_000, ) - assert error == "" - assert success is True + assert success is True, message + assert message == "Success" proposals = subtensor.queries.query_map( module="Triumvirate", @@ -881,7 +881,7 @@ def test_get_vote_data(subtensor, alice_wallet): threshold=3, ) - success, error = vote( + success, message = vote( subtensor=subtensor, wallet=alice_wallet, hotkey=alice_wallet.hotkey.ss58_address, @@ -890,8 +890,8 @@ def test_get_vote_data(subtensor, alice_wallet): approve=True, ) - assert error == "" - assert success is True + assert success is True, message + assert message == "Success" proposal = subtensor.chain.get_vote_data( proposal_hash=proposal_hash, @@ -932,7 +932,7 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): assert proposals.records == [] - success, error = await async_propose( + success, message = await async_propose( subtensor=async_subtensor, wallet=alice_wallet, proposal=await async_subtensor.substrate.compose_call( @@ -947,8 +947,8 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): duration=1_000_000, ) - assert error == "" assert success is True + assert message == "Success" proposals = await async_subtensor.queries.query_map( module="Triumvirate", @@ -989,7 +989,7 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): threshold=3, ) - success, error = await async_vote( + success, message = await async_vote( subtensor=async_subtensor, wallet=alice_wallet, hotkey=alice_wallet.hotkey.ss58_address, @@ -998,8 +998,8 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): approve=True, ) - assert error == "" - assert success is True + assert success is True, message + assert message == "Success" proposal = await async_subtensor.chain.get_vote_data( proposal_hash=proposal_hash, diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index bc0fd762c7..ca2fe2941b 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -165,11 +165,8 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): success, message = 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 success is True, message + assert message == "Success" assert subtensor.subnets.register_subnet(dave_wallet) assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( @@ -327,9 +324,8 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): wait_for_inclusion=True, wait_for_finalization=True, ) - - assert message == "Success with `set_children_extrinsic` response." - assert success is True + assert success is True, message + assert message == "Success" set_children_block = subtensor.block @@ -413,7 +409,8 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): wait_for_inclusion=True, wait_for_finalization=True, ) - assert success, message + assert success is True, message + assert message == "Success" set_children_block = subtensor.block @@ -491,11 +488,8 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa ) = 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 success is True, message + assert message == "Success" assert await async_subtensor.subnets.register_subnet(dave_wallet) assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( @@ -656,8 +650,8 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa wait_for_inclusion=True, wait_for_finalization=True, ) - assert message == "Success with `set_children_extrinsic` response." - assert success is True + assert success is True, message + assert message == "Success" logging.console.info(f"[orange]success: {success}, message: {message}[/orange]") set_children_block = await async_subtensor.chain.get_current_block() @@ -741,8 +735,8 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa wait_for_inclusion=True, wait_for_finalization=True, ) - assert message == "Success with `set_children_extrinsic` response." - assert success is True + assert success is True, message + assert message == "Success" logging.console.info(f"[orange]success: {success}, message: {message}[/orange]") set_children_block = await async_subtensor.chain.get_current_block() diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index 2617a6259c..913fd0db9d 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -73,7 +73,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): enable=True, ) assert success, message - assert message == "", "❌ Cannot enable user liquidity." + assert message == "Success", "❌ Cannot enable user liquidity." # Add steak to call add_liquidity assert subtensor.staking.add_stake( @@ -105,7 +105,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ Cannot add liquidity." + assert message == "Success", "❌ Cannot add liquidity." # Get liquidity liquidity_positions = subtensor.subnets.get_liquidity_list( @@ -138,7 +138,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ cannot modify liquidity position." + assert message == "Success", "❌ cannot modify liquidity position." liquidity_positions = subtensor.subnets.get_liquidity_list( wallet=alice_wallet, netuid=alice_subnet_netuid @@ -169,7 +169,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ cannot modify liquidity position." + assert message == "Success", "❌ cannot modify liquidity position." liquidity_positions = subtensor.subnets.get_liquidity_list( wallet=alice_wallet, netuid=alice_subnet_netuid @@ -220,7 +220,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ Cannot add liquidity." + assert message == "Success", "❌ Cannot add liquidity." liquidity_positions = subtensor.subnets.get_liquidity_list( wallet=alice_wallet, netuid=alice_subnet_netuid @@ -284,7 +284,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ Cannot remove liquidity." + assert message == "Success", "❌ Cannot remove liquidity." # Make sure all liquidity positions removed assert ( @@ -364,7 +364,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): enable=True, ) assert success, message - assert message == "", "❌ Cannot enable user liquidity." + assert message == "Success", "❌ Cannot enable user liquidity." # Add steak to call add_liquidity assert await async_subtensor.staking.add_stake( @@ -398,7 +398,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ Cannot add liquidity." + assert message == "Success", "❌ Cannot add liquidity." # Get liquidity liquidity_positions = await async_subtensor.subnets.get_liquidity_list( @@ -431,7 +431,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ cannot modify liquidity position." + assert message == "Success", "❌ cannot modify liquidity position." liquidity_positions = await async_subtensor.subnets.get_liquidity_list( wallet=alice_wallet, netuid=alice_subnet_netuid @@ -462,7 +462,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ cannot modify liquidity position." + assert message == "Success", "❌ cannot modify liquidity position." liquidity_positions = await async_subtensor.subnets.get_liquidity_list( wallet=alice_wallet, netuid=alice_subnet_netuid @@ -515,7 +515,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ Cannot add liquidity." + assert message == "Success", "❌ Cannot add liquidity." liquidity_positions = await async_subtensor.subnets.get_liquidity_list( wallet=alice_wallet, netuid=alice_subnet_netuid @@ -581,7 +581,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): wait_for_finalization=True, ) assert success, message - assert message == "", "❌ Cannot remove liquidity." + assert message == "Success", "❌ Cannot remove liquidity." # Make sure all liquidity positions removed assert ( diff --git a/tests/unit_tests/extrinsics/asyncex/test_root.py b/tests/unit_tests/extrinsics/asyncex/test_root.py index 70acf9857c..329d051c15 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_root.py +++ b/tests/unit_tests/extrinsics/asyncex/test_root.py @@ -1,8 +1,7 @@ import pytest -from bittensor.core.errors import SubstrateRequestException from bittensor.core.extrinsics.asyncex import root as async_root - +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance @@ -57,7 +56,7 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", - return_value=(True, ""), + return_value=ExtrinsicResponse(True, "Success"), ) mocked_query = mocker.patch.object( subtensor.substrate, @@ -95,7 +94,8 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): storage_function="Uids", params=[0, "fake_hotkey_address"], ) - assert result is True + assert result.success is True + assert result.message == "Success" @pytest.mark.asyncio @@ -122,7 +122,7 @@ async def test_root_register_extrinsic_insufficient_balance( wait_for_finalization=True, ) - assert result is False + assert result.success is False subtensor.get_balance.assert_called_once_with( fake_wallet.coldkeypub.ss58_address, @@ -161,7 +161,7 @@ async def test_root_register_extrinsic_unlock_failed(subtensor, fake_wallet, moc # Asserts mocked_unlock_key.assert_called_once_with(fake_wallet) - assert result is False + assert result.success is False @pytest.mark.asyncio @@ -206,7 +206,7 @@ async def test_root_register_extrinsic_already_registered( mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) - assert result is True + assert result.success is True @pytest.mark.asyncio @@ -241,7 +241,7 @@ async def test_root_register_extrinsic_transaction_failed( mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", - return_value=(False, "Transaction failed"), + return_value=ExtrinsicResponse(False, "Transaction failed"), ) # Call @@ -259,7 +259,7 @@ async def test_root_register_extrinsic_transaction_failed( ) mocked_compose_call.assert_called_once() mocked_sign_and_send_extrinsic.assert_called_once() - assert result is False + assert result.success is False @pytest.mark.asyncio @@ -292,7 +292,7 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", - return_value=(True, ""), + return_value=ExtrinsicResponse(True, ""), ) mocked_query = mocker.patch.object( subtensor.substrate, @@ -320,4 +320,4 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc storage_function="Uids", params=[0, "fake_hotkey_address"], ) - assert result is False + assert result.success is False diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index 796c3d45f0..fbf3373796 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -1,6 +1,6 @@ import pytest from bittensor_wallet import Wallet - +from bittensor.core.types import ExtrinsicResponse from bittensor.core.extrinsics import registration from bittensor.core.subtensor import Subtensor from bittensor.utils.registration import POWSolution @@ -203,7 +203,7 @@ def test_burned_register_extrinsic( mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic", - return_value=(recycle_success, "Mock error message"), + return_value=ExtrinsicResponse(recycle_success, "Mock error message"), ) mocker.patch.object( mock_subtensor, "is_hotkey_registered", return_value=is_registered @@ -214,7 +214,7 @@ def test_burned_register_extrinsic( subtensor=mock_subtensor, wallet=mock_wallet, netuid=123 ) # Assert - assert result == expected_result, f"Test failed for test_id: {test_id}" + assert result.success == expected_result, f"Test failed for test_id: {test_id}" def test_set_subnet_identity_extrinsic_is_success(mock_subtensor, mock_wallet, mocker): From 903b16f0eb7b6a3c873f3c53d7adfcc0a4842b47 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 15:00:59 -0700 Subject: [PATCH 201/416] `root_register_extrinsic`, `subtensor.burned_register` and `subtensor.root_register` + updated tests --- bittensor/core/extrinsics/asyncex/root.py | 69 ++++---- bittensor/core/extrinsics/root.py | 73 ++++---- migration.md | 3 +- tests/e2e_tests/test_commitment.py | 12 +- tests/e2e_tests/test_delegate.py | 116 +++++++------ tests/e2e_tests/test_dendrite.py | 8 +- tests/e2e_tests/test_hotkeys.py | 36 ++-- tests/e2e_tests/test_incentive.py | 8 +- tests/e2e_tests/test_liquid_alpha.py | 8 +- tests/e2e_tests/test_metagraph.py | 48 +++--- tests/e2e_tests/test_neuron_certificate.py | 8 +- tests/e2e_tests/test_reveal_commitments.py | 8 +- tests/e2e_tests/test_root_set_weights.py | 4 +- tests/e2e_tests/test_staking.py | 182 +++++++++++--------- tests/e2e_tests/test_subtensor_functions.py | 8 +- 15 files changed, 325 insertions(+), 266 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/root.py b/bittensor/core/extrinsics/asyncex/root.py index bb318cdb90..c6e76546ac 100644 --- a/bittensor/core/extrinsics/asyncex/root.py +++ b/bittensor/core/extrinsics/asyncex/root.py @@ -1,7 +1,8 @@ import asyncio from typing import Optional, TYPE_CHECKING -from bittensor.utils import u16_normalized_float, unlock_key +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import u16_normalized_float, unlock_key, get_function_name from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -41,7 +42,7 @@ async def root_register_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Registers the neuron to the root network. @@ -56,8 +57,14 @@ async def root_register_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ + if not (unlock := unlock_key(wallet)).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + netuid = 0 logging.info( f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]" @@ -80,15 +87,9 @@ async def root_register_extrinsic( current_recycle = Balance.from_rao(int(recycle_call)) if balance < current_recycle: - logging.error( - f"[red]Insufficient balance {balance} to register neuron. " - f"Current recycle is {current_recycle} TAO[/red]." - ) - return False - - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False + message = f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO" + logging.error(f"[red]{message}[/red].") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) logging.debug( f"Checking if hotkey ([blue]{wallet.hotkey_str}[/blue]) is registered on root." @@ -97,10 +98,11 @@ async def root_register_extrinsic( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - logging.error( - ":white_heavy_check_mark: [green]Already registered on root network.[/green]" + message = "Already registered on root network." + logging.error(f":white_heavy_check_mark: [green]{message}[/green]") + return ExtrinsicResponse( + message=message, extrinsic_function=get_function_name() ) - return True logging.info(":satellite: [magenta]Registering to root network...[/magenta]") call = await subtensor.substrate.compose_call( @@ -108,7 +110,7 @@ async def root_register_extrinsic( call_function="root_register", call_params={"hotkey": wallet.hotkey.ss58_address}, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -117,24 +119,25 @@ async def root_register_extrinsic( raise_error=raise_error, ) - if not success: - logging.error(f":cross_mark: [red]Failed error:[/red] {message}") + if not response.success: + logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") await asyncio.sleep(0.5) - return False + return response # Successful registration, final check for neuron and pubkey - else: - uid = await subtensor.substrate.query( - module="SubtensorModule", - storage_function="Uids", - params=[netuid, wallet.hotkey.ss58_address], + uid = await subtensor.substrate.query( + module="SubtensorModule", + storage_function="Uids", + params=[netuid, wallet.hotkey.ss58_address], + ) + if uid is not None: + response.data = {"uid": uid} + logging.info( + f":white_heavy_check_mark: [green]Registered with UID: {uid}[/green]." ) - if uid is not None: - logging.info( - f":white_heavy_check_mark: [green]Registered with UID[/green] [blue]{uid}[/blue]." - ) - return True - else: - # neuron not found, try again - logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - return False + return response + + # neuron not found, try again + message = "Unknown error. Neuron not found." + logging.error(f":cross_mark: [red]{message}[/red]") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) diff --git a/bittensor/core/extrinsics/root.py b/bittensor/core/extrinsics/root.py index 161ca53fd0..9117832efd 100644 --- a/bittensor/core/extrinsics/root.py +++ b/bittensor/core/extrinsics/root.py @@ -1,10 +1,8 @@ import time from typing import Optional, TYPE_CHECKING -from bittensor.utils import ( - u16_normalized_float, - unlock_key, -) +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import u16_normalized_float, unlock_key, get_function_name from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -42,7 +40,7 @@ def root_register_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Registers the neuron to the root network. @@ -57,8 +55,14 @@ def root_register_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ + if not (unlock := unlock_key(wallet)).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + netuid = 0 logging.info( f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]" @@ -79,15 +83,9 @@ def root_register_extrinsic( current_recycle = Balance.from_rao(int(recycle_call)) if balance < current_recycle: - logging.error( - f"[red]Insufficient balance {balance} to register neuron. " - f"Current recycle is {current_recycle} TAO[/red]." - ) - return False - - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False + message = f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO" + logging.error(f"[red]{message}[/red].") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) logging.debug( f"Checking if hotkey ([blue]{wallet.hotkey_str}[/blue]) is registered on root." @@ -96,10 +94,11 @@ def root_register_extrinsic( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - logging.error( - ":white_heavy_check_mark: [green]Already registered on root network.[/green]" + message = "Already registered on root network." + logging.error(f":white_heavy_check_mark: [green]{message}[/green]") + return ExtrinsicResponse( + message=message, extrinsic_function=get_function_name() ) - return True logging.info(":satellite: [magenta]Registering to root network...[/magenta]") call = subtensor.substrate.compose_call( @@ -107,7 +106,7 @@ def root_register_extrinsic( call_function="root_register", call_params={"hotkey": wallet.hotkey.ss58_address}, ) - success, err_msg = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -116,24 +115,26 @@ def root_register_extrinsic( raise_error=raise_error, ) - if not success: - logging.error(f":cross_mark: [red]Failed error:[/red] {err_msg}") + if not response.success: + logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") time.sleep(0.5) - return False + return response # Successful registration, final check for neuron and pubkey - else: - uid = subtensor.substrate.query( - module="SubtensorModule", - storage_function="Uids", - params=[netuid, wallet.hotkey.ss58_address], + uid = subtensor.substrate.query( + module="SubtensorModule", + storage_function="Uids", + params=[netuid, wallet.hotkey.ss58_address], + ) + if uid is not None: + response.data = {"uid": uid} + logging.info( + f":white_heavy_check_mark: [green]Registered with UID: {uid}[/green]." ) - if uid is not None: - logging.info( - f":white_heavy_check_mark: [green]Registered with UID[/green] [blue]{uid}[/blue]." - ) - return True - else: - # neuron not found, try again - logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - return False + return response + + # neuron not found + # neuron not found, try again + message = "Unknown error. Neuron not found." + logging.error(f":cross_mark: [red]{message}[/red]") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) diff --git a/migration.md b/migration.md index f7053cada2..5a98819699 100644 --- a/migration.md +++ b/migration.md @@ -227,5 +227,6 @@ wait_for_finalization: bool = False, All extrinsics and related subtensor calls now return an object of class `bittensor.core.types.ExtrinsicResponse` Additional changes in extrinsics: - - with `commit_reveal_extrinsic` or `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the `{"reveal_round": reveal_round}`. + - `commit_reveal_extrinsic` and `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the `{"reveal_round": reveal_round}`. - in positive case, all extrinsics return `response.message = "Success"` + - `root_register_extrinsic`, `subtensor.burned_register` (with netuid=0) and `subtensor.root_register` has response `ExtrinsicResponse`. In successful case `.data` holds the `{"uid": uid}` where is `uid` is uid of registered neuron. diff --git a/tests/e2e_tests/test_commitment.py b/tests/e2e_tests/test_commitment.py index d31bf739ef..825f3a3625 100644 --- a/tests/e2e_tests/test_commitment.py +++ b/tests/e2e_tests/test_commitment.py @@ -33,7 +33,7 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): assert subtensor.subnets.burned_register( wallet=alice_wallet, netuid=dave_subnet_netuid, - ) + ).success uid = subtensor.subnets.get_uid_for_hotkey_on_subnet( hotkey_ss58=alice_wallet.hotkey.ss58_address, @@ -109,10 +109,12 @@ async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): data="Hello World!", ) - assert await sub.subnets.burned_register( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - ) + assert ( + await sub.subnets.burned_register( + wallet=alice_wallet, + netuid=dave_subnet_netuid, + ) + ).success uid = await sub.subnets.get_uid_for_hotkey_on_subnet( alice_wallet.hotkey.ss58_address, diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 49224e5273..d387b30112 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -45,11 +45,11 @@ def test_identity(subtensor, alice_wallet, bob_wallet): identities = subtensor.delegates.get_delegate_identities() assert alice_wallet.coldkey.ss58_address not in identities - subtensor.extrinsics.root_register( - alice_wallet, + assert subtensor.extrinsics.root_register( + wallet=alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, - ) + ).success identities = subtensor.delegates.get_delegate_identities() assert alice_wallet.coldkey.ss58_address not in identities @@ -109,11 +109,13 @@ async def test_identity_async(async_subtensor, alice_wallet, bob_wallet): 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, - ) + assert ( + await async_subtensor.extrinsics.root_register( + wallet=alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + ).success identities = await async_subtensor.delegates.get_delegate_identities() assert alice_wallet.coldkey.ss58_address not in identities @@ -171,26 +173,26 @@ def test_change_take(subtensor, alice_wallet, bob_wallet): 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, + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + take=0.1, raise_error=True, ) - subtensor.extrinsics.root_register( - alice_wallet, + assert subtensor.extrinsics.root_register( + wallet=alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, - ) + ).success take = subtensor.delegates.get_delegate_take(alice_wallet.hotkey.ss58_address) assert take == DEFAULT_DELEGATE_TAKE with pytest.raises(NonAssociatedColdKey): subtensor.delegates.set_delegate_take( - bob_wallet, - alice_wallet.hotkey.ss58_address, - 0.1, + wallet=bob_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + take=0.1, raise_error=True, ) @@ -263,11 +265,13 @@ async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): raise_error=True, ) - await async_subtensor.extrinsics.root_register( - alice_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + assert ( + await async_subtensor.extrinsics.root_register( + wallet=alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + ).success take = await async_subtensor.delegates.get_delegate_take( alice_wallet.hotkey.ss58_address @@ -368,16 +372,16 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): subtensor.delegates.is_hotkey_delegate(bob_wallet.hotkey.ss58_address) is False ) - subtensor.extrinsics.root_register( - alice_wallet, + assert subtensor.extrinsics.root_register( + wallet=alice_wallet, wait_for_inclusion=True, wait_for_finalization=True, - ) - subtensor.extrinsics.root_register( - bob_wallet, + ).success + assert subtensor.extrinsics.root_register( + wallet=bob_wallet, wait_for_inclusion=True, wait_for_finalization=True, - ) + ).success assert ( subtensor.delegates.is_hotkey_delegate(alice_wallet.hotkey.ss58_address) is True @@ -521,16 +525,20 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): 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.extrinsics.root_register( + wallet=alice_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + ).success + assert ( + await async_subtensor.extrinsics.root_register( + wallet=bob_wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + ).success assert ( await async_subtensor.delegates.is_hotkey_delegate( @@ -670,15 +678,15 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ minimum_required_stake = subtensor.staking.get_minimum_required_stake() assert minimum_required_stake == Balance(0) - subtensor.subnets.burned_register( + assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid, - ) + ).success - subtensor.subnets.burned_register( + assert subtensor.subnets.burned_register( wallet=dave_wallet, netuid=alice_subnet_netuid, - ) + ).success success = subtensor.staking.add_stake( wallet=dave_wallet, @@ -753,15 +761,19 @@ async def test_nominator_min_required_stake_async( 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, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ) + ).success - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid, + ) + ).success success = await async_subtensor.staking.add_stake( wallet=dave_wallet, @@ -813,7 +825,7 @@ def test_get_vote_data(subtensor, alice_wallet): """ logging.console.info("Testing [green]test_get_vote_data[/green].") - assert subtensor.extrinsics.root_register(alice_wallet), ( + assert subtensor.extrinsics.root_register(alice_wallet).success, ( "Can not register Alice in root SN." ) @@ -920,7 +932,7 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): """ logging.console.info("Testing [green]test_get_vote_data_async[/green].") - assert await async_subtensor.extrinsics.root_register(alice_wallet), ( + assert (await async_subtensor.extrinsics.root_register(alice_wallet)).success, ( "Can not register Alice in root SN." ) diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index 73d646aadf..fb82738055 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -98,7 +98,7 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): assert status is True # Register Bob to the network - assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid), ( + assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid).success, ( "Unable to register Bob as a neuron" ) @@ -247,9 +247,9 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall 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" + assert ( + await async_subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid) + ).success, "Unable to register Bob as a neuron" metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index ca2fe2941b..c45eeb9caf 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -66,7 +66,7 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): assert subtensor.subnets.burned_register( wallet=alice_wallet, netuid=dave_subnet_netuid, - ) + ).success assert subtensor.wallets.does_hotkey_exist(hotkey) is True assert subtensor.wallets.get_hotkey_owner(hotkey) == coldkey @@ -121,10 +121,12 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): is False ) - assert await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dave_subnet_netuid, + ) + ).success assert await async_subtensor.wallets.does_hotkey_exist(hotkey) is True assert await async_subtensor.wallets.get_hotkey_owner(hotkey) == coldkey @@ -227,13 +229,13 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): assert subtensor.subnets.burned_register( wallet=alice_wallet, netuid=dave_subnet_netuid, - ) + ).success logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=dave_subnet_netuid, - ) + ).success logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") success, children, error = subtensor.wallets.get_children( @@ -550,16 +552,20 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa raise_error=True, ) - assert await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dave_subnet_netuid, + ) + ).success 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, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, + ) + ).success logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") success, children, error = await async_subtensor.wallets.get_children( diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index 22dfdfc8ca..f6f7c890d9 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -64,7 +64,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) # Register Bob as a neuron on the subnet - assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid), ( + assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid).success, ( "Unable to register Bob as a neuron" ) @@ -240,9 +240,9 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal ) # 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) + ).success, "Unable to register Bob as a neuron" # Assert two neurons are in network assert ( diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index 20b56a6d87..dbe41c7b67 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -66,7 +66,7 @@ def test_liquid_alpha(subtensor, alice_wallet): ) # Register a neuron (Alice) to the subnet - assert subtensor.subnets.burned_register(alice_wallet, netuid), ( + assert subtensor.subnets.burned_register(alice_wallet, netuid).success, ( "Unable to register Alice as a neuron" ) @@ -259,9 +259,9 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): 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" - ) + assert ( + await async_subtensor.subnets.burned_register(alice_wallet, netuid) + ).success, "Unable to register Alice as a neuron" # Stake to become to top neuron after the first epoch assert await async_subtensor.staking.add_stake( diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index f30cb43f1b..92f9ebb538 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -75,7 +75,7 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): assert len(metagraph.uids) == 1, "Metagraph doesn't have exactly 1 neuron" logging.console.info("Register Bob to the subnet") - assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid), ( + assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid).success, ( "Unable to register Bob as a neuron" ) @@ -115,9 +115,9 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): metagraph_pre_dave = subtensor.metagraphs.metagraph(netuid=alice_subnet_netuid) logging.console.info("Register Dave as a neuron") - assert subtensor.subnets.burned_register(dave_wallet, alice_subnet_netuid), ( - "Unable to register Dave as a neuron" - ) + assert subtensor.subnets.burned_register( + dave_wallet, alice_subnet_netuid + ).success, "Unable to register Dave as a neuron" metagraph.sync(subtensor=subtensor._subtensor) @@ -238,9 +238,11 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w 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" + assert ( + await async_subtensor.subnets.burned_register( + bob_wallet, alice_subnet_netuid + ) + ).success, "Unable to register Bob as a neuron" logging.console.info("Refresh the metagraph") await metagraph.sync(subtensor=async_subtensor._subtensor) @@ -282,9 +284,11 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w ) 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" + assert ( + await async_subtensor.subnets.burned_register( + dave_wallet, alice_subnet_netuid + ) + ).success, "Unable to register Dave as a neuron" await metagraph.sync(subtensor=async_subtensor._subtensor) @@ -560,7 +564,7 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): assert subtensor.subnets.burned_register( bob_wallet, netuid=alice_subnet_netuid, - ) + ).success metagraph_info = subtensor.metagraphs.get_metagraph_info(netuid=alice_subnet_netuid) @@ -815,10 +819,12 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): async_subtensor, alice_wallet, alice_subnet_netuid ) - assert await async_subtensor.subnets.burned_register( - bob_wallet, - netuid=alice_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + bob_wallet, + netuid=alice_subnet_netuid, + ) + ).success metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( netuid=alice_subnet_netuid @@ -995,7 +1001,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid, - ) + ).success fields = [ SelectiveMetagraphIndex.Name, @@ -1228,10 +1234,12 @@ async def test_metagraph_info_with_indexes_async( async_subtensor, alice_wallet, alice_subnet_netuid ) - assert await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ) + ).success fields = [ SelectiveMetagraphIndex.Name, diff --git a/tests/e2e_tests/test_neuron_certificate.py b/tests/e2e_tests/test_neuron_certificate.py index 0a1748d8eb..880a8b8662 100644 --- a/tests/e2e_tests/test_neuron_certificate.py +++ b/tests/e2e_tests/test_neuron_certificate.py @@ -25,7 +25,7 @@ async def test_neuron_certificate(subtensor, alice_wallet): assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" # Register Alice as a neuron on the subnet - assert subtensor.subnets.burned_register(alice_wallet, netuid), ( + assert subtensor.subnets.burned_register(alice_wallet, netuid).success, ( "Unable to register Alice as a neuron" ) @@ -79,9 +79,9 @@ async def test_neuron_certificate_async(async_subtensor, alice_wallet): ) # 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" - ) + assert ( + await async_subtensor.subnets.burned_register(alice_wallet, netuid) + ).success, "Unable to register Alice as a neuron" # Serve Alice's axon with a certificate axon = Axon(wallet=alice_wallet) diff --git a/tests/e2e_tests/test_reveal_commitments.py b/tests/e2e_tests/test_reveal_commitments.py index 9ad12eaa76..99d6fed29a 100644 --- a/tests/e2e_tests/test_reveal_commitments.py +++ b/tests/e2e_tests/test_reveal_commitments.py @@ -48,7 +48,7 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid, - ), "Bob's neuron was not register." + ).success, "Bob's neuron was not register." # Verify subnet 2 created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -162,9 +162,9 @@ 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 - ), "Bob's neuron was not register." + assert ( + await async_subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid) + ).success, "Bob's neuron was not register." # Verify subnet 2 created successfully assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index 7d18dccfcf..03392decc3 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -68,7 +68,7 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall default_tempo = 10 if subtensor.chain.is_fast_blocks() else 360 # Register Alice in root network (0) - assert subtensor.extrinsics.root_register(alice_wallet) + assert subtensor.extrinsics.root_register(alice_wallet).success # Assert Alice is successfully registered to root alice_root_neuron = subtensor.neurons.neurons(netuid=0)[0] @@ -188,7 +188,7 @@ async def test_root_reg_hyperparams_async( 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 (await async_subtensor.extrinsics.root_register(alice_wallet)).success # Assert Alice is successfully registered to root alice_root_neuron = (await async_subtensor.neurons.neurons(netuid=0))[0] diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index c67169e3b2..3227f8f00e 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -38,15 +38,15 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - subtensor.subnets.burned_register( + assert subtensor.subnets.burned_register( wallet=alice_wallet, netuid=alice_subnet_netuid, - ) + ).success logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - subtensor.subnets.burned_register( + assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid, - ) + ).success logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") stake = subtensor.staking.get_stake( @@ -216,15 +216,19 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) async_subtensor, alice_wallet, alice_subnet_netuid ) - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ) + ).success 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, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ) + ).success logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") stake = await async_subtensor.staking.get_stake( @@ -395,10 +399,10 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): assert wait_to_start_call(subtensor, alice_wallet, netuid) for netuid in netuids: - subtensor.subnets.burned_register( + assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=netuid, - ) + ).success for netuid in netuids: stake = subtensor.staking.get_stake( @@ -714,12 +718,12 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - subtensor.extrinsics.burned_register( + assert subtensor.extrinsics.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid, wait_for_inclusion=True, wait_for_finalization=True, - ) + ).success initial_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -904,12 +908,14 @@ async def test_safe_staking_scenarios_async( async_subtensor, alice_wallet, alice_subnet_netuid ) - await async_subtensor.extrinsics.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - ) + assert ( + await async_subtensor.extrinsics.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + ).success initial_stake = await async_subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -1078,14 +1084,14 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): assert wait_to_start_call(subtensor, bob_wallet, dest_netuid) # Register Alice on both subnets - subtensor.subnets.burned_register( + assert subtensor.subnets.burned_register( wallet=alice_wallet, netuid=origin_netuid, - ) - subtensor.subnets.burned_register( + ).success + assert subtensor.subnets.burned_register( wallet=alice_wallet, netuid=dest_netuid, - ) + ).success # Add initial stake to swap from initial_stake_amount = Balance.from_tao(10_000) @@ -1192,14 +1198,18 @@ async def test_safe_swap_stake_scenarios_async( 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, - ) - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dest_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=origin_netuid, + ) + ).success + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dest_netuid, + ) + ).success # Add initial stake to swap from initial_stake_amount = Balance.from_tao(10_000) @@ -1323,15 +1333,15 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) - subtensor.subnets.burned_register( + assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid, - ) + ).success - subtensor.subnets.burned_register( + assert subtensor.subnets.burned_register( wallet=dave_wallet, netuid=alice_subnet_netuid, - ) + ).success response = subtensor.staking.move_stake( wallet=alice_wallet, @@ -1488,15 +1498,19 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ async_subtensor, bob_wallet, bob_subnet_netuid ) - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ) + ).success - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid, + ) + ).success response = await async_subtensor.staking.move_stake( wallet=alice_wallet, @@ -1619,10 +1633,10 @@ 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( + assert subtensor.subnets.burned_register( wallet=alice_wallet, netuid=alice_subnet_netuid, - ) + ).success assert subtensor.staking.add_stake( wallet=alice_wallet, @@ -1661,10 +1675,10 @@ 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( + assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=dave_subnet_netuid, - ) + ).success response = subtensor.staking.transfer_stake( alice_wallet, @@ -1749,10 +1763,12 @@ async def test_transfer_stake_async( async_subtensor, alice_wallet, alice_subnet_netuid ) - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ) + ).success assert await async_subtensor.staking.add_stake( wallet=alice_wallet, @@ -1793,10 +1809,12 @@ async def test_transfer_stake_async( async_subtensor, dave_wallet, dave_subnet_netuid ) - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, + ) + ).success response = await async_subtensor.staking.transfer_stake( alice_wallet, @@ -1889,12 +1907,12 @@ def test_unstaking_with_limit( assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid_2, - ) + ).success assert subtensor.subnets.burned_register( wallet=dave_wallet, netuid=alice_subnet_netuid_2, - ) + ).success # Register second SN alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 @@ -1909,12 +1927,12 @@ def test_unstaking_with_limit( assert subtensor.subnets.burned_register( wallet=bob_wallet, netuid=alice_subnet_netuid_3, - ) + ).success assert subtensor.subnets.burned_register( wallet=dave_wallet, netuid=alice_subnet_netuid_3, - ) + ).success # Check Bob's stakes are empty. assert ( @@ -2000,15 +2018,19 @@ async def test_unstaking_with_limit_async( ) # Register Bob and Dave in SN2 - assert await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid_2, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_2, + ) + ).success - assert await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_2, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_2, + ) + ).success # Register second SN alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 @@ -2020,15 +2042,19 @@ async def test_unstaking_with_limit_async( 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, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_3, + ) + ).success - assert await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_3, - ) + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_3, + ) + ).success # Check Bob's stakes are empty. assert ( diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 52b379f4de..2f52d62763 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -155,7 +155,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall assert wait_to_start_call(subtensor, alice_wallet, netuid) # Register Bob to the subnet - assert subtensor.subnets.burned_register(bob_wallet, netuid), ( + assert subtensor.subnets.burned_register(bob_wallet, netuid).success, ( "Unable to register Bob as a neuron" ) @@ -348,9 +348,9 @@ async def test_subtensor_extrinsics_async( 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" - ) + assert ( + await async_subtensor.subnets.burned_register(bob_wallet, netuid) + ).success, "Unable to register Bob as a neuron" # Verify Bob's UID on netuid 2 is 1 assert ( From cad691585140c8b9beb7fd92ce1c5fccd8b14320 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 16:28:24 -0700 Subject: [PATCH 202/416] `burned_register_extrinsic`, `register_subnet_extrinsic`, `register_extrinsic`, + `set_subnet_identity_extrinsic` + related subtensor calls + fixed tests --- bittensor/core/async_subtensor.py | 4 +- .../core/extrinsics/asyncex/registration.py | 162 +++++++++-------- bittensor/core/extrinsics/registration.py | 165 ++++++++++-------- bittensor/core/subtensor.py | 4 +- .../test_set_subnet_identity_extrinsic.py | 24 +-- .../extrinsics/asyncex/test_registration.py | 61 ++++--- .../extrinsics/test_registration.py | 55 +++--- tests/unit_tests/test_subtensor_extended.py | 8 +- 8 files changed, 270 insertions(+), 213 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e716018193..329f561dd0 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4905,7 +4905,7 @@ async def register( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ): + ) -> ExtrinsicResponse: """ Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. @@ -5349,7 +5349,7 @@ async def set_subnet_identity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ Sets the identity of a subnet for a specific wallet and network. diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index 2e67368be5..b11667d498 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -10,7 +10,8 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging from bittensor.utils.registration import log_no_torch_error, create_pow_async, torch @@ -27,7 +28,7 @@ async def burned_register_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """Registers the wallet to chain by recycling TAO. Parameters: @@ -42,18 +43,24 @@ async def burned_register_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: True if the extrinsic was successful. Otherwise, False. + ExtrinsicResponse: The result object of the extrinsic execution. """ block_hash = await subtensor.substrate.get_chain_head() if not await subtensor.subnet_exists(netuid, block_hash=block_hash): logging.error( f":cross_mark: [red]Failed error:[/red] subnet [blue]{netuid}[/blue] does not exist." ) - return False + return ExtrinsicResponse( + False, + f"Subnet #{netuid} does not exist", + extrinsic_function=get_function_name(), + ) if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) logging.info( f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue][magenta] ...[/magenta]" @@ -71,12 +78,15 @@ async def burned_register_extrinsic( ) if not neuron.is_null: - logging.info(":white_heavy_check_mark: [green]Already Registered[/green]") + message = "Already registered." + logging.info(f":white_heavy_check_mark: [green]{message}[/green]") logging.info(f"\t\tuid: [blue]{neuron.uid}[/blue]") logging.info(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") logging.info(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") logging.info(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") - return True + return ExtrinsicResponse( + message=message, extrinsic_function=get_function_name() + ) logging.debug(":satellite: [magenta]Recycling TAO for Registration...[/magenta]") @@ -95,19 +105,20 @@ async def burned_register_extrinsic( logging.info( f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{fee}[/blue]." ) - success, message = await subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) - if not success: - logging.error(f":cross_mark: [red]Failed error:[/red] {message}") + if not response.success: + logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") await asyncio.sleep(0.5) - return False + return response # 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. @@ -125,12 +136,16 @@ async def burned_register_extrinsic( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - logging.info(":white_heavy_check_mark: [green]Registered[/green]") - return True + message = "Registered." + logging.info(f":white_heavy_check_mark: [green]{message}[/green]") + return response # neuron not found, try again - logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - return False + message = "Unknown error. Neuron not found." + logging.error(f":cross_mark: [red]{message}[/red]") + response.success = False + response.message = message + return response async def register_subnet_extrinsic( @@ -140,7 +155,7 @@ async def register_subnet_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Registers a new subnetwork on the Bittensor blockchain asynchronously. @@ -155,16 +170,15 @@ async def register_subnet_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ 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 + message = f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO." + logging.error(message) + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) call = await subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -175,26 +189,27 @@ async def register_subnet_extrinsic( }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) if not wait_for_finalization and not wait_for_inclusion: - return True + return response - if success: + if response.success: logging.success( ":white_heavy_check_mark: [green]Successfully registered subnet[/green]" ) - return True + return response - logging.error(f"Failed to register subnet: {message}") - return False + logging.error(f"Failed to register subnet: {response.message}") + return response async def register_extrinsic( @@ -213,7 +228,7 @@ async def register_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """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, @@ -239,15 +254,14 @@ async def register_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ block_hash = await subtensor.substrate.get_chain_head() logging.debug("[magenta]Checking subnet status... [/magenta]") if not await subtensor.subnet_exists(netuid, block_hash=block_hash): - logging.error( - f":cross_mark: [red]Failed error:[/red] subnet [blue]{netuid}[/blue] does not exist." - ) - return False + message = f"Subnet #{netuid} does not exist." + logging.error(f":cross_mark: [red]Failed error:[/red] {message}") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) logging.info( f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue] [magenta]...[/magenta]" @@ -257,12 +271,13 @@ async def register_extrinsic( ) if not neuron.is_null: - logging.info(":white_heavy_check_mark: [green]Already Registered[/green]") + message = "Already registered." + logging.info(f":white_heavy_check_mark: [green]{message}[/green]") logging.info(f"\t\tuid: [blue]{neuron.uid}[/blue]") logging.info(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") logging.info(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") logging.info(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") - return True + return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) logging.debug( f"Registration hotkey: {wallet.hotkey.ss58_address}, Public coldkey: " @@ -271,7 +286,9 @@ async def register_extrinsic( if not torch: log_no_torch_error() - return False + return ExtrinsicResponse( + False, "No torch installed.", extrinsic_function=get_function_name() + ) # Attempt rolling registration. attempts = 1 @@ -283,7 +300,9 @@ async def register_extrinsic( # Solve latest POW. if cuda: if not torch.cuda.is_available(): - return False + return ExtrinsicResponse( + False, "CUDA not available.", extrinsic_function=get_function_name() + ) pow_result = await create_pow_async( subtensor=subtensor, @@ -316,10 +335,11 @@ async def register_extrinsic( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - logging.error( - f":white_heavy_check_mark: [green]Already registered on netuid:[/green] [blue]{netuid}[/blue]" + message = f"Already registered on netuid: {netuid}" + logging.info(f":white_heavy_check_mark: [green]{message}[/green]") + return ExtrinsicResponse( + True, message, extrinsic_function=get_function_name() ) - return True # pow successful, proceed to submit pow to chain for registration else: @@ -338,45 +358,45 @@ async def register_extrinsic( "coldkey": wallet.coldkeypub.ss58_address, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) - if not success: + if not response.success: # Look error here # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs - if "HotKeyAlreadyRegisteredInSubNet" in message: + if "HotKeyAlreadyRegisteredInSubNet" in response.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]: {message}") + return response await asyncio.sleep(0.5) # Successful registration, final check for neuron and pubkey - if success: + if response.success: logging.info(":satellite: Checking Registration status...") is_registered = await subtensor.is_hotkey_registered( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: logging.success( - ":white_heavy_check_mark: [green]Registered[/green]" + ":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]" - ) - continue + return response + + # neuron not found, try again + logging.error( + ":cross_mark: [red]Unknown error. Neuron not found.[/red]" + ) + continue else: # Exited loop because pow is no longer valid. logging.error("[red]POW is stale.[/red]") @@ -391,8 +411,11 @@ async def register_extrinsic( ) else: # Failed to register after max attempts. - logging.error("[red]No more attempts.[/red]") - return False + message = "No more attempts." + logging.error(f"[red]{message}[/red]") + return ExtrinsicResponse( + False, message, extrinsic_function=get_function_name() + ) async def set_subnet_identity_extrinsic( @@ -411,7 +434,7 @@ async def set_subnet_identity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Set the identity information for a given subnet. @@ -435,14 +458,14 @@ async def set_subnet_identity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = await subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -461,25 +484,26 @@ async def set_subnet_identity_extrinsic( }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) if not wait_for_finalization and not wait_for_inclusion: - return True, message + return response - if success: + if response.success: logging.success( f":white_heavy_check_mark: [green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" ) - return True, f"Identities for subnet {netuid} are set." + return response - logging.error( - f":cross_mark: Failed to set identity for subnet [blue]{netuid}[/blue]: {message}" - ) - return False, f"Failed to set identity for subnet {netuid}: {message}" + message = f"Failed to set identity for subnet #{netuid}" + logging.error(f":cross_mark: {message}: {response.message}") + response.message = message + return response diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index 6f667ff7b6..f710ef96e0 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -10,7 +10,8 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.extrinsics.utils import get_extrinsic_fee -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging from bittensor.utils.registration import create_pow, log_no_torch_error, torch @@ -27,7 +28,7 @@ def burned_register_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """Registers the wallet to chain by recycling TAO. Parameters: @@ -42,18 +43,24 @@ def burned_register_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - success: True if the extrinsic was successful. Otherwise, False. + ExtrinsicResponse: The result object of the extrinsic execution. """ block = subtensor.get_current_block() if not subtensor.subnet_exists(netuid, block=block): logging.error( f":cross_mark: [red]Failed error:[/red] subnet [blue]{netuid}[/blue] does not exist." ) - return False + return ExtrinsicResponse( + False, + f"Subnet #{netuid} does not exist", + extrinsic_function=get_function_name(), + ) if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) logging.info( f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue][magenta] ...[/magenta]" @@ -65,12 +72,15 @@ def burned_register_extrinsic( old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) if not neuron.is_null: - logging.info(":white_heavy_check_mark: [green]Already Registered[/green]") + message = "Already registered." + logging.info(f":white_heavy_check_mark: [green]{message}[/green]") logging.info(f"\t\tuid: [blue]{neuron.uid}[/blue]") logging.info(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") logging.info(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") logging.info(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") - return True + return ExtrinsicResponse( + message=message, extrinsic_function=get_function_name() + ) recycle_amount = subtensor.recycle(netuid=netuid, block=block) logging.debug(":satellite: [magenta]Recycling TAO for Registration...[/magenta]") @@ -89,19 +99,20 @@ def burned_register_extrinsic( logging.info( f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{fee}[/blue]." ) - success, message = subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) - if not success: - logging.error(f":cross_mark: [red]Failed error:[/red] {message}") + if not response.success: + logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") time.sleep(0.5) - return False + return response # 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. @@ -117,12 +128,16 @@ def burned_register_extrinsic( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address, block=block ) if is_registered: - logging.info(":white_heavy_check_mark: [green]Registered[/green]") - return True + message = "Registered" + logging.info(f":white_heavy_check_mark: [green]{message}[/green]") + return response # neuron not found, try again - logging.error(":cross_mark: [red]Unknown error. Neuron not found.[/red]") - return False + message = "Unknown error. Neuron not found." + logging.error(f":cross_mark: [red]{message}[/red]") + response.success = False + response.message = message + return response def register_subnet_extrinsic( @@ -132,7 +147,7 @@ def register_subnet_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Registers a new subnetwork on the Bittensor blockchain. @@ -147,16 +162,15 @@ def register_subnet_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) burn_cost = 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 + message = f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO." + logging.error(message) + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -167,26 +181,27 @@ def register_subnet_extrinsic( }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) if not wait_for_finalization and not wait_for_inclusion: - return True + return response - if success: + if response.success: logging.success( ":white_heavy_check_mark: [green]Successfully registered subnet[/green]" ) - return True + return response - logging.error(f"Failed to register subnet: {message}") - return False + logging.error(f"Failed to register subnet: {response.message}") + return response def register_extrinsic( @@ -205,7 +220,7 @@ def register_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. Parameters: @@ -228,16 +243,15 @@ def register_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ logging.debug("[magenta]Checking subnet status... [/magenta]") block = subtensor.get_current_block() if not subtensor.subnet_exists(netuid, block=block): - logging.error( - f":cross_mark: [red]Failed error:[/red] subnet [blue]{netuid}[/blue] does not exist." - ) - return False + message = f"Subnet #{netuid} does not exist." + logging.error(f":cross_mark: [red]Failed error:[/red] {message}") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) logging.info( f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue] [magenta]...[/magenta]" @@ -247,12 +261,13 @@ def register_extrinsic( ) if not neuron.is_null: - logging.info(":white_heavy_check_mark: [green]Already Registered[/green]") + message = "Already registered." + logging.info(f":white_heavy_check_mark: [green]{message}[/green]") logging.info(f"\t\tuid: [blue]{neuron.uid}[/blue]") logging.info(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") logging.info(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") logging.info(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") - return True + return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) logging.debug( f"Registration hotkey: {wallet.hotkey.ss58_address}, Public coldkey: " @@ -261,7 +276,9 @@ def register_extrinsic( if not torch: log_no_torch_error() - return False + return ExtrinsicResponse( + False, "No torch installed.", extrinsic_function=get_function_name() + ) # Attempt rolling registration. attempts = 1 @@ -273,7 +290,9 @@ def register_extrinsic( # Solve latest POW. if cuda: if not torch.cuda.is_available(): - return False + return ExtrinsicResponse( + False, "CUDA not available.", extrinsic_function=get_function_name() + ) pow_result = create_pow( subtensor=subtensor, @@ -306,10 +325,11 @@ def register_extrinsic( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - logging.error( - f":white_heavy_check_mark: [green]Already registered on netuid:[/green] [blue]{netuid}[/blue]" + message = f"Already registered on netuid: {netuid}" + logging.info(f":white_heavy_check_mark: [green]{message}[/green]") + return ExtrinsicResponse( + True, message, extrinsic_function=get_function_name() ) - return True # pow successful, proceed to submit pow to chain for registration else: @@ -329,48 +349,45 @@ def register_extrinsic( "coldkey": wallet.coldkeypub.ss58_address, }, ) - logging.debug( - ":satellite: [magenta]Sending POW Register Extrinsic...[/magenta]" - ) - success, message = subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) - if not success: + if not response.success: # Look error here # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs - if "HotKeyAlreadyRegisteredInSubNet" in message: + if "HotKeyAlreadyRegisteredInSubNet" in response.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]: {message}") + return response time.sleep(0.5) # Successful registration, final check for neuron and pubkey - if success: + if response.success: logging.info(":satellite: Checking Registration status...") is_registered = subtensor.is_hotkey_registered( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: logging.success( - ":white_heavy_check_mark: [green]Registered[/green]" + ":white_heavy_check_mark: [green]Registered.[/green]" ) - return True - else: + return response + # neuron not found, try again - logging.error( - ":cross_mark: [red]Unknown error. Neuron not found.[/red]" - ) - continue + logging.error( + ":cross_mark: [red]Unknown error. Neuron not found.[/red]" + ) + continue else: # Exited loop because pow is no longer valid. logging.error("[red]POW is stale.[/red]") @@ -385,8 +402,11 @@ def register_extrinsic( ) else: # Failed to register after max attempts. - logging.error("[red]No more attempts.[/red]") - return False + message = "No more attempts." + logging.error(f"[red]{message}[/red]") + return ExtrinsicResponse( + False, message, extrinsic_function=get_function_name() + ) def set_subnet_identity_extrinsic( @@ -405,7 +425,7 @@ def set_subnet_identity_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Set the identity information for a given subnet. @@ -429,14 +449,14 @@ def set_subnet_identity_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -455,25 +475,26 @@ def set_subnet_identity_extrinsic( }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) if not wait_for_finalization and not wait_for_inclusion: - return True, f"Identities for subnet {netuid} are sent to the chain." + return response - if success: + if response.success: logging.success( f":white_heavy_check_mark: [green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" ) - return True, f"Identities for subnet {netuid} are set." - else: - logging.error( - f":cross_mark: Failed to set identity for subnet [blue]{netuid}[/blue]: {message}" - ) - return False, f"Failed to set identity for subnet {netuid}: {message}" + return response + + message = f"Failed to set identity for subnet #{netuid}" + logging.error(f":cross_mark: {message}: {response.message}") + response.message = message + return response diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index d04fdb0c56..cc06731866 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3742,7 +3742,7 @@ def register( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Registers a neuron on the Bittensor subnet with provided netuid using the provided wallet. @@ -4173,7 +4173,7 @@ def set_subnet_identity( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ Sets the identity of a subnet for a specific wallet and network. diff --git a/tests/e2e_tests/test_set_subnet_identity_extrinsic.py b/tests/e2e_tests/test_set_subnet_identity_extrinsic.py index de52ad4550..228cfdd744 100644 --- a/tests/e2e_tests/test_set_subnet_identity_extrinsic.py +++ b/tests/e2e_tests/test_set_subnet_identity_extrinsic.py @@ -40,7 +40,7 @@ def test_set_subnet_identity_extrinsic_happy_pass(subtensor, alice_wallet): wallet=alice_wallet, netuid=netuid, subnet_identity=subnet_identity, - )[0] + ).success is True ), "Set subnet identity failed" @@ -96,7 +96,7 @@ async def test_set_subnet_identity_extrinsic_happy_pass_async( netuid=netuid, subnet_identity=subnet_identity, ) - )[0] is True, "Set subnet identity failed" + ).success is True, "Set subnet identity failed" # Check SubnetIdentity of the subnet assert ( @@ -112,14 +112,6 @@ def test_set_subnet_identity_extrinsic_failed(subtensor, alice_wallet, bob_walle 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]" @@ -156,7 +148,7 @@ def test_set_subnet_identity_extrinsic_failed(subtensor, alice_wallet, bob_walle wallet=bob_wallet, netuid=netuid, subnet_identity=subnet_identity, - )[0] + ).success is False ), "Set subnet identity failed" @@ -173,14 +165,6 @@ async def test_set_subnet_identity_extrinsic_failed_async( 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]" @@ -222,7 +206,7 @@ async def test_set_subnet_identity_extrinsic_failed_async( netuid=netuid, subnet_identity=subnet_identity, ) - )[0] is False, "Set subnet identity failed" + ).success is False, "Set subnet identity failed" logging.console.success( "✅ Passed [blue]test_set_subnet_identity_extrinsic_failed[/blue]" diff --git a/tests/unit_tests/extrinsics/asyncex/test_registration.py b/tests/unit_tests/extrinsics/asyncex/test_registration.py index 03f00aab82..21f3c72338 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_registration.py +++ b/tests/unit_tests/extrinsics/asyncex/test_registration.py @@ -1,4 +1,5 @@ import pytest +from bittensor.core.types import ExtrinsicResponse from bittensor.core.extrinsics.asyncex import registration as async_registration @@ -27,7 +28,7 @@ async def test_register_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=ExtrinsicResponse(True, "") ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, "is_hotkey_registered", return_value=True @@ -60,11 +61,13 @@ async def test_register_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, + calling_function="register_extrinsic", ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" ) - assert result is True + + assert result[0] @pytest.mark.asyncio @@ -92,7 +95,7 @@ async def test_register_extrinsic_success_with_cuda(subtensor, fake_wallet, 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, None) + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, "is_hotkey_registered", return_value=True @@ -126,11 +129,12 @@ async def test_register_extrinsic_success_with_cuda(subtensor, fake_wallet, mock wait_for_finalization=True, period=None, raise_error=False, + calling_function="register_extrinsic", ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" ) - assert result is True + assert result[0] @pytest.mark.asyncio @@ -170,13 +174,18 @@ async def test_register_extrinsic_failed_with_cuda(subtensor, fake_wallet, mocke netuid=1, block_hash=subtensor.substrate.get_chain_head.return_value, ) - assert result is False + assert result == ExtrinsicResponse( + False, + "CUDA not available.", + extrinsic_function="register_extrinsic", + ) @pytest.mark.asyncio async def test_register_extrinsic_subnet_not_exists(subtensor, fake_wallet, mocker): """Tests registration when subnet does not exist.""" # Preps + netuid = 14 mocked_subnet_exists = mocker.patch.object( subtensor, "subnet_exists", return_value=False ) @@ -185,21 +194,26 @@ async def test_register_extrinsic_subnet_not_exists(subtensor, fake_wallet, mock result = await async_registration.register_extrinsic( subtensor=subtensor, wallet=fake_wallet, - netuid=1, + netuid=netuid, ) # Asserts mocked_subnet_exists.assert_called_once_with( - 1, + netuid, block_hash=subtensor.substrate.get_chain_head.return_value, ) - assert result is False + assert result == ExtrinsicResponse( + False, + f"Subnet #{netuid} does not exist.", + extrinsic_function="register_extrinsic", + ) @pytest.mark.asyncio async def test_register_extrinsic_already_registered(subtensor, fake_wallet, mocker): """Tests registration when the key is already registered.""" # Preps + netuid = 14 mocked_get_neuron = mocker.patch.object( subtensor, "get_neuron_for_pubkey_and_subnet", @@ -207,19 +221,20 @@ async def test_register_extrinsic_already_registered(subtensor, fake_wallet, moc ) # Call - result = await async_registration.register_extrinsic( + success, message = await async_registration.register_extrinsic( subtensor=subtensor, wallet=fake_wallet, - netuid=1, + netuid=netuid, ) # Asserts mocked_get_neuron.assert_called_once_with( hotkey_ss58=fake_wallet.hotkey.ss58_address, - netuid=1, + netuid=netuid, block_hash=subtensor.substrate.get_chain_head.return_value, ) - assert result is True + assert success is True + assert message == f"Already registered." @pytest.mark.asyncio @@ -252,7 +267,9 @@ async def is_stale_side_effect(*_, **__): ) 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") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(False, "Test Error"), ) # Call @@ -284,8 +301,10 @@ async def is_stale_side_effect(*_, **__): wait_for_finalization=True, period=None, raise_error=False, + calling_function="register_extrinsic", ) - assert result is False + assert result[0] is False + assert result[1] == "No more attempts." @pytest.mark.asyncio @@ -305,9 +324,7 @@ async def test_set_subnet_identity_extrinsic_is_success(subtensor, fake_wallet, 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, ""], + subtensor, "sign_and_send_extrinsic" ) # Call @@ -349,9 +366,10 @@ async def test_set_subnet_identity_extrinsic_is_success(subtensor, fake_wallet, wait_for_finalization=True, period=None, raise_error=False, + calling_function="set_subnet_identity_extrinsic", ) - assert result == (True, "Identities for subnet 123 are set.") + assert result == mocked_sign_and_send_extrinsic.return_value @pytest.mark.asyncio @@ -374,7 +392,6 @@ async def test_set_subnet_identity_extrinsic_is_failed(subtensor, fake_wallet, m mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", - return_value=[False, fake_error_message], ) # Call @@ -418,9 +435,7 @@ async def test_set_subnet_identity_extrinsic_is_failed(subtensor, fake_wallet, m wait_for_finalization=True, period=None, raise_error=False, + calling_function="set_subnet_identity_extrinsic", ) - assert result == ( - False, - f"Failed to set identity for subnet {netuid}: {fake_error_message}", - ) + assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index fbf3373796..286491bd55 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -45,11 +45,18 @@ def mock_new_wallet(mocker): @pytest.mark.parametrize( - "subnet_exists, neuron_is_null, cuda_available, expected_result, test_id", + "subnet_exists, neuron_is_null, cuda_available, expected_result, test_id, expected_message", [ - (False, True, True, False, "subnet-does-not-exist"), - (True, False, True, True, "neuron-already-registered"), - (True, True, False, False, "cuda-unavailable"), + ( + False, + True, + True, + False, + "subnet-does-not-exist", + "Subnet #123 does not exist.", + ), + (True, False, True, True, "neuron-already-registered", "Already registered."), + (True, True, False, False, "cuda-unavailable", "CUDA not available."), ], ) def test_register_extrinsic_without_pow( @@ -61,6 +68,7 @@ def test_register_extrinsic_without_pow( expected_result, test_id, mocker, + expected_message, ): # Arrange mocker.patch.object(mock_subtensor, "subnet_exists", return_value=subnet_exists) @@ -93,18 +101,26 @@ def test_register_extrinsic_without_pow( ) # Assert - assert result == expected_result, f"Test failed for test_id: {test_id}" + assert result == ExtrinsicResponse( + expected_result, expected_message, extrinsic_function="register_extrinsic" + ), f"Test failed for test_id: {test_id}" @pytest.mark.parametrize( - "pow_success, pow_stale, registration_success, cuda, hotkey_registered, expected_result, test_id", + "pow_success, pow_stale, registration_success, cuda, hotkey_registered, expected_result", [ - (True, False, True, False, False, True, "successful-with-valid-pow"), - (True, False, True, True, False, True, "successful-with-valid-cuda-pow"), + (True, False, True, False, False, True), + (True, False, True, True, False, True), # Pow failed but key was registered already - (False, False, False, False, True, True, "hotkey-registered"), + (False, False, False, False, True, True), # Pow was a success but registration failed with error 'key already registered' - (True, False, False, False, False, True, "registration-fail-key-registered"), + (True, False, False, False, False, False), + ], + ids=[ + "successful-with-valid-pow", + "successful-with-valid-cuda-pow", + "hotkey-registered", + "registration-fail-key-registered", ], ) def test_register_extrinsic_with_pow( @@ -117,7 +133,6 @@ def test_register_extrinsic_with_pow( cuda, hotkey_registered, expected_result, - test_id, mocker, ): # Arrange @@ -132,7 +147,9 @@ def test_register_extrinsic_with_pow( mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic", - return_value=(registration_success, "HotKeyAlreadyRegisteredInSubNet"), + return_value=ExtrinsicResponse( + registration_success, "HotKeyAlreadyRegisteredInSubNet" + ), ) mocker.patch("torch.cuda.is_available", return_value=cuda) @@ -162,7 +179,7 @@ def test_register_extrinsic_with_pow( ) # Assert - assert result == expected_result, f"Test failed for test_id: {test_id}." + assert result[0] is expected_result @pytest.mark.parametrize( @@ -232,7 +249,7 @@ def test_set_subnet_identity_extrinsic_is_success(mock_subtensor, mock_wallet, m 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=(True, "Success") + mock_subtensor, "sign_and_send_extrinsic" ) # Call @@ -274,9 +291,10 @@ def test_set_subnet_identity_extrinsic_is_success(mock_subtensor, mock_wallet, m wait_for_finalization=True, period=None, raise_error=False, + calling_function="set_subnet_identity_extrinsic", ) - assert result == (True, "Identities for subnet 123 are set.") + assert result == mocked_sign_and_send_extrinsic.return_value def test_set_subnet_identity_extrinsic_is_failed(mock_subtensor, mock_wallet, mocker): @@ -298,7 +316,6 @@ def test_set_subnet_identity_extrinsic_is_failed(mock_subtensor, mock_wallet, mo mocked_sign_and_send_extrinsic = mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic", - return_value=(False, fake_error_message), ) # Call @@ -340,9 +357,7 @@ def test_set_subnet_identity_extrinsic_is_failed(mock_subtensor, mock_wallet, mo wait_for_finalization=True, period=None, raise_error=False, + calling_function="set_subnet_identity_extrinsic", ) - assert result == ( - False, - f"Failed to set identity for subnet {netuid}: {fake_error_message}", - ) + assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index f452362499..7db7dd86ed 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1159,12 +1159,10 @@ def test_register(mock_substrate, subtensor, fake_wallet, mocker): return_value=NeuronInfo.get_null_neuron(), ) - result = subtensor.register( - fake_wallet, + assert subtensor.register( + wallet=fake_wallet, netuid=1, - ) - - assert result is True + ).success subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( hotkey_ss58=fake_wallet.hotkey.ss58_address, From bd9e833e6ef1cb86756520d2fcab7a154eef5396 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 16:36:03 -0700 Subject: [PATCH 203/416] oops, need to remove --- bittensor/core/extrinsics/asyncex/serving.py | 50 -------------------- 1 file changed, 50 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 6a511718d1..402f43e2ae 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -17,56 +17,6 @@ from bittensor_wallet import Wallet -async def do_serve_axon( - subtensor: "AsyncSubtensor", - 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.async_subtensor.AsyncSubtensor): 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, 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 = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params.dict(), - ) - success, message = 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, - ) - return success, message - - async def serve_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", From b85d5dd23dfae523a5cac8d8bc810c2133961a78 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 16:45:20 -0700 Subject: [PATCH 204/416] improve `ExtrinsicResponse` description --- bittensor/core/types.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 2a1210ad57..447bb2498b 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -282,6 +282,11 @@ class ExtrinsicResponse: This class is designed to give developers a consistent way to represent the outcome of an extrinsic call — whether it succeeded or failed — along with useful metadata for debugging, logging, or higher-level business logic. + The object also implements tuple-like behavior: + * Iteration yields ``(success, message)``. + * Indexing is supported: ``response[0] -> success``, ``response[1] -> message``. + * ``len(response)`` returns 2. + Attributes: success: Indicates if the extrinsic execution was successful. message: A status or informational message returned from the execution (e.g., "Successfully registered subnet"). @@ -310,6 +315,11 @@ class ExtrinsicResponse: print(success, message) True Successfully registered subnet + + print(response[0]) + True + print(response[1]) + 'Successfully registered subnet' """ success: bool = True From ba12eea555ad59bd34fca98458c842cfd78a75dc Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 17:04:23 -0700 Subject: [PATCH 205/416] update `publish_metadata` extrinsic + tests --- bittensor/core/extrinsics/asyncex/serving.py | 61 +++++++++++-------- bittensor/core/extrinsics/serving.py | 62 +++++++++++--------- tests/unit_tests/extrinsics/test_serving.py | 55 +++++++---------- 3 files changed, 92 insertions(+), 86 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 402f43e2ae..58c6d83018 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -4,7 +4,9 @@ from bittensor.core.errors import MetadataError from bittensor.core.settings import version_as_int from bittensor.core.types import AxonServeCallParams +from bittensor.core.types import ExtrinsicResponse from bittensor.utils import ( + get_function_name, networking as net, unlock_key, Certificate, @@ -31,7 +33,7 @@ async def serve_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Subscribes a Bittensor endpoint to the subtensor chain. @@ -53,12 +55,14 @@ async def serve_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ # Decrypt hotkey if not (unlock := unlock_key(wallet, "hotkey")).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) params = AxonServeCallParams( **{ @@ -81,10 +85,9 @@ async def serve_extrinsic( ) neuron_up_to_date = not neuron.is_null and params == neuron if neuron_up_to_date: - logging.debug( - f"Axon already served on: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue]" - ) - return True + message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" + logging.debug(f"[blue]{message}[/blue]") + return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) logging.debug( f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " @@ -101,7 +104,7 @@ async def serve_extrinsic( call_function=call_function, call_params=params.dict(), ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -109,17 +112,18 @@ async def serve_extrinsic( sign_with="hotkey", period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if success: + if response.success: logging.debug( f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " f"[green]{subtensor.network}:{netuid}[/green]" ) - return True + return response - logging.error(f"Failed: {message}") - return False + logging.error(f"Failed: {response.message}") + return response async def serve_axon_extrinsic( @@ -131,7 +135,7 @@ async def serve_axon_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Serves the axon to the network. @@ -149,11 +153,14 @@ async def serve_axon_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(axon.wallet, "hotkey")).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + external_port = axon.external_port # ---- Get external ip ---- @@ -173,7 +180,7 @@ async def serve_axon_extrinsic( external_ip = axon.external_ip # ---- Subscribe to chain ---- - serve_success = await serve_extrinsic( + response = await serve_extrinsic( subtensor=subtensor, wallet=axon.wallet, ip=external_ip, @@ -186,7 +193,7 @@ async def serve_axon_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - return serve_success + return response async def publish_metadata( @@ -200,7 +207,7 @@ async def publish_metadata( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Publishes metadata on the Bittensor network using the specified wallet and network identifier. @@ -222,16 +229,17 @@ async def publish_metadata( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. Raises: MetadataError: If there is an error in submitting the extrinsic, or if the response from the blockchain indicates - failure. + failure. """ - if not (unlock := unlock_key(wallet, "hotkey")).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) fields = [{f"{data_type}": data}] if reset_bonds: @@ -247,7 +255,7 @@ async def publish_metadata( }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, sign_with="hotkey", @@ -255,11 +263,12 @@ async def publish_metadata( wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if success: - return True - raise MetadataError(message) + if response.success: + return response + raise MetadataError(response.message) async def get_metadata( diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index dd62fa3669..51ce310022 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -3,7 +3,9 @@ from bittensor.core.errors import MetadataError from bittensor.core.settings import version_as_int from bittensor.core.types import AxonServeCallParams +from bittensor.core.types import ExtrinsicResponse from bittensor.utils import ( + get_function_name, networking as net, unlock_key, Certificate, @@ -30,7 +32,7 @@ def serve_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Subscribes a Bittensor endpoint to the subtensor chain. @@ -52,12 +54,14 @@ def serve_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ # Decrypt hotkey if not (unlock := unlock_key(wallet, "hotkey")).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) params = AxonServeCallParams( **{ @@ -80,10 +84,9 @@ def serve_extrinsic( ) neuron_up_to_date = not neuron.is_null and params == neuron if neuron_up_to_date: - logging.debug( - f"Axon already served on: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue]" - ) - return True + message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" + logging.debug(f"[blue]{message}[/blue]") + return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) logging.debug( f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " @@ -101,7 +104,7 @@ def serve_extrinsic( call_params=params.dict(), ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -109,17 +112,18 @@ def serve_extrinsic( sign_with="hotkey", period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if success: + if response.success: logging.debug( f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " f"[green]{subtensor.network}:{netuid}[/green]" ) - return True + return response - logging.error(f"Failed: {message}") - return False + logging.error(f"Failed: {response.message}") + return response def serve_axon_extrinsic( @@ -131,7 +135,7 @@ def serve_axon_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Serves the axon to the network. @@ -149,11 +153,14 @@ def serve_axon_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(axon.wallet, "hotkey")).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + external_port = axon.external_port # ---- Get external ip ---- @@ -171,7 +178,7 @@ def serve_axon_extrinsic( external_ip = axon.external_ip # ---- Subscribe to chain ---- - serve_success = serve_extrinsic( + response = serve_extrinsic( subtensor=subtensor, wallet=axon.wallet, ip=external_ip, @@ -184,7 +191,7 @@ def serve_axon_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - return serve_success + return response def publish_metadata( @@ -198,7 +205,7 @@ def publish_metadata( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Publishes metadata on the Bittensor network using the specified wallet and network identifier. @@ -220,16 +227,16 @@ def publish_metadata( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. - + ExtrinsicResponse: The result object of the extrinsic execution. Raises: MetadataError: If there is an error in submitting the extrinsic, or if the response from the blockchain indicates - failure. + failure. """ - if not (unlock := unlock_key(wallet, "hotkey")).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) fields = [{f"{data_type}": data}] if reset_bonds: @@ -244,7 +251,7 @@ def publish_metadata( }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, sign_with="hotkey", @@ -252,11 +259,12 @@ def publish_metadata( wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if success: - return True - raise MetadataError(message) + if response.success: + return response + raise MetadataError(response.message) def get_metadata( diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 3964df1cd2..67b124c295 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -1,25 +1,8 @@ -# 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. - from unittest.mock import MagicMock, patch import pytest from bittensor_wallet import Wallet - +from bittensor.core.types import ExtrinsicResponse from bittensor.core.axon import Axon from bittensor.core.subtensor import Subtensor from bittensor.core.extrinsics import serving @@ -112,7 +95,9 @@ def test_serve_extrinsic_happy_path( ): # Prep mocker.patch.object( - mock_subtensor, "sign_and_send_extrinsic", return_value=(True, "") + mock_subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, ""), ) # Call result = serving.serve_extrinsic( @@ -129,7 +114,7 @@ def test_serve_extrinsic_happy_path( ) # Assert - assert result == expected, f"Test ID: {test_id}" + assert result.success == expected, f"Test ID: {test_id}" # Various edge cases @@ -168,7 +153,9 @@ def test_serve_extrinsic_edge_cases( ): # Prep mocker.patch.object( - mock_subtensor, "sign_and_send_extrinsic", return_value=(True, "") + mock_subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(True, ""), ) # Call @@ -186,7 +173,7 @@ def test_serve_extrinsic_edge_cases( ) # Assert - assert result == expected, f"Test ID: {test_id}" + assert result.success == expected, f"Test ID: {test_id}" # Various error cases @@ -225,7 +212,9 @@ def test_serve_extrinsic_error_cases( ): # Prep mocker.patch.object( - mock_subtensor, "sign_and_send_extrinsic", return_value=(False, "") + mock_subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(False, ""), ) # Call result = serving.serve_extrinsic( @@ -242,7 +231,7 @@ def test_serve_extrinsic_error_cases( ) # Assert - assert result == expected_error_message, f"Test ID: {test_id}" + assert result.success == expected_error_message, f"Test ID: {test_id}" @pytest.mark.parametrize( @@ -296,7 +285,9 @@ def test_serve_axon_extrinsic( else MagicMock(return_value="192.168.1.1"), ): mocker.patch.object( - mock_subtensor, "sign_and_send_extrinsic", return_value=(serve_success, "") + mock_subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(serve_success, ""), ) # Calls @@ -319,11 +310,11 @@ def test_serve_axon_extrinsic( ) # Assert - assert result == expected_result, f"Test ID: {test_id}" + assert result.success == expected_result, f"Test ID: {test_id}" @pytest.mark.parametrize( - "wait_for_inclusion, wait_for_finalization, net_uid, type_u, data, response_success, expected_result, test_id", + "wait_for_inclusion, wait_for_finalization, net_uid, type_u, data, response_success, test_id", [ ( True, @@ -331,8 +322,7 @@ def test_serve_axon_extrinsic( 1, "Sha256", b"mock_bytes_data", - (True, ""), - True, + ExtrinsicResponse(True, ""), "happy-path-wait", ), ( @@ -341,8 +331,7 @@ def test_serve_axon_extrinsic( 1, "Sha256", b"mock_bytes_data", - (True, ""), - True, + ExtrinsicResponse(True, ""), "happy-path-no-wait", ), ], @@ -357,7 +346,6 @@ def test_publish_metadata( type_u, data, response_success, - expected_result, test_id, ): # Arrange @@ -378,7 +366,7 @@ def test_publish_metadata( wait_for_finalization=wait_for_finalization, ) # Assert - assert result == expected_result, f"Test ID: {test_id}" + assert result.success is True, f"Test ID: {test_id}" mocked_sign_and_send_extrinsic.assert_called_once_with( call=mock_subtensor.substrate.compose_call.return_value, wallet=mock_wallet, @@ -387,4 +375,5 @@ def test_publish_metadata( wait_for_finalization=wait_for_finalization, period=None, raise_error=False, + calling_function="publish_metadata", ) From b2fcfcec38b9b5f1cfd5128e82a2b8896384f89d Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 17:34:49 -0700 Subject: [PATCH 206/416] update `publish_metadata` -> `publish_metadata_extrinsic` --- bittensor/core/async_subtensor.py | 160 ++++++++++--------- bittensor/core/extrinsics/asyncex/serving.py | 2 +- bittensor/core/extrinsics/serving.py | 2 +- bittensor/core/subtensor.py | 118 +++++++------- migration.md | 3 + tests/e2e_tests/test_staking.py | 2 +- tests/unit_tests/extrinsics/test_serving.py | 4 +- tests/unit_tests/test_subtensor.py | 4 +- 8 files changed, 152 insertions(+), 143 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 329f561dd0..be76ddcb27 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -62,7 +62,7 @@ from bittensor.core.extrinsics.asyncex.root import root_register_extrinsic from bittensor.core.extrinsics.asyncex.serving import ( get_last_bonds_reset, - publish_metadata, + publish_metadata_extrinsic, get_metadata, ) from bittensor.core.extrinsics.asyncex.serving import serve_axon_extrinsic @@ -3992,60 +3992,6 @@ async def recycle( ) return None if call is None else Balance.from_rao(int(call)) - async def set_reveal_commitment( - self, - wallet, - netuid: int, - data: str, - 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. - - 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. - - Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. - """ - - encrypted, reveal_round = get_encrypted_commitment( - data, blocks_until_reveal, block_time - ) - - # increase reveal_round in return + 1 because we want to fetch data from the chain after that round was revealed - # and stored. - data_ = {"encrypted": encrypted, "reveal_round": reveal_round} - return await publish_metadata( - subtensor=self, - wallet=wallet, - netuid=netuid, - 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( self, netuid: int, @@ -5102,69 +5048,69 @@ async def reveal_weights( return response - async def root_set_pending_childkey_cooldown( + async def root_register( self, wallet: "Wallet", - cooldown: int, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> ExtrinsicResponse: - """Sets the pending childkey cooldown. + """ + Register neuron by recycling some TAO. Parameters: - wallet: bittensor wallet instance. - cooldown: the number of blocks to setting pending childkey cooldown. - 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. + 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: ExtrinsicResponse: The result object of the extrinsic execution. - - Note: This operation can only be successfully performed if your wallet has root privileges. """ - return await root_set_pending_childkey_cooldown_extrinsic( + + return await root_register_extrinsic( subtensor=self, wallet=wallet, - cooldown=cooldown, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - async def root_register( + 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, ) -> ExtrinsicResponse: - """ - Register neuron by recycling some TAO. + """Sets the pending childkey cooldown. 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. + wallet: bittensor wallet instance. + cooldown: the number of blocks to setting pending childkey cooldown. + 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: ExtrinsicResponse: The result object of the extrinsic execution. - """ - return await root_register_extrinsic( + Note: This operation can only be successfully performed if your wallet has root privileges. + """ + return await root_set_pending_childkey_cooldown_extrinsic( subtensor=self, wallet=wallet, + cooldown=cooldown, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -5529,7 +5475,7 @@ async def serve_axon( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Registers an ``Axon`` serving endpoint on the Bittensor network for a specific neuron. @@ -5573,7 +5519,7 @@ async def set_commitment( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Commits arbitrary data to the Bittensor network by publishing metadata. @@ -5603,7 +5549,7 @@ async def set_commitment( Note: See """ - return await publish_metadata( + return await publish_metadata_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -5615,6 +5561,62 @@ async def set_commitment( wait_for_finalization=wait_for_finalization, ) + async def set_reveal_commitment( + self, + wallet, + netuid: int, + data: str, + 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, + ) -> ExtrinsicResponse: + """ + Commits arbitrary data to the Bittensor network by publishing metadata. + + 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: + ExtrinsicResponse: The result object of the extrinsic execution. + + Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + """ + + encrypted, reveal_round = get_encrypted_commitment( + data, blocks_until_reveal, block_time + ) + + # increase reveal_round in return + 1 because we want to fetch data from the chain after that round was revealed + # and stored. + data_ = {"encrypted": encrypted, "reveal_round": reveal_round} + response = await publish_metadata_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + data_type="TimelockEncrypted", + data=data_, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + response.data = {"reveal_round": reveal_round} + return response + async def start_call( self, wallet: "Wallet", diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 58c6d83018..9ab8b078b9 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -196,7 +196,7 @@ async def serve_axon_extrinsic( return response -async def publish_metadata( +async def publish_metadata_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 51ce310022..3c916b6fb9 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -194,7 +194,7 @@ def serve_axon_extrinsic( return response -def publish_metadata( +def publish_metadata_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index cc06731866..b5a1b096cc 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -67,7 +67,7 @@ from bittensor.core.extrinsics.root import root_register_extrinsic from bittensor.core.extrinsics.serving import ( get_last_bonds_reset, - publish_metadata, + publish_metadata_extrinsic, get_metadata, serve_axon_extrinsic, ) @@ -2925,60 +2925,6 @@ def recycle(self, netuid: int, block: Optional[int] = None) -> Optional[Balance] call = self.get_hyperparameter(param_name="Burn", netuid=netuid, block=block) return None if call is None else Balance.from_rao(int(call)) - def set_reveal_commitment( - self, - wallet, - netuid: int, - data: str, - 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. - - 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. - - Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. - """ - - encrypted, reveal_round = get_encrypted_commitment( - data, blocks_until_reveal, block_time - ) - - # increase reveal_round in return + 1 because we want to fetch data from the chain after that round was revealed - # and stored. - data_ = {"encrypted": encrypted, "reveal_round": reveal_round} - return publish_metadata( - subtensor=self, - wallet=wallet, - netuid=netuid, - 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]: """ Retrieves the subnet information for a single subnet in the network. @@ -4353,7 +4299,7 @@ def serve_axon( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Registers an ``Axon`` serving endpoint on the Bittensor network for a specific neuron. @@ -4397,7 +4343,7 @@ def set_commitment( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Commits arbitrary data to the Bittensor network by publishing metadata. @@ -4428,7 +4374,7 @@ def set_commitment( Note: See """ - return publish_metadata( + return publish_metadata_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -4440,6 +4386,62 @@ def set_commitment( wait_for_finalization=wait_for_finalization, ) + def set_reveal_commitment( + self, + wallet, + netuid: int, + data: str, + 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, + ) -> ExtrinsicResponse: + """ + Commits arbitrary data to the Bittensor network by publishing metadata. + + 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: + ExtrinsicResponse: The result object of the extrinsic execution. + + Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + """ + + encrypted, reveal_round = get_encrypted_commitment( + data, blocks_until_reveal, block_time + ) + + # increase reveal_round in return + 1 because we want to fetch data from the chain after that round was revealed + # and stored. + data_ = {"encrypted": encrypted, "reveal_round": reveal_round} + response = publish_metadata_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + data_type="TimelockEncrypted", + data=data_, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + response.data = data_ + return response + def start_call( self, wallet: "Wallet", diff --git a/migration.md b/migration.md index 5a98819699..2a06221155 100644 --- a/migration.md +++ b/migration.md @@ -197,6 +197,9 @@ 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` + - Changes in `.publish_metadata` and subtensor's related calls: + - `publish_metadata` renamed to `publish_metadata_extrinsic` + - The response `subtensor.set_reveal_commitment` contains the field `date={"encrypted": encrypted, "reveal_round": reveal_round}` - [x] `.add_stake_extrinsic` and `subtensor.add_stake` - Changes in `.add_stake_extrinsic` and `subtensor.add_stake`: - parameter `old_balance` removed from async version diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 3227f8f00e..b292e5ec47 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -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).success + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success # Verify subnet created successfully assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 67b124c295..0af11ae913 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -356,7 +356,7 @@ def test_publish_metadata( ) as mocked_sign_and_send_extrinsic, ): # Act - result = serving.publish_metadata( + result = serving.publish_metadata_extrinsic( subtensor=mock_subtensor, wallet=mock_wallet, netuid=net_uid, @@ -375,5 +375,5 @@ def test_publish_metadata( wait_for_finalization=wait_for_finalization, period=None, raise_error=False, - calling_function="publish_metadata", + calling_function="publish_metadata_extrinsic", ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 080a2737a5..9586620c61 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1266,7 +1266,9 @@ def test_commit(subtensor, fake_wallet, mocker): # Preps fake_netuid = 1 fake_data = "some data to network" - mocked_publish_metadata = mocker.patch.object(subtensor_module, "publish_metadata") + mocked_publish_metadata = mocker.patch.object( + subtensor_module, "publish_metadata_extrinsic" + ) # Call result = subtensor.set_commitment(fake_wallet, fake_netuid, fake_data) From 7a56176765bb4bd9382402879dbb5efba827d703 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 18:32:29 -0700 Subject: [PATCH 207/416] `add_stake_extrinsic`, `subtensor.add_stake`, `add_stake_multiple_extrinsic`, `subtensor.add_stake_multiple` + tests --- bittensor/core/async_subtensor.py | 4 +- bittensor/core/extrinsics/asyncex/staking.py | 61 +++-- bittensor/core/extrinsics/staking.py | 61 +++-- bittensor/core/subtensor.py | 4 +- tests/e2e_tests/test_delegate.py | 38 +-- tests/e2e_tests/test_dendrite.py | 40 +-- tests/e2e_tests/test_liquid_alpha.py | 16 +- tests/e2e_tests/test_liquidity.py | 32 +-- tests/e2e_tests/test_metagraph.py | 16 +- tests/e2e_tests/test_root_set_weights.py | 18 +- tests/e2e_tests/test_set_weights.py | 16 +- tests/e2e_tests/test_staking.py | 249 ++++++++++--------- 12 files changed, 307 insertions(+), 248 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index be76ddcb27..88183d3c13 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4446,7 +4446,7 @@ async def add_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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 @@ -4554,7 +4554,7 @@ async def add_stake_multiple( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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. diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index ef32256ca7..a065f8c7c7 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -2,8 +2,9 @@ from typing import Optional, Sequence, TYPE_CHECKING from async_substrate_interface.errors import SubstrateRequestException - +from bittensor.core.types import ExtrinsicResponse from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.utils import unlock_key, format_error_message, get_function_name from bittensor.core.types import UIDs from bittensor.utils import unlock_key, format_error_message from bittensor.utils.balance import Balance @@ -27,7 +28,7 @@ async def add_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ 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 @@ -50,7 +51,7 @@ async def add_stake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. Raises: SubstrateRequestException: Raised if the extrinsic fails to be included in the block within the timeout. @@ -59,7 +60,9 @@ async def add_stake_extrinsic( # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" @@ -87,11 +90,14 @@ async def add_stake_extrinsic( # Check enough to stake. if amount > old_balance: - logging.error(":cross_mark: [red]Not enough stake:[/red]") + message = "Not enough stake" + logging.error(f":cross_mark: [red]{message}:[/red]") logging.error(f"\t\tbalance:{old_balance}") logging.error(f"\t\tamount: {amount}") logging.error(f"\t\twallet: {wallet.name}") - return False + return ExtrinsicResponse( + False, f"{message}.", extrinsic_function=get_function_name() + ) call_params = { "hotkey": hotkey_ss58, @@ -138,7 +144,7 @@ async def add_stake_extrinsic( call_function=call_function, call_params=call_params, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -148,11 +154,12 @@ async def add_stake_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if success: # If we successfully staked. + if response.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 + return response logging.success(":white_heavy_check_mark: [green]Finalized[/green]") @@ -179,15 +186,15 @@ async def add_stake_extrinsic( logging.info( f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" ) - return True + return response - if safe_staking and "Custom error: 8" in message: + if safe_staking and "Custom error: 8" in response.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: {response.message}.[/red]") + return response async def add_stake_multiple_extrinsic( @@ -200,7 +207,7 @@ async def add_stake_multiple_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey on subnet with corresponding netuid. @@ -219,12 +226,14 @@ async def add_stake_multiple_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) assert all( [ @@ -235,7 +244,9 @@ async def add_stake_multiple_extrinsic( ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." if len(hotkey_ss58s) == 0: - return True + return ExtrinsicResponse( + True, "Success", extrinsic_function=get_function_name() + ) assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." @@ -250,7 +261,9 @@ async def add_stake_multiple_extrinsic( if sum(amount.tao for amount in new_amounts) == 0: # Staking 0 tao - return True + return ExtrinsicResponse( + True, "Success", extrinsic_function=get_function_name() + ) logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" @@ -290,6 +303,7 @@ async def add_stake_multiple_extrinsic( ] successful_stakes = 0 + response = ExtrinsicResponse(False, "", extrinsic_function=get_function_name()) for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( zip(hotkey_ss58s, new_amounts, old_stakes, netuids) ): @@ -315,7 +329,7 @@ async def add_stake_multiple_extrinsic( "netuid": netuid, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -325,10 +339,11 @@ async def add_stake_multiple_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) # If we successfully staked. - if success: + if response.success: if not wait_for_finalization and not wait_for_inclusion: old_balance -= amount successful_stakes += 1 @@ -358,7 +373,7 @@ async def add_stake_multiple_extrinsic( old_balance = new_balance successful_stakes += 1 else: - logging.error(f":cross_mark: [red]Failed: {message}.[/red]") + logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") continue except SubstrateRequestException as error: @@ -375,9 +390,9 @@ async def add_stake_multiple_extrinsic( logging.info( f"Balance: [blue]{initial_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) - return True + return response - return False + return response async def set_auto_stake_extrinsic( diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 52383d5ac1..99b692250d 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -1,8 +1,9 @@ from typing import Optional, TYPE_CHECKING, Sequence from async_substrate_interface.errors import SubstrateRequestException - +from bittensor.core.types import ExtrinsicResponse from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.utils import unlock_key, format_error_message, get_function_name from bittensor.core.types import UIDs from bittensor.utils import unlock_key, format_error_message from bittensor.utils.balance import Balance @@ -26,7 +27,7 @@ def add_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ 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 @@ -49,7 +50,7 @@ def add_stake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. Raises: SubstrateRequestException: Raised if the extrinsic fails to be included in the block within the timeout. @@ -58,7 +59,9 @@ def add_stake_extrinsic( # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" @@ -82,11 +85,14 @@ def add_stake_extrinsic( # Check enough to stake. if amount > old_balance: - logging.error(":cross_mark: [red]Not enough stake:[/red]") + message = "Not enough stake" + logging.error(f":cross_mark: [red]{message}:[/red]") logging.error(f"\t\tbalance:{old_balance}") logging.error(f"\t\tamount: {amount}") logging.error(f"\t\twallet: {wallet.name}") - return False + return ExtrinsicResponse( + False, f"{message}.", extrinsic_function=get_function_name() + ) call_params = { "hotkey": hotkey_ss58, @@ -134,7 +140,7 @@ def add_stake_extrinsic( call_params=call_params, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -144,12 +150,13 @@ def add_stake_extrinsic( nonce_key="coldkeypub", period=period, raise_error=raise_error, + calling_function=get_function_name(), ) # If we successfully staked. - if success: + if response.success: # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - return True + return response logging.success(":white_heavy_check_mark: [green]Finalized[/green]") @@ -173,15 +180,15 @@ def add_stake_extrinsic( logging.info( f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" ) - return True + return response - if safe_staking and "Custom error: 8" in message: + if safe_staking and "Custom error: 8" in response.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: {response.message}.[/red]") + return response def add_stake_multiple_extrinsic( @@ -194,7 +201,7 @@ def add_stake_multiple_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey on subnet with corresponding netuid. @@ -213,12 +220,14 @@ def add_stake_multiple_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) assert all( [ @@ -229,7 +238,9 @@ def add_stake_multiple_extrinsic( ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." if len(hotkey_ss58s) == 0: - return True + return ExtrinsicResponse( + True, "Success", extrinsic_function=get_function_name() + ) assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." @@ -244,7 +255,9 @@ def add_stake_multiple_extrinsic( if sum(amount.tao for amount in new_amounts) == 0: # Staking 0 tao - return True + return ExtrinsicResponse( + True, "Success", extrinsic_function=get_function_name() + ) logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" @@ -282,6 +295,7 @@ def add_stake_multiple_extrinsic( ] successful_stakes = 0 + response = ExtrinsicResponse(False, "", extrinsic_function=get_function_name()) for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( zip(hotkey_ss58s, new_amounts, old_stakes, netuids) ): @@ -307,7 +321,7 @@ def add_stake_multiple_extrinsic( "netuid": netuid, }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -317,10 +331,11 @@ def add_stake_multiple_extrinsic( sign_with="coldkey", period=period, raise_error=raise_error, + calling_function=get_function_name(), ) # If we successfully staked. - if success: + if response.success: # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: old_balance -= amount @@ -348,7 +363,7 @@ def add_stake_multiple_extrinsic( old_balance = new_balance successful_stakes += 1 else: - logging.error(f":cross_mark: [red]Failed[/red]: {message}") + logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") continue except SubstrateRequestException as error: @@ -365,9 +380,9 @@ def add_stake_multiple_extrinsic( logging.info( f"Balance: [blue]{initial_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) - return True + return response - return False + return response def set_auto_stake_extrinsic( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b5a1b096cc..88eb85e790 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3284,7 +3284,7 @@ def add_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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 @@ -3392,7 +3392,7 @@ def add_stake_multiple( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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. diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index d387b30112..9d1128132f 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -454,12 +454,12 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): is True ) - subtensor.staking.add_stake( + assert subtensor.staking.add_stake( wallet=bob_wallet, netuid=alice_subnet_netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), - ) + ).success # let chain update validator_permits subtensor.wait_for_block(subtensor.block + set_tempo + 1) @@ -623,12 +623,14 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): ) )[0] is True - await async_subtensor.staking.add_stake( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(10_000), - ) + assert ( + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(10_000), + ) + ).success # let chain update validator_permits await async_subtensor.wait_for_block(await async_subtensor.block + set_tempo + 1) @@ -688,13 +690,12 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ netuid=alice_subnet_netuid, ).success - success = subtensor.staking.add_stake( + assert subtensor.staking.add_stake( wallet=dave_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1000), - ) - assert success is True + ).success stake = subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, @@ -775,13 +776,14 @@ async def test_nominator_min_required_stake_async( ) ).success - success = await async_subtensor.staking.add_stake( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1000), - ) - assert success is True + assert ( + await async_subtensor.staking.add_stake( + wallet=dave_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1000), + ) + ).success stake = await async_subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index fb82738055..0aa7d9e6f2 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -61,7 +61,7 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): netuid=alice_subnet_netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), - ) + ).success # set tempo to 10 block for non-fast-runtime assert sudo_set_admin_utils( substrate=subtensor.substrate, @@ -129,7 +129,7 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=tao, - ) + ).success # Refresh metagraph metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) @@ -203,14 +203,16 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall 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, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(5), - wait_for_inclusion=False, - wait_for_finalization=False, - ) + 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, + ) + ).success # set tempo to 10 block for non-fast-runtime assert await async_sudo_set_admin_utils( substrate=async_subtensor.substrate, @@ -273,14 +275,16 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall 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, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=tao, - wait_for_inclusion=False, - wait_for_finalization=False, - ) + 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, + ) + ).success # Refresh metagraph metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index dbe41c7b67..29d86e08e3 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -76,7 +76,7 @@ def test_liquid_alpha(subtensor, alice_wallet): netuid=netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), - ), "Unable to stake to Alice neuron" + ).success, "Unable to stake to Alice neuron" # Assert liquid alpha is disabled assert ( @@ -264,12 +264,14 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): ).success, "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, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(10_000), - ), "Unable to stake to Alice neuron" + 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), + ) + ).success, "Unable to stake to Alice neuron" # Assert liquid alpha is disabled assert ( diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index 913fd0db9d..0b2f504495 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -81,7 +81,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, amount=Balance.from_tao(1), - ), "❌ Cannot cannot add stake to Alice from Alice." + ).success, "❌ Cannot cannot add stake to Alice from Alice." # wait for the next block to give the chain time to update the stake subtensor.wait_for_block() @@ -196,7 +196,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, amount=Balance.from_tao(1000), - ), "❌ Cannot add stake from Bob to Alice." + ).success, "❌ Cannot add stake from Bob to Alice." # wait for the next block to give the chain time to update the stake subtensor.wait_for_block() @@ -367,12 +367,14 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): assert message == "Success", "❌ Cannot enable user liquidity." # Add steak to call add_liquidity - 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), - ), "❌ Cannot cannot add stake to Alice from Alice." + 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), + ) + ).success, "❌ 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() @@ -484,12 +486,14 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): ) # Add stake from Bob to Alice - 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), - ), "❌ Cannot add stake from Bob to Alice." + 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), + ) + ).success, "❌ 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() diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 92f9ebb538..7071c0868c 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -145,7 +145,7 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=tao, - ), "Failed to add stake for Bob" + ).success, "Failed to add stake for Bob" logging.console.info("Assert stake is added after updating metagraph") metagraph.sync(subtensor=subtensor._subtensor) @@ -313,12 +313,14 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w 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, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=tao, - ), "Failed to add stake for Bob" + assert ( + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=tao, + ) + ).success, "Failed to add stake for Bob" logging.console.info("Assert stake is added after updating metagraph") await metagraph.sync(subtensor=async_subtensor._subtensor) diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index 03392decc3..dc906a86ef 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -92,7 +92,7 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), period=16, - ), "Unable to stake from Bob to Alice" + ).success, "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 @@ -209,13 +209,15 @@ async def test_root_reg_hyperparams_async( ) assert await async_subtensor.subnets.tempo(netuid=netuid) == default_tempo - assert await async_subtensor.staking.add_stake( - wallet=bob_wallet, - netuid=netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1), - period=16, - ), "Unable to stake from Bob to Alice" + assert ( + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1), + period=16, + ) + ).success, "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 diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 23bac7a970..5eb1cf5662 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -111,7 +111,7 @@ def test_set_weights_uses_next_nonce(subtensor, alice_wallet): netuid=netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), - ) + ).success # Set weight hyperparameters per subnet for netuid in netuids: @@ -275,12 +275,14 @@ async def test_set_weights_uses_next_nonce_async(async_subtensor, alice_wallet): # Stake to become to top neuron after the first epoch for netuid in netuids: - 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), - ) + 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), + ) + ).success # Set weight hyperparameters per subnet for netuid in netuids: diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index b292e5ec47..7bf0d5a841 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -57,15 +57,13 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): assert stake == Balance(0).set_unit(alice_subnet_netuid) - success = subtensor.staking.add_stake( + assert subtensor.staking.add_stake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), period=16, - ) - - assert success is True + ).success stake_alice = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -239,15 +237,15 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) assert stake == Balance(0).set_unit(alice_subnet_netuid) - success = await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1), - period=16, - ) - - assert success is True + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1), + period=16, + ) + ).success stake_alice = await async_subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -431,14 +429,12 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): alice_balance = balances[alice_wallet.coldkey.ss58_address] - success = subtensor.staking.add_stake_multiple( + assert subtensor.staking.add_stake_multiple( wallet=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 + ).success stakes = [ subtensor.staking.get_stake( @@ -578,14 +574,14 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) 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 + assert ( + 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], + ) + ).success stakes = [ await async_subtensor.staking.get_stake( @@ -737,16 +733,18 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) stake_amount = Balance.from_tao(100) # 1. Strict params - should fail - success = subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=stake_amount, - safe_staking=True, - rate_tolerance=0.005, # 0.5% - allow_partial_stake=False, - ) - assert success is False + assert ( + subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=stake_amount, + safe_staking=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ).success + is False + ), "Staking should fail." current_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -759,7 +757,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) logging.console.info(f"[orange]Current stake: {current_stake}[orange]") # 2. Partial allowed - should succeed partially - success = subtensor.staking.add_stake( + assert subtensor.staking.add_stake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -767,8 +765,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) safe_staking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=True, - ) - assert success is True + ).success partial_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -784,7 +781,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) # 3. Higher threshold - should succeed fully amount = Balance.from_tao(100) - success = subtensor.staking.add_stake( + assert subtensor.staking.add_stake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -792,8 +789,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) safe_staking=True, rate_tolerance=0.22, # 22% allow_partial_stake=False, - ) - assert success is True + ).success full_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -929,16 +925,17 @@ async def test_safe_staking_scenarios_async( stake_amount = Balance.from_tao(100) # 1. Strict params - should fail - success = await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=stake_amount, - safe_staking=True, - rate_tolerance=0.005, # 0.5% - allow_partial_stake=False, - ) - assert success is False + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=stake_amount, + safe_staking=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ) + ).success is False, "Staking should fail." current_stake = await async_subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -951,16 +948,17 @@ async def test_safe_staking_scenarios_async( logging.console.info(f"[orange]Current stake: {current_stake}[orange]") # 2. Partial allowed - should succeed partially - success = await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=stake_amount, - safe_staking=True, - rate_tolerance=0.005, # 0.5% - allow_partial_stake=True, - ) - assert success is True + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=stake_amount, + safe_staking=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=True, + ) + ).success partial_stake = await async_subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -976,16 +974,17 @@ async def test_safe_staking_scenarios_async( # 3. Higher threshold - should succeed fully amount = Balance.from_tao(100) - success = await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=amount, - safe_staking=True, - rate_tolerance=0.22, # 22% - allow_partial_stake=False, - ) - assert success is True + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=amount, + safe_staking=True, + rate_tolerance=0.22, # 22% + allow_partial_stake=False, + ) + ).success full_stake = await async_subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -1095,13 +1094,12 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): # Add initial stake to swap from initial_stake_amount = Balance.from_tao(10_000) - success = subtensor.staking.add_stake( + assert subtensor.staking.add_stake( wallet=alice_wallet, netuid=origin_netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=initial_stake_amount, - ) - assert success is True + ).success origin_stake = subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, @@ -1213,13 +1211,14 @@ async def test_safe_swap_stake_scenarios_async( # Add initial stake to swap from initial_stake_amount = Balance.from_tao(10_000) - success = await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=origin_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=initial_stake_amount, - ) - assert success is True + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=origin_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=initial_stake_amount, + ) + ).success origin_stake = await async_subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, @@ -1308,7 +1307,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): netuid=alice_subnet_netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1_000), - ) + ).success stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) @@ -1406,7 +1405,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): hotkey_ss58=dave_wallet.hotkey.ss58_address, amount=Balance.from_tao(1000), allow_partial_stake=True, - ) + ).success dave_stake = subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, @@ -1464,12 +1463,14 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ async_subtensor, alice_wallet, alice_subnet_netuid ) - 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(1_000), - ) + 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(1_000), + ) + ).success stakes = await async_subtensor.staking.get_stake_for_coldkey( alice_wallet.coldkey.ss58_address @@ -1571,13 +1572,15 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ ) logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") - assert await async_subtensor.staking.add_stake( - wallet=dave_wallet, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, - amount=Balance.from_tao(1000), - allow_partial_stake=True, - ) + assert ( + await async_subtensor.staking.add_stake( + wallet=dave_wallet, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + amount=Balance.from_tao(1000), + allow_partial_stake=True, + ) + ).success dave_stake = await async_subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, @@ -1643,7 +1646,7 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): netuid=alice_subnet_netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1_000), - ) + ).success alice_stakes = subtensor.staking.get_stake_for_coldkey( alice_wallet.coldkey.ss58_address @@ -1770,12 +1773,14 @@ async def test_transfer_stake_async( ) ).success - 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(1_000), - ) + 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(1_000), + ) + ).success alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( alice_wallet.coldkey.ss58_address @@ -1948,14 +1953,15 @@ def test_unstaking_with_limit( netuid=alice_subnet_netuid_2, amount=Balance.from_tao(10000), period=16, - ), f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + assert subtensor.staking.add_stake( wallet=bob_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid_3, amount=Balance.from_tao(15000), period=16, - ), f"Cant add stake to dave in SN {alice_subnet_netuid_3}" + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" # Check that both stakes are presented in result bob_stakes = subtensor.staking.get_stake_info_for_coldkey( @@ -2066,20 +2072,25 @@ async def test_unstaking_with_limit_async( # Bob stakes to Dave in both SNs - assert await async_subtensor.staking.add_stake( - wallet=bob_wallet, - netuid=alice_subnet_netuid_2, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - amount=Balance.from_tao(10000), - period=16, - ), f"Cant add stake to dave in SN {alice_subnet_netuid_2}" - assert await async_subtensor.staking.add_stake( - wallet=bob_wallet, - netuid=alice_subnet_netuid_3, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(15000), - period=16, - ), f"Cant add stake to dave in SN {alice_subnet_netuid_3}" + assert ( + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=alice_subnet_netuid_2, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + amount=Balance.from_tao(10000), + period=16, + ) + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + + assert ( + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=alice_subnet_netuid_3, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(15000), + period=16, + ) + ).success, 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( From 55c40643650decbafdc14d004d7aee560618e2e3 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 18:51:07 -0700 Subject: [PATCH 208/416] `start_call_extrinsic`, `subtensor.start_call` + tests --- bittensor/core/async_subtensor.py | 2 +- .../core/extrinsics/asyncex/start_call.py | 24 ++++++++----------- bittensor/core/extrinsics/start_call.py | 24 ++++++++----------- bittensor/core/subtensor.py | 2 +- .../extrinsics/asyncex/test_start_call.py | 3 ++- tests/unit_tests/extrinsics/test_staking.py | 14 +++++++---- .../unit_tests/extrinsics/test_start_call.py | 3 ++- 7 files changed, 35 insertions(+), 37 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 88183d3c13..838b439158 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5625,7 +5625,7 @@ async def start_call( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ 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). diff --git a/bittensor/core/extrinsics/asyncex/start_call.py b/bittensor/core/extrinsics/asyncex/start_call.py index 3a2c64937a..d2a8747694 100644 --- a/bittensor/core/extrinsics/asyncex/start_call.py +++ b/bittensor/core/extrinsics/asyncex/start_call.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING, Optional -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging if TYPE_CHECKING: @@ -16,7 +17,7 @@ async def start_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ 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). @@ -33,13 +34,13 @@ async def start_call_extrinsic( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) async with subtensor.substrate as substrate: start_call = await substrate.compose_call( @@ -48,19 +49,14 @@ async def start_call_extrinsic( call_params={"netuid": netuid}, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=start_call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if not wait_for_finalization and not wait_for_inclusion: - return True, message - - if success: - return True, "Success with `start_call` response." - - return True, message + return response diff --git a/bittensor/core/extrinsics/start_call.py b/bittensor/core/extrinsics/start_call.py index 4a0b37e382..9c3026d07b 100644 --- a/bittensor/core/extrinsics/start_call.py +++ b/bittensor/core/extrinsics/start_call.py @@ -1,6 +1,7 @@ from typing import TYPE_CHECKING, Optional -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging if TYPE_CHECKING: @@ -16,7 +17,7 @@ def start_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ 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). @@ -33,13 +34,13 @@ def start_call_extrinsic( wait_for_finalization: Whether to wait for finalization of the extrinsic. 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) start_call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -47,19 +48,14 @@ def start_call_extrinsic( call_params={"netuid": netuid}, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=start_call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if not wait_for_finalization and not wait_for_inclusion: - return True, message - - if success: - return True, "Success with `start_call` response." - - return True, message + return response diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 88eb85e790..26b00b0a3c 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4450,7 +4450,7 @@ def start_call( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ 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). diff --git a/tests/unit_tests/extrinsics/asyncex/test_start_call.py b/tests/unit_tests/extrinsics/asyncex/test_start_call.py index 68507acbd2..4f9bdddbd9 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_start_call.py +++ b/tests/unit_tests/extrinsics/asyncex/test_start_call.py @@ -13,7 +13,7 @@ async def test_start_call_extrinsics(subtensor, mocker, fake_wallet): substrate = subtensor.substrate.__aenter__.return_value substrate.compose_call = mocker.AsyncMock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(True, "") + subtensor, "sign_and_send_extrinsic", return_value=(True, "Success") ) # Call @@ -37,6 +37,7 @@ async def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wait_for_finalization=False, period=None, raise_error=False, + calling_function="start_call_extrinsic", ) assert success is True diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 8260590968..f8e7dd6892 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -1,6 +1,8 @@ +import pytest + from bittensor.core.extrinsics import staking from bittensor.utils.balance import Balance -import pytest +from bittensor.core.types import ExtrinsicResponse def test_add_stake_extrinsic(mocker): @@ -11,7 +13,7 @@ def test_add_stake_extrinsic(mocker): "get_balance.return_value": Balance(10), "get_existential_deposit.return_value": Balance(1), "get_hotkey_owner.return_value": "hotkey_owner", - "sign_and_send_extrinsic.return_value": (True, ""), + "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, "Success"), } ) fake_wallet_ = mocker.Mock( @@ -37,7 +39,7 @@ def test_add_stake_extrinsic(mocker): ) # Asserts - assert result is True + assert result.success is True fake_subtensor.substrate.compose_call.assert_called_once_with( call_module="SubtensorModule", @@ -54,6 +56,7 @@ def test_add_stake_extrinsic(mocker): use_nonce=True, period=None, raise_error=False, + calling_function="add_stake_extrinsic", ) @@ -63,7 +66,7 @@ def test_add_stake_multiple_extrinsic(mocker): fake_subtensor = mocker.Mock( **{ "get_balance.return_value": Balance(10.0), - "sign_and_send_extrinsic.return_value": (True, ""), + "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), "substrate.query_multi.return_value": [ ( mocker.Mock( @@ -111,7 +114,7 @@ def test_add_stake_multiple_extrinsic(mocker): ) # Asserts - assert result is True + assert result.success is True assert fake_subtensor.substrate.compose_call.call_count == 2 assert fake_subtensor.sign_and_send_extrinsic.call_count == 2 @@ -143,6 +146,7 @@ def test_add_stake_multiple_extrinsic(mocker): raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, + calling_function="add_stake_multiple_extrinsic", ) diff --git a/tests/unit_tests/extrinsics/test_start_call.py b/tests/unit_tests/extrinsics/test_start_call.py index 03373fa955..dd00bf2028 100644 --- a/tests/unit_tests/extrinsics/test_start_call.py +++ b/tests/unit_tests/extrinsics/test_start_call.py @@ -11,7 +11,7 @@ def test_start_call_extrinsics(subtensor, mocker, fake_wallet): subtensor.substrate.compose_call = mocker.Mock() mocked_sign_and_send_extrinsic = mocker.patch.object( - subtensor, "sign_and_send_extrinsic", return_value=(True, "") + subtensor, "sign_and_send_extrinsic", return_value=(True, "Success") ) # Call @@ -35,6 +35,7 @@ def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wait_for_finalization=False, period=None, raise_error=False, + calling_function="start_call_extrinsic", ) assert success is True From 0e003df16f653280425bd5103fe984b15b138189 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 19:17:19 -0700 Subject: [PATCH 209/416] `increase_take_extrinsic`, `decrease_take_extrinsic`, `subtensor.set_delegate_take` + tests --- bittensor/core/async_subtensor.py | 13 ++++---- bittensor/core/extrinsics/asyncex/take.py | 18 ++++++++--- bittensor/core/extrinsics/take.py | 17 +++++++--- bittensor/core/subtensor.py | 13 ++++---- tests/e2e_tests/test_delegate.py | 36 ++++++++++++--------- tests/unit_tests/test_async_subtensor.py | 36 ++++++++++++--------- tests/unit_tests/test_subtensor_extended.py | 12 +++---- 7 files changed, 86 insertions(+), 59 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 838b439158..4ac73d6c09 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5220,7 +5220,7 @@ async def set_delegate_take( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ 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. @@ -5259,8 +5259,9 @@ async def set_delegate_take( current_take_u16 = int(current_take * 0xFFFF) if current_take_u16 == take_u16: - logging.info(":white_heavy_check_mark: [green]Already Set[/green]") - return True, "" + message = f"The take for {hotkey_ss58} is already set to {take}" + logging.info(f":white_heavy_check_mark: [green]{message}[/green].") + return ExtrinsicResponse(True, message) logging.info(f"Updating {hotkey_ss58} take: current={current_take} new={take}") @@ -5270,7 +5271,7 @@ async def set_delegate_take( else decrease_take_extrinsic ) - success, message = await extrinsic_call( + response = await extrinsic_call( subtensor=self, wallet=wallet, hotkey_ss58=hotkey_ss58, @@ -5281,10 +5282,10 @@ async def set_delegate_take( wait_for_inclusion=wait_for_inclusion, ) - if success: + if response.success: logging.info(":white_heavy_check_mark: [green]Take Updated[/green]") - return success, message + return response async def set_subnet_identity( self, diff --git a/bittensor/core/extrinsics/asyncex/take.py b/bittensor/core/extrinsics/asyncex/take.py index a4f4a6e6e4..a60272461a 100644 --- a/bittensor/core/extrinsics/asyncex/take.py +++ b/bittensor/core/extrinsics/asyncex/take.py @@ -1,8 +1,10 @@ from typing import TYPE_CHECKING, Optional from bittensor_wallet.bittensor_wallet import Wallet + +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging -from bittensor.utils import unlock_key if TYPE_CHECKING: from bittensor.core.async_subtensor import AsyncSubtensor @@ -17,7 +19,7 @@ async def increase_take_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Sets the delegate 'take' percentage for a neuron identified by its hotkey. Parameters: @@ -41,7 +43,9 @@ async def increase_take_extrinsic( unlock = unlock_key(wallet, raise_error=raise_error) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = await subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -59,6 +63,7 @@ async def increase_take_extrinsic( wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) @@ -71,7 +76,7 @@ async def decrease_take_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Sets the delegate 'take' percentage for a neuron identified by its hotkey. @@ -95,7 +100,9 @@ async def decrease_take_extrinsic( unlock = unlock_key(wallet, raise_error=raise_error) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = await subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -113,4 +120,5 @@ async def decrease_take_extrinsic( raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + calling_function=get_function_name(), ) diff --git a/bittensor/core/extrinsics/take.py b/bittensor/core/extrinsics/take.py index 8f5af392f8..40411f3740 100644 --- a/bittensor/core/extrinsics/take.py +++ b/bittensor/core/extrinsics/take.py @@ -2,7 +2,8 @@ from bittensor_wallet.bittensor_wallet import Wallet -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse +from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging if TYPE_CHECKING: @@ -18,7 +19,7 @@ def increase_take_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Sets the delegate 'take' percentage for a neuron identified by its hotkey. Parameters: @@ -41,7 +42,9 @@ def increase_take_extrinsic( unlock = unlock_key(wallet, raise_error=raise_error) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -59,6 +62,7 @@ def increase_take_extrinsic( wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) @@ -71,7 +75,7 @@ def decrease_take_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Sets the delegate `take` percentage for a neuron identified by its hotkey. @@ -95,7 +99,9 @@ def decrease_take_extrinsic( unlock = unlock_key(wallet, raise_error=raise_error) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -113,4 +119,5 @@ def decrease_take_extrinsic( raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + calling_function=get_function_name(), ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 26b00b0a3c..f7f92ad64b 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4044,7 +4044,7 @@ def set_delegate_take( wait_for_finalization: bool = True, raise_error: bool = False, period: Optional[int] = None, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """ 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. @@ -4083,8 +4083,9 @@ def set_delegate_take( current_take_u16 = int(current_take * 0xFFFF) if current_take_u16 == take_u16: - logging.info(":white_heavy_check_mark: [green]Already Set[/green]") - return True, "" + message = f"The take for {hotkey_ss58} is already set to {take}" + logging.info(f":white_heavy_check_mark: [green]{message}[/green].") + return ExtrinsicResponse(True, message) logging.info(f"Updating {hotkey_ss58} take: current={current_take} new={take}") @@ -4094,7 +4095,7 @@ def set_delegate_take( else decrease_take_extrinsic ) - success, message = extrinsic_call( + response = extrinsic_call( subtensor=self, wallet=wallet, hotkey_ss58=hotkey_ss58, @@ -4105,10 +4106,10 @@ def set_delegate_take( wait_for_inclusion=wait_for_inclusion, ) - if success: + if response.success: logging.info(":white_heavy_check_mark: [green]Take Updated[/green]") - return success, message + return response def set_subnet_identity( self, diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 9d1128132f..6851fc6110 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -204,12 +204,12 @@ def test_change_take(subtensor, alice_wallet, bob_wallet): raise_error=True, ) - subtensor.delegates.set_delegate_take( + assert subtensor.delegates.set_delegate_take( alice_wallet, alice_wallet.hotkey.ss58_address, 0.1, raise_error=True, - ) + ).success take = subtensor.delegates.get_delegate_take(alice_wallet.hotkey.ss58_address) assert take == 0.09999237048905166 @@ -234,12 +234,12 @@ def test_change_take(subtensor, alice_wallet, bob_wallet): }, ) - subtensor.delegates.set_delegate_take( + assert subtensor.delegates.set_delegate_take( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, take=0.15, raise_error=True, - ) + ).success take = subtensor.delegates.get_delegate_take(alice_wallet.hotkey.ss58_address) assert take == 0.14999618524452582 @@ -294,12 +294,14 @@ async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): raise_error=True, ) - await async_subtensor.delegates.set_delegate_take( - alice_wallet, - alice_wallet.hotkey.ss58_address, - 0.1, - raise_error=True, - ) + assert ( + await async_subtensor.delegates.set_delegate_take( + alice_wallet, + alice_wallet.hotkey.ss58_address, + 0.1, + raise_error=True, + ) + ).success take = await async_subtensor.delegates.get_delegate_take( alice_wallet.hotkey.ss58_address @@ -328,12 +330,14 @@ async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): }, ) - await async_subtensor.delegates.set_delegate_take( - wallet=alice_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - take=0.15, - raise_error=True, - ) + assert ( + await async_subtensor.delegates.set_delegate_take( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + take=0.15, + raise_error=True, + ) + ).success take = await async_subtensor.delegates.get_delegate_take( alice_wallet.hotkey.ss58_address diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index e3931a3fdd..a4c3e1fa15 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2687,11 +2687,13 @@ async def test_set_children(subtensor, fake_wallet, mocker): async def test_set_delegate_take_equal(subtensor, fake_wallet, mocker): mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - await subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.18, - ) + assert ( + await subtensor.set_delegate_take( + fake_wallet, + fake_wallet.hotkey.ss58_address, + 0.18, + ) + ).success subtensor.substrate.submit_extrinsic.assert_not_called() @@ -2705,11 +2707,13 @@ async def test_set_delegate_take_increase( ) mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - await subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.2, - ) + assert ( + await subtensor.set_delegate_take( + fake_wallet, + fake_wallet.hotkey.ss58_address, + 0.2, + ) + ).success assert_submit_signed_extrinsic( mock_substrate, @@ -2734,11 +2738,13 @@ async def test_set_delegate_take_decrease( ) mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - await subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.1, - ) + assert ( + await subtensor.set_delegate_take( + fake_wallet, + fake_wallet.hotkey.ss58_address, + 0.1, + ) + ).success assert_submit_signed_extrinsic( mock_substrate, diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 7db7dd86ed..54ceba0d69 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -959,11 +959,11 @@ def test_set_children(mock_substrate, subtensor, fake_wallet): def test_set_delegate_take_equal(mock_substrate, subtensor, fake_wallet, mocker): mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - subtensor.set_delegate_take( + assert subtensor.set_delegate_take( fake_wallet, fake_wallet.hotkey.ss58_address, 0.18, - ) + ).success mock_substrate.submit_extrinsic.assert_not_called() @@ -971,11 +971,11 @@ def test_set_delegate_take_equal(mock_substrate, subtensor, fake_wallet, mocker) def test_set_delegate_take_increase(mock_substrate, subtensor, fake_wallet, mocker): mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - subtensor.set_delegate_take( + assert subtensor.set_delegate_take( fake_wallet, fake_wallet.hotkey.ss58_address, 0.2, - ) + ).success assert_submit_signed_extrinsic( mock_substrate, @@ -994,11 +994,11 @@ def test_set_delegate_take_increase(mock_substrate, subtensor, fake_wallet, mock def test_set_delegate_take_decrease(mock_substrate, subtensor, fake_wallet, mocker): mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - subtensor.set_delegate_take( + assert subtensor.set_delegate_take( fake_wallet, fake_wallet.hotkey.ss58_address, 0.1, - ) + ).success assert_submit_signed_extrinsic( mock_substrate, From c50507224efdc90e27f290dcc0531fa86568cf80 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 19:33:06 -0700 Subject: [PATCH 210/416] `transfer_extrinsic`, `subtensor.transfer` + tests --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/extrinsics/asyncex/transfer.py | 51 +++++++----- bittensor/core/extrinsics/transfer.py | 51 +++++++----- bittensor/core/subtensor.py | 2 +- tests/e2e_tests/test_transfer.py | 80 ++++++++++--------- .../extrinsics/asyncex/test_transfer.py | 29 +++---- tests/unit_tests/extrinsics/test_transfer.py | 28 +++---- 7 files changed, 129 insertions(+), 114 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 4ac73d6c09..5f82ebeaa8 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5770,7 +5770,7 @@ async def transfer( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - ) -> bool: + ) -> ExtrinsicResponse: """ Transfer token of amount to destination. diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index 3e3d6d7193..ce7639b9d8 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -1,12 +1,13 @@ import asyncio from typing import TYPE_CHECKING, Optional - +from bittensor.core.types import ExtrinsicResponse 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_function_name, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -27,7 +28,7 @@ async def transfer_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """Transfers funds from this wallet to the destination public key address. Parameters: @@ -47,22 +48,23 @@ async def transfer_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ + # Unlock wallet coldkey. + if not (unlock := unlock_key(wallet)).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + if amount is None and not transfer_all: - logging.error("If not transferring all, `amount` must be specified.") - return False + message = "If not transferring all, `amount` must be specified." + logging.error(message) + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) # Validate destination address. if not is_valid_bittensor_address_or_public_key(destination): - logging.error( - f":cross_mark: [red]Invalid destination SS58 address[/red]: {destination}" - ) - return False - - logging.info(f"Initiating transfer on network: {subtensor.network}") - # Unlock wallet coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False + message = f"Invalid destination SS58 address: {destination}" + logging.error(f":cross_mark: [red]{message}[/red].") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) # Check balance. logging.info( @@ -87,14 +89,18 @@ async def transfer_extrinsic( # Check if we have enough balance. if transfer_all is True: if (account_balance - fee) < existential_deposit: - logging.error("Not enough balance to transfer") - return False + message = "Not enough balance to transfer." + logging.error(message) + return ExtrinsicResponse( + False, message, extrinsic_function=get_function_name() + ) elif account_balance < (amount + fee + existential_deposit): + message = "Not enough balance." logging.error(":cross_mark: [red]Not enough balance[/red]") logging.error(f"\t\tBalance:\t[blue]{account_balance}[/blue]") logging.error(f"\t\tAmount:\t[blue]{amount}[/blue]") logging.error(f"\t\tFor fee:\t[blue]{fee}[/blue]") - return False + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) logging.info(":satellite: [magenta]Transferring... bool: +) -> ExtrinsicResponse: """Transfers funds from this wallet to the destination public key address. Parameters: @@ -46,22 +47,23 @@ def transfer_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ + # Unlock wallet coldkey. + if not (unlock := unlock_key(wallet)).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + if amount is None and not transfer_all: - logging.error("If not transferring all, `amount` must be specified.") - return False + message = "If not transferring all, `amount` must be specified." + logging.error(message) + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) # Validate destination address. if not is_valid_bittensor_address_or_public_key(destination): - logging.error( - f":cross_mark: [red]Invalid destination SS58 address[/red]: {destination}" - ) - return False - - logging.info(f"Initiating transfer on network: {subtensor.network}") - # Unlock wallet coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return False + message = f"Invalid destination SS58 address: {destination}" + logging.error(f":cross_mark: [red]{message}[/red].") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) # Check balance. logging.info( @@ -84,14 +86,18 @@ def transfer_extrinsic( # Check if we have enough balance. if transfer_all is True: if (account_balance - fee) < existential_deposit: - logging.error("Not enough balance to transfer") - return False + message = "Not enough balance to transfer." + logging.error(message) + return ExtrinsicResponse( + False, message, extrinsic_function=get_function_name() + ) elif account_balance < (amount + fee + existential_deposit): + message = "Not enough balance." logging.error(":cross_mark: [red]Not enough balance[/red]") logging.error(f"\t\tBalance:\t[blue]{account_balance}[/blue]") logging.error(f"\t\tAmount:\t[blue]{amount}[/blue]") logging.error(f"\t\tFor fee:\t[blue]{fee}[/blue]") - return False + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) logging.info(":satellite: [magenta]Transferring...[/magenta]") @@ -103,16 +109,17 @@ def transfer_extrinsic( call_params=call_params, ) - success, message = subtensor.sign_and_send_extrinsic( + response = 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, + calling_function=get_function_name(), ) - if success: + if response.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]") @@ -135,7 +142,7 @@ def transfer_extrinsic( logging.info( f"Balance: [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) - return True + return response - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return False + logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + return response diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index f7f92ad64b..4cf67b3d9a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4595,7 +4595,7 @@ def transfer( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, - ) -> bool: + ) -> ExtrinsicResponse: """ Transfer token of amount to destination. diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py index 89613f6553..cfa3eede93 100644 --- a/tests/e2e_tests/test_transfer.py +++ b/tests/e2e_tests/test_transfer.py @@ -44,7 +44,7 @@ def test_transfer(subtensor, alice_wallet): amount=transfer_value, wait_for_finalization=True, wait_for_inclusion=True, - ) + ).success # Account details after transfer balance_after = subtensor.wallets.get_balance(alice_wallet.coldkeypub.ss58_address) @@ -85,13 +85,15 @@ async def test_transfer_async(async_subtensor, alice_wallet): ) # Transfer Tao - assert await async_subtensor.extrinsics.transfer( - wallet=alice_wallet, - destination=dest_coldkey, - amount=transfer_value, - wait_for_finalization=True, - wait_for_inclusion=True, - ) + assert ( + await async_subtensor.extrinsics.transfer( + wallet=alice_wallet, + destination=dest_coldkey, + amount=transfer_value, + wait_for_finalization=True, + wait_for_inclusion=True, + ) + ).success # Account details after transfer balance_after = await async_subtensor.wallets.get_balance( alice_wallet.coldkeypub.ss58_address @@ -121,7 +123,7 @@ def test_transfer_all(subtensor, alice_wallet): amount=Balance.from_tao(2.0), wait_for_finalization=True, wait_for_inclusion=True, - ) + ).success # Account details before transfer existential_deposit = subtensor.chain.get_existential_deposit() assert subtensor.extrinsics.transfer( @@ -132,7 +134,7 @@ def test_transfer_all(subtensor, alice_wallet): wait_for_finalization=True, wait_for_inclusion=True, keep_alive=True, - ) + ).success balance_after = subtensor.wallets.get_balance( dummy_account_1.coldkeypub.ss58_address ) @@ -145,7 +147,7 @@ def test_transfer_all(subtensor, alice_wallet): wait_for_inclusion=True, wait_for_finalization=True, keep_alive=False, - ) + ).success balance_after = subtensor.wallets.get_balance( dummy_account_2.coldkeypub.ss58_address ) @@ -165,37 +167,43 @@ async def test_transfer_all_async(async_subtensor, alice_wallet): dummy_account_2.create_new_coldkey(use_password=False, overwrite=True) # fund the first dummy account - assert await async_subtensor.extrinsics.transfer( - wallet=alice_wallet, - destination=dummy_account_1.coldkeypub.ss58_address, - amount=Balance.from_tao(2.0), - wait_for_finalization=True, - wait_for_inclusion=True, - ) + assert ( + await async_subtensor.extrinsics.transfer( + wallet=alice_wallet, + destination=dummy_account_1.coldkeypub.ss58_address, + amount=Balance.from_tao(2.0), + wait_for_finalization=True, + wait_for_inclusion=True, + ) + ).success # Account details before transfer existential_deposit = await async_subtensor.chain.get_existential_deposit() - assert await async_subtensor.extrinsics.transfer( - wallet=dummy_account_1, - destination=dummy_account_2.coldkeypub.ss58_address, - amount=None, - transfer_all=True, - wait_for_finalization=True, - wait_for_inclusion=True, - keep_alive=True, - ) + assert ( + await async_subtensor.extrinsics.transfer( + wallet=dummy_account_1, + destination=dummy_account_2.coldkeypub.ss58_address, + amount=None, + transfer_all=True, + wait_for_finalization=True, + wait_for_inclusion=True, + keep_alive=True, + ) + ).success balance_after = await async_subtensor.wallets.get_balance( dummy_account_1.coldkeypub.ss58_address ) assert balance_after == existential_deposit - assert await async_subtensor.extrinsics.transfer( - wallet=dummy_account_2, - destination=alice_wallet.coldkeypub.ss58_address, - amount=None, - transfer_all=True, - wait_for_inclusion=True, - wait_for_finalization=True, - keep_alive=False, - ) + assert ( + await async_subtensor.extrinsics.transfer( + wallet=dummy_account_2, + destination=alice_wallet.coldkeypub.ss58_address, + amount=None, + transfer_all=True, + wait_for_inclusion=True, + wait_for_finalization=True, + keep_alive=False, + ) + ).success balance_after = await async_subtensor.wallets.get_balance( dummy_account_2.coldkeypub.ss58_address ) diff --git a/tests/unit_tests/extrinsics/asyncex/test_transfer.py b/tests/unit_tests/extrinsics/asyncex/test_transfer.py index 3992fa33bb..df0a82a56d 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_transfer.py +++ b/tests/unit_tests/extrinsics/asyncex/test_transfer.py @@ -1,5 +1,7 @@ import pytest + from bittensor.core.extrinsics.asyncex import transfer as async_transfer +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance @@ -37,7 +39,7 @@ async def test_transfer_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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -69,8 +71,9 @@ async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, + calling_function="transfer_extrinsic", ) - assert result is True + assert result.success is True @pytest.mark.asyncio @@ -109,7 +112,7 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( ) 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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(False, "") ) # Call @@ -142,8 +145,9 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( wait_for_finalization=True, period=None, raise_error=False, + calling_function="transfer_extrinsic", ) - assert result is False + assert result.success is False @pytest.mark.asyncio @@ -199,7 +203,7 @@ async def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, m mocked_get_existential_deposit.assert_called_once_with( block_hash=mocked_get_chain_head.return_value ) - assert result is False + assert result.success is False @pytest.mark.asyncio @@ -230,7 +234,7 @@ async def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mo # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - assert result is False + assert result.success is False @pytest.mark.asyncio @@ -241,12 +245,6 @@ async def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocke fake_destination = "invalid_address" fake_amount = Balance(15) - mocked_is_valid_address = mocker.patch.object( - async_transfer, - "is_valid_bittensor_address_or_public_key", - return_value=True, - ) - mocked_unlock_key = mocker.patch.object( async_transfer, "unlock_key", @@ -266,9 +264,8 @@ async def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocke ) # Asserts - mocked_is_valid_address.assert_called_once_with(fake_destination) mocked_unlock_key.assert_called_once_with(fake_wallet) - assert result is False + assert result.success is False @pytest.mark.asyncio @@ -307,7 +304,7 @@ async def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( ) 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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -332,4 +329,4 @@ async def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( ) assert mocked_compose_call.call_count == 0 assert mocked_sign_and_send_extrinsic.call_count == 0 - assert result is False + assert result.success is False diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index b885977de8..b56c3dbc90 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -1,6 +1,7 @@ import pytest from bittensor.core.extrinsics import transfer from bittensor.utils.balance import Balance +from bittensor.core.types import ExtrinsicResponse def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): @@ -36,7 +37,7 @@ def test_transfer_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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -68,8 +69,9 @@ def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, + calling_function="transfer_extrinsic", ) - assert result is True + assert result.success is True def test_transfer_extrinsic_call_successful_with_failed_response( @@ -107,7 +109,7 @@ def test_transfer_extrinsic_call_successful_with_failed_response( ) 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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(False, "") ) # Call @@ -139,8 +141,9 @@ def test_transfer_extrinsic_call_successful_with_failed_response( wait_for_finalization=True, period=None, raise_error=False, + calling_function="transfer_extrinsic", ) - assert result is False + assert result.success is False def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, mocker): @@ -194,7 +197,7 @@ def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, mocker) mocked_get_existential_deposit.assert_called_once_with( block=subtensor.substrate.get_block_number.return_value ) - assert result is False + assert result.success is False def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mocker): @@ -224,7 +227,7 @@ def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mocker): # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - assert result is False + assert result.success is False def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocker): @@ -234,12 +237,6 @@ def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocker): 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", @@ -259,9 +256,8 @@ def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocker): ) # Asserts - mocked_is_valid_address.assert_called_once_with(fake_destination) mocked_unlock_key.assert_called_once_with(fake_wallet) - assert result is False + assert result.success is False def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( @@ -299,7 +295,7 @@ def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( ) 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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -319,4 +315,4 @@ def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( 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 + assert result.success is False From d7527eefeed581cfa35fce5417043b53710636a9 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 20:29:16 -0700 Subject: [PATCH 211/416] `unstake_extrinsic`, `unstake_all_extrinsic`, `unstake_multiple_extrinsic`, `subtensor.unstake`, `subtensor.unstake_all`, `subtensor.unstake_multiple` --- bittensor/core/async_subtensor.py | 6 +- .../core/extrinsics/asyncex/unstaking.py | 249 +++++++++--------- bittensor/core/extrinsics/unstaking.py | 249 +++++++++--------- bittensor/core/subtensor.py | 6 +- tests/e2e_tests/test_liquidity.py | 20 +- tests/e2e_tests/test_staking.py | 8 +- .../extrinsics/asyncex/test_unstaking.py | 12 +- tests/unit_tests/extrinsics/test_unstaking.py | 14 +- 8 files changed, 292 insertions(+), 272 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 5f82ebeaa8..46e14ba034 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5866,7 +5866,7 @@ async def unstake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Removes a specified amount of stake from a single hotkey account. This function is critical for adjusting individual neuron stakes within the Bittensor network. @@ -5922,7 +5922,7 @@ async def unstake_all( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. Parameters: @@ -6007,7 +6007,7 @@ async def unstake_multiple( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 5a42e58b45..6bf725b8e0 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -1,10 +1,11 @@ import asyncio from typing import Optional, TYPE_CHECKING - +from bittensor.core.types import ExtrinsicResponse from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.utils import unlock_key, format_error_message, get_function_name from bittensor.core.types import UIDs from bittensor.utils import unlock_key, format_error_message from bittensor.utils.balance import Balance @@ -28,7 +29,7 @@ async def unstake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Removes stake into the wallet coldkey from the specified hotkey ``uid``. @@ -49,12 +50,14 @@ async def unstake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" @@ -74,118 +77,110 @@ async def unstake_extrinsic( # Check enough to unstake. if amount > old_stake: - logging.error( - 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 - - try: - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_unstaked": amount.rao, - } - if safe_unstaking: - 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 Unstaking from:[/magenta] " - 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], " - f"with partial unstake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) + message = f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {wallet.hotkey_str}" + logging.error(f":cross_mark: [red]{message}[/red]") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "remove_stake_limit" - else: - logging_info = ( - f":satellite: [magenta]Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - call_function = "remove_stake" + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_unstaked": amount.rao, + } + if safe_unstaking: + pool = await subtensor.subnet(netuid=netuid) + base_price = pool.price.tao - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, + if pool.netuid == 0: + price_with_tolerance = base_price + else: + price_with_tolerance = base_price * (1 - rate_tolerance) + + logging_info = ( + f":satellite: [magenta]Safe Unstaking from:[/magenta] " + 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], " + f"with partial unstake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue]" ) - fee = await get_extrinsic_fee( - subtensor=subtensor, call=call, keypair=wallet.coldkeypub, netuid=netuid + + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } ) - logging.info(f"{logging_info} for fee [blue]{fee}[/blue][magenta]...[/magenta]") - 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 = "remove_stake_limit" + else: + logging_info = ( + f":satellite: [magenta]Unstaking from:[/magenta] " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]" ) + call_function = "remove_stake" - 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 + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, + ) + fee = await get_extrinsic_fee( + subtensor=subtensor, call=call, keypair=wallet.coldkeypub, netuid=netuid + ) + logging.info(f"{logging_info} for fee [blue]{fee}[/blue][magenta]...[/magenta]") + response = 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, + calling_function=get_function_name(), + ) - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + if response.success: # If we successfully unstaked. + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return response - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[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.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.success(":white_heavy_check_mark: [green]Finalized[/green]") - 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." - ) - else: - logging.error(f":cross_mark: [red]Failed: {message}.[/red]") - return False + logging.info( + f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " + f"[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.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 response - except SubstrateRequestException as error: + if safe_unstaking and "Custom error: 8" in response.message: logging.error( - f":cross_mark: [red]Unstake filed with 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: {response.message}.[/red]") + return response async def unstake_all_extrinsic( @@ -198,7 +193,7 @@ async def unstake_all_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. Parameters: @@ -216,14 +211,13 @@ async def unstake_all_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call_params = { "hotkey": hotkey, @@ -253,6 +247,7 @@ async def unstake_all_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) @@ -267,7 +262,7 @@ async def unstake_multiple_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. @@ -286,12 +281,14 @@ async def unstake_multiple_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ # Unlock coldkey. if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) # or amounts or unstake_all (no both) if amounts and unstake_all: @@ -309,7 +306,9 @@ async def unstake_multiple_extrinsic( amounts = [amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids)] if sum(amount.tao for amount in amounts) == 0: # Staking 0 tao - return True + return ExtrinsicResponse( + True, "Success", extrinsic_function=get_function_name() + ) assert all( [ @@ -320,7 +319,9 @@ async def unstake_multiple_extrinsic( ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." if len(hotkey_ss58s) == 0: - return True + return ExtrinsicResponse( + True, "Success", extrinsic_function=get_function_name() + ) assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." @@ -353,6 +354,9 @@ async def unstake_multiple_extrinsic( ) successful_unstakes = 0 + response = ExtrinsicResponse( + False, "Failed", extrinsic_function=get_function_name() + ) for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( zip(hotkey_ss58s, amounts, old_stakes, netuids) ): @@ -394,7 +398,7 @@ async def unstake_multiple_extrinsic( f"[blue]{netuid}[/blue] for fee [blue]{fee}[/blue]" ) - staking_response, err_msg = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -404,9 +408,10 @@ async def unstake_multiple_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if staking_response is True: # If we successfully unstaked. + if response.success: # If we successfully unstaked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: @@ -431,14 +436,16 @@ async 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: {response.message}.[/red]") continue except SubstrateRequestException as error: logging.error( f":cross_mark: [red]Multiple unstake filed with error: {format_error_message(error)}[/red]" ) - return False + if raise_error: + raise error + return response if successful_unstakes != 0: logging.info( @@ -452,6 +459,6 @@ async def unstake_multiple_extrinsic( logging.info( f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) - return True + return response - return False + return response diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 01834a33b7..2a72ae3d8d 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -1,9 +1,10 @@ from typing import Optional, TYPE_CHECKING - +from bittensor.core.types import ExtrinsicResponse from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.core.extrinsics.utils import get_old_stakes +from bittensor.utils import unlock_key, format_error_message, get_function_name from bittensor.core.types import UIDs from bittensor.utils import unlock_key, format_error_message from bittensor.utils.balance import Balance @@ -27,7 +28,7 @@ def unstake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Removes stake into the wallet coldkey from the specified hotkey ``uid``. @@ -48,12 +49,14 @@ def unstake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ # Decrypt keys, if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) logging.info( f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" @@ -72,119 +75,109 @@ def unstake_extrinsic( # Check enough to unstake. if amount > old_stake: - logging.error( - 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 + message = f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {wallet.hotkey_str}" + logging.error(f":cross_mark: [red]{message}[/red]") + return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - try: - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_unstaked": amount.rao, - } + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_unstaked": amount.rao, + } - if safe_unstaking: - pool = subtensor.subnet(netuid=netuid) - base_price = pool.price.tao + if safe_unstaking: + 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 Unstaking from:[/magenta] " - 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], " - f"with partial unstake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "remove_stake_limit" + if pool.netuid == 0: + price_with_tolerance = base_price else: - logging_info = ( - f":satellite: [magenta]Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - call_function = "remove_stake" - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, + price_with_tolerance = base_price * (1 - rate_tolerance) + + logging_info = ( + f":satellite: [magenta]Safe Unstaking from:[/magenta] " + 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], " + f"with partial unstake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue]" ) - fee = get_extrinsic_fee( - subtensor=subtensor, netuid=netuid, call=call, keypair=wallet.coldkeypub + + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } ) - logging.info(f"{logging_info} for fee [blue]{fee}[/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, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=period, - raise_error=raise_error, + call_function = "remove_stake_limit" + else: + logging_info = ( + f":satellite: [magenta]Unstaking from:[/magenta] " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]" ) + call_function = "remove_stake" - 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 + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, + ) + fee = get_extrinsic_fee( + subtensor=subtensor, netuid=netuid, call=call, keypair=wallet.coldkeypub + ) + logging.info(f"{logging_info} for fee [blue]{fee}[/blue][magenta]...[/magenta]") + + response = 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, + calling_function=get_function_name(), + ) - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + if response.success: # If we successfully unstaked. + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return response - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - block = subtensor.get_current_block() - new_balance = subtensor.get_balance( - wallet.coldkeypub.ss58_address, block=block - ) - new_stake = subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block=block, - ) - 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.success(":white_heavy_check_mark: [green]Finalized[/green]") - 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." - ) - else: - logging.error(f":cross_mark: [red]Failed: {message}.[/red]") - return False + logging.info( + f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " + f"[magenta]...[/magenta]" + ) + block = subtensor.get_current_block() + new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) + new_stake = subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block=block, + ) + 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 response - except SubstrateRequestException as error: + if safe_unstaking and "Custom error: 8" in response.message: logging.error( - f":cross_mark: [red]Unstake filed with 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: {response.message}.[/red]") + return response def unstake_all_extrinsic( @@ -197,7 +190,7 @@ def unstake_all_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. Parameters: @@ -215,14 +208,13 @@ def unstake_all_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) call_params = { "hotkey": hotkey, @@ -241,7 +233,7 @@ def unstake_all_extrinsic( call_params=call_params, ) - success, message = subtensor.sign_and_send_extrinsic( + return subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -251,10 +243,9 @@ def unstake_all_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - return success, message - def unstake_multiple_extrinsic( subtensor: "Subtensor", @@ -267,7 +258,7 @@ def unstake_multiple_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> bool: +) -> ExtrinsicResponse: """ Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. @@ -286,12 +277,14 @@ def unstake_multiple_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - bool: True if the subnet registration was successful, False otherwise. + ExtrinsicResponse: The result object of the extrinsic execution. """ # Unlock coldkey. if not (unlock := unlock_key(wallet)).success: logging.error(unlock.message) - return False + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) # or amounts or unstake_all (no both) if amounts and unstake_all: @@ -309,7 +302,9 @@ def unstake_multiple_extrinsic( amounts = [amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids)] if sum(amount.tao for amount in amounts) == 0: # Staking 0 tao - return True + return ExtrinsicResponse( + True, "Success", extrinsic_function=get_function_name() + ) assert all( [ @@ -320,7 +315,9 @@ def unstake_multiple_extrinsic( ), "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." if len(hotkey_ss58s) == 0: - return True + return ExtrinsicResponse( + True, "Success", extrinsic_function=get_function_name() + ) assert len(netuids) == len(hotkey_ss58s) == len(amounts), ( "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." @@ -348,6 +345,9 @@ def unstake_multiple_extrinsic( ) successful_unstakes = 0 + response = ExtrinsicResponse( + False, "Failed", extrinsic_function=get_function_name() + ) for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( zip(hotkey_ss58s, amounts, old_stakes, netuids) ): @@ -389,7 +389,7 @@ def unstake_multiple_extrinsic( f"[blue]{netuid}[/blue] for fee [blue]{fee}[/blue]" ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -399,9 +399,10 @@ def unstake_multiple_extrinsic( use_nonce=True, period=period, raise_error=raise_error, + calling_function=get_function_name(), ) - if success: # If we successfully unstaked. + if response.success: # If we successfully unstaked. # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: @@ -425,14 +426,16 @@ def unstake_multiple_extrinsic( ) successful_unstakes += 1 else: - logging.error(f":cross_mark: [red]Failed: {message}.[/red]") + logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") continue except SubstrateRequestException as error: logging.error( f":cross_mark: [red]Multiple unstake filed with error: {format_error_message(error)}[/red]" ) - return False + if raise_error: + raise error + return response if successful_unstakes != 0: logging.info( @@ -443,6 +446,6 @@ def unstake_multiple_extrinsic( logging.info( f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) - return True + return response - return False + return response diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 4cf67b3d9a..450b283607 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4691,7 +4691,7 @@ def unstake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ Removes a specified amount of stake from a single hotkey account. This function is critical for adjusting individual neuron stakes within the Bittensor network. @@ -4748,7 +4748,7 @@ def unstake_all( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Unstakes all TAO/Alpha associated with a hotkey from the specified subnets on the Bittensor network. Parameters: @@ -4832,7 +4832,7 @@ def unstake_multiple( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> bool: + ) -> ExtrinsicResponse: """ 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. diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index 0b2f504495..d180dc3759 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -263,7 +263,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): rate_tolerance=0.9, # keep high rate tolerance to avoid flaky behavior wait_for_inclusion=True, wait_for_finalization=True, - ) + ).success # Check that fees_alpha comes too after all unstake liquidity_position_first = subtensor.subnets.get_liquidity_list( @@ -555,14 +555,16 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): 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, - ) + 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, + ) + ).success # Check that fees_alpha comes too after all unstake liquidity_position_first = ( diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 7bf0d5a841..e5e49122cf 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -492,7 +492,7 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): amounts=[Balance.from_tao(100) for _ in netuids], ) - assert success is True + assert success.success is True for netuid, old_stake in zip(netuids, stakes): stake = subtensor.staking.get_stake( @@ -639,7 +639,7 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) amounts=[Balance.from_tao(100) for _ in netuids], ) - assert success is True + assert success.success is True for netuid, old_stake in zip(netuids, stakes): stake = await async_subtensor.staking.get_stake( @@ -1988,7 +1988,7 @@ def test_unstaking_with_limit( hotkey=si.hotkey_ss58, netuid=si.netuid, rate_tolerance=rate_tolerance, - )[0] + ).success # Make sure both unstake were successful. bob_stakes = subtensor.staking.get_stake_info_for_coldkey( @@ -2119,7 +2119,7 @@ async def test_unstaking_with_limit_async( hotkey=si.hotkey_ss58, rate_tolerance=rate_tolerance, ) - )[0] + ).success # Make sure both unstake were successful. bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 2f70f9bc68..2124f2377a 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -1,6 +1,7 @@ import pytest from bittensor.core.extrinsics.asyncex import unstaking +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance @@ -14,7 +15,7 @@ async def test_unstake_extrinsic(fake_wallet, mocker): **{ "get_hotkey_owner.return_value": "hotkey_owner", "get_stake_for_coldkey_and_hotkey.return_value": Balance(10.0), - "sign_and_send_extrinsic.return_value": (True, ""), + "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), "get_stake.return_value": Balance(10.0), "substrate": fake_substrate, } @@ -39,7 +40,7 @@ async def test_unstake_extrinsic(fake_wallet, mocker): ) # Asserts - assert result is True + assert result.success is True fake_subtensor.substrate.compose_call.assert_awaited_once_with( call_module="SubtensorModule", @@ -60,6 +61,7 @@ async def test_unstake_extrinsic(fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="unstake_extrinsic", ) @@ -107,6 +109,7 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="unstake_all_extrinsic", ) @@ -121,7 +124,7 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): **{ "get_hotkey_owner.return_value": "hotkey_owner", "get_stake_for_coldkey_and_hotkey.return_value": [Balance(10.0)], - "sign_and_send_extrinsic.return_value": (True, ""), + "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), "tx_rate_limit.return_value": 0, "substrate": fake_substrate, } @@ -148,7 +151,7 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): ) # Asserts - assert result is True + assert result.success is True assert fake_subtensor.substrate.compose_call.call_count == 1 assert fake_subtensor.sign_and_send_extrinsic.call_count == 1 @@ -180,4 +183,5 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="unstake_multiple_extrinsic", ) diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index f4719cdbb2..5ed2da82fd 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -1,4 +1,5 @@ from bittensor.core.extrinsics import unstaking +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance @@ -11,7 +12,7 @@ def test_unstake_extrinsic(fake_wallet, mocker): **{ "get_hotkey_owner.return_value": "hotkey_owner", "get_stake_for_coldkey_and_hotkey.return_value": Balance(10.0), - "sign_and_send_extrinsic.return_value": (True, ""), + "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), "get_stake.return_value": Balance(10.0), "substrate": fake_substrate, } @@ -35,7 +36,7 @@ def test_unstake_extrinsic(fake_wallet, mocker): ) # Asserts - assert result is True + assert result.success is True fake_subtensor.substrate.compose_call.assert_called_once_with( call_module="SubtensorModule", @@ -56,6 +57,7 @@ def test_unstake_extrinsic(fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="unstake_extrinsic", ) @@ -64,7 +66,7 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): fake_subtensor = mocker.Mock( **{ "subnet.return_value": mocker.Mock(price=100), - "sign_and_send_extrinsic.return_value": (True, ""), + "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), } ) @@ -102,6 +104,7 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="unstake_all_extrinsic", ) @@ -115,7 +118,7 @@ def test_unstake_multiple_extrinsic(fake_wallet, mocker): **{ "get_hotkey_owner.return_value": "hotkey_owner", "get_stake_for_coldkey_and_hotkey.return_value": [Balance(10.0)], - "sign_and_send_extrinsic.return_value": (True, ""), + "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), "tx_rate_limit.return_value": 0, "substrate": fake_substrate, } @@ -142,7 +145,7 @@ def test_unstake_multiple_extrinsic(fake_wallet, mocker): ) # Asserts - assert result is True + assert result.success is True assert fake_subtensor.substrate.compose_call.call_count == 1 assert fake_subtensor.sign_and_send_extrinsic.call_count == 1 @@ -174,4 +177,5 @@ def test_unstake_multiple_extrinsic(fake_wallet, mocker): use_nonce=True, period=None, raise_error=False, + calling_function="unstake_multiple_extrinsic", ) From 74047ae295beaf42a5dbdb4cf87de0c36509b885 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 20:43:40 -0700 Subject: [PATCH 212/416] update migration.md --- migration.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/migration.md b/migration.md index 2a06221155..55d14e1a8f 100644 --- a/migration.md +++ b/migration.md @@ -42,11 +42,12 @@ ``` -2. Unify extrinsic return values by introducing an ExtrinsicResponse class. Extrinsics currently return either a boolean or a tuple. +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) + - Ease of processing. + - This class should contain success, message, and optionally data. + - Opportunity to expand the content of the extrinsic's response at any time upon community request or based on new technical requirements any time. 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. From 445ee18dda0f4af3f0eb411ddb285890eb8fc0eb Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 20:46:55 -0700 Subject: [PATCH 213/416] update `ExtrinsicResponse.__str__` --- bittensor/core/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 447bb2498b..b856704994 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -335,7 +335,7 @@ def __iter__(self): def __str__(self): return str( - f"ExtrinsicResponse:" + f"{self.__class__.__name__}:" f"\n\tsuccess: {self.success}" f"\n\tmessage: {self.message}" f"\n\terror: {self.error}" From 3e2dea1de8897dbebfd9eedf5394eb02a684fdde Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 21:12:54 -0700 Subject: [PATCH 214/416] opps, update response processing in tests --- tests/e2e_tests/test_reveal_commitments.py | 32 +++++++++++----------- tests/e2e_tests/test_staking.py | 28 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/tests/e2e_tests/test_reveal_commitments.py b/tests/e2e_tests/test_reveal_commitments.py index 99d6fed29a..7d7807cd2c 100644 --- a/tests/e2e_tests/test_reveal_commitments.py +++ b/tests/e2e_tests/test_reveal_commitments.py @@ -59,27 +59,27 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): 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, + wallet=alice_wallet, + netuid=alice_subnet_netuid, + data=message_alice, + blocks_until_reveal=BLOCKS_UNTIL_REVEAL, + block_time=BLOCK_TIME, ) - assert response[0] is True + assert response.success 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, + wallet=bob_wallet, + netuid=alice_subnet_netuid, + data=message_bob, + blocks_until_reveal=BLOCKS_UNTIL_REVEAL, block_time=BLOCK_TIME, ) - assert response[0] is True + assert response.success is True - target_reveal_round = response[1] + target_reveal_round = response.data.get("reveal_round") # 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. @@ -124,7 +124,7 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): @pytest.mark.asyncio -async def test_set_reveal_commitment(async_subtensor, alice_wallet, bob_wallet): +async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wallet): """ Tests the set/reveal commitments with TLE (time-locked encrypted commitments) mechanism. @@ -181,7 +181,7 @@ async def test_set_reveal_commitment(async_subtensor, alice_wallet, bob_wallet): BLOCKS_UNTIL_REVEAL, BLOCK_TIME, ) - assert response[0] is True + assert response.success is True # Set commitment from Bob's hotkey message_bob = f"This is test message with time {time.time()} from Bob." @@ -193,9 +193,9 @@ async def test_set_reveal_commitment(async_subtensor, alice_wallet, bob_wallet): BLOCKS_UNTIL_REVEAL, block_time=BLOCK_TIME, ) - assert response[0] is True + assert response.success is True - target_reveal_round = response[1] + target_reveal_round = response.data.get("reveal_round") # 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. diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index e5e49122cf..af05d5ef59 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -169,7 +169,7 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): logging.console.info(f"Alice stake before unstake: {stake}") # unstale all to check in later - success = subtensor.staking.unstake( + response = subtensor.staking.unstake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -177,7 +177,7 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): period=16, ) - assert success is True + assert response.success is True stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -799,7 +799,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) # Test Unstaking Scenarios # 1. Strict params - should fail - success = subtensor.staking.unstake( + response = subtensor.staking.unstake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -808,7 +808,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) rate_tolerance=0.005, # 0.5% allow_partial_stake=False, ) - assert success is False, "Unstake should fail." + assert response.success is False current_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -824,7 +824,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) ) # 2. Partial allowed - should succeed partially - success = subtensor.staking.unstake( + response = subtensor.staking.unstake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -833,7 +833,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) rate_tolerance=0.005, # 0.5% allow_partial_stake=True, ) - assert success is True + assert response.success is True partial_unstake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -846,7 +846,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) ) # 3. Higher threshold - should succeed fully - success = subtensor.staking.unstake( + response = subtensor.staking.unstake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -855,7 +855,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) rate_tolerance=0.3, # 30% allow_partial_stake=False, ) - assert success is True, "Unstake should succeed" + assert response.success is True, "Unstake should succeed" logging.console.success("✅ Test [green]test_safe_staking_scenarios[/green] passed") @@ -994,7 +994,7 @@ async def test_safe_staking_scenarios_async( # Test Unstaking Scenarios # 1. Strict params - should fail - success = await async_subtensor.staking.unstake( + response = await async_subtensor.staking.unstake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -1003,7 +1003,7 @@ async def test_safe_staking_scenarios_async( rate_tolerance=0.005, # 0.5% allow_partial_stake=False, ) - assert success is False, "Unstake should fail." + assert response.success is False, "Unstake should fail." current_stake = await async_subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -1019,7 +1019,7 @@ async def test_safe_staking_scenarios_async( ) # 2. Partial allowed - should succeed partially - success = await async_subtensor.staking.unstake( + response = await async_subtensor.staking.unstake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -1028,7 +1028,7 @@ async def test_safe_staking_scenarios_async( rate_tolerance=0.005, # 0.5% allow_partial_stake=True, ) - assert success is True + assert response.success is True partial_unstake = await async_subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, @@ -1041,7 +1041,7 @@ async def test_safe_staking_scenarios_async( ) # 3. Higher threshold - should succeed fully - success = await async_subtensor.staking.unstake( + response = await async_subtensor.staking.unstake( wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, @@ -1050,7 +1050,7 @@ async def test_safe_staking_scenarios_async( rate_tolerance=0.3, # 30% allow_partial_stake=False, ) - assert success is True, "Unstake should succeed" + assert response.success is True, "Unstake should succeed" logging.console.success( "✅ Test [green]test_safe_staking_scenarios_async[/green] passed" ) From f7640653f367d7d1e167f6abc55ddca9b8c6a334 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 21:29:00 -0700 Subject: [PATCH 215/416] trigger cached venv properly --- .github/workflows/_run-e2e-single.yaml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index 60bf153065..c3d6bc8ca4 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -38,11 +38,14 @@ jobs: - 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 }}- + path: ~/.cache/uv + key: > + uvwheels-${{ runner.os }}-${{ runner.arch }} + -py${{ steps.setup-python.outputs.python-version }} + -${{ hashFiles('pyproject.toml') }} + restore-keys: | + uvwheels-${{ runner.os }}-${{ runner.arch }}-py${{ steps.setup-python.outputs.python-version }}- + uvwheels-${{ runner.os }}-${{ runner.arch }}- - name: Install dependencies run: uv sync --extra dev --dev From 7f15d1dc64b8e292ace5daffae301ef9b110fa1a Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 8 Sep 2025 22:14:54 -0700 Subject: [PATCH 216/416] try avoid deps downloading freez --- .github/workflows/_run-e2e-single.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/_run-e2e-single.yaml b/.github/workflows/_run-e2e-single.yaml index c3d6bc8ca4..29b09650da 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -38,14 +38,14 @@ jobs: - name: Cache uv and venv uses: actions/cache@v4 with: - path: ~/.cache/uv - key: > - uvwheels-${{ runner.os }}-${{ runner.arch }} - -py${{ steps.setup-python.outputs.python-version }} - -${{ hashFiles('pyproject.toml') }} + path: | + ~/.cache/uv + .venv + key: | + uv-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} restore-keys: | - uvwheels-${{ runner.os }}-${{ runner.arch }}-py${{ steps.setup-python.outputs.python-version }}- - uvwheels-${{ runner.os }}-${{ runner.arch }}- + uv-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} + uv-${{ runner.os }}-${{ runner.arch }}-py${{ matrix.python-version }}- - name: Install dependencies run: uv sync --extra dev --dev From 52d46f2073b136995859888d340c442c47c09ec1 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 9 Sep 2025 13:20:12 -0700 Subject: [PATCH 217/416] update message --- bittensor/core/extrinsics/asyncex/registration.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index b11667d498..f3a29b1d42 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -287,7 +287,7 @@ async def register_extrinsic( if not torch: log_no_torch_error() return ExtrinsicResponse( - False, "No torch installed.", extrinsic_function=get_function_name() + False, "Torch is not installed.", extrinsic_function=get_function_name() ) # Attempt rolling registration. From 4e02e5c3bb37a65fb42cadb6498d16baa9c0a594 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 17 Sep 2025 00:34:35 -0700 Subject: [PATCH 218/416] fix `get_extrinsic_fee` --- bittensor/core/extrinsics/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 397a57a3fb..4cafb2acef 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -49,9 +49,9 @@ def get_old_stakes( def get_extrinsic_fee( + subtensor: "Subtensor", call: "GenericCall", keypair: "Keypair", - subtensor: "Subtensor", netuid: Optional[int] = None, ): """ From 37bf840dc274a144ef35e07f9b5c91888e08aa51 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 17 Sep 2025 02:54:20 -0700 Subject: [PATCH 219/416] reveal_weights_extrinsic: add `raise_error` + args order --- bittensor/core/extrinsics/asyncex/weights.py | 14 ++++++++------ bittensor/core/extrinsics/weights.py | 12 +++++++----- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 49db1361b8..25b4ecbe22 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -123,7 +123,9 @@ async def reveal_weights_extrinsic( This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper error handling and user interaction when required. """ - if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() @@ -143,20 +145,20 @@ async def reveal_weights_extrinsic( response = 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, - nonce_key="hotkey", use_nonce=True, + nonce_key="hotkey", + period=period, raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, calling_function=get_function_name(), ) if response.success: logging.info(response.message) else: - logging.error(f"{get_function_name}: {response.message}") + logging.error(f"{get_function_name()}: {response.message}") return response diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index db5de001c9..c8bc8acc54 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -125,7 +125,9 @@ def reveal_weights_extrinsic( This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper error handling and user interaction when required. """ - if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: logging.error(unlock.message) return ExtrinsicResponse( False, unlock.message, extrinsic_function=get_function_name() @@ -146,13 +148,13 @@ def reveal_weights_extrinsic( response = 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, sign_with="hotkey", + use_nonce=True, nonce_key="hotkey", + period=period, raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, calling_function=get_function_name(), ) From 213daa607fd795f144cdf114eb7293692a00ef1a Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 17 Sep 2025 02:55:13 -0700 Subject: [PATCH 220/416] add args names in chain_interactions.py --- tests/e2e_tests/utils/chain_interactions.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index edbb8305bc..acb876d152 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -384,7 +384,7 @@ async def async_sudo_set_admin_utils( call=sudo_call, keypair=wallet.coldkey ) response: "AsyncExtrinsicReceipt" = await substrate.submit_extrinsic( - extrinsic, + extrinsic=extrinsic, wait_for_inclusion=True, wait_for_finalization=True, ) @@ -407,7 +407,7 @@ def root_set_subtensor_hyperparameter_values( extrinsic = substrate.create_signed_extrinsic(call=call, keypair=wallet.coldkey) response: "ExtrinsicReceipt" = substrate.submit_extrinsic( - extrinsic, + extrinsic=extrinsic, wait_for_inclusion=True, wait_for_finalization=True, ) @@ -427,7 +427,7 @@ def set_identity( additional="", ): return subtensor.sign_and_send_extrinsic( - subtensor.substrate.compose_call( + call=subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="set_identity", call_params={ @@ -440,7 +440,7 @@ def set_identity( "additional": additional, }, ), - wallet, + wallet=wallet, wait_for_inclusion=True, wait_for_finalization=True, ) @@ -458,7 +458,7 @@ async def async_set_identity( additional="", ): return await subtensor.sign_and_send_extrinsic( - await subtensor.substrate.compose_call( + call=await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="set_identity", call_params={ @@ -471,7 +471,7 @@ async def async_set_identity( "additional": additional, }, ), - wallet, + wallet=wallet, wait_for_inclusion=True, wait_for_finalization=True, ) @@ -479,7 +479,7 @@ async def async_set_identity( def propose(subtensor, wallet, proposal, duration): return subtensor.sign_and_send_extrinsic( - subtensor.substrate.compose_call( + call=subtensor.substrate.compose_call( call_module="Triumvirate", call_function="propose", call_params={ @@ -488,7 +488,7 @@ def propose(subtensor, wallet, proposal, duration): "duration": duration, }, ), - wallet, + wallet=wallet, wait_for_finalization=True, wait_for_inclusion=True, ) @@ -525,7 +525,7 @@ def vote( approve, ): return subtensor.sign_and_send_extrinsic( - subtensor.substrate.compose_call( + call=subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="vote", call_params={ @@ -535,7 +535,7 @@ def vote( "proposal": proposal, }, ), - wallet, + wallet=wallet, wait_for_inclusion=True, wait_for_finalization=True, ) From cff98f48f8b3b64e51e39a89ae3817734d2fe168 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 17 Sep 2025 02:59:01 -0700 Subject: [PATCH 221/416] sign_and_send_extrinsic: update args order --- bittensor/core/async_subtensor.py | 20 +++++++++----------- bittensor/core/subtensor.py | 23 +++++++++++------------ migration.md | 1 + 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 46e14ba034..75654ec30b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4341,13 +4341,13 @@ async def sign_and_send_extrinsic( self, call: "GenericCall", wallet: "Wallet", - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, sign_with: str = "coldkey", use_nonce: bool = False, - period: Optional[int] = None, nonce_key: str = "hotkey", + period: Optional[int] = None, raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, calling_function: Optional[str] = None, ) -> ExtrinsicResponse: """ @@ -4356,15 +4356,15 @@ async def sign_and_send_extrinsic( Parameters: call: a prepared Call object wallet: the wallet whose coldkey will be used to sign the extrinsic - wait_for_inclusion: whether to wait until the extrinsic call is included on the chain - wait_for_finalization: whether to wait until the extrinsic call is finalized on the chain sign_with: the wallet's keypair to use for the signing. Options are "coldkey", "hotkey", "coldkeypub" use_nonce: unique identifier for the transaction related with hot/coldkey. + nonce_key: the type on nonce to use. Options are "hotkey" or "coldkey". 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. - nonce_key: the type on nonce to use. Options are "hotkey" or "coldkey". raise_error: raises the relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: whether to wait until the extrinsic call is included on the chain + wait_for_finalization: whether to wait until the extrinsic call is finalized on the chain calling_function: the name of the calling function. Returns: @@ -4987,11 +4987,9 @@ async def reveal_weights( salt: Salt, 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, - mechid: int = 0, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> ExtrinsicResponse: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 450b283607..2fab92a9af 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3177,13 +3177,13 @@ def sign_and_send_extrinsic( self, call: "GenericCall", wallet: "Wallet", - wait_for_inclusion: bool = True, - wait_for_finalization: bool = False, sign_with: str = "coldkey", use_nonce: bool = False, - period: Optional[int] = None, nonce_key: str = "hotkey", + period: Optional[int] = None, raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = False, calling_function: Optional[str] = None, ) -> ExtrinsicResponse: """ @@ -3192,15 +3192,15 @@ def sign_and_send_extrinsic( Parameters: call: a prepared Call object wallet: the wallet whose coldkey will be used to sign the extrinsic - wait_for_inclusion: whether to wait until the extrinsic call is included on the chain - wait_for_finalization: whether to wait until the extrinsic call is finalized on the chain sign_with: the wallet's keypair to use for the signing. Options are "coldkey", "hotkey", "coldkeypub" use_nonce: unique identifier for the transaction related with hot/coldkey. + nonce_key: the type on nonce to use. Options are "hotkey" or "coldkey". 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. - nonce_key: the type on nonce to use. Options are "hotkey" or "coldkey". raise_error: raises the relevant exception rather than returning `False` if unsuccessful. + wait_for_inclusion: whether to wait until the extrinsic call is included on the chain + wait_for_finalization: whether to wait until the extrinsic call is finalized on the chain calling_function: the name of the calling function. Returns: @@ -3825,10 +3825,9 @@ def reveal_weights( 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, - mechid: int = 0, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> ExtrinsicResponse: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -3873,10 +3872,10 @@ def reveal_weights( weights=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, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) except Exception as error: response.error = error if not response.error else response.error diff --git a/migration.md b/migration.md index 55d14e1a8f..15e40bd39c 100644 --- a/migration.md +++ b/migration.md @@ -234,3 +234,4 @@ Additional changes in extrinsics: - `commit_reveal_extrinsic` and `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the `{"reveal_round": reveal_round}`. - in positive case, all extrinsics return `response.message = "Success"` - `root_register_extrinsic`, `subtensor.burned_register` (with netuid=0) and `subtensor.root_register` has response `ExtrinsicResponse`. In successful case `.data` holds the `{"uid": uid}` where is `uid` is uid of registered neuron. + - `subtensor.sign_and_send_extrinsic` has updated arguments order. The list of arguments is the same. From 1b14804f04db29f3a509dd4fba4fdcbc33f52fdf Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 17 Sep 2025 04:28:08 -0700 Subject: [PATCH 222/416] fix tests after merge `staging` --- tests/e2e_tests/test_commit_weights.py | 76 ++++++++++++++++----- tests/e2e_tests/test_hotkeys.py | 2 +- tests/e2e_tests/test_incentive.py | 17 ++++- tests/e2e_tests/test_liquid_alpha.py | 49 +++++++++---- tests/e2e_tests/test_metagraph.py | 6 +- tests/e2e_tests/test_set_weights.py | 13 ++++ tests/e2e_tests/test_staking.py | 17 ++++- tests/e2e_tests/test_subtensor_functions.py | 57 ++++++++++------ tests/unit_tests/test_subtensor.py | 4 +- tests/unit_tests/test_subtensor_extended.py | 8 +-- 10 files changed, 187 insertions(+), 62 deletions(-) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 57764ad1b1..c3ce57d204 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -2,6 +2,7 @@ import pytest import retry import time +import asyncio from bittensor.core.extrinsics.sudo import ( sudo_set_mechanism_count_extrinsic, @@ -19,6 +20,10 @@ execute_and_wait_for_next_nonce, wait_epoch, ) +from tests.e2e_tests.utils.e2e_test_utils import ( + async_wait_to_start_call, + wait_to_start_call, +) TESTED_SUB_SUBNETS = 2 @@ -59,6 +64,10 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): subtensor, alice_wallet, netuid, TESTED_SUB_SUBNETS ), "Cannot create sub-subnets." + assert wait_to_start_call(subtensor, alice_wallet, netuid), ( + "Subnet isn't active yet." + ) + # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( substrate=subtensor.substrate, @@ -86,9 +95,8 @@ async def test_commit_and_reveal_weights_legacy(subtensor, 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"}, + call_params={"netuid": netuid, "weights_set_rate_limit": 0}, ) - assert error is None assert status is True @@ -123,7 +131,7 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): ) # Commit weights - success, message = subtensor.extrinsics.commit_weights( + response = subtensor.extrinsics.commit_weights( wallet=alice_wallet, netuid=netuid, mechid=mechid, @@ -134,7 +142,8 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): wait_for_finalization=True, ) - assert success is True, message + assert response.success, response.message + logging.console.info(f"block: {subtensor.block} response: {response}") storage_index = get_mechid_storage_index(netuid, mechid) weight_commits = subtensor.queries.query_module( @@ -142,6 +151,8 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): name="WeightCommits", params=[storage_index, alice_wallet.hotkey.ss58_address], ) + logging.console.info(f"weight_commits: {weight_commits}") + # 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] @@ -153,7 +164,7 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): ) # Wait until the reveal block range - await wait_epoch(subtensor, netuid) + subtensor.wait_for_block(subtensor.subnets.get_next_epoch_start_block(netuid) + 1) # Reveal weights success, message = subtensor.extrinsics.reveal_weights( @@ -198,8 +209,18 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal """ logging.console.info("Testing test_commit_and_reveal_weights_async") + # turn off admin freeze window limit for testing + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_admin_freeze_window", + call_params={"window": 0}, + ) + )[0] is True, "Failed to set admin freeze window to 0" + netuid = await async_subtensor.subnets.get_total_subnets() # 2 - set_tempo = 100 if await async_subtensor.chain.is_fast_blocks() else 10 + set_tempo = 50 if await async_subtensor.chain.is_fast_blocks() else 10 # Register root as Alice assert await async_subtensor.subnets.register_subnet(alice_wallet), ( @@ -211,6 +232,10 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal "Subnet wasn't created successfully" ) + assert async_wait_to_start_call(async_subtensor, alice_wallet, netuid), ( + "Subnet isn't active yet." + ) + # Enable commit_reveal on the subnet assert await async_sudo_set_hyperparameter_bool( substrate=async_subtensor.substrate, @@ -237,9 +262,8 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal substrate=async_subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", - call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, + call_params={"netuid": netuid, "weights_set_rate_limit": 0}, ) - assert error is None assert status is True @@ -277,7 +301,6 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal wait_for_inclusion=True, wait_for_finalization=True, ) - assert success is True weight_commits = await async_subtensor.queries.query_module( @@ -285,6 +308,8 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal name="WeightCommits", params=[netuid, alice_wallet.hotkey.ss58_address], ) + logging.console.info(f"weight_commits: {weight_commits}") + # 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] @@ -296,12 +321,13 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal ) # Wait until the reveal block range - await async_wait_epoch(async_subtensor, netuid) + # await async_wait_epoch(async_subtensor, netuid) + # await async_subtensor.wait_for_block(await async_subtensor.subnets.get_next_epoch_start_block(netuid) + 1) # Reveal weights success, message = await async_subtensor.extrinsics.reveal_weights( - alice_wallet, - netuid, + wallet=alice_wallet, + netuid=netuid, uids=weight_uids, weights=weight_vals, salt=salt, @@ -309,7 +335,7 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal wait_for_finalization=True, ) - assert success is True + assert success is True, message # Query the Weights storage map revealed_weights = await async_subtensor.queries.query_module( @@ -333,13 +359,13 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal # 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) + weights_ = np.array([(counter + 1) / 10], dtype=np.float32) weight_uids_, weight_vals_ = convert_weights_and_uids_for_emit( uids=uids_, weights=weights_ ) - return salt_, weight_uids_, weight_vals_ + salt_ = [18, 179, 107, counter, 165, 211, 141, 197] + return weight_uids_, weight_vals_, salt_ @pytest.mark.asyncio @@ -380,7 +406,7 @@ async def test_commit_weights_uses_next_nonce(subtensor, alice_wallet): # weights sensitive to epoch changes assert sudo_set_admin_utils( - substrate=local_chain, + substrate=subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_tempo", call_params={ @@ -455,6 +481,9 @@ def send_commit(salt_, weight_uids_, weight_vals_): for call in range(AMOUNT_OF_COMMIT_WEIGHTS): weight_uids, weight_vals, salt = get_weights_and_salt(call) + logging.console.info( + f"Sending commit with uids: {weight_uids}, weight: {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 @@ -511,6 +540,15 @@ async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_walle """ logging.console.info("Testing test_commit_and_reveal_weights") + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_admin_freeze_window", + call_params={"window": 0}, + ) + )[0] is True, "Failed to set admin freeze window to 0" + subnet_tempo = 50 if await async_subtensor.chain.is_fast_blocks() else 10 netuid = await async_subtensor.subnets.get_total_subnets() # 2 @@ -640,6 +678,9 @@ async def send_commit_(): for call in range(AMOUNT_OF_COMMIT_WEIGHTS): weight_uids, weight_vals, salt = get_weights_and_salt(call) + logging.console.info( + f"Sending commit with uids: {weight_uids}, weight: {weight_vals}" + ) 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 @@ -658,6 +699,7 @@ async def send_commit_(): # Wait a few blocks waiting_block = ( (await async_subtensor.block + await async_subtensor.subnets.tempo(netuid) * 2) + + 12 if await async_subtensor.chain.is_fast_blocks() else None ) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index c45eeb9caf..3e23ac88a6 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -757,7 +757,7 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" ) - await async_subtensor.wait_for_block(cooldown) + await async_subtensor.wait_for_block(cooldown + 1) success, children, error = await async_subtensor.wallets.get_children( hotkey=alice_wallet.hotkey.ss58_address, diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index f6f7c890d9..f426ce00a0 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -77,6 +77,8 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): subtensor.wait_for_block( subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 1 ) + subtensor.wait_for_block(next_epoch_start_block + 1) + # Get current miner/validator stats alice_neuron = subtensor.neurons.neurons(netuid=alice_subnet_netuid)[0] @@ -213,6 +215,16 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal logging.console.info("Testing [blue]test_incentive[/blue]") alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + # turn off admin freeze window limit for testing + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_admin_freeze_window", + call_params={"window": 0}, + ) + )[0] is True, "Failed to set admin freeze window to 0" + # Register root as Alice - the subnet owner and validator assert await async_subtensor.subnets.register_subnet(alice_wallet), ( "Subnet wasn't created" @@ -250,7 +262,10 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal ), "Alice & Bob not registered in the subnet" # Wait for the first epoch to pass - await async_wait_epoch(async_subtensor, alice_subnet_netuid) + next_epoch_start_block = await async_subtensor.subnets.get_next_epoch_start_block( + netuid=alice_subnet_netuid + ) + await async_subtensor.wait_for_block(next_epoch_start_block + 1) # Get current miner/validator stats alice_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[ diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index 29d86e08e3..0dd6e0a4fb 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -5,6 +5,7 @@ from tests.e2e_tests.utils.chain_interactions import ( async_sudo_set_hyperparameter_bool, async_sudo_set_hyperparameter_values, + async_sudo_set_admin_utils, sudo_set_hyperparameter_bool, sudo_set_hyperparameter_values, sudo_set_admin_utils, @@ -55,7 +56,7 @@ def test_liquid_alpha(subtensor, alice_wallet): # Register root as Alice assert subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet" + "Unable to register the subnet." ) # Verify subnet created successfully @@ -67,7 +68,7 @@ def test_liquid_alpha(subtensor, alice_wallet): # Register a neuron (Alice) to the subnet assert subtensor.subnets.burned_register(alice_wallet, netuid).success, ( - "Unable to register Alice as a neuron" + "Unable to register Alice as a neuron." ) # Stake to become to top neuron after the first epoch @@ -76,7 +77,7 @@ def test_liquid_alpha(subtensor, alice_wallet): netuid=netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), - ).success, "Unable to stake to Alice neuron" + ).success, "Unable to stake to Alice neuron." # Assert liquid alpha is disabled assert ( @@ -118,12 +119,12 @@ def test_liquid_alpha(subtensor, alice_wallet): wallet=alice_wallet, call_function="sudo_set_alpha_values", call_params=call_params, - ), "Unable to set alpha_values" + ), "Unable to set alpha_values." assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_high == 54099, ( "Failed to set alpha high" ) assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_low == 26001, ( - "Failed to set alpha low" + "Failed to set alpha low." ) # Testing alpha high upper and lower bounds @@ -132,14 +133,14 @@ def test_liquid_alpha(subtensor, alice_wallet): alpha_high_too_low = 87 # Test needs to wait for the amount of tempo in the chain equal to OwnerHyperparamRateLimit - owner_hyperparam_ratelimit = subtensor.substrate.query( - module="SubtensorModule", storage_function="OwnerHyperparamRateLimit" + owner_hyperparam_ratelimit = subtensor.queries.query_subtensor( + "OwnerHyperparamRateLimit" ).value logging.console.info( f"OwnerHyperparamRateLimit is {owner_hyperparam_ratelimit} tempo(s)." ) subtensor.wait_for_block( - subtensor.block + subtensor.tempo(netuid) * owner_hyperparam_ratelimit + subtensor.block + subtensor.subnets.tempo(netuid) * owner_hyperparam_ratelimit ) call_params = liquid_alpha_call_params(netuid, f"6553, {alpha_high_too_low}") @@ -245,23 +246,35 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): """ logging.console.info("Testing [blue]test_liquid_alpha_async[/blue]") + # turn off admin freeze window limit for testing + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_admin_freeze_window", + call_params={"window": 0}, + ) + )[0] is True, "Failed to set admin freeze window to 0" + u16_max = 65535 netuid = 2 # Register root as Alice assert await async_subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet" + "Unable to register the subnet." ) # Verify subnet created successfully - assert await async_subtensor.subnets.subnet_exists(netuid) + assert await async_subtensor.subnets.subnet_exists(netuid), "Subnet does not exist." - assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid) + assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid), ( + "Subnet failed to start." + ) # Register a neuron (Alice) to the subnet assert ( await async_subtensor.subnets.burned_register(alice_wallet, netuid) - ).success, "Unable to register Alice as a neuron" + ).success, "Unable to register Alice as a neuron." # Stake to become to top neuron after the first epoch assert ( @@ -325,6 +338,18 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): # 1. Test setting Alpha_high too low alpha_high_too_low = 87 + # Test needs to wait for the amount of tempo in the chain equal to OwnerHyperparamRateLimit + owner_hyperparam_ratelimit = ( + await async_subtensor.queries.query_subtensor("OwnerHyperparamRateLimit") + ).value + logging.console.info( + f"OwnerHyperparamRateLimit is {owner_hyperparam_ratelimit} tempo(s)." + ) + await async_subtensor.wait_for_block( + await async_subtensor.block + + await async_subtensor.subnets.tempo(netuid) * owner_hyperparam_ratelimit + ) + 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, diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 7071c0868c..fd20fcb3b8 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -2,10 +2,11 @@ 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.core.chain_data.metagraph_info import MetagraphInfo from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.e2e_test_utils import ( @@ -730,6 +731,7 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): ("5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", Balance(0).set_unit(1)) ], validators=None, + commitments=None, ) assert metagraph_info == expected_metagraph_info @@ -811,6 +813,7 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): tao_dividends_per_hotkey=[], alpha_dividends_per_hotkey=[], validators=None, + commitments=None, ), metagraph_info, ] @@ -1230,6 +1233,7 @@ async def test_metagraph_info_with_indexes_async( tao_dividends_per_hotkey=None, alpha_dividends_per_hotkey=None, validators=None, + commitments=None, ) assert await async_wait_to_start_call( diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 5eb1cf5662..f29b129198 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -1,7 +1,10 @@ +import time + import numpy as np import pytest import retry + from bittensor.core.extrinsics.sudo import ( sudo_set_mechanism_count_extrinsic, sudo_set_admin_freeze_window_extrinsic, @@ -228,6 +231,16 @@ async def test_set_weights_uses_next_nonce_async(async_subtensor, alice_wallet): """ logging.console.info("Testing [blue]test_set_weights_uses_next_nonce_async[/blue]") + # turn off admin freeze window limit for testing + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_admin_freeze_window", + call_params={"window": 0}, + ) + )[0] is True, "Failed to set admin freeze window to 0" + netuids = [2, 3] subnet_tempo = 50 diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index af05d5ef59..78cca1f887 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -1,6 +1,7 @@ -import pytest import asyncio +import pytest + from bittensor import logging from bittensor.core.chain_data.stake_info import StakeInfo from bittensor.core.errors import ChainError @@ -696,7 +697,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) ) # Change the tempo of the subnet - TEMPO_TO_SET = 100 if subtensor.chain.is_fast_blocks() else 20 + TEMPO_TO_SET = 50 if subtensor.chain.is_fast_blocks() else 20 assert ( sudo_set_admin_utils( substrate=subtensor.substrate, @@ -873,6 +874,16 @@ async def test_safe_staking_scenarios_async( """ logging.console.info("Testing [blue]test_safe_staking_scenarios_async[/blue]") + # turn off admin freeze window limit for testing + assert ( + await async_sudo_set_admin_utils( + substrate=async_subtensor.substrate, + wallet=alice_wallet, + call_function="sudo_set_admin_freeze_window", + call_params={"window": 0}, + ) + )[0] is True, "Failed to set admin freeze window to 0" + 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) @@ -883,7 +894,7 @@ async def test_safe_staking_scenarios_async( ) # Change the tempo of the subnet - TEMPO_TO_SET = 100 if await async_subtensor.chain.is_fast_blocks() else 20 + TEMPO_TO_SET = 50 if await async_subtensor.chain.is_fast_blocks() else 20 assert ( await async_sudo_set_admin_utils( substrate=async_subtensor.substrate, diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 2f52d62763..0466ee5141 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -1,10 +1,13 @@ import asyncio -import time import pytest -from bittensor.utils.btlogging import logging + +from bittensor.core.extrinsics.asyncex.utils import ( + get_extrinsic_fee as get_extrinsic_fee_async, +) from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.utils.balance import Balance +from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.chain_interactions import ( async_wait_epoch, wait_epoch, @@ -63,7 +66,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall # Assert correct balance is fetched for Alice 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" + "Balance for Alice wallet doesn't match with pre-def value." ) # Subnet burn cost is initially lower before we register a subnet @@ -71,7 +74,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall # Register subnet assert subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet" + "Unable to register the subnet." ) # Subnet burn cost is increased immediately after a subnet is registered @@ -86,7 +89,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall "mechid": 1, }, ) - register_fee = get_extrinsic_fee(call, alice_wallet.hotkey, subtensor) + register_fee = get_extrinsic_fee(subtensor, call, alice_wallet.hotkey) # Assert that the burn cost changed after registering a subnet assert Balance.from_tao(pre_subnet_creation_cost) < Balance.from_tao( @@ -98,7 +101,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall alice_wallet.coldkeypub.ss58_address ) assert alice_balance_post_sn + pre_subnet_creation_cost + register_fee == initial_alice_balance, ( - "Balance is the same even after registering a subnet" + "Balance is the same even after registering a subnet." ) # Subnet 2 is added after registration @@ -276,21 +279,35 @@ async def test_subtensor_extrinsics_async( "Unable to register the subnet" ) + # TODO: in SDKv10 replace this logic with using `ExtrinsicResponse.extrinsic_fee` + call = await async_subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register_network", + call_params={ + "hotkey": alice_wallet.hotkey.ss58_address, + "mechid": 1, + }, + ) + register_fee = await get_extrinsic_fee_async( + async_subtensor, call, alice_wallet.hotkey + ) + # 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" + ), "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" - ) + assert ( + alice_balance_post_sn + pre_subnet_creation_cost + register_fee + == 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] @@ -301,7 +318,7 @@ async def test_subtensor_extrinsics_async( # Default subnetwork difficulty assert await async_subtensor.subnets.difficulty(netuid) == 10_000_000, ( - "Couldn't fetch correct subnet difficulty" + "Couldn't fetch correct subnet difficulty." ) # Verify Alice is registered to netuid 2 and Bob isn't registered to any @@ -309,29 +326,29 @@ async def test_subtensor_extrinsics_async( hotkey_ss58=alice_wallet.hotkey.ss58_address ) == [ netuid, - ], "Alice is not registered to netuid 2 as expected" + ], "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" + ), "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" + ), "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" + ), "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" + ), f"Alice's hotkey is not registered on netuid {netuid}" 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" + ), f"Bob's hotkey is unexpectedly registered on netuid {netuid}" # Verify Alice's UID on netuid 2 is 0 assert ( @@ -339,7 +356,7 @@ async def test_subtensor_extrinsics_async( hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=netuid ) == 0 - ), "UID for Alice's hotkey on netuid 2 is not 0 as expected" + ), "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 @@ -350,7 +367,7 @@ async def test_subtensor_extrinsics_async( # Register Bob to the subnet assert ( await async_subtensor.subnets.burned_register(bob_wallet, netuid) - ).success, "Unable to register Bob as a neuron" + ).success, "Unable to register Bob as a neuron." # Verify Bob's UID on netuid 2 is 1 assert ( @@ -358,7 +375,7 @@ async def test_subtensor_extrinsics_async( hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=netuid ) == 1 - ), "UID for Bob's hotkey on netuid 2 is not 1 as expected" + ), "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) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 9586620c61..62558c8bda 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1917,10 +1917,10 @@ def test_reveal_weights(subtensor, fake_wallet, mocker): version_key=version_as_int, weights=weights, salt=salt, + period=16, + raise_error=False, wait_for_inclusion=False, wait_for_finalization=False, - period=16, - raise_error=True, mechid=0, ) diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 54ceba0d69..f6544f6299 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1,8 +1,7 @@ import unittest.mock -import pytest - import async_substrate_interface.errors +import pytest from bittensor.core.chain_data.axon_info import AxonInfo from bittensor.core.chain_data.chain_identity import ChainIdentity @@ -15,7 +14,6 @@ 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 @@ -1302,8 +1300,8 @@ def test_sign_and_send_extrinsic(mock_substrate, subtensor, fake_wallet, mocker) call = mocker.Mock() subtensor.sign_and_send_extrinsic( - call, - fake_wallet, + call=call, + wallet=fake_wallet, use_nonce=True, period=10, ) From 1ba1749f6942ce8eaa6f96748708df7aa03a32e1 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 22 Sep 2025 17:14:55 -0700 Subject: [PATCH 223/416] ExtrinsicResponse --- bittensor/core/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index b856704994..e70ea26b98 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -325,9 +325,9 @@ class ExtrinsicResponse: success: bool = True message: str = None error: Optional[Exception] = None - data: Optional[Any] = None extrinsic_function: Optional[str] = None extrinsic: Optional[GenericExtrinsic] = None + data: Optional[Any] = None def __iter__(self): yield self.success From fb08085de53387ed094ac5e9710cf5383baa0bab Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 22 Sep 2025 22:30:54 -0700 Subject: [PATCH 224/416] fixes after rebase --- bittensor/core/async_subtensor.py | 12 +- .../core/extrinsics/asyncex/mechanism.py | 134 ++++++++++------- bittensor/core/extrinsics/mechanism.py | 135 +++++++++++------- bittensor/core/extrinsics/serving.py | 2 +- bittensor/core/subtensor.py | 19 +-- bittensor/core/types.py | 6 +- tests/e2e_tests/test_commit_reveal.py | 15 +- tests/e2e_tests/test_commit_weights.py | 6 +- tests/e2e_tests/test_hotkeys.py | 4 +- tests/e2e_tests/test_incentive.py | 1 - tests/e2e_tests/test_metagraph.py | 5 +- tests/e2e_tests/test_staking.py | 1 - tests/e2e_tests/test_subtensor_functions.py | 7 +- .../extrinsics/asyncex/test_mechanisms.py | 9 +- .../unit_tests/extrinsics/test_mechanisms.py | 9 +- tests/unit_tests/test_async_subtensor.py | 6 +- tests/unit_tests/test_subtensor.py | 16 +-- 17 files changed, 233 insertions(+), 154 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 75654ec30b..ddc5a46f62 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -123,7 +123,6 @@ LiquidityPosition, ) from bittensor.utils.weight_utils import ( - convert_uids_and_weights, U16_MAX, ) @@ -4669,6 +4668,7 @@ async def commit_weights( 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. + mechid: The subnet mechanism unique identifier. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4985,11 +4985,13 @@ async def reveal_weights( uids: UIDs, weights: Weights, salt: Salt, + max_retries: int = 5, version_key: int = version_as_int, period: Optional[int] = 16, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + mechid: int = 0, ) -> ExtrinsicResponse: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -5001,13 +5003,14 @@ async def reveal_weights( uids: NumPy array of neuron UIDs for which weights are being revealed. weights: NumPy array of weight values corresponding to each UID. salt: NumPy array 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. 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: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. mechid: The subnet mechanism unique identifier. Returns: @@ -5371,6 +5374,7 @@ async def set_weights( 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. + mechid: The subnet mechanism unique identifier. Returns: ExtrinsicResponse: The result object of the extrinsic execution. diff --git a/bittensor/core/extrinsics/asyncex/mechanism.py b/bittensor/core/extrinsics/asyncex/mechanism.py index 91cfa81175..652a122bc7 100644 --- a/bittensor/core/extrinsics/asyncex/mechanism.py +++ b/bittensor/core/extrinsics/asyncex/mechanism.py @@ -2,9 +2,15 @@ from bittensor_drand import get_encrypted_commit +from bittensor.core.types import ExtrinsicResponse from bittensor.core.settings import version_as_int from bittensor.core.types import Salt, UIDs, Weights -from bittensor.utils import unlock_key, get_mechid_storage_index +from bittensor.utils import ( + unlock_key, + get_mechid_storage_index, + get_function_name, + format_error_message, +) from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -29,7 +35,7 @@ async def commit_mechanism_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. Parameters: @@ -49,9 +55,7 @@ async def commit_mechanism_weights_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: signing_keypair = "hotkey" @@ -60,7 +64,9 @@ async def commit_mechanism_weights_extrinsic( ) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Generate the hash of the weights @@ -82,7 +88,7 @@ async def commit_mechanism_weights_extrinsic( "commit_hash": commit_hash, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -94,19 +100,24 @@ async def commit_mechanism_weights_extrinsic( raise_error=raise_error, ) - if success: - logging.debug(message) - return True, message + if response.success: + logging.debug(response.message) + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: if raise_error: raise error - logging.error(str(error)) - return False, str(error) + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) async def commit_timelocked_mechanism_weights_extrinsic( @@ -123,7 +134,7 @@ async def commit_timelocked_mechanism_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. Parameters: @@ -144,9 +155,7 @@ async def commit_timelocked_mechanism_weights_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: signing_keypair = "hotkey" @@ -155,7 +164,9 @@ async def commit_timelocked_mechanism_weights_extrinsic( ) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) uids, weights = convert_and_normalize_weights_and_uids(uids, weights) @@ -192,7 +203,7 @@ async def commit_timelocked_mechanism_weights_extrinsic( "commit_reveal_version": commit_reveal_version, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -204,19 +215,28 @@ async def commit_timelocked_mechanism_weights_extrinsic( raise_error=raise_error, ) - if success: - logging.debug(message) - return True, f"reveal_round:{reveal_round}" + if response.success: + logging.debug(response.message) + response.data = { + "commit_for_reveal": commit_for_reveal, + "reveal_round": reveal_round, + } + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: if raise_error: raise error - logging.error(str(error)) - return False, str(error) + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) async def reveal_mechanism_weights_extrinsic( @@ -232,7 +252,7 @@ async def reveal_mechanism_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. @@ -253,9 +273,7 @@ async def reveal_mechanism_weights_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: signing_keypair = "hotkey" @@ -264,7 +282,9 @@ async def reveal_mechanism_weights_extrinsic( ) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) uids, weights = convert_and_normalize_weights_and_uids(uids, weights) @@ -280,7 +300,7 @@ async def reveal_mechanism_weights_extrinsic( "version_key": version_key, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -292,19 +312,24 @@ async def reveal_mechanism_weights_extrinsic( raise_error=raise_error, ) - if success: - logging.debug(message) - return True, message + if response.success: + logging.debug(response.message) + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: if raise_error: raise error - logging.error(str(error)) - return False, str(error) + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) async def set_mechanism_weights_extrinsic( @@ -319,7 +344,7 @@ async def set_mechanism_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. @@ -339,9 +364,7 @@ async def set_mechanism_weights_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: signing_keypair = "hotkey" @@ -350,7 +373,9 @@ async def set_mechanism_weights_extrinsic( ) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) # Convert, reformat and normalize. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) @@ -366,7 +391,7 @@ async def set_mechanism_weights_extrinsic( "version_key": version_key, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -378,16 +403,21 @@ async def set_mechanism_weights_extrinsic( raise_error=raise_error, ) - if success: + if response.success: logging.debug("Successfully set weights and Finalized.") - return True, message + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: if raise_error: raise error - logging.error(str(error)) - return False, str(error) + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) diff --git a/bittensor/core/extrinsics/mechanism.py b/bittensor/core/extrinsics/mechanism.py index 05a4f4aaf6..d74ffb7a0d 100644 --- a/bittensor/core/extrinsics/mechanism.py +++ b/bittensor/core/extrinsics/mechanism.py @@ -1,10 +1,15 @@ from typing import TYPE_CHECKING, Optional, Union - +from bittensor.core.types import ExtrinsicResponse from bittensor_drand import get_encrypted_commit from bittensor.core.settings import version_as_int from bittensor.core.types import Salt, UIDs, Weights -from bittensor.utils import unlock_key, get_mechid_storage_index +from bittensor.utils import ( + unlock_key, + get_mechid_storage_index, + get_function_name, + format_error_message, +) from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -29,7 +34,7 @@ def commit_mechanism_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. Parameters: @@ -49,9 +54,7 @@ def commit_mechanism_weights_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: signing_keypair = "hotkey" @@ -60,7 +63,9 @@ def commit_mechanism_weights_extrinsic( ) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Generate the hash of the weights @@ -82,7 +87,7 @@ def commit_mechanism_weights_extrinsic( "commit_hash": commit_hash, }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -94,19 +99,24 @@ def commit_mechanism_weights_extrinsic( raise_error=raise_error, ) - if success: - logging.debug(message) - return True, message + if response.success: + logging.debug(response.message) + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: if raise_error: raise error - logging.error(str(error)) - return False, str(error) + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) def commit_timelocked_mechanism_weights_extrinsic( @@ -123,7 +133,7 @@ def commit_timelocked_mechanism_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. Parameters: @@ -144,9 +154,7 @@ def commit_timelocked_mechanism_weights_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: signing_keypair = "hotkey" @@ -155,7 +163,9 @@ def commit_timelocked_mechanism_weights_extrinsic( ) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) uids, weights = convert_and_normalize_weights_and_uids(uids, weights) @@ -192,7 +202,7 @@ def commit_timelocked_mechanism_weights_extrinsic( "commit_reveal_version": commit_reveal_version, }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -204,19 +214,28 @@ def commit_timelocked_mechanism_weights_extrinsic( raise_error=raise_error, ) - if success: - logging.debug(message) - return True, f"reveal_round:{reveal_round}" + if response.success: + logging.debug(response.message) + response.data = { + "commit_for_reveal": commit_for_reveal, + "reveal_round": reveal_round, + } + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: if raise_error: raise error - logging.error(str(error)) - return False, str(error) + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) def reveal_mechanism_weights_extrinsic( @@ -232,7 +251,7 @@ def reveal_mechanism_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. @@ -253,9 +272,7 @@ def reveal_mechanism_weights_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: signing_keypair = "hotkey" @@ -264,7 +281,9 @@ def reveal_mechanism_weights_extrinsic( ) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) uids, weights = convert_and_normalize_weights_and_uids(uids, weights) @@ -280,7 +299,7 @@ def reveal_mechanism_weights_extrinsic( "version_key": version_key, }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -292,19 +311,24 @@ def reveal_mechanism_weights_extrinsic( raise_error=raise_error, ) - if success: - logging.debug(message) - return True, message + if response.success: + logging.debug(response.message) + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: if raise_error: raise error - logging.error(str(error)) - return False, str(error) + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) def set_mechanism_weights_extrinsic( @@ -319,7 +343,7 @@ def set_mechanism_weights_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. @@ -339,9 +363,7 @@ def set_mechanism_weights_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: signing_keypair = "hotkey" @@ -350,7 +372,9 @@ def set_mechanism_weights_extrinsic( ) if not unlock.success: logging.error(unlock.message) - return False, unlock.message + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) # Convert, reformat and normalize. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) @@ -366,7 +390,7 @@ def set_mechanism_weights_extrinsic( "version_key": version_key, }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -378,16 +402,21 @@ def set_mechanism_weights_extrinsic( raise_error=raise_error, ) - if success: + if response.success: logging.debug("Successfully set weights and Finalized.") - return True, message + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: if raise_error: raise error - logging.error(str(error)) - return False, str(error) + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 3c916b6fb9..3f08e5b63e 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -269,7 +269,7 @@ def publish_metadata_extrinsic( def get_metadata( subtensor: "Subtensor", netuid: int, hotkey: str, block: Optional[int] = None -) -> bytes: +) -> Union[str, dict]: """Fetches metadata from the blockchain for a given hotkey and netuid.""" commit_data = subtensor.substrate.query( module="Commitments", diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 2fab92a9af..a8356e1c6d 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -71,7 +71,6 @@ get_metadata, serve_axon_extrinsic, ) -from bittensor.core.extrinsics.weights import set_weights_extrinsic from bittensor.core.extrinsics.staking import ( add_stake_extrinsic, add_stake_multiple_extrinsic, @@ -128,7 +127,6 @@ LiquidityPosition, ) from bittensor.utils.weight_utils import ( - convert_uids_and_weights, U16_MAX, ) @@ -3507,6 +3505,7 @@ def commit_weights( 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. + mechid: Subnet mechanism unique identifier. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -3536,15 +3535,15 @@ def commit_weights( weights=weights, salt=salt, period=period, - raise_error=True, + raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - if success: + if response.success: break - except Exception as e: + except Exception as error: response.error = error if not response.error else response.error - logging.error(f"Error committing weights: {e}") + logging.error(f"Error committing weights: {error}") retries += 1 return response @@ -3828,6 +3827,7 @@ def reveal_weights( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + mechid: int = 0, ) -> ExtrinsicResponse: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -3839,13 +3839,14 @@ def reveal_weights( uids: NumPy array of neuron UIDs for which weights are being revealed. weights: NumPy array of weight values corresponding to each UID. salt: NumPy array 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. 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: Waits for the transaction to be included in a block. + wait_for_finalization: Waits for the transaction to be finalized on the blockchain. mechid: The subnet mechanism unique identifier. Returns: diff --git a/bittensor/core/types.py b/bittensor/core/types.py index e70ea26b98..84894d616e 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -11,7 +11,11 @@ from bittensor.core import settings from bittensor.core.chain_data import NeuronInfo, NeuronInfoLite from bittensor.core.config import Config -from bittensor.utils import determine_chain_endpoint_and_network, networking, Certificate +from bittensor.utils import ( + determine_chain_endpoint_and_network, + networking, + Certificate, +) from bittensor.utils.btlogging import logging # Type annotations for UIDs and weights. diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 24369067ca..3f460391c8 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -217,7 +217,9 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle ] # Ensure no weights are available as of now - assert subtensor.subnets.weights(netuid=alice_subnet_netuid, mechid=mechid) == [] + assert ( + subtensor.subnets.weights(netuid=alice_subnet_netuid, mechid=mechid) == [] + ) logging.console.success("No weights are available before next epoch.") # 5 is safety drand offset @@ -326,7 +328,7 @@ 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( + assert await async_sudo_set_hyperparameter_bool( substrate=async_subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_commit_reveal_weights_enabled", @@ -362,7 +364,10 @@ async def test_commit_and_reveal_weights_cr4_async( netuid=alice_subnet_netuid ) ).weights_rate_limit == 0, "Failed to set weights_rate_limit" - assert await async_subtensor.subnets.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 @@ -466,7 +471,9 @@ async def test_commit_and_reveal_weights_cr4_async( # Ensure no weights are available as of now assert ( - await async_subtensor.subnets.weights(netuid=alice_subnet_netuid, mechid=mechid) + await async_subtensor.subnets.weights( + netuid=alice_subnet_netuid, mechid=mechid + ) == [] ) logging.console.success("No weights are available before next epoch.") diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index c3ce57d204..53c8c389df 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -164,7 +164,9 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): ) # Wait until the reveal block range - subtensor.wait_for_block(subtensor.subnets.get_next_epoch_start_block(netuid) + 1) + subtensor.wait_for_block( + subtensor.subnets.get_next_epoch_start_block(netuid) + 1 + ) # Reveal weights success, message = subtensor.extrinsics.reveal_weights( @@ -180,7 +182,7 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): assert success is True, message - revealed_weights = subtensor.weights(netuid, mechid=mechid) + revealed_weights = subtensor.subnets.weights(netuid, mechid=mechid) # Assert that the revealed weights are set correctly assert revealed_weights is not None, "Weight reveal not found in storage" diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 3e23ac88a6..6416b5c404 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -160,7 +160,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0) - dave_subnet_netuid = subtensor.get_total_subnets() # 2 + dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 set_tempo = 10 # affect to non-fast-blocks mode # Set cooldown @@ -218,7 +218,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): ) with pytest.raises(SubnetNotExists): - subtensor.set_children( + subtensor.extrinsics.set_children( alice_wallet, alice_wallet.hotkey.ss58_address, netuid=3, diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index f426ce00a0..4e6e14c468 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -77,7 +77,6 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): subtensor.wait_for_block( subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 1 ) - subtensor.wait_for_block(next_epoch_start_block + 1) # Get current miner/validator stats alice_neuron = subtensor.neurons.neurons(netuid=alice_subnet_netuid)[0] diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index fd20fcb3b8..6b9385fd51 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -1115,6 +1115,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): tao_dividends_per_hotkey=None, alpha_dividends_per_hotkey=None, validators=None, + commitments=None, ) logging.console.info("✅ Passed [blue]test_metagraph_info_with_indexes[/blue]") @@ -1371,7 +1372,7 @@ def test_blocks(subtensor): """ logging.console.info("Testing [blue]test_blocks[/blue]") - get_current_block = subtensor.get_current_block() + get_current_block = subtensor.chain.get_current_block() block = subtensor.block # Several random tests fell during the block finalization period. Fast blocks of 0.25 seconds (very fast) @@ -1403,5 +1404,5 @@ async def test_blocks_async(subtensor): assert re.match("0x[a-z0-9]{64}", block_hash) subtensor.wait_for_block(block + 10) - assert subtensor.get_current_block() in [block + 10, block + 11] + assert subtensor.chain.get_current_block() in [block + 10, block + 11] logging.console.info("✅ Passed [blue]test_blocks_async[/blue]") diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 78cca1f887..1f4c20282b 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -675,7 +675,6 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) """ logging.console.info("Testing [blue]test_safe_staking_scenarios[/blue]") - # turn off admin freeze window limit for testing assert ( sudo_set_admin_utils( diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 0466ee5141..aebe05871b 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -100,9 +100,10 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall alice_balance_post_sn = subtensor.wallets.get_balance( alice_wallet.coldkeypub.ss58_address ) - assert alice_balance_post_sn + pre_subnet_creation_cost + register_fee == initial_alice_balance, ( - "Balance is the same even after registering a subnet." - ) + assert ( + alice_balance_post_sn + pre_subnet_creation_cost + register_fee + == initial_alice_balance + ), "Balance is the same even after registering a subnet." # Subnet 2 is added after registration assert subtensor.subnets.get_subnets() == [0, 1, 2] diff --git a/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py b/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py index e4611d94ae..b872721c9a 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py +++ b/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py @@ -1,5 +1,6 @@ import pytest from bittensor.core.extrinsics.asyncex import mechanism +from bittensor.core.types import ExtrinsicResponse @pytest.mark.asyncio @@ -23,7 +24,7 @@ async def test_commit_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet mocked_generate_weight_hash = mocker.patch.object(mechanism, "generate_weight_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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -113,7 +114,7 @@ async def test_commit_timelocked_mechanism_weights_extrinsic( mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", - return_value=( + return_value=ExtrinsicResponse( True, f"reveal_round:{mocked_get_encrypted_commit.return_value[1]}", ), @@ -196,7 +197,7 @@ async def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet ) 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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -265,7 +266,7 @@ async def test_mechanism_sub_weights_extrinsic(mocker, subtensor, fake_wallet): mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", - return_value=( + return_value=ExtrinsicResponse( True, "", ), diff --git a/tests/unit_tests/extrinsics/test_mechanisms.py b/tests/unit_tests/extrinsics/test_mechanisms.py index a162a0f53c..b4b416a632 100644 --- a/tests/unit_tests/extrinsics/test_mechanisms.py +++ b/tests/unit_tests/extrinsics/test_mechanisms.py @@ -1,5 +1,6 @@ import pytest from bittensor.core.extrinsics import mechanism +from bittensor.core.types import ExtrinsicResponse def test_commit_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): @@ -22,7 +23,7 @@ def test_commit_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): mocked_generate_weight_hash = mocker.patch.object(mechanism, "generate_weight_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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -109,7 +110,7 @@ def test_commit_timelocked_mechanism_weights_extrinsic(mocker, subtensor, fake_w mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", - return_value=( + return_value=ExtrinsicResponse( True, f"reveal_round:{mocked_get_encrypted_commit.return_value[1]}", ), @@ -191,7 +192,7 @@ def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): ) 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, "") + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call @@ -259,7 +260,7 @@ def test_mechanism_sub_weights_extrinsic(mocker, subtensor, fake_wallet): mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", - return_value=( + return_value=ExtrinsicResponse( True, "", ), diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index a4c3e1fa15..354e466e3b 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2815,7 +2815,7 @@ async def test_set_weights_success(subtensor, fake_wallet, mocker): weights=fake_weights, period=8, mechid=0, - raise_error=True, + raise_error=False, ) mocked_weights_rate_limit.assert_called_once_with(fake_netuid) assert result is True @@ -2876,7 +2876,9 @@ async def test_commit_weights_success(subtensor, fake_wallet, mocker): fake_weights = [100, 200, 300] max_retries = 3 - mocked_commit_weights_extrinsic = mocker.AsyncMock(return_value=ExtrinsicResponse(True, "Success")) + mocked_commit_weights_extrinsic = mocker.AsyncMock( + return_value=ExtrinsicResponse(True, "Success") + ) mocker.patch.object( async_subtensor, "commit_mechanism_weights_extrinsic", diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 62558c8bda..534247f3dd 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1208,7 +1208,7 @@ def test_set_weights(subtensor, mocker, fake_wallet): wait_for_finalization=fake_wait_for_finalization, period=8, mechid=0, - raise_error=True, + raise_error=False, ) assert result == expected_result @@ -1933,10 +1933,6 @@ def test_reveal_weights_false(subtensor, fake_wallet, mocker): weights = [0.1, 0.2, 0.3, 0.4] salt = [4, 2, 2, 1] - expected_result = ExtrinsicResponse( - False, - "No attempt made. Perhaps it is too soon to reveal weights!", - ) mocked_extrinsic = mocker.patch.object( subtensor_module, "reveal_mechanism_weights_extrinsic" ) @@ -1954,7 +1950,7 @@ def test_reveal_weights_false(subtensor, fake_wallet, mocker): # Assertion assert result == mocked_extrinsic.return_value - assert mocked_extrinsic.call_count == 5 + assert mocked_extrinsic.call_count == 1 def test_get_subnet_burn_cost_success(subtensor, mocker): @@ -3061,9 +3057,11 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): mocked_commit_timelocked_mechanism_weights_extrinsic = mocker.patch.object( subtensor_module, "commit_timelocked_mechanism_weights_extrinsic" ) - mocked_commit_timelocked_mechanism_weights_extrinsic.return_value = ExtrinsicResponse( - True, - "Weights committed successfully", + mocked_commit_timelocked_mechanism_weights_extrinsic.return_value = ( + ExtrinsicResponse( + True, + "Weights committed successfully", + ) ) mocker.patch.object(subtensor, "blocks_since_last_update", return_value=181) mocker.patch.object(subtensor, "weights_rate_limit", return_value=180) From d4252d630642f02acdf423d68850f988a0f84d6e Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 09:54:05 -0700 Subject: [PATCH 225/416] delete commit reveal v3 + tests --- .../core/extrinsics/asyncex/commit_reveal.py | 119 ----- bittensor/core/extrinsics/asyncex/weights.py | 505 ++++++++++++------ bittensor/core/extrinsics/commit_reveal.py | 119 ----- bittensor/core/extrinsics/weights.py | 504 +++++++++++------ .../extrinsics/asyncex/test_commit_reveal.py | 275 ---------- .../extrinsics/test_commit_reveal.py | 270 ---------- 6 files changed, 677 insertions(+), 1115 deletions(-) delete mode 100644 bittensor/core/extrinsics/asyncex/commit_reveal.py delete mode 100644 bittensor/core/extrinsics/commit_reveal.py delete mode 100644 tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py delete mode 100644 tests/unit_tests/extrinsics/test_commit_reveal.py diff --git a/bittensor/core/extrinsics/asyncex/commit_reveal.py b/bittensor/core/extrinsics/asyncex/commit_reveal.py deleted file mode 100644 index 88da6b9736..0000000000 --- a/bittensor/core/extrinsics/asyncex/commit_reveal.py +++ /dev/null @@ -1,119 +0,0 @@ -"""This module provides async functionality for commit reveal in the Bittensor network.""" - -from typing import Optional, Union, TYPE_CHECKING - -import numpy as np -from bittensor_drand import get_encrypted_commit -from numpy.typing import NDArray - -from bittensor.core.settings import version_as_int -from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import get_function_name, unlock_key -from bittensor.utils.btlogging import logging -from bittensor.utils.weight_utils import convert_and_normalize_weights_and_uids - -if TYPE_CHECKING: - from bittensor_wallet import Wallet - from bittensor.core.async_subtensor import AsyncSubtensor - from bittensor.utils.registration import torch - - -async def commit_reveal_extrinsic( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - 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, -) -> ExtrinsicResponse: - """ - Commits and reveals weights for a given subtensor and wallet with provided uids and weights. - - 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. - 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: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - - current_block = await subtensor.substrate.get_block(None) - subnet_hyperparameters = await subtensor.get_subnet_hyperparameters( - netuid, block_hash=current_block["header"]["hash"] - ) - 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["header"]["number"], - 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]..." - ) - - 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, - }, - ) - response = 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, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: - logging.success( - f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." - ) - response.data = {"reveal_round": reveal_round} - return response - - logging.error(response.message) - return response diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index 25b4ecbe22..a66c8e6213 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -1,111 +1,263 @@ -"""This module provides async functionality for working with weights in the Bittensor network.""" +"""Module provides async commit and reveal weights extrinsic.""" -from typing import Union, TYPE_CHECKING, Optional -from bittensor.core.types import ExtrinsicResponse -import numpy as np -from numpy.typing import NDArray +from typing import Optional, Union, TYPE_CHECKING + +from bittensor_drand import get_encrypted_commit from bittensor.core.settings import version_as_int -from bittensor.utils import get_function_name, unlock_key +from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights +from bittensor.utils import ( + format_error_message, + get_function_name, + get_mechid_storage_index, + unlock_key, +) from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, - convert_uids_and_weights, + generate_weight_hash, ) if TYPE_CHECKING: - from bittensor_wallet import Wallet from bittensor.core.async_subtensor import AsyncSubtensor - from bittensor.utils.registration import torch + from bittensor_wallet import Wallet -async def commit_weights_extrinsic( +async def commit_timelocked_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - commit_hash: str, + mechid: int, + uids: UIDs, + weights: Weights, + block_time: Union[int, float], + 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, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> ExtrinsicResponse: - """ - 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. + """Commits the weights for a specific sub subnet mechanism on the Bittensor blockchain using the provided wallet. 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. + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + mechid: The subnet mechanism unique identifier + uids: The list of neuron UIDs that the weights are being set for. + weights: The corresponding weights to be set for each UID. + block_time: The number of seconds for block duration. + commit_reveal_version: The version of the commit-reveal in the chain. + 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: 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. + 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: ExtrinsicResponse: The result object of the extrinsic execution. - - This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper - error handling and user interaction when required. """ - if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: - logging.error(unlock.message) + try: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + + current_block = await subtensor.block + subnet_hyperparameters = await subtensor.get_subnet_hyperparameters( + netuid, block=current_block + ) + tempo = subnet_hyperparameters.tempo + subnet_reveal_period_epochs = subnet_hyperparameters.commit_reveal_period + + storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) + + # 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=storage_index, + subnet_reveal_period_epochs=subnet_reveal_period_epochs, + block_time=block_time, + hotkey=wallet.hotkey.public_key, + ) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_timelocked_mechanism_weights", + call_params={ + "netuid": netuid, + "mecid": mechid, + "commit": commit_for_reveal, + "reveal_round": reveal_round, + "commit_reveal_version": commit_reveal_version, + }, + ) + response = 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, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if response.success: + logging.debug(response.message) + response.data = { + "commit_for_reveal": commit_for_reveal, + "reveal_round": reveal_round, + } + return response + + logging.error(response.message) + return response + + except Exception as error: + if raise_error: + raise error + + logging.error(str(error)) return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="commit_weights", - call_params={ - "netuid": netuid, - "commit_hash": commit_hash, - }, - ) - response = 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, - nonce_key="hotkey", - sign_with="hotkey", - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: - logging.info(response.message) - else: - logging.error(f"{get_function_name}: {response.message}") - return response + +async def commit_weights_extrinsic( + subtensor: "AsyncSubtensor", + wallet: "Wallet", + netuid: int, + mechid: int, + uids: UIDs, + weights: Weights, + salt: Salt, + version_key: int = version_as_int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> ExtrinsicResponse: + """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. + + Parameters: + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + mechid: The subnet mechanism unique identifier. + uids: NumPy array of neuron UIDs for which weights are being committed. + weights: NumPy array of weight values corresponding to each UID. + salt: list of randomly generated integers as salt to generated weighted hash. + 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 inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + ExtrinsicResponse: The result object of the extrinsic execution. + """ + try: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + + storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) + # Generate the hash of the weights + commit_hash = generate_weight_hash( + address=wallet.hotkey.ss58_address, + netuid=storage_index, + uids=list(uids), + values=list(weights), + salt=salt, + version_key=version_key, + ) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_mechanism_weights", + call_params={ + "netuid": netuid, + "mecid": mechid, + "commit_hash": commit_hash, + }, + ) + response = 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, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if response.success: + logging.debug(response.message) + return response + + logging.error(response.message) + return response + + except Exception as error: + if raise_error: + raise error + + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) async def reveal_weights_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", netuid: int, - uids: list[int], - weights: list[int], - salt: list[int], + mechid: int, + uids: UIDs, + weights: Weights, + salt: Salt, version_key: int, period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> ExtrinsicResponse: """ - 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. + Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. Parameters: - subtensor: The subtensor instance used for blockchain interaction. - wallet: The wallet associated with the neuron revealing the weights. + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. + mechid: The subnet mechanism unique identifier. 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. @@ -113,131 +265,152 @@ async def reveal_weights_extrinsic( 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: 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. + 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: ExtrinsicResponse: The result object of the extrinsic execution. - - This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper - error handling and user interaction when required. """ - if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="reveal_mechanism_weights", + call_params={ + "netuid": netuid, + "mecid": mechid, + "uids": uids, + "values": weights, + "salt": salt, + "version_key": version_key, + }, + ) + response = 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, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="reveal_weights", - call_params={ - "netuid": netuid, - "uids": uids, - "values": weights, - "salt": salt, - "version_key": version_key, - }, - ) - response = await subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - sign_with="hotkey", - use_nonce=True, - nonce_key="hotkey", - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - calling_function=get_function_name(), - ) - - if response.success: - logging.info(response.message) - else: - logging.error(f"{get_function_name()}: {response.message}") - return response + if response.success: + logging.debug(response.message) + return response + + logging.error(response.message) + return response + + except Exception as error: + if raise_error: + raise error + + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) async def set_weights_extrinsic( subtensor: "AsyncSubtensor", 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, - period: Optional[int] = 8, + mechid: int, + uids: UIDs, + weights: Weights, + version_key: int, + period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> ExtrinsicResponse: """ - Sets the given weights and values on chain for a given wallet hotkey account. + Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. 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. + subtensor: AsyncSubtensor instance. + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + mechid: The subnet mechanism unique identifier. + uids: List of neuron UIDs for which weights are being revealed. + weights: List of weight values corresponding to each UID. + 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: 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. + 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: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + + # Convert, reformat and normalize. + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_mechanism_weights", + call_params={ + "netuid": netuid, + "mecid": mechid, + "dests": uids, + "weights": weights, + "version_key": version_key, + }, ) + response = 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, + ) + + if response.success: + logging.debug("Successfully set weights and Finalized.") + return response - # 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 = 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, - }, - ) - response = 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, - calling_function=get_function_name(), - ) - - if response.success: - logging.info("Successfully set weights and Finalized.") - else: - logging.error(f"{get_function_name}: {response.message}") - - return response + logging.error(response.message) + return response + + except Exception as error: + if raise_error: + raise error + + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py deleted file mode 100644 index 4f8ab2224e..0000000000 --- a/bittensor/core/extrinsics/commit_reveal.py +++ /dev/null @@ -1,119 +0,0 @@ -"""This module provides sync functionality for commit reveal in the Bittensor network.""" - -from typing import Union, TYPE_CHECKING, Optional -from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import get_function_name, unlock_key -import numpy as np -from bittensor_drand import get_encrypted_commit -from numpy.typing import NDArray - -from bittensor.core.settings import version_as_int -from bittensor.utils.btlogging import logging -from bittensor.utils.weight_utils import convert_and_normalize_weights_and_uids - -if TYPE_CHECKING: - from bittensor_wallet import Wallet - from bittensor.core.subtensor import Subtensor - from bittensor.utils.registration import torch - - -# TODO: remove in SDKv10 -def commit_reveal_extrinsic( - subtensor: "Subtensor", - wallet: "Wallet", - 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, -) -> ExtrinsicResponse: - """ - Commits and reveals weights for a given subtensor and wallet with provided uids and weights. - - 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. - 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: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - 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 - - # 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]..." - ) - - 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, - }, - ) - response = 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, - calling_function=get_function_name(), - ) - - if response.success: - logging.success( - f"[green]Finalized![/green] Weights committed with reveal round [blue]{reveal_round}[/blue]." - ) - response.data = {"reveal_round": reveal_round} - return response - - logging.error(response.message) - return response diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index c8bc8acc54..94b69bea9b 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -1,113 +1,264 @@ -"""Module sync commit weights and reveal weights extrinsic.""" +"""Module provides sync commit and reveal weights extrinsic.""" -from typing import TYPE_CHECKING, Optional, Union +from typing import Optional, Union, TYPE_CHECKING -import numpy as np -from numpy.typing import NDArray +from bittensor_drand import get_encrypted_commit from bittensor.core.settings import version_as_int -from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import get_function_name, unlock_key +from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights +from bittensor.utils import ( + format_error_message, + get_function_name, + get_mechid_storage_index, + unlock_key, +) from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, - convert_uids_and_weights, + generate_weight_hash, ) if TYPE_CHECKING: from bittensor.core.subtensor import Subtensor from bittensor_wallet import Wallet - from bittensor.utils.registration import torch -def commit_weights_extrinsic( +def commit_timelocked_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - commit_hash: str, + mechid: int, + uids: UIDs, + weights: Weights, + block_time: Union[int, float], + 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, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> ExtrinsicResponse: """ - 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. + Commits the weights for a specific sub subnet mechanism on the Bittensor blockchain using the provided wallet. 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. + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + mechid: The subnet mechanism unique identifier + uids: The list of neuron UIDs that the weights are being set for. + weights: The corresponding weights to be set for each UID. + block_time: The number of seconds for block duration. + commit_reveal_version: The version of the commit-reveal in the chain. + 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: 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. + 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: ExtrinsicResponse: The result object of the extrinsic execution. + """ + try: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + + 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 + + storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) + + # 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=storage_index, + subnet_reveal_period_epochs=subnet_reveal_period_epochs, + block_time=block_time, + hotkey=wallet.hotkey.public_key, + ) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_timelocked_mechanism_weights", + call_params={ + "netuid": netuid, + "mecid": mechid, + "commit": commit_for_reveal, + "reveal_round": reveal_round, + "commit_reveal_version": commit_reveal_version, + }, + ) + response = 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, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if response.success: + logging.debug(response.message) + response.data = { + "commit_for_reveal": commit_for_reveal, + "reveal_round": reveal_round, + } + return response + + logging.error(response.message) + return response + + except Exception as error: + if raise_error: + raise error + + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) + - This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper - error handling and user interaction when required. +def commit_weights_extrinsic( + subtensor: "Subtensor", + wallet: "Wallet", + netuid: int, + mechid: int, + uids: UIDs, + weights: Weights, + salt: Salt, + version_key: int = version_as_int, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, +) -> ExtrinsicResponse: + """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. + + Parameters: + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The subnet unique identifier. + mechid: The subnet mechanism unique identifier. + uids: NumPy array of neuron UIDs for which weights are being committed. + weights: NumPy array of weight values corresponding to each UID. + salt: list of randomly generated integers as salt to generated weighted hash. + 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 inclusion of the transaction. + wait_for_finalization: Whether to wait for the finalization of the transaction. + + Returns: + ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: - logging.error(unlock.message) + try: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + + storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) + # Generate the hash of the weights + commit_hash = generate_weight_hash( + address=wallet.hotkey.ss58_address, + netuid=storage_index, + uids=list(uids), + values=list(weights), + salt=salt, + version_key=version_key, + ) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="commit_mechanism_weights", + call_params={ + "netuid": netuid, + "mecid": mechid, + "commit_hash": commit_hash, + }, + ) + response = 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, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if response.success: + logging.debug(response.message) + return response + + logging.error(response.message) + return response + + except Exception as error: + if raise_error: + raise error + + logging.error(str(error)) return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="commit_weights", - call_params={ - "netuid": netuid, - "commit_hash": commit_hash, - }, - ) - response = 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, - sign_with="hotkey", - nonce_key="hotkey", - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: - logging.info(response.message) - else: - logging.error(f"{get_function_name()}: {response.message}") - return response - - -# TODO: deprecate in SDKv10 + def reveal_weights_extrinsic( subtensor: "Subtensor", wallet: "Wallet", netuid: int, - uids: list[int], - weights: list[int], - salt: list[int], + mechid: int, + uids: UIDs, + weights: Weights, + salt: Salt, version_key: int, period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> ExtrinsicResponse: """ - 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. + Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. Parameters: - subtensor: The subtensor instance used for blockchain interaction. - wallet: The wallet associated with the neuron revealing the weights. + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. netuid: The unique identifier of the subnet. + mechid: The subnet mechanism unique identifier. 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. @@ -115,131 +266,152 @@ def reveal_weights_extrinsic( 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: 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. + 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: ExtrinsicResponse: The result object of the extrinsic execution. - - This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper - error handling and user interaction when required. """ - if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="reveal_mechanism_weights", + call_params={ + "netuid": netuid, + "mecid": mechid, + "uids": uids, + "values": weights, + "salt": salt, + "version_key": version_key, + }, ) + response = 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, + sign_with="hotkey", + nonce_key="hotkey", + raise_error=raise_error, + ) + + if response.success: + logging.debug(response.message) + return response - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="reveal_weights", - call_params={ - "netuid": netuid, - "uids": uids, - "values": weights, - "salt": salt, - "version_key": version_key, - }, - ) - - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - sign_with="hotkey", - use_nonce=True, - nonce_key="hotkey", - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - calling_function=get_function_name(), - ) - - if response.success: - logging.info(response.message) - else: - logging.error(f"{get_function_name()}: {response.message}") - return response + logging.error(response.message) + return response + + except Exception as error: + if raise_error: + raise error + + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) 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, - period: Optional[int] = 8, + mechid: int, + uids: UIDs, + weights: Weights, + version_key: int, + period: Optional[int] = None, raise_error: bool = False, - wait_for_inclusion: bool = False, - wait_for_finalization: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, ) -> ExtrinsicResponse: """ - Sets the given weights and values on a chain for a wallet hotkey account. + Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. 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. + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The unique identifier of the subnet. + mechid: The subnet mechanism unique identifier. + uids: List of neuron UIDs for which weights are being revealed. + weights: List of weight values corresponding to each UID. + 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: 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. + 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: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet, unlock_type="hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) + ).success: + logging.error(unlock.message) + return ExtrinsicResponse( + False, unlock.message, extrinsic_function=get_function_name() + ) + + # Convert, reformat and normalize. + uids, weights = convert_and_normalize_weights_and_uids(uids, weights) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_mechanism_weights", + call_params={ + "netuid": netuid, + "mecid": mechid, + "dests": uids, + "weights": weights, + "version_key": version_key, + }, ) + response = 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, + ) + + if response.success: + logging.debug("Successfully set weights and Finalized.") + return response - # 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, - }, - ) - response = 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, - calling_function=get_function_name(), - ) - - if response.success: - logging.info("Successfully set weights and Finalized.") - else: - logging.error(f"{get_function_name}: {response.message}") - return response + logging.error(response.message) + return response + + except Exception as error: + if raise_error: + raise error + + logging.error(str(error)) + return ExtrinsicResponse( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_function_name(), + ) diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py deleted file mode 100644 index 4c52b6b457..0000000000 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ /dev/null @@ -1,275 +0,0 @@ -import numpy as np -import pytest -import torch - -from bittensor.core.chain_data import SubnetHyperparameters -from bittensor.core.extrinsics.asyncex import commit_reveal as async_commit_reveal -from bittensor.core.types import ExtrinsicResponse - - -@pytest.fixture -def hyperparams(): - yield SubnetHyperparameters( - rho=0, - kappa=0, - immunity_period=0, - min_allowed_weights=0, - max_weight_limit=0.0, - tempo=0, - min_difficulty=0, - max_difficulty=0, - weights_version=0, - weights_rate_limit=0, - adjustment_interval=0, - activity_cutoff=0, - registration_allowed=False, - target_regs_per_interval=0, - min_burn=0, - max_burn=0, - bonds_moving_avg=0, - max_regs_per_block=0, - serving_rate_limit=0, - max_validators=0, - adjustment_alpha=0, - difficulty=0, - commit_reveal_period=0, - commit_reveal_weights_enabled=True, - alpha_high=0, - alpha_low=0, - liquid_alpha_enabled=False, - alpha_sigmoid_steepness=0, - yuma_version=3, - subnet_is_active=False, - transfers_enabled=False, - bonds_reset_enabled=False, - user_liquidity_enabled=False, - ) - - -@pytest.mark.asyncio -async def test_commit_reveal_v3_extrinsic_success_with_torch( - mocker, subtensor, hyperparams, fake_wallet -): - """Test successful commit-reveal with torch tensors.""" - # Preps - fake_netuid = 1 - fake_uids = torch.tensor([1, 2, 3], dtype=torch.int64) - fake_weights = torch.tensor([0.1, 0.2, 0.7], dtype=torch.float32) - fake_commit_for_reveal = b"mock_commit_for_reveal" - fake_reveal_round = 1 - - # Mocks - - mocked_uids = mocker.Mock() - mocked_weights = mocker.Mock() - mocked_convert_weights_and_uids_for_emit = mocker.patch.object( - async_commit_reveal, - "convert_and_normalize_weights_and_uids", - return_value=(mocked_uids, mocked_weights), - ) - mocked_get_encrypted_commit = mocker.patch.object( - async_commit_reveal, - "get_encrypted_commit", - return_value=(fake_commit_for_reveal, fake_reveal_round), - ) - 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=ExtrinsicResponse(True, "Success"), - ) - mock_block = mocker.patch.object( - subtensor.substrate, - "get_block", - return_value={"header": {"number": 1, "hash": "fakehash"}}, - ) - mock_hyperparams = mocker.patch.object( - subtensor, - "get_subnet_hyperparameters", - return_value=hyperparams, - ) - - # Call - success, message = await async_commit_reveal.commit_reveal_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - assert success is True - assert message == "Success" - mocked_convert_weights_and_uids_for_emit.assert_called_once_with( - fake_uids, fake_weights - ) - mocked_get_encrypted_commit.assert_called_once_with( - uids=mocked_uids, - weights=mocked_weights, - subnet_reveal_period_epochs=mock_hyperparams.return_value.commit_reveal_period, - version_key=async_commit_reveal.version_as_int, - tempo=mock_hyperparams.return_value.tempo, - netuid=fake_netuid, - current_block=mock_block.return_value["header"]["number"], - block_time=12.0, - hotkey=fake_wallet.hotkey.public_key, - ) - 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, - sign_with="hotkey", - period=None, - raise_error=False, - calling_function="commit_reveal_extrinsic", - ) - - -@pytest.mark.asyncio -async def test_commit_reveal_v3_extrinsic_success_with_numpy( - mocker, subtensor, hyperparams, fake_wallet -): - """Test successful commit-reveal with numpy arrays.""" - # Preps - fake_netuid = 1 - fake_uids = np.array([1, 2, 3], dtype=np.int64) - fake_weights = np.array([0.1, 0.2, 0.7], dtype=np.float32) - - mock_convert = mocker.patch.object( - async_commit_reveal, - "convert_and_normalize_weights_and_uids", - return_value=(fake_uids, fake_weights), - ) - mock_encode_drand = mocker.patch.object( - async_commit_reveal, "get_encrypted_commit", return_value=(b"commit", 0) - ) - 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=ExtrinsicResponse(True, "Success"), - ) - mocker.patch.object(subtensor.substrate, "get_block_number", return_value=1) - mocker.patch.object( - subtensor, - "get_subnet_hyperparameters", - return_value=hyperparams, - ) - - # Call - success, message = await async_commit_reveal.commit_reveal_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=False, - wait_for_finalization=False, - ) - - # Asserts - assert success is True - assert message == "Success" - mock_convert.assert_called_once_with(fake_uids, fake_weights) - mock_encode_drand.assert_called_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, - raise_error=False, - calling_function="commit_reveal_extrinsic", - ) - - -@pytest.mark.asyncio -async def test_commit_reveal_v3_extrinsic_response_false( - mocker, subtensor, hyperparams, fake_wallet -): - """Test unsuccessful commit-reveal with torch.""" - # Preps - fake_netuid = 1 - fake_uids = torch.tensor([1, 2, 3], dtype=torch.int64) - fake_weights = torch.tensor([0.1, 0.2, 0.7], dtype=torch.float32) - fake_commit_for_reveal = b"mock_commit_for_reveal" - fake_reveal_round = 1 - - # Mocks - mocker.patch.object( - async_commit_reveal, - "convert_and_normalize_weights_and_uids", - return_value=(fake_uids, fake_weights), - ) - mocker.patch.object( - async_commit_reveal, - "get_encrypted_commit", - return_value=(fake_commit_for_reveal, fake_reveal_round), - ) - 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=ExtrinsicResponse(False, "Failed"), - ) - mocker.patch.object(subtensor.substrate, "get_block_number", return_value=1) - mocker.patch.object( - subtensor, - "get_subnet_hyperparameters", - return_value=hyperparams, - ) - - # Call - success, message = await async_commit_reveal.commit_reveal_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - assert success is False - assert message == "Failed" - 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, - sign_with="hotkey", - period=None, - raise_error=False, - calling_function="commit_reveal_extrinsic", - ) - - -@pytest.mark.asyncio -async def test_commit_reveal_v3_extrinsic_exception(mocker, subtensor, fake_wallet): - """Test exception handling in commit-reveal.""" - # Preps - fake_netuid = 1 - fake_uids = [1, 2, 3] - fake_weights = [0.1, 0.2, 0.7] - - mocker.patch.object( - async_commit_reveal, - "convert_and_normalize_weights_and_uids", - side_effect=Exception("Test Error"), - ) - - # Call - with pytest.raises(Exception): - await async_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/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py deleted file mode 100644 index c3ddc17383..0000000000 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ /dev/null @@ -1,270 +0,0 @@ -import numpy as np -import pytest -import torch - -from bittensor.core import subtensor as subtensor_module -from bittensor.core.chain_data import SubnetHyperparameters -from bittensor.core.extrinsics import commit_reveal -from bittensor.core.types import ExtrinsicResponse - - -@pytest.fixture -def hyperparams(): - yield SubnetHyperparameters( - rho=0, - kappa=0, - immunity_period=0, - min_allowed_weights=0, - max_weight_limit=0.0, - tempo=0, - min_difficulty=0, - max_difficulty=0, - weights_version=0, - weights_rate_limit=0, - adjustment_interval=0, - activity_cutoff=0, - registration_allowed=False, - target_regs_per_interval=0, - min_burn=0, - max_burn=0, - bonds_moving_avg=0, - max_regs_per_block=0, - serving_rate_limit=0, - max_validators=0, - adjustment_alpha=0, - difficulty=0, - commit_reveal_period=0, - commit_reveal_weights_enabled=True, - alpha_high=0, - alpha_low=0, - liquid_alpha_enabled=False, - alpha_sigmoid_steepness=0, - yuma_version=3, - subnet_is_active=False, - transfers_enabled=False, - bonds_reset_enabled=False, - user_liquidity_enabled=False, - ) - - -def test_commit_reveal_v3_extrinsic_success_with_torch( - mocker, subtensor, hyperparams, fake_wallet -): - """Test successful commit-reveal with torch tensors.""" - # Preps - fake_netuid = 1 - fake_uids = torch.tensor([1, 2, 3], dtype=torch.int64) - fake_weights = torch.tensor([0.1, 0.2, 0.7], dtype=torch.float32) - fake_commit_for_reveal = b"mock_commit_for_reveal" - fake_reveal_round = 1 - - # Mocks - - mocked_uids = mocker.Mock() - mocked_weights = mocker.Mock() - mocked_convert_weights_and_uids_for_emit = mocker.patch.object( - commit_reveal, - "convert_and_normalize_weights_and_uids", - return_value=(mocked_uids, mocked_weights), - ) - mocker.patch.object(subtensor, "get_subnet_reveal_period_epochs") - mocked_get_encrypted_commit = mocker.patch.object( - commit_reveal, - "get_encrypted_commit", - return_value=(fake_commit_for_reveal, fake_reveal_round), - ) - 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=ExtrinsicResponse(True, "Success"), - ) - mock_block = mocker.patch.object(subtensor, "get_current_block", return_value=1) - mock_hyperparams = mocker.patch.object( - subtensor, - "get_subnet_hyperparameters", - return_value=hyperparams, - ) - - # Call - success, message = commit_reveal.commit_reveal_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=True, - wait_for_finalization=True, - period=None, - ) - - # Asserts - assert success is True - assert message == "Success" - mocked_convert_weights_and_uids_for_emit.assert_called_once_with( - fake_uids, fake_weights - ) - mocked_get_encrypted_commit.assert_called_once_with( - uids=mocked_uids, - weights=mocked_weights, - subnet_reveal_period_epochs=mock_hyperparams.return_value.commit_reveal_period, - version_key=commit_reveal.version_as_int, - tempo=mock_hyperparams.return_value.tempo, - netuid=fake_netuid, - current_block=mock_block.return_value, - block_time=12.0, - hotkey=fake_wallet.hotkey.public_key, - ) - 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, - sign_with="hotkey", - period=None, - raise_error=False, - calling_function="commit_reveal_extrinsic", - ) - - -def test_commit_reveal_v3_extrinsic_success_with_numpy( - mocker, subtensor, hyperparams, fake_wallet -): - """Test successful commit-reveal with numpy arrays.""" - # Preps - fake_netuid = 1 - fake_uids = np.array([1, 2, 3], dtype=np.int64) - fake_weights = np.array([0.1, 0.2, 0.7], dtype=np.float32) - - mock_convert = mocker.patch.object( - commit_reveal, - "convert_and_normalize_weights_and_uids", - return_value=(fake_uids, fake_weights), - ) - mock_encode_drand = mocker.patch.object( - commit_reveal, "get_encrypted_commit", return_value=(b"commit", 0) - ) - 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=ExtrinsicResponse(True, "Success"), - ) - mocker.patch.object(subtensor, "get_current_block", return_value=1) - mocker.patch.object( - subtensor, - "get_subnet_hyperparameters", - return_value=hyperparams, - ) - - # Call - success, message = commit_reveal.commit_reveal_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=False, - wait_for_finalization=False, - ) - - # Asserts - assert success is True - assert message == "Success" - mock_convert.assert_called_once_with(fake_uids, fake_weights) - mock_encode_drand.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, - raise_error=False, - calling_function="commit_reveal_extrinsic", - ) - - -def test_commit_reveal_v3_extrinsic_response_false( - mocker, subtensor, hyperparams, fake_wallet -): - """Test unsuccessful commit-reveal with torch.""" - # Preps - fake_netuid = 1 - fake_uids = torch.tensor([1, 2, 3], dtype=torch.int64) - fake_weights = torch.tensor([0.1, 0.2, 0.7], dtype=torch.float32) - fake_commit_for_reveal = b"mock_commit_for_reveal" - fake_reveal_round = 1 - - # Mocks - mocker.patch.object( - commit_reveal, - "convert_and_normalize_weights_and_uids", - return_value=(fake_uids, fake_weights), - ) - mocker.patch.object( - commit_reveal, - "get_encrypted_commit", - return_value=(fake_commit_for_reveal, fake_reveal_round), - ) - 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=ExtrinsicResponse(False, "Failed"), - ) - mocker.patch.object(subtensor, "get_current_block", return_value=1) - mocker.patch.object( - subtensor, - "get_subnet_hyperparameters", - return_value=hyperparams, - ) - - # Call - success, message = commit_reveal.commit_reveal_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - assert success is False - assert message == "Failed" - 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, - sign_with="hotkey", - period=None, - raise_error=False, - calling_function="commit_reveal_extrinsic", - ) - - -def test_commit_reveal_v3_extrinsic_exception(mocker, subtensor, fake_wallet): - """Test exception handling in commit-reveal.""" - # Preps - fake_netuid = 1 - fake_uids = [1, 2, 3] - fake_weights = [0.1, 0.2, 0.7] - - mocker.patch.object( - commit_reveal, - "convert_and_normalize_weights_and_uids", - side_effect=Exception("Test Error"), - ) - - # 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, - ) From 21af7627257ebb1a2d76a08417bcadebb0c2eae2 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 09:55:32 -0700 Subject: [PATCH 226/416] removing deprecated extrinsics and replacing them with consistent ones --- .../core/extrinsics/asyncex/mechanism.py | 423 ------------------ bittensor/core/extrinsics/mechanism.py | 422 ----------------- .../extrinsics/asyncex/test_mechanisms.py | 313 ------------- .../extrinsics/test_commit_weights.py | 122 ----- .../unit_tests/extrinsics/test_set_weights.py | 93 ---- 5 files changed, 1373 deletions(-) delete mode 100644 tests/unit_tests/extrinsics/test_commit_weights.py delete mode 100644 tests/unit_tests/extrinsics/test_set_weights.py diff --git a/bittensor/core/extrinsics/asyncex/mechanism.py b/bittensor/core/extrinsics/asyncex/mechanism.py index 652a122bc7..e69de29bb2 100644 --- a/bittensor/core/extrinsics/asyncex/mechanism.py +++ b/bittensor/core/extrinsics/asyncex/mechanism.py @@ -1,423 +0,0 @@ -from typing import TYPE_CHECKING, Optional, Union - -from bittensor_drand import get_encrypted_commit - -from bittensor.core.types import ExtrinsicResponse -from bittensor.core.settings import version_as_int -from bittensor.core.types import Salt, UIDs, Weights -from bittensor.utils import ( - unlock_key, - get_mechid_storage_index, - get_function_name, - format_error_message, -) -from bittensor.utils.btlogging import logging -from bittensor.utils.weight_utils import ( - convert_and_normalize_weights_and_uids, - generate_weight_hash, -) - -if TYPE_CHECKING: - from bittensor_wallet import Wallet - from bittensor.core.async_subtensor import AsyncSubtensor - - -async def commit_mechanism_weights_extrinsic( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuid: int, - mechid: int, - uids: UIDs, - weights: Weights, - salt: Salt, - version_key: int = version_as_int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. - - Parameters: - subtensor: AsyncSubtensor instance. - wallet: Bittensor Wallet instance. - netuid: The subnet unique identifier. - mechid: The subnet mechanism unique identifier. - uids: NumPy array of neuron UIDs for which weights are being committed. - weights: NumPy array of weight values corresponding to each UID. - salt: list of randomly generated integers as salt to generated weighted hash. - 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 inclusion of the transaction. - wait_for_finalization: Whether to wait for the finalization of the transaction. - - Returns: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - try: - signing_keypair = "hotkey" - unlock = unlock_key( - wallet=wallet, raise_error=raise_error, unlock_type=signing_keypair - ) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) - # Generate the hash of the weights - commit_hash = generate_weight_hash( - address=wallet.hotkey.ss58_address, - netuid=storage_index, - uids=list(uids), - values=list(weights), - salt=salt, - version_key=version_key, - ) - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="commit_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit_hash": commit_hash, - }, - ) - response = 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, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) - - if response.success: - logging.debug(response.message) - return response - - logging.error(response.message) - return response - - except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) - - -async def commit_timelocked_mechanism_weights_extrinsic( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuid: int, - mechid: int, - uids: UIDs, - weights: Weights, - block_time: Union[int, float], - commit_reveal_version: int = 4, - version_key: int = version_as_int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. - - Parameters: - subtensor: AsyncSubtensor instance. - wallet: Bittensor Wallet instance. - netuid: The unique identifier of the subnet. - mechid: The subnet mechanism unique identifier. - uids: The list of neuron UIDs that the weights are being set for. - weights: The corresponding weights to be set for each UID. - block_time: The number of seconds for block duration. - commit_reveal_version: The version of the commit-reveal in the chain. - 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 inclusion of the transaction. - wait_for_finalization: Whether to wait for the finalization of the transaction. - - Returns: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - try: - signing_keypair = "hotkey" - unlock = unlock_key( - wallet=wallet, raise_error=raise_error, unlock_type=signing_keypair - ) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - - current_block = await subtensor.block - subnet_hyperparameters = await subtensor.get_subnet_hyperparameters( - netuid, block=current_block - ) - tempo = subnet_hyperparameters.tempo - subnet_reveal_period_epochs = subnet_hyperparameters.commit_reveal_period - - storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) - - # 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=storage_index, - subnet_reveal_period_epochs=subnet_reveal_period_epochs, - block_time=block_time, - hotkey=wallet.hotkey.public_key, - ) - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="commit_timelocked_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit": commit_for_reveal, - "reveal_round": reveal_round, - "commit_reveal_version": commit_reveal_version, - }, - ) - response = 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, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) - - if response.success: - logging.debug(response.message) - response.data = { - "commit_for_reveal": commit_for_reveal, - "reveal_round": reveal_round, - } - return response - - logging.error(response.message) - return response - - except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) - - -async def reveal_mechanism_weights_extrinsic( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuid: int, - mechid: int, - uids: UIDs, - weights: Weights, - salt: Salt, - version_key: int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """ - Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. - - Parameters: - subtensor: AsyncSubtensor instance. - wallet: Bittensor Wallet instance. - netuid: The unique identifier of the subnet. - mechid: The subnet mechanism unique identifier. - 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. - 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: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - try: - signing_keypair = "hotkey" - unlock = unlock_key( - wallet=wallet, raise_error=raise_error, unlock_type=signing_keypair - ) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="reveal_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "uids": uids, - "values": weights, - "salt": salt, - "version_key": version_key, - }, - ) - response = 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, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) - - if response.success: - logging.debug(response.message) - return response - - logging.error(response.message) - return response - - except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) - - -async def set_mechanism_weights_extrinsic( - subtensor: "AsyncSubtensor", - wallet: "Wallet", - netuid: int, - mechid: int, - uids: UIDs, - weights: Weights, - version_key: int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """ - Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. - - Parameters: - subtensor: AsyncSubtensor instance. - wallet: Bittensor Wallet instance. - netuid: The unique identifier of the subnet. - mechid: The subnet mechanism unique identifier. - uids: List of neuron UIDs for which weights are being revealed. - weights: List of weight values corresponding to each UID. - 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 inclusion of the transaction. - wait_for_finalization: Whether to wait for the finalization of the transaction. - - Returns: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - try: - signing_keypair = "hotkey" - unlock = unlock_key( - wallet=wallet, raise_error=raise_error, unlock_type=signing_keypair - ) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - # Convert, reformat and normalize. - uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "dests": uids, - "weights": weights, - "version_key": version_key, - }, - ) - response = 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=signing_keypair, - sign_with=signing_keypair, - raise_error=raise_error, - ) - - if response.success: - logging.debug("Successfully set weights and Finalized.") - return response - - logging.error(response.message) - return response - - except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) diff --git a/bittensor/core/extrinsics/mechanism.py b/bittensor/core/extrinsics/mechanism.py index d74ffb7a0d..e69de29bb2 100644 --- a/bittensor/core/extrinsics/mechanism.py +++ b/bittensor/core/extrinsics/mechanism.py @@ -1,422 +0,0 @@ -from typing import TYPE_CHECKING, Optional, Union -from bittensor.core.types import ExtrinsicResponse -from bittensor_drand import get_encrypted_commit - -from bittensor.core.settings import version_as_int -from bittensor.core.types import Salt, UIDs, Weights -from bittensor.utils import ( - unlock_key, - get_mechid_storage_index, - get_function_name, - format_error_message, -) -from bittensor.utils.btlogging import logging -from bittensor.utils.weight_utils import ( - convert_and_normalize_weights_and_uids, - generate_weight_hash, -) - -if TYPE_CHECKING: - from bittensor_wallet import Wallet - from bittensor.core.subtensor import Subtensor - - -def commit_mechanism_weights_extrinsic( - subtensor: "Subtensor", - wallet: "Wallet", - netuid: int, - mechid: int, - uids: UIDs, - weights: Weights, - salt: Salt, - version_key: int = version_as_int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. - - Parameters: - subtensor: Subtensor instance. - wallet: Bittensor Wallet instance. - netuid: The subnet unique identifier. - mechid: The subnet mechanism unique identifier. - uids: NumPy array of neuron UIDs for which weights are being committed. - weights: NumPy array of weight values corresponding to each UID. - salt: list of randomly generated integers as salt to generated weighted hash. - 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 inclusion of the transaction. - wait_for_finalization: Whether to wait for the finalization of the transaction. - - Returns: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - try: - signing_keypair = "hotkey" - unlock = unlock_key( - wallet=wallet, raise_error=raise_error, unlock_type=signing_keypair - ) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) - # Generate the hash of the weights - commit_hash = generate_weight_hash( - address=wallet.hotkey.ss58_address, - netuid=storage_index, - uids=list(uids), - values=list(weights), - salt=salt, - version_key=version_key, - ) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="commit_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit_hash": commit_hash, - }, - ) - response = 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, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) - - if response.success: - logging.debug(response.message) - return response - - logging.error(response.message) - return response - - except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) - - -def commit_timelocked_mechanism_weights_extrinsic( - subtensor: "Subtensor", - wallet: "Wallet", - netuid: int, - mechid: int, - uids: UIDs, - weights: Weights, - block_time: Union[int, float], - commit_reveal_version: int = 4, - version_key: int = version_as_int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """Commits the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. - - Parameters: - subtensor: Subtensor instance. - wallet: Bittensor Wallet instance. - netuid: The unique identifier of the subnet. - mechid: The sub-subnet unique identifier. - uids: The list of neuron UIDs that the weights are being set for. - weights: The corresponding weights to be set for each UID. - block_time: The number of seconds for block duration. - commit_reveal_version: The version of the commit-reveal in the chain. - 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 inclusion of the transaction. - wait_for_finalization: Whether to wait for the finalization of the transaction. - - Returns: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - try: - signing_keypair = "hotkey" - unlock = unlock_key( - wallet=wallet, raise_error=raise_error, unlock_type=signing_keypair - ) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - 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 - - storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) - - # 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=storage_index, - subnet_reveal_period_epochs=subnet_reveal_period_epochs, - block_time=block_time, - hotkey=wallet.hotkey.public_key, - ) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="commit_timelocked_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit": commit_for_reveal, - "reveal_round": reveal_round, - "commit_reveal_version": commit_reveal_version, - }, - ) - response = 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, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) - - if response.success: - logging.debug(response.message) - response.data = { - "commit_for_reveal": commit_for_reveal, - "reveal_round": reveal_round, - } - return response - - logging.error(response.message) - return response - - except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) - - -def reveal_mechanism_weights_extrinsic( - subtensor: "Subtensor", - wallet: "Wallet", - netuid: int, - mechid: int, - uids: UIDs, - weights: Weights, - salt: Salt, - version_key: int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """ - Reveals the weights for a specific sub subnet on the Bittensor blockchain using the provided wallet. - - Parameters: - subtensor: Subtensor instance. - wallet: Bittensor Wallet instance. - netuid: The unique identifier of the subnet. - mechid: The subnet mechanism unique identifier. - 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. - 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: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - try: - signing_keypair = "hotkey" - unlock = unlock_key( - wallet=wallet, raise_error=raise_error, unlock_type=signing_keypair - ) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="reveal_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "uids": uids, - "values": weights, - "salt": salt, - "version_key": version_key, - }, - ) - response = 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, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) - - if response.success: - logging.debug(response.message) - return response - - logging.error(response.message) - return response - - except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) - - -def set_mechanism_weights_extrinsic( - subtensor: "Subtensor", - wallet: "Wallet", - netuid: int, - mechid: int, - uids: UIDs, - weights: Weights, - version_key: int, - period: Optional[int] = None, - raise_error: bool = False, - wait_for_inclusion: bool = True, - wait_for_finalization: bool = True, -) -> ExtrinsicResponse: - """ - Sets the passed weights in the chain for hotkeys in the sub-subnet of the passed subnet. - - Parameters: - subtensor: Subtensor instance. - wallet: Bittensor Wallet instance. - netuid: The unique identifier of the subnet. - mechid: The subnet mechanism unique identifier. - uids: List of neuron UIDs for which weights are being revealed. - weights: List of weight values corresponding to each UID. - 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 inclusion of the transaction. - wait_for_finalization: Whether to wait for the finalization of the transaction. - - Returns: - ExtrinsicResponse: The result object of the extrinsic execution. - """ - try: - signing_keypair = "hotkey" - unlock = unlock_key( - wallet=wallet, raise_error=raise_error, unlock_type=signing_keypair - ) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - # Convert, reformat and normalize. - uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "dests": uids, - "weights": weights, - "version_key": version_key, - }, - ) - response = 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, - sign_with=signing_keypair, - nonce_key=signing_keypair, - raise_error=raise_error, - ) - - if response.success: - logging.debug("Successfully set weights and Finalized.") - return response - - logging.error(response.message) - return response - - except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) diff --git a/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py b/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py index b872721c9a..e69de29bb2 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py +++ b/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py @@ -1,313 +0,0 @@ -import pytest -from bittensor.core.extrinsics.asyncex import mechanism -from bittensor.core.types import ExtrinsicResponse - - -@pytest.mark.asyncio -async def test_commit_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `commit_mechanism_weights_extrinsic` extrinsic.""" - # Preps - fake_wallet.hotkey.ss58_address = "hotkey" - - netuid = mocker.Mock() - mechid = mocker.Mock() - uids = [] - weights = [] - salt = [] - - mocked_unlock_key = mocker.patch.object( - mechanism, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocked_get_mechanism_storage_index = mocker.patch.object( - mechanism, "get_mechid_storage_index" - ) - mocked_generate_weight_hash = mocker.patch.object(mechanism, "generate_weight_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=ExtrinsicResponse(True, "") - ) - - # Call - result = await mechanism.commit_mechanism_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=netuid, - mechid=mechid, - uids=uids, - weights=weights, - salt=salt, - ) - - # Asserts - mocked_unlock_key.assert_called_once_with( - wallet=fake_wallet, raise_error=False, unlock_type="hotkey" - ) - mocked_get_mechanism_storage_index.assert_called_once_with( - netuid=netuid, mechid=mechid - ) - mocked_generate_weight_hash.assert_called_once_with( - address=fake_wallet.hotkey.ss58_address, - netuid=mocked_get_mechanism_storage_index.return_value, - uids=list(uids), - values=list(weights), - salt=salt, - version_key=mechanism.version_as_int, - ) - mocked_compose_call.assert_awaited_once_with( - call_module="SubtensorModule", - call_function="commit_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit_hash": mocked_generate_weight_hash.return_value, - }, - ) - mocked_sign_and_send_extrinsic.assert_awaited_once_with( - wallet=fake_wallet, - call=mocked_compose_call.return_value, - nonce_key="hotkey", - sign_with="hotkey", - use_nonce=True, - period=None, - raise_error=False, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert result == mocked_sign_and_send_extrinsic.return_value - - -@pytest.mark.asyncio -async def test_commit_timelocked_mechanism_weights_extrinsic( - mocker, subtensor, fake_wallet -): - """Test successful `commit_mechanism_weights_extrinsic` extrinsic.""" - # Preps - fake_wallet.hotkey.ss58_address = "hotkey" - - netuid = mocker.Mock() - mechid = mocker.Mock() - uids = [] - weights = [] - block_time = mocker.Mock() - - mocked_unlock_key = mocker.patch.object( - mechanism, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - mechanism, - "convert_and_normalize_weights_and_uids", - return_value=(uids, weights), - ) - mocked_get_current_block = mocker.patch.object(subtensor, "get_current_block") - mocked_get_subnet_hyperparameters = mocker.patch.object( - subtensor, "get_subnet_hyperparameters" - ) - mocked_get_mechanism_storage_index = mocker.patch.object( - mechanism, "get_mechid_storage_index" - ) - mocked_get_encrypted_commit = mocker.patch.object( - mechanism, - "get_encrypted_commit", - 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=ExtrinsicResponse( - True, - f"reveal_round:{mocked_get_encrypted_commit.return_value[1]}", - ), - ) - - # Call - result = await mechanism.commit_timelocked_mechanism_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=netuid, - mechid=mechid, - uids=uids, - weights=weights, - block_time=block_time, - ) - - # Asserts - mocked_unlock_key.assert_called_once_with( - wallet=fake_wallet, raise_error=False, unlock_type="hotkey" - ) - mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) - mocked_get_mechanism_storage_index.assert_called_once_with( - netuid=netuid, mechid=mechid - ) - mocked_get_encrypted_commit.assert_called_once_with( - uids=uids, - weights=weights, - subnet_reveal_period_epochs=mocked_get_subnet_hyperparameters.return_value.commit_reveal_period, - version_key=mechanism.version_as_int, - tempo=mocked_get_subnet_hyperparameters.return_value.tempo, - netuid=mocked_get_mechanism_storage_index.return_value, - current_block=mocked_get_current_block.return_value, - block_time=block_time, - hotkey=fake_wallet.hotkey.public_key, - ) - mocked_compose_call.assert_awaited_once_with( - call_module="SubtensorModule", - call_function="commit_timelocked_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit": mocked_get_encrypted_commit.return_value[0], - "reveal_round": mocked_get_encrypted_commit.return_value[1], - "commit_reveal_version": 4, - }, - ) - mocked_sign_and_send_extrinsic.assert_awaited_once_with( - wallet=fake_wallet, - call=mocked_compose_call.return_value, - nonce_key="hotkey", - sign_with="hotkey", - use_nonce=True, - period=None, - raise_error=False, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert result == mocked_sign_and_send_extrinsic.return_value - - -@pytest.mark.asyncio -async def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `reveal_mechanism_weights_extrinsic` extrinsic.""" - # Preps - fake_wallet.hotkey.ss58_address = "hotkey" - - netuid = mocker.Mock() - mechid = mocker.Mock() - uids = [] - weights = [] - salt = [] - - mocked_unlock_key = mocker.patch.object( - mechanism, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - mechanism, - "convert_and_normalize_weights_and_uids", - return_value=(uids, 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=ExtrinsicResponse(True, "") - ) - - # Call - result = await mechanism.reveal_mechanism_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=netuid, - mechid=mechid, - uids=uids, - weights=weights, - salt=salt, - version_key=mechanism.version_as_int, - ) - - # Asserts - mocked_unlock_key.assert_called_once_with( - wallet=fake_wallet, raise_error=False, unlock_type="hotkey" - ) - mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) - mocked_compose_call.assert_awaited_once_with( - call_module="SubtensorModule", - call_function="reveal_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "uids": mocked_convert_and_normalize_weights_and_uids.return_value[0], - "values": mocked_convert_and_normalize_weights_and_uids.return_value[0], - "salt": salt, - "version_key": mechanism.version_as_int, - }, - ) - mocked_sign_and_send_extrinsic.assert_awaited_once_with( - wallet=fake_wallet, - call=mocked_compose_call.return_value, - nonce_key="hotkey", - sign_with="hotkey", - use_nonce=True, - period=None, - raise_error=False, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert result == mocked_sign_and_send_extrinsic.return_value - - -@pytest.mark.asyncio -async def test_mechanism_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Verify that the `set_mechanism_weights_extrinsic` function works as expected.""" - # Preps - fake_wallet.hotkey.ss58_address = "hotkey" - - netuid = mocker.Mock() - mechid = mocker.Mock() - uids = [] - weights = [] - - mocked_unlock_key = mocker.patch.object( - mechanism, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - mechanism, - "convert_and_normalize_weights_and_uids", - return_value=(uids, 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=ExtrinsicResponse( - True, - "", - ), - ) - - # Call - result = await mechanism.set_mechanism_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=netuid, - mechid=mechid, - uids=uids, - weights=weights, - version_key=mechanism.version_as_int, - ) - - # Asserts - mocked_unlock_key.assert_called_once_with( - wallet=fake_wallet, raise_error=False, unlock_type="hotkey" - ) - mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) - mocked_compose_call.assert_awaited_once_with( - call_module="SubtensorModule", - call_function="set_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "dests": uids, - "weights": weights, - "version_key": mechanism.version_as_int, - }, - ) - mocked_sign_and_send_extrinsic.assert_awaited_once_with( - wallet=fake_wallet, - call=mocked_compose_call.return_value, - nonce_key="hotkey", - sign_with="hotkey", - use_nonce=True, - period=None, - raise_error=False, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_commit_weights.py b/tests/unit_tests/extrinsics/test_commit_weights.py deleted file mode 100644 index c54ba78fa8..0000000000 --- a/tests/unit_tests/extrinsics/test_commit_weights.py +++ /dev/null @@ -1,122 +0,0 @@ -import pytest - -from bittensor.core.extrinsics.weights import ( - commit_weights_extrinsic, - reveal_weights_extrinsic, -) -from bittensor.core.types import ExtrinsicResponse - - -@pytest.mark.parametrize( - "sign_and_send_return", - [ - ExtrinsicResponse(True, "Success"), - ExtrinsicResponse(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_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, - calling_function="commit_weights_extrinsic", - ) - assert result == sign_and_send_return - - -@pytest.mark.parametrize( - "sign_and_send_return", - [ - ExtrinsicResponse(True, "Success"), - ExtrinsicResponse(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() - - 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 = reveal_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - 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 - mocked_compose_call.assert_called_once_with( - call_module="SubtensorModule", - call_function="reveal_weights", - call_params={ - "netuid": fake_netuid, - "uids": fake_uids, - "values": fake_weights, - "salt": fake_salt, - "version_key": fake_version_key, - }, - ) - 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, - calling_function="reveal_weights_extrinsic", - ) - assert result == sign_and_send_return diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py deleted file mode 100644 index 382a36d5c7..0000000000 --- a/tests/unit_tests/extrinsics/test_set_weights.py +++ /dev/null @@ -1,93 +0,0 @@ -from unittest.mock import MagicMock, patch - -import pytest - -from bittensor.core.extrinsics.weights import ( - set_weights_extrinsic, -) -from bittensor.core.types import ExtrinsicResponse -from bittensor.core.subtensor import Subtensor - - -@pytest.fixture -def mock_subtensor(): - mock = MagicMock(spec=Subtensor) - mock.network = "mock_network" - mock.substrate = MagicMock() - return mock - - -@pytest.mark.parametrize( - "uids, weights, version_key, wait_for_inclusion, wait_for_finalization, expected_success, expected_message", - [ - ( - [1, 2], - [0.5, 0.5], - 0, - True, - False, - True, - "Successfully set weights and Finalized.", - ), - ( - [1, 2], - [0.5, 0.4], - 0, - False, - False, - True, - "Not waiting for finalization or inclusion.", - ), - ( - [1, 2], - [0.5, 0.5], - 0, - True, - False, - False, - "Mock error message", - ), - ], - ids=[ - "happy-flow", - "not-waiting-finalization-inclusion", - "error-flow", - ], -) -def test_set_weights_extrinsic( - mock_subtensor, - fake_wallet, - uids, - weights, - version_key, - wait_for_inclusion, - wait_for_finalization, - expected_success, - expected_message, -): - # uids_tensor = torch.tensor(uids, dtype=torch.int64) - # weights_tensor = torch.tensor(weights, dtype=torch.float32) - with ( - patch( - "bittensor.utils.weight_utils.convert_weights_and_uids_for_emit", - return_value=(uids, weights), - ), - patch.object( - mock_subtensor, - "sign_and_send_extrinsic", - return_value=ExtrinsicResponse(expected_success, expected_message), - ), - ): - result, message = set_weights_extrinsic( - subtensor=mock_subtensor, - wallet=fake_wallet, - netuid=123, - uids=uids, - weights=weights, - version_key=version_key, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - - assert result == expected_success, f"Test {expected_message} failed." - assert message == expected_message, f"Test {expected_message} failed." From 505520090b29b6536b9d5d39dda72b2471d9ac14 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 09:55:56 -0700 Subject: [PATCH 227/416] delete unnecessary comments --- bittensor/utils/weight_utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index 92c798a12d..b56604c720 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -22,7 +22,6 @@ U16_MAX = 65535 -# Uses in `bittensor.utils.weight_utils.process_weights_for_netuid` @legacy_torch_api_compat def normalize_max_weight( x: Union[NDArray[np.float32], "torch.FloatTensor"], limit: float = 0.1 @@ -71,7 +70,6 @@ def normalize_max_weight( return y -# Metagraph uses this function. def convert_weight_uids_and_vals_to_tensor( n: int, uids: list[int], weights: list[int] ) -> Union[NDArray[np.float32], "torch.FloatTensor"]: @@ -102,7 +100,6 @@ def convert_weight_uids_and_vals_to_tensor( return row_weights -# Metagraph uses this function. def convert_root_weight_uids_and_vals_to_tensor( n: int, uids: list[int], weights: list[int], subnets: list[int] ) -> Union[NDArray[np.float32], "torch.FloatTensor"]: @@ -164,7 +161,6 @@ def convert_bond_uids_and_vals_to_tensor( return row_bonds -# This is used by the community via `bittensor.api.extrinsics.set_weights.set_weights_extrinsic` def convert_weights_and_uids_for_emit( uids: Union[NDArray[np.int64], "torch.LongTensor"], weights: Union[NDArray[np.float32], "torch.FloatTensor"], From ee0683e264c3e744f8beb410221b394c47f5a6d9 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 09:56:19 -0700 Subject: [PATCH 228/416] update subtensor calls --- bittensor/core/async_subtensor.py | 22 +++++++++++----------- bittensor/core/subtensor.py | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ddc5a46f62..0723a2fb4f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -72,12 +72,6 @@ set_auto_stake_extrinsic, ) from bittensor.core.extrinsics.asyncex.start_call import start_call_extrinsic -from bittensor.core.extrinsics.asyncex.mechanism import ( - commit_mechanism_weights_extrinsic, - commit_timelocked_mechanism_weights_extrinsic, - reveal_mechanism_weights_extrinsic, - set_mechanism_weights_extrinsic, -) from bittensor.core.extrinsics.asyncex.take import ( decrease_take_extrinsic, increase_take_extrinsic, @@ -88,9 +82,15 @@ unstake_extrinsic, unstake_multiple_extrinsic, ) +from bittensor.core.extrinsics.asyncex.weights import ( + commit_timelocked_weights_extrinsic, + commit_weights_extrinsic, + reveal_weights_extrinsic, + set_weights_extrinsic, +) from bittensor.core.metagraph import AsyncMetagraph from bittensor.core.settings import version_as_int, TYPE_REGISTRY -from bittensor.core.types import ExtrinsicResponse, ParamWithTypes, SubtensorMixin +from bittensor.core.types import ExtrinsicResponse from bittensor.core.types import ( ParamWithTypes, Salt, @@ -4692,7 +4692,7 @@ async def commit_weights( while retries < max_retries and response.success is False: try: - response = await commit_mechanism_weights_extrinsic( + response = await commit_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -5028,7 +5028,7 @@ async def reveal_weights( while retries < max_retries and response.success is False: try: - response = await reveal_mechanism_weights_extrinsic( + response = await reveal_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -5419,7 +5419,7 @@ async def _blocks_weight_limit() -> bool: f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) - response = await commit_timelocked_mechanism_weights_extrinsic( + response = await commit_timelocked_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -5449,7 +5449,7 @@ async def _blocks_weight_limit() -> bool: f"Setting weights for subnet #[blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) - response = await set_mechanism_weights_extrinsic( + response = await set_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index a8356e1c6d..25f53bbe31 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -47,12 +47,6 @@ remove_liquidity_extrinsic, toggle_user_liquidity_extrinsic, ) -from bittensor.core.extrinsics.mechanism import ( - commit_mechanism_weights_extrinsic, - commit_timelocked_mechanism_weights_extrinsic, - reveal_mechanism_weights_extrinsic, - set_mechanism_weights_extrinsic, -) from bittensor.core.extrinsics.move_stake import ( transfer_stake_extrinsic, swap_stake_extrinsic, @@ -87,13 +81,19 @@ unstake_extrinsic, unstake_multiple_extrinsic, ) +from bittensor.core.extrinsics.weights import ( + commit_timelocked_weights_extrinsic, + commit_weights_extrinsic, + reveal_weights_extrinsic, + set_weights_extrinsic, +) from bittensor.core.metagraph import Metagraph from bittensor.core.settings import ( version_as_int, SS58_FORMAT, TYPE_REGISTRY, ) -from bittensor.core.types import ExtrinsicResponse, ParamWithTypes, SubtensorMixin +from bittensor.core.types import ExtrinsicResponse from bittensor.core.types import ( ParamWithTypes, Salt, @@ -3526,7 +3526,7 @@ def commit_weights( while retries < max_retries and response.success is False: try: - response = commit_mechanism_weights_extrinsic( + response = commit_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -3864,7 +3864,7 @@ def reveal_weights( while retries < max_retries and response.success is False: try: - response = reveal_mechanism_weights_extrinsic( + response = reveal_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -4224,7 +4224,7 @@ def _blocks_weight_limit() -> bool: ) if self.commit_reveal_enabled(netuid=netuid): - # go with `commit_timelocked_mechanism_weights_extrinsic` extrinsic + # go with `commit_reveal_weights_extrinsic` extrinsic while ( retries < max_retries @@ -4236,7 +4236,7 @@ def _blocks_weight_limit() -> bool: f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) try: - response = commit_timelocked_mechanism_weights_extrinsic( + response = commit_timelocked_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, @@ -4271,7 +4271,7 @@ def _blocks_weight_limit() -> bool: f"Setting weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) - response = set_mechanism_weights_extrinsic( + response = set_weights_extrinsic( subtensor=self, wallet=wallet, netuid=netuid, From 82f8f4d5dde54297d17e2ca2c4ec58a7048a92da Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 09:56:41 -0700 Subject: [PATCH 229/416] fix test, delete unnecessary ones --- tests/e2e_tests/test_dendrite.py | 6 + .../extrinsics/asyncex/test_weights.py | 393 ++++++++---------- .../{test_mechanisms.py => test_weights.py} | 161 +++---- tests/unit_tests/test_async_subtensor.py | 188 --------- tests/unit_tests/test_subtensor.py | 119 +----- 5 files changed, 251 insertions(+), 616 deletions(-) rename tests/unit_tests/extrinsics/{test_mechanisms.py => test_weights.py} (76%) diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index 0aa7d9e6f2..7912182941 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -131,6 +131,9 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): amount=tao, ).success + # Waiting to give the chain a chance to update its state + subtensor.wait_for_block() + # Refresh metagraph metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) bob_neuron = metagraph.neurons[1] @@ -286,6 +289,9 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall ) ).success + # Waiting to give the chain a chance to update its state + await async_subtensor.wait_for_block() + # Refresh metagraph metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) bob_neuron = metagraph.neurons[1] diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index 73ad9a38f2..5b6260a580 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -1,338 +1,291 @@ import pytest -from bittensor.core import async_subtensor -from bittensor.core.extrinsics.asyncex import weights as async_weights + +from bittensor.core.extrinsics.asyncex import weights as weights_module from bittensor.core.settings import version_as_int from bittensor.core.types import ExtrinsicResponse @pytest.mark.asyncio -async def test_set_weights_extrinsic_success_with_finalization( - subtensor, fake_wallet, mocker -): - """Tests set_weights_extrinsic when weights are successfully set with finalization.""" +async def test_commit_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_weights_extrinsic` extrinsic.""" # Preps - fake_netuid = 1 - fake_uids = mocker.Mock() - fake_weights = mocker.Mock() + fake_wallet.hotkey.ss58_address = "hotkey" - mocked_convert_types = mocker.patch.object( - async_weights, - "convert_uids_and_weights", - return_value=(mocker.Mock(), mocker.Mock()), + netuid = mocker.Mock() + mechid = mocker.Mock() + uids = [] + weights = [] + salt = [] + + mocked_get_mechanism_storage_index = mocker.patch.object( + weights_module, "get_mechid_storage_index" ) - mocker_converter_normalize = mocker.patch.object( - async_weights, - "convert_and_normalize_weights_and_uids", - return_value=(mocker.Mock(), mocker.Mock()), + mocked_generate_weight_hash = mocker.patch.object( + weights_module, "generate_weight_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=ExtrinsicResponse(True, "") ) # Call - result, message = await async_weights.set_weights_extrinsic( + result = await weights_module.commit_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=True, - wait_for_finalization=True, + netuid=netuid, + mechid=mechid, + uids=uids, + weights=weights, + salt=salt, ) # Asserts - 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_get_mechanism_storage_index.assert_called_once_with( + netuid=netuid, mechid=mechid + ) + mocked_generate_weight_hash.assert_called_once_with( + address=fake_wallet.hotkey.ss58_address, + netuid=mocked_get_mechanism_storage_index.return_value, + uids=list(uids), + values=list(weights), + salt=salt, + version_key=weights_module.version_as_int, ) mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="set_weights", + call_function="commit_mechanism_weights", call_params={ - "dests": mocker_converter_normalize.return_value[0], - "weights": mocker_converter_normalize.return_value[1], - "netuid": fake_netuid, - "version_key": version_as_int, + "netuid": netuid, + "mecid": mechid, + "commit_hash": mocked_generate_weight_hash.return_value, }, ) - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, + mocked_sign_and_send_extrinsic.assert_awaited_once_with( wallet=fake_wallet, - wait_for_finalization=True, - wait_for_inclusion=True, - period=8, - use_nonce=True, + call=mocked_compose_call.return_value, nonce_key="hotkey", sign_with="hotkey", + use_nonce=True, + period=None, raise_error=False, - calling_function="set_weights_extrinsic", + wait_for_inclusion=True, + wait_for_finalization=True, ) - assert result is True - assert message == "" + assert result == mocked_sign_and_send_extrinsic.return_value @pytest.mark.asyncio -async def test_set_weights_extrinsic_no_waiting(subtensor, fake_wallet, mocker): - """Tests set_weights_extrinsic when no waiting for inclusion or finalization.""" +async def test_commit_timelocked_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_timelocked_weights_extrinsic` extrinsic.""" # Preps - fake_netuid = 1 - fake_uids = [1, 2, 3] - fake_weights = [0.1, 0.2, 0.7] + fake_wallet.hotkey.ss58_address = "hotkey" - 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, + netuid = mocker.Mock() + mechid = mocker.Mock() + uids = [] + weights = [] + block_time = mocker.Mock() + + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + weights_module, "convert_and_normalize_weights_and_uids", + return_value=(uids, weights), + ) + mocked_get_current_block = mocker.patch.object(subtensor, "get_current_block") + mocked_get_subnet_hyperparameters = mocker.patch.object( + subtensor, "get_subnet_hyperparameters" + ) + mocked_get_mechanism_storage_index = mocker.patch.object( + weights_module, "get_mechid_storage_index" + ) + mocked_get_encrypted_commit = mocker.patch.object( + weights_module, + "get_encrypted_commit", 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=ExtrinsicResponse( - True, "Not waiting for finalization or inclusion." + True, + f"reveal_round:{mocked_get_encrypted_commit.return_value[1]}", ), ) # Call - result, message = await async_weights.set_weights_extrinsic( + result = await weights_module.commit_timelocked_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=False, - wait_for_finalization=False, + netuid=netuid, + mechid=mechid, + uids=uids, + weights=weights, + block_time=block_time, ) # Asserts - 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_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) + mocked_get_mechanism_storage_index.assert_called_once_with( + netuid=netuid, mechid=mechid + ) + mocked_get_encrypted_commit.assert_called_once_with( + uids=uids, + weights=weights, + subnet_reveal_period_epochs=mocked_get_subnet_hyperparameters.return_value.commit_reveal_period, + version_key=weights_module.version_as_int, + tempo=mocked_get_subnet_hyperparameters.return_value.tempo, + netuid=mocked_get_mechanism_storage_index.return_value, + current_block=mocked_get_current_block.return_value, + block_time=block_time, + hotkey=fake_wallet.hotkey.public_key, ) mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="set_weights", + call_function="commit_timelocked_mechanism_weights", call_params={ - "dests": mocker_converter_normalize.return_value[0], - "weights": mocker_converter_normalize.return_value[1], - "netuid": fake_netuid, - "version_key": version_as_int, + "netuid": netuid, + "mecid": mechid, + "commit": mocked_get_encrypted_commit.return_value[0], + "reveal_round": mocked_get_encrypted_commit.return_value[1], + "commit_reveal_version": 4, }, ) - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, + mocked_sign_and_send_extrinsic.assert_awaited_once_with( wallet=fake_wallet, - wait_for_finalization=False, - wait_for_inclusion=False, - period=8, - use_nonce=True, + call=mocked_compose_call.return_value, nonce_key="hotkey", sign_with="hotkey", + use_nonce=True, + period=None, raise_error=False, - calling_function="set_weights_extrinsic", + wait_for_inclusion=True, + wait_for_finalization=True, ) - assert result is True - assert message == "Not waiting for finalization or inclusion." + assert result == mocked_sign_and_send_extrinsic.return_value @pytest.mark.asyncio -async def test_set_weights_extrinsic_failure(subtensor, fake_wallet, mocker): - """Tests set_weights_extrinsic when setting weights fails.""" +async def test_reveal_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `reveal_weights_extrinsic` extrinsic.""" # Preps - fake_netuid = 1 - fake_uids = [1, 2, 3] - fake_weights = [0.1, 0.2, 0.7] + fake_wallet.hotkey.ss58_address = "hotkey" - 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, + netuid = mocker.Mock() + mechid = mocker.Mock() + uids = [] + weights = [] + salt = [] + + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + weights_module, "convert_and_normalize_weights_and_uids", - return_value=(mocker.Mock(), mocker.Mock()), + return_value=(uids, 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=ExtrinsicResponse(False, "Test error message"), + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call - result, message = await async_weights.set_weights_extrinsic( + result = await weights_module.reveal_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=True, - wait_for_finalization=True, + netuid=netuid, + mechid=mechid, + uids=uids, + weights=weights, + salt=salt, + version_key=weights_module.version_as_int, ) # Asserts - 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_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="set_weights", + call_function="reveal_mechanism_weights", call_params={ - "dests": mocker_converter_normalize.return_value[0], - "weights": mocker_converter_normalize.return_value[1], - "netuid": fake_netuid, - "version_key": version_as_int, + "netuid": netuid, + "mecid": mechid, + "uids": mocked_convert_and_normalize_weights_and_uids.return_value[0], + "values": mocked_convert_and_normalize_weights_and_uids.return_value[0], + "salt": salt, + "version_key": weights_module.version_as_int, }, ) - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, + mocked_sign_and_send_extrinsic.assert_awaited_once_with( wallet=fake_wallet, - wait_for_finalization=True, - wait_for_inclusion=True, - period=8, - use_nonce=True, + call=mocked_compose_call.return_value, nonce_key="hotkey", sign_with="hotkey", - raise_error=False, - calling_function="set_weights_extrinsic", - ) - assert result is False - assert message == "Test error message" - - -@pytest.mark.asyncio -async def test_set_weights_extrinsic_exception(subtensor, fake_wallet, mocker): - """Tests set_weights_extrinsic when an exception is raised.""" - # Preps - fake_netuid = 1 - fake_uids = [1, 2, 3] - fake_weights = [0.1, 0.2, 0.7] - - 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()), - ) - - mocker.patch.object(subtensor.substrate, "compose_call") - mocker.patch.object( - subtensor, "sign_and_send_extrinsic", side_effect=Exception("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 -async def test_commit_weights_extrinsic_success(subtensor, fake_wallet, mocker): - """Tests commit_weights_extrinsic when the commit is successful.""" - # Preps - fake_netuid = 1 - fake_commit_hash = "test_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=ExtrinsicResponse(True, "") - ) - - # Call - result, message = await async_weights.commit_weights_extrinsic( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - commit_hash=fake_commit_hash, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - # Asserts - 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, - wait_for_inclusion=True, - wait_for_finalization=True, use_nonce=True, period=None, raise_error=False, - nonce_key="hotkey", - sign_with="hotkey", - calling_function="commit_weights_extrinsic", + wait_for_inclusion=True, + wait_for_finalization=True, ) - assert result is True - assert message == "" + assert result == mocked_sign_and_send_extrinsic.return_value @pytest.mark.asyncio -async def test_commit_weights_extrinsic_failure(subtensor, fake_wallet, mocker): - """Tests commit_weights_extrinsic when the commit fails.""" +async def test_set_weights_extrinsic(mocker, subtensor, fake_wallet): + """Verify that the `set_weights_extrinsic` function works as expected.""" # Preps - fake_netuid = 1 - fake_commit_hash = "test_hash" + fake_wallet.hotkey.ss58_address = "hotkey" + + netuid = mocker.Mock() + mechid = mocker.Mock() + uids = [] + weights = [] + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + weights_module, + "convert_and_normalize_weights_and_uids", + return_value=(uids, 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=ExtrinsicResponse(False, "Commit failed."), + return_value=ExtrinsicResponse( + True, + "", + ), ) # Call - result, message = await async_weights.commit_weights_extrinsic( + result = await weights_module.set_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, - netuid=fake_netuid, - commit_hash=fake_commit_hash, - wait_for_inclusion=True, - wait_for_finalization=True, + netuid=netuid, + mechid=mechid, + uids=uids, + weights=weights, + version_key=weights_module.version_as_int, ) # Asserts - mocked_compose_call.assert_called_once_with( + mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) + mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="commit_weights", - call_params={"netuid": fake_netuid, "commit_hash": fake_commit_hash}, + call_function="set_mechanism_weights", + call_params={ + "netuid": netuid, + "mecid": mechid, + "dests": uids, + "weights": weights, + "version_key": weights_module.version_as_int, + }, ) - mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mocked_compose_call.return_value, + mocked_sign_and_send_extrinsic.assert_awaited_once_with( wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, + call=mocked_compose_call.return_value, + nonce_key="hotkey", + sign_with="hotkey", use_nonce=True, period=None, raise_error=False, - nonce_key="hotkey", - sign_with="hotkey", - calling_function="commit_weights_extrinsic", + wait_for_inclusion=True, + wait_for_finalization=True, ) - assert result is False - assert message == "Commit failed." + assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_mechanisms.py b/tests/unit_tests/extrinsics/test_weights.py similarity index 76% rename from tests/unit_tests/extrinsics/test_mechanisms.py rename to tests/unit_tests/extrinsics/test_weights.py index b4b416a632..a052d69bb5 100644 --- a/tests/unit_tests/extrinsics/test_mechanisms.py +++ b/tests/unit_tests/extrinsics/test_weights.py @@ -1,10 +1,9 @@ -import pytest -from bittensor.core.extrinsics import mechanism +from bittensor.core.extrinsics import weights as weights_module from bittensor.core.types import ExtrinsicResponse -def test_commit_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `commit_mechanism_weights_extrinsic` extrinsic.""" +def test_commit_timelocked_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_timelocked_weights_extrinsic` extrinsic.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" @@ -12,53 +11,71 @@ def test_commit_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): mechid = mocker.Mock() uids = [] weights = [] - salt = [] + block_time = mocker.Mock() - mocked_unlock_key = mocker.patch.object( - mechanism, "unlock_key", return_value=mocker.Mock(success=True) + mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( + weights_module, + "convert_and_normalize_weights_and_uids", + return_value=(uids, weights), + ) + mocked_get_current_block = mocker.patch.object(subtensor, "get_current_block") + mocked_get_subnet_hyperparameters = mocker.patch.object( + subtensor, "get_subnet_hyperparameters" ) mocked_get_sub_subnet_storage_index = mocker.patch.object( - mechanism, "get_mechid_storage_index" + weights_module, "get_mechid_storage_index" + ) + mocked_get_encrypted_commit = mocker.patch.object( + weights_module, + "get_encrypted_commit", + return_value=(mocker.Mock(), mocker.Mock()), ) - mocked_generate_weight_hash = mocker.patch.object(mechanism, "generate_weight_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=ExtrinsicResponse(True, "") + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse( + True, + f"reveal_round:{mocked_get_encrypted_commit.return_value[1]}", + ), ) # Call - result = mechanism.commit_mechanism_weights_extrinsic( + result = weights_module.commit_timelocked_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, mechid=mechid, uids=uids, weights=weights, - salt=salt, + block_time=block_time, ) # Asserts - mocked_unlock_key.assert_called_once_with( - wallet=fake_wallet, raise_error=False, unlock_type="hotkey" - ) + mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_get_sub_subnet_storage_index.assert_called_once_with( netuid=netuid, mechid=mechid ) - mocked_generate_weight_hash.assert_called_once_with( - address=fake_wallet.hotkey.ss58_address, + mocked_get_encrypted_commit.assert_called_once_with( + uids=uids, + weights=weights, + subnet_reveal_period_epochs=mocked_get_subnet_hyperparameters.return_value.commit_reveal_period, + version_key=weights_module.version_as_int, + tempo=mocked_get_subnet_hyperparameters.return_value.tempo, netuid=mocked_get_sub_subnet_storage_index.return_value, - uids=list(uids), - values=list(weights), - salt=salt, - version_key=mechanism.version_as_int, + current_block=mocked_get_current_block.return_value, + block_time=block_time, + hotkey=fake_wallet.hotkey.public_key, ) mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", - call_function="commit_mechanism_weights", + call_function="commit_timelocked_mechanism_weights", call_params={ "netuid": netuid, "mecid": mechid, - "commit_hash": mocked_generate_weight_hash.return_value, + "commit": mocked_get_encrypted_commit.return_value[0], + "reveal_round": mocked_get_encrypted_commit.return_value[1], + "commit_reveal_version": 4, }, ) mocked_sign_and_send_extrinsic.assert_called_once_with( @@ -75,8 +92,8 @@ def test_commit_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): assert result == mocked_sign_and_send_extrinsic.return_value -def test_commit_timelocked_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `commit_mechanism_weights_extrinsic` extrinsic.""" +def test_commit_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `commit_weights_extrinsic` extrinsic.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" @@ -84,77 +101,49 @@ def test_commit_timelocked_mechanism_weights_extrinsic(mocker, subtensor, fake_w mechid = mocker.Mock() uids = [] weights = [] - block_time = mocker.Mock() + salt = [] - mocked_unlock_key = mocker.patch.object( - mechanism, "unlock_key", return_value=mocker.Mock(success=True) - ) - mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - mechanism, - "convert_and_normalize_weights_and_uids", - return_value=(uids, weights), - ) - mocked_get_current_block = mocker.patch.object(subtensor, "get_current_block") - mocked_get_subnet_hyperparameters = mocker.patch.object( - subtensor, "get_subnet_hyperparameters" - ) mocked_get_sub_subnet_storage_index = mocker.patch.object( - mechanism, "get_mechid_storage_index" + weights_module, "get_mechid_storage_index" ) - mocked_get_encrypted_commit = mocker.patch.object( - mechanism, - "get_encrypted_commit", - return_value=(mocker.Mock(), mocker.Mock()), + mocked_generate_weight_hash = mocker.patch.object( + weights_module, "generate_weight_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=ExtrinsicResponse( - True, - f"reveal_round:{mocked_get_encrypted_commit.return_value[1]}", - ), + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) # Call - result = mechanism.commit_timelocked_mechanism_weights_extrinsic( + result = weights_module.commit_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, mechid=mechid, uids=uids, weights=weights, - block_time=block_time, + salt=salt, ) # Asserts - mocked_unlock_key.assert_called_once_with( - wallet=fake_wallet, raise_error=False, unlock_type="hotkey" - ) - mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_get_sub_subnet_storage_index.assert_called_once_with( netuid=netuid, mechid=mechid ) - mocked_get_encrypted_commit.assert_called_once_with( - uids=uids, - weights=weights, - subnet_reveal_period_epochs=mocked_get_subnet_hyperparameters.return_value.commit_reveal_period, - version_key=mechanism.version_as_int, - tempo=mocked_get_subnet_hyperparameters.return_value.tempo, + mocked_generate_weight_hash.assert_called_once_with( + address=fake_wallet.hotkey.ss58_address, netuid=mocked_get_sub_subnet_storage_index.return_value, - current_block=mocked_get_current_block.return_value, - block_time=block_time, - hotkey=fake_wallet.hotkey.public_key, + uids=list(uids), + values=list(weights), + salt=salt, + version_key=weights_module.version_as_int, ) mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", - call_function="commit_timelocked_mechanism_weights", + call_function="commit_mechanism_weights", call_params={ "netuid": netuid, "mecid": mechid, - "commit": mocked_get_encrypted_commit.return_value[0], - "reveal_round": mocked_get_encrypted_commit.return_value[1], - "commit_reveal_version": 4, + "commit_hash": mocked_generate_weight_hash.return_value, }, ) mocked_sign_and_send_extrinsic.assert_called_once_with( @@ -171,8 +160,8 @@ def test_commit_timelocked_mechanism_weights_extrinsic(mocker, subtensor, fake_w assert result == mocked_sign_and_send_extrinsic.return_value -def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): - """Test successful `reveal_mechanism_weights_extrinsic` extrinsic.""" +def test_reveal_weights_extrinsic(mocker, subtensor, fake_wallet): + """Test successful `reveal_weights_extrinsic` extrinsic.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" @@ -182,11 +171,8 @@ def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): weights = [] salt = [] - mocked_unlock_key = mocker.patch.object( - mechanism, "unlock_key", return_value=mocker.Mock(success=True) - ) mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - mechanism, + weights_module, "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) @@ -196,7 +182,7 @@ def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): ) # Call - result = mechanism.reveal_mechanism_weights_extrinsic( + result = weights_module.reveal_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, @@ -204,13 +190,10 @@ def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): uids=uids, weights=weights, salt=salt, - version_key=mechanism.version_as_int, + version_key=weights_module.version_as_int, ) # Asserts - mocked_unlock_key.assert_called_once_with( - wallet=fake_wallet, raise_error=False, unlock_type="hotkey" - ) mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", @@ -221,7 +204,7 @@ def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): "uids": mocked_convert_and_normalize_weights_and_uids.return_value[0], "values": mocked_convert_and_normalize_weights_and_uids.return_value[0], "salt": salt, - "version_key": mechanism.version_as_int, + "version_key": weights_module.version_as_int, }, ) mocked_sign_and_send_extrinsic.assert_called_once_with( @@ -238,8 +221,8 @@ def test_reveal_mechanism_weights_extrinsic(mocker, subtensor, fake_wallet): assert result == mocked_sign_and_send_extrinsic.return_value -def test_mechanism_sub_weights_extrinsic(mocker, subtensor, fake_wallet): - """Verify that the `set_mechanism_weights_extrinsic` function works as expected.""" +def test_set_weights_extrinsic(mocker, subtensor, fake_wallet): + """Verify that the `set_weights_extrinsic` function works as expected.""" # Preps fake_wallet.hotkey.ss58_address = "hotkey" @@ -248,11 +231,8 @@ def test_mechanism_sub_weights_extrinsic(mocker, subtensor, fake_wallet): uids = [] weights = [] - mocked_unlock_key = mocker.patch.object( - mechanism, "unlock_key", return_value=mocker.Mock(success=True) - ) mocked_convert_and_normalize_weights_and_uids = mocker.patch.object( - mechanism, + weights_module, "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) @@ -267,20 +247,17 @@ def test_mechanism_sub_weights_extrinsic(mocker, subtensor, fake_wallet): ) # Call - result = mechanism.set_mechanism_weights_extrinsic( + result = weights_module.set_weights_extrinsic( subtensor=subtensor, wallet=fake_wallet, netuid=netuid, mechid=mechid, uids=uids, weights=weights, - version_key=mechanism.version_as_int, + version_key=weights_module.version_as_int, ) # Asserts - mocked_unlock_key.assert_called_once_with( - wallet=fake_wallet, raise_error=False, unlock_type="hotkey" - ) mocked_convert_and_normalize_weights_and_uids.assert_called_once_with(uids, weights) mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", @@ -290,7 +267,7 @@ def test_mechanism_sub_weights_extrinsic(mocker, subtensor, fake_wallet): "mecid": mechid, "dests": uids, "weights": weights, - "version_key": mechanism.version_as_int, + "version_key": weights_module.version_as_int, }, ) mocked_sign_and_send_extrinsic.assert_called_once_with( diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 354e466e3b..31020ec84c 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2760,194 +2760,6 @@ async def test_set_delegate_take_decrease( ) -@pytest.mark.asyncio -async def test_set_weights_success(subtensor, fake_wallet, mocker): - """Tests set_weights with the successful weight setting on the first try.""" - # Preps - fake_netuid = 1 - fake_uids = [1, 2, 3] - fake_weights = [0.3, 0.5, 0.2] - max_retries = 1 - - mocked_get_uid_for_hotkey_on_subnet = mocker.patch.object( - subtensor, "get_uid_for_hotkey_on_subnet" - ) - subtensor.get_uid_for_hotkey_on_subnet = mocked_get_uid_for_hotkey_on_subnet - - mocked_blocks_since_last_update = mocker.AsyncMock(return_value=2) - subtensor.blocks_since_last_update = mocked_blocks_since_last_update - - mocked_weights_rate_limit = mocker.AsyncMock(return_value=1) - subtensor.weights_rate_limit = mocked_weights_rate_limit - - mocked_set_weights_extrinsic = mocker.AsyncMock( - return_value=ExtrinsicResponse(True, "Success") - ) - mocker.patch.object( - async_subtensor, "set_mechanism_weights_extrinsic", mocked_set_weights_extrinsic - ) - - # Call - result, message = await subtensor.set_weights( - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - max_retries=max_retries, - ) - - # Asserts - mocked_get_uid_for_hotkey_on_subnet.assert_called_once_with( - fake_wallet.hotkey.ss58_address, fake_netuid - ) - mocked_blocks_since_last_update.assert_called_once_with( - fake_netuid, mocked_get_uid_for_hotkey_on_subnet.return_value - ) - mocked_weights_rate_limit.assert_called_once_with(fake_netuid) - mocked_set_weights_extrinsic.assert_called_once_with( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - version_key=async_subtensor.version_as_int, - wait_for_finalization=True, - wait_for_inclusion=True, - weights=fake_weights, - period=8, - mechid=0, - raise_error=False, - ) - mocked_weights_rate_limit.assert_called_once_with(fake_netuid) - assert result is True - assert message == "Success" - - -@pytest.mark.asyncio -async def test_set_weights_with_exception(subtensor, fake_wallet, mocker): - """Tests set_weights when set_weights_extrinsic raises an exception.""" - # Preps - fake_netuid = 1 - fake_uids = [1, 2, 3] - fake_weights = [0.3, 0.5, 0.2] - fake_uid = 10 - max_retries = 1 - - mocked_get_uid_for_hotkey_on_subnet = mocker.AsyncMock(return_value=fake_uid) - subtensor.get_uid_for_hotkey_on_subnet = mocked_get_uid_for_hotkey_on_subnet - - mocked_blocks_since_last_update = mocker.AsyncMock(return_value=10) - subtensor.blocks_since_last_update = mocked_blocks_since_last_update - - mocked_weights_rate_limit = mocker.AsyncMock(return_value=5) - subtensor.weights_rate_limit = mocked_weights_rate_limit - - mocked_set_weights_extrinsic = mocker.AsyncMock( - side_effect=Exception("Test exception") - ) - mocker.patch.object( - async_subtensor, "set_mechanism_weights_extrinsic", mocked_set_weights_extrinsic - ) - - # Call - result, message = await subtensor.set_weights( - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - max_retries=max_retries, - ) - - # Asserts - assert mocked_get_uid_for_hotkey_on_subnet.call_count == 1 - assert mocked_blocks_since_last_update.call_count == 1 - assert mocked_weights_rate_limit.call_count == 1 - assert mocked_set_weights_extrinsic.call_count == max_retries - assert result is False - assert message == "No attempt made. Perhaps it is too soon to set weights!" - - -@pytest.mark.asyncio -async def test_commit_weights_success(subtensor, fake_wallet, mocker): - """Tests commit_weights when the weights are committed successfully.""" - # Preps - fake_netuid = 1 - fake_salt = [12345, 67890] - fake_uids = [1, 2, 3] - fake_weights = [100, 200, 300] - max_retries = 3 - - mocked_commit_weights_extrinsic = mocker.AsyncMock( - return_value=ExtrinsicResponse(True, "Success") - ) - mocker.patch.object( - async_subtensor, - "commit_mechanism_weights_extrinsic", - mocked_commit_weights_extrinsic, - ) - - # Call - result, message = await subtensor.commit_weights( - wallet=fake_wallet, - netuid=fake_netuid, - salt=fake_salt, - uids=fake_uids, - weights=fake_weights, - max_retries=max_retries, - ) - - # Asserts - mocked_commit_weights_extrinsic.assert_called_once_with( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - salt=fake_salt, - uids=fake_uids, - weights=fake_weights, - wait_for_inclusion=False, - wait_for_finalization=False, - period=16, - mechid=0, - raise_error=True, - ) - assert result is True - assert message == "Success" - - -@pytest.mark.asyncio -async def test_commit_weights_with_exception(subtensor, fake_wallet, mocker): - """Tests commit_weights when an exception is raised during weight commitment.""" - # Preps - fake_netuid = 1 - fake_salt = [12345, 67890] - fake_uids = [1, 2, 3] - fake_weights = [100, 200, 300] - max_retries = 1 - - mocked_commit_weights_extrinsic = mocker.AsyncMock( - side_effect=Exception("Test exception") - ) - mocker.patch.object( - async_subtensor, - "commit_mechanism_weights_extrinsic", - mocked_commit_weights_extrinsic, - ) - - # Call - result, message = await subtensor.commit_weights( - wallet=fake_wallet, - netuid=fake_netuid, - salt=fake_salt, - uids=fake_uids, - weights=fake_weights, - max_retries=max_retries, - ) - - # Asserts - assert mocked_commit_weights_extrinsic.call_count == max_retries - assert result is False - assert "No attempt made. Perhaps it is too soon to commit weights!" in message - - @pytest.mark.asyncio async def test_get_all_subnets_info_success(mocker, subtensor): """Test get_all_subnets_info returns correct data when subnet information is found.""" diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 534247f3dd..34ff218220 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1150,69 +1150,6 @@ def test_is_hotkey_registered_with_netuid(subtensor, mocker): assert result == mocked_is_hotkey_registered_on_subnet.return_value -def test_set_weights(subtensor, mocker, fake_wallet): - """Successful set_weights call.""" - # Preps - fake_netuid = 1 - fake_uids = [2, 4] - fake_weights = [0.4, 0.6] - fake_wait_for_inclusion = False - fake_wait_for_finalization = False - fake_max_retries = 5 - - expected_result = ExtrinsicResponse(True, None) - - mocked_get_uid_for_hotkey_on_subnet = mocker.MagicMock() - subtensor.get_uid_for_hotkey_on_subnet = mocked_get_uid_for_hotkey_on_subnet - - mocked_blocks_since_last_update = mocker.MagicMock(return_value=2) - subtensor.blocks_since_last_update = mocked_blocks_since_last_update - - mocked_weights_rate_limit = mocker.MagicMock(return_value=1) - subtensor.weights_rate_limit = mocked_weights_rate_limit - - mocked_set_weights_extrinsic = mocker.patch.object( - subtensor_module, - "set_mechanism_weights_extrinsic", - return_value=expected_result, - ) - - # Call - result = subtensor.set_weights( - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - version_key=settings.version_as_int, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - max_retries=fake_max_retries, - ) - - # Asserts - mocked_get_uid_for_hotkey_on_subnet.assert_called_once_with( - fake_wallet.hotkey.ss58_address, fake_netuid - ) - mocked_blocks_since_last_update.assert_called_with( - fake_netuid, mocked_get_uid_for_hotkey_on_subnet.return_value - ) - mocked_weights_rate_limit.assert_called_with(fake_netuid) - mocked_set_weights_extrinsic.assert_called_with( - subtensor=subtensor, - wallet=fake_wallet, - netuid=fake_netuid, - uids=fake_uids, - weights=fake_weights, - version_key=settings.version_as_int, - wait_for_inclusion=fake_wait_for_inclusion, - wait_for_finalization=fake_wait_for_finalization, - period=8, - mechid=0, - raise_error=False, - ) - assert result == expected_result - - def test_serve_axon(subtensor, mocker): """Tests successful serve_axon call.""" # Prep @@ -1834,54 +1771,6 @@ def test_get_existential_deposit(subtensor, mocker): assert result == Balance.from_rao(value) -def test_commit_weights(subtensor, fake_wallet, mocker): - """Successful commit_weights call.""" - # Preps - netuid = 1 - salt = [1, 3] - uids = [2, 4] - weights = [0.4, 0.6] - wait_for_inclusion = False - wait_for_finalization = False - max_retries = 5 - - expected_result = ExtrinsicResponse(True, None) - mocked_commit_weights_extrinsic = mocker.patch.object( - subtensor_module, - "commit_mechanism_weights_extrinsic", - return_value=expected_result, - ) - - # Call - result = subtensor.commit_weights( - wallet=fake_wallet, - netuid=netuid, - salt=salt, - uids=uids, - weights=weights, - version_key=settings.version_as_int, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - max_retries=max_retries, - ) - - # Asserts - mocked_commit_weights_extrinsic.assert_called_once_with( - subtensor=subtensor, - wallet=fake_wallet, - netuid=netuid, - salt=salt, - uids=uids, - weights=weights, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=16, - mechid=0, - raise_error=True, - ) - assert result == expected_result - - def test_reveal_weights(subtensor, fake_wallet, mocker): """Successful test_reveal_weights call.""" # Preps @@ -1892,7 +1781,7 @@ def test_reveal_weights(subtensor, fake_wallet, mocker): expected_result = ExtrinsicResponse(True, None) mocked_extrinsic = mocker.patch.object( subtensor_module, - "reveal_mechanism_weights_extrinsic", + "reveal_weights_extrinsic", return_value=expected_result, ) @@ -1933,9 +1822,7 @@ def test_reveal_weights_false(subtensor, fake_wallet, mocker): weights = [0.1, 0.2, 0.3, 0.4] salt = [4, 2, 2, 1] - mocked_extrinsic = mocker.patch.object( - subtensor_module, "reveal_mechanism_weights_extrinsic" - ) + mocked_extrinsic = mocker.patch.object(subtensor_module, "reveal_weights_extrinsic") # Call result = subtensor.reveal_weights( @@ -3055,7 +2942,7 @@ def test_set_weights_with_commit_reveal_enabled(subtensor, fake_wallet, mocker): subtensor, "commit_reveal_enabled", return_value=True ) mocked_commit_timelocked_mechanism_weights_extrinsic = mocker.patch.object( - subtensor_module, "commit_timelocked_mechanism_weights_extrinsic" + subtensor_module, "commit_timelocked_weights_extrinsic" ) mocked_commit_timelocked_mechanism_weights_extrinsic.return_value = ( ExtrinsicResponse( From 609999e32e4e60b17ce8a4c6d8bd8d5eb7b5f076 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 09:56:52 -0700 Subject: [PATCH 230/416] Update MIGRATION.md --- migration.md => MIGRATION.md | 64 +++++++++++------------------------- 1 file changed, 20 insertions(+), 44 deletions(-) rename migration.md => MIGRATION.md (86%) diff --git a/migration.md b/MIGRATION.md similarity index 86% rename from migration.md rename to MIGRATION.md index 15e40bd39c..6594516cd9 100644 --- a/migration.md +++ b/MIGRATION.md @@ -2,45 +2,6 @@ ## 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, - raise_error: bool = True, - ) -> 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_swapping: bool = False, - period: Optional[int] = None, - raise_error: bool = True, - 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. @@ -150,7 +111,6 @@ It must include: # 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` @@ -171,11 +131,10 @@ These parameters will now exist in all extrinsics and related calls (default val ```py period: Optional[int] = None, raise_error: bool = False, -wait_for_inclusion: bool = False, -wait_for_finalization: bool = False, +wait_for_inclusion: bool = True, +wait_for_finalization: bool = True, ``` - [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` @@ -231,7 +190,24 @@ wait_for_finalization: bool = False, All extrinsics and related subtensor calls now return an object of class `bittensor.core.types.ExtrinsicResponse` Additional changes in extrinsics: - - `commit_reveal_extrinsic` and `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the `{"reveal_round": reveal_round}`. + - `commit_timelocked_mechanism_weights_extrinsic` and `subtensor.set_weights` (with `commit_reveal_enabled=True`), the response is the usual `ExtrinsicResponse` (`success` and `message` unchanged), plus field `data` holds the `{"reveal_round": reveal_round}`. - in positive case, all extrinsics return `response.message = "Success"` - `root_register_extrinsic`, `subtensor.burned_register` (with netuid=0) and `subtensor.root_register` has response `ExtrinsicResponse`. In successful case `.data` holds the `{"uid": uid}` where is `uid` is uid of registered neuron. - `subtensor.sign_and_send_extrinsic` has updated arguments order. The list of arguments is the same. + +Removing deprecated extrinsics and replacing them with consistent ones: +- `commit_reveal_extrinsic` (without mechanisms support) + related tests +- `bittensor.core.extrinsics.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_timelocked_weights_extrinsic` +- `bittensor.core.extrinsics.asyncex.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_timelocked_weights_extrinsic` + +- `commit_weights_extrinsic`(without mechanisms support) + related tests +- `bittensor.core.extrinsics.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_weights_extrinsic` +- `bittensor.core.extrinsics.asyncex.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_weights_extrinsic` + +- `reveal_weights_extrinsic`(without mechanisms support) + related tests +- `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` +- `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` + +- `set_weights_extrinsic`(without mechanisms support) + related tests +- `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` +- `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` \ No newline at end of file From 54c9a52bf2115315daf1cf448b9567d20a1e1fc8 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 12:55:44 -0700 Subject: [PATCH 231/416] docstrings update + small refactoring and improvement --- MIGRATION.md | 11 +- bittensor/core/async_subtensor.py | 566 +++++++------- bittensor/core/axon.py | 234 +++--- bittensor/core/chain_data/__init__.py | 52 +- bittensor/core/chain_data/axon_info.py | 14 +- bittensor/core/chain_data/delegate_info.py | 25 +- .../core/chain_data/delegate_info_lite.py | 18 +- bittensor/core/chain_data/dynamic_info.py | 8 +- bittensor/core/chain_data/info_base.py | 12 +- bittensor/core/chain_data/ip_info.py | 6 +- bittensor/core/chain_data/neuron_info.py | 60 +- bittensor/core/chain_data/neuron_info_lite.py | 42 +- bittensor/core/chain_data/prometheus_info.py | 10 +- .../chain_data/scheduled_coldkey_swap_info.py | 6 +- bittensor/core/chain_data/stake_info.py | 6 +- .../core/chain_data/subnet_hyperparameters.py | 66 +- bittensor/core/chain_data/subnet_state.py | 4 +- bittensor/core/chain_data/utils.py | 38 +- .../core/chain_data/weight_commit_info.py | 8 +- bittensor/core/config.py | 7 +- bittensor/core/dendrite.py | 188 +++-- bittensor/core/errors.py | 96 +-- bittensor/core/extrinsics/asyncex/root.py | 12 +- bittensor/core/extrinsics/asyncex/utils.py | 2 +- bittensor/core/extrinsics/root.py | 10 +- bittensor/core/extrinsics/utils.py | 6 +- bittensor/core/metagraph.py | 174 ++--- bittensor/core/stream.py | 45 +- bittensor/core/subtensor.py | 706 +++++++++--------- bittensor/core/subtensor_api/__init__.py | 12 +- bittensor/core/synapse.py | 113 ++- bittensor/core/tensor.py | 26 +- bittensor/core/threadpool.py | 5 +- bittensor/core/timelock.py | 6 +- bittensor/core/types.py | 16 +- bittensor/utils/__init__.py | 57 +- bittensor/utils/axon_utils.py | 22 +- bittensor/utils/balance.py | 23 +- bittensor/utils/btlogging/format.py | 28 +- bittensor/utils/btlogging/helpers.py | 6 +- bittensor/utils/btlogging/loggingmachine.py | 30 +- bittensor/utils/easy_imports.py | 20 +- bittensor/utils/liquidity.py | 2 +- bittensor/utils/networking.py | 25 +- bittensor/utils/registration/__init__.py | 16 +- bittensor/utils/registration/async_pow.py | 104 ++- bittensor/utils/registration/pow.py | 187 +++-- bittensor/utils/registration/register_cuda.py | 23 +- bittensor/utils/subnets.py | 8 +- bittensor/utils/substrate_utils/storage.py | 4 +- bittensor/utils/version.py | 14 +- bittensor/utils/weight_utils.py | 111 ++- 52 files changed, 1561 insertions(+), 1729 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 6594516cd9..b5876a5768 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -210,4 +210,13 @@ Removing deprecated extrinsics and replacing them with consistent ones: - `set_weights_extrinsic`(without mechanisms support) + related tests - `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` -- `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` \ No newline at end of file +- `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` + + +### Subtensor changes +- method `all_subnets` has renamed parameter from `block_number` to `block`. +- method `query_subtensor` has updated parameters order. +- method `query_module` has updated parameters order. +- method `query_map_subtensor` has updated parameters order. +- method `query_map` has updated parameters order. +- \ No newline at end of file diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 0723a2fb4f..df3086308d 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -155,22 +155,18 @@ def __init__( ): """Initializes an AsyncSubtensor instance for blockchain interaction. - Arguments: + Parameters: network: The network name or type to connect to (e.g., "finney", "test"). If ``None``, uses the default network from config. config: Configuration object for the AsyncSubtensor instance. If ``None``, uses the default configuration. - log_verbose: Enables or disables verbose logging. Defaults to ``False``. + log_verbose: Enables or disables verbose logging. fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. - Defaults to ``None``. - retry_forever: Whether to retry forever on connection errors. Defaults to ``False``. - _mock: Whether this is a mock instance. Mainly for testing purposes. Defaults to ``False``. + retry_forever: Whether to retry forever on connection errors. + _mock: Whether this is a mock instance. Mainly for testing purposes. archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases where you are requesting a block that is too old for your current (presumably lite) node. - Defaults to ``None``. websocket_shutdown_timer: Amount of time, in seconds, to wait after the last response from the chain to - close the connection. Defaults to ``5.0``. - Returns: - None + close the connection. Raises: ConnectionError: If unable to connect to the specified network. @@ -367,7 +363,7 @@ async def encode_params( This method takes a call definition (which specifies parameter types) and actual parameter values, then encodes them into a hex string that can be used for blockchain transactions. - Arguments: + Parameters: call_definition: A dictionary containing parameter type definitions. Should have a "params" key with a list of parameter definitions. params: The actual parameter values to encode. Can be either a list (for positional parameters) or a @@ -426,12 +422,11 @@ async def get_hyperparameter( This method queries the blockchain for subnet-specific hyperparameters such as difficulty, tempo, immunity period, and other network configuration values. - Arguments: + Parameters: param_name: The name of the hyperparameter to retrieve (e.g., "Difficulty", "Tempo", "ImmunityPeriod"). netuid: The unique identifier of the subnet. - block: The block number at which to retrieve the hyperparameter. Do not specify if using block_hash or - reuse_block. - block_hash: The hash of the blockchain block for the query. Do not specify if using block or reuse_block. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: @@ -474,17 +469,15 @@ def _get_substrate( ) -> Union[AsyncSubstrateInterface, RetryAsyncSubstrate]: """Creates the Substrate instance based on provided arguments. - This internal method creates either a standard AsyncSubstrateInterface or a RetryAsyncSubstrate depending on - the configuration parameters. + This internal method creates either a standard AsyncSubstrateInterface or a RetryAsyncSubstrate depending on the + configuration parameters. - Arguments: + Parameters: fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. - Defaults to ``None``. - retry_forever: Whether to retry forever on connection errors. Defaults to ``False``. - _mock: Whether this is a mock instance. Mainly for testing purposes. Defaults to ``False``. + retry_forever: Whether to retry forever on connection errors. + _mock: Whether this is a mock instance. Mainly for testing purposes. archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in - cases where you are requesting a block that is too old for your current (presumably lite) node. Defaults - to ``None``. + cases where you are requesting a block that is too old for your current (presumably lite) node. ws_shutdown_timer: Amount of time, in seconds, to wait after the last response from the chain to close the connection. @@ -531,17 +524,15 @@ async def query_constant( inflation rates, consensus rules, or validation thresholds, providing a deeper understanding of the Bittensor network's operational parameters. - Arguments: + Parameters: module_name: The name of the module containing the constant (e.g., "Balances", "SubtensorModule"). constant_name: The name of the constant to retrieve (e.g., "ExistentialDeposit"). - block: The blockchain block number at which to query the constant. Do not specify if using block_hash or - reuse_block. - block_hash: The hash of the blockchain block at which to query the constant. Do not specify if using - block or reuse_block. - reuse_block: Whether to reuse the blockchain block at which to query the constant. Defaults to ``False``. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - Optional[async_substrate_interface.types.ScaleObj]: The value of the constant if found, ``None`` otherwise. + The value of the constant if found, ``None`` otherwise. Example: # Get existential deposit constant @@ -569,25 +560,23 @@ async def query_map( self, module: str, name: str, + params: Optional[list] = None, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - params: Optional[list] = None, ) -> "AsyncQueryMapResult": """Queries map storage from any module on the Bittensor blockchain. This function retrieves data structures that represent key-value mappings, essential for accessing complex and structured data within the blockchain modules. - Arguments: + Parameters: module: The name of the module from which to query the map storage (e.g., "SubtensorModule", "System"). name: The specific storage function within the module to query (e.g., "Bonds", "Weights"). - block: The blockchain block number at which to perform the query. Defaults to ``None`` (latest block). - 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. Defaults to - ``False``. params: Parameters to be passed to the query. Defaults to ``None``. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: AsyncQueryMapResult: A data structure representing the map storage if found, None otherwise. @@ -612,22 +601,21 @@ async def query_map( async def query_map_subtensor( self, name: str, + params: Optional[list] = None, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - params: Optional[list] = None, ) -> "AsyncQueryMapResult": """Queries map storage from the Subtensor module on the Bittensor blockchain. This function is designed to retrieve a map-like data structure, which can include various neuron-specific details or network-wide attributes. - Arguments: + Parameters: name: The name of the map storage function to query. - block: The blockchain block number at which to perform the query. - 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. params: A list of parameters to pass to the query function. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: An object containing the map-like data structure, or ``None`` if not found. @@ -648,23 +636,22 @@ async def query_module( self, module: str, name: str, + params: Optional[list] = None, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - params: Optional[list] = None, ) -> Optional[Union["ScaleObj", Any]]: """Queries any module storage on the Bittensor blockchain with the specified parameters and block number. This function is a generic query interface that allows for flexible and diverse data retrieval from various blockchain modules. - Arguments: + Parameters: module: The name of the module from which to query data. name: The name of the storage function within the module. - block: The blockchain block number at which to perform the query. - 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. params: A list of parameters to pass to the query function. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: An object containing the requested data if found, ``None`` otherwise. @@ -694,13 +681,12 @@ async def query_runtime_api( and retrieve data encoded in Scale Bytes format. This function is essential for advanced users who need to interact with specific runtime methods and decode complex data types. - Arguments: + Parameters: runtime_api: The name of the runtime API to query. method: The specific method within the runtime API to call. params: The parameters to pass to the method call. - block: the block number for this query. Do not specify if using block_hash or reuse_block. - block_hash: The hash of the blockchain block number at which to perform the query. Do not specify if using - block or reuse_block. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: @@ -720,22 +706,21 @@ async def query_runtime_api( async def query_subtensor( self, name: str, + params: Optional[list] = None, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - params: Optional[list] = None, ) -> Optional[Union["ScaleObj", Any]]: """Queries named storage from the Subtensor module on the Bittensor blockchain. This function is used to retrieve specific data or parameters from the blockchain, such as stake, rank, or other neuron-specific attributes. - Arguments: + Parameters: name: The name of the storage function to query. - block: The blockchain block number at which to perform the query. - 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. params: A list of parameters to pass to the query function. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: query_response: An object containing the requested data. @@ -763,16 +748,15 @@ async def state_call( """Makes a state call to the Bittensor blockchain, allowing for direct queries of the blockchain's state. This function is typically used for advanced queries that require specific method calls and data inputs. - Arguments: + Parameters: method: The method name for the state call. data: The data to be passed to the method. - block: The blockchain block number at which to perform the state call. - 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. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - result (dict[Any, Any]): The result of the rpc call. + The result of the rpc call. The state call function provides a more direct and flexible way of querying blockchain data, useful for specific use cases where standard queries are insufficient. @@ -794,19 +778,17 @@ async def block(self): async def all_subnets( self, - block_number: Optional[int] = None, + block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, ) -> Optional[list[DynamicInfo]]: """Queries the blockchain for comprehensive information about all subnets, including their dynamic parameters and operational status. - Arguments: - block_number: The block number to query the subnet information from. Do not specify if using block_hash or - reuse_block. - block_hash: The hash of the blockchain block number for the query. Do not specify if using reuse_block or - block. - reuse_block: Whether to reuse the last-used blockchain block hash. Do not set if using block_hash or block. + Parameters: + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: Optional[list[DynamicInfo]]: A list of DynamicInfo objects, each containing detailed information about a @@ -817,7 +799,7 @@ async def all_subnets( subnets = await subtensor.all_subnets() """ block_hash = await self.determine_block_hash( - block=block_number, block_hash=block_hash, reuse_block=reuse_block + block=block, block_hash=block_hash, reuse_block=reuse_block ) if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash @@ -838,7 +820,7 @@ async def all_subnets( ) else: logging.warning( - f"Unable to fetch subnet prices for block {block_number}, block hash {block_hash}: {subnet_prices}" + f"Unable to fetch subnet prices for block {block}, block hash {block_hash}: {subnet_prices}" ) return DynamicInfo.list_from_dicts(decoded) @@ -852,12 +834,11 @@ async def blocks_since_last_step( """Queries the blockchain to determine how many blocks have passed since the last epoch step for a specific subnet. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. - block: The block number for this query. Do not specify if using block_hash or reuse_block. - block_hash: The hash of the blockchain block number for the query. Do not specify if using reuse_block or - block. - reuse_block: Whether to reuse the last-used blockchain block hash. Do not set if using block_hash or block. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: The number of blocks since the last step in the subnet, or None if the query fails. @@ -881,12 +862,12 @@ async def blocks_since_last_step( async def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int]: """Returns the number of blocks since the last update, or ``None`` if the subnetwork or UID does not exist. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. uid: The unique identifier of the neuron. Returns: - Optional[int]: The number of blocks since the last update, or None if the subnetwork or UID does not exist. + The number of blocks since the last update, or None if the subnetwork or UID does not exist. Example: # Get blocks since last update for UID 5 in subnet 1 @@ -958,14 +939,14 @@ async def commit_reveal_enabled( The commit reveal feature is designed to solve the weight-copying problem by giving would-be weight-copiers access only to stale weights. Copying stale weights should result in subnet validators departing from consensus. - Arguments: + Parameters: netuid: The unique identifier of the subnet for which to check the commit-reveal mechanism. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - bool: True if commit-reveal mechanism is enabled, False otherwise. + True if commit-reveal mechanism is enabled, False otherwise. Example: # Check if commit-reveal is enabled for subnet 1 @@ -1000,15 +981,14 @@ async def difficulty( computational effort required for validating transactions and participating in the network's consensus mechanism. - Arguments: + Parameters: netuid: The unique identifier of the subnet. - block: The block number for the query. Do not specify if using block_hash or reuse_block. - 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. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - Optional[int]: The value of the 'Difficulty' hyperparameter if the subnet exists, None otherwise. + The value of the 'Difficulty' hyperparameter if the subnet exists, None otherwise. Example: # Get difficulty for subnet 1 @@ -1042,15 +1022,14 @@ async def does_hotkey_exist( This method queries the SubtensorModule's Owner storage function to determine if the hotkey is registered. - Arguments: + Parameters: hotkey_ss58: The SS58 address of the hotkey. - block: The block number for this query. Do not specify if using block_hash or reuse_block. - block_hash: The hash of the block number to check the hotkey against. Do not specify if using reuse_block - or block. - reuse_block: Whether to reuse the last-used blockchain hash. Do not set if using block_hash or block. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - bool: True if the hotkey is known by the chain and there are accounts, False otherwise. + True if the hotkey is known by the chain and there are accounts, False otherwise. Example: # Check if hotkey exists @@ -1084,10 +1063,9 @@ async def get_admin_freeze_window( """ Returns the number of blocks when dependent transactions will be frozen for execution. - Arguments: - block: The block number at which to retrieve the hyperparameter. Do not specify if using block_hash or - reuse_block. - block_hash: The hash of the blockchain block for the query. Do not specify if using block or reuse_block. + Parameters: + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: @@ -1113,13 +1091,13 @@ async def get_all_subnets_info( This function provides comprehensive data on each subnet, including its characteristics and operational parameters. - Arguments: - block: The block number for the query. - block_hash: The block hash for the query. - reuse_block: Whether to reuse the last-used block hash. + Parameters: + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - list[SubnetInfo]: A list of SubnetInfo objects, each containing detailed information about a subnet. + A list of SubnetInfo objects, each containing detailed information about a subnet. Example: # Get all subnet information @@ -1214,11 +1192,11 @@ async def get_balance( This method queries the System module's Account storage to get the current balance of a coldkey address. The balance represents the amount of TAO tokens held by the specified address. - Arguments: + Parameters: address: The coldkey address in SS58 format. - block: The block number for the query. - block_hash: The block hash for the query. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: Balance: The balance object containing the account's TAO balance. @@ -1254,14 +1232,14 @@ async def get_balances( mapping each address to its corresponding balance. This is more efficient than calling get_balance multiple times. - Arguments: + Parameters: *addresses: Variable number of coldkey addresses in SS58 format. - block: The block number for the query. - block_hash: The block hash for the query. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - dict[str, Balance]: A dictionary mapping each address to its Balance object. + A dictionary mapping each address to its Balance object. Example: # Get balances for multiple addresses @@ -1325,7 +1303,7 @@ async def get_block_hash(self, block: Optional[int] = None) -> str: to each block's data. It is crucial for verifying transactions, ensuring data consistency, and maintaining the trustworthiness of the blockchain. - Arguments: + Parameters: block: The block number for which the hash is to be retrieved. If ``None``, returns the latest block hash. Returns: @@ -1359,12 +1337,12 @@ async def get_parents( """This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys storage function to get the children and formats them before returning as a tuple. - Arguments: + Parameters: hotkey: The child hotkey SS58. netuid: The netuid value. - block: The block number for which the children are to be retrieved. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: A list of formatted parents [(proportion, parent)] @@ -1402,16 +1380,16 @@ async def get_children( returning as a tuple. It provides information about the child neurons that a validator has set for weight distribution. - Arguments: + Parameters: hotkey: The hotkey value. netuid: The netuid value. - block: The block number for which the children are to be retrieved. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - tuple[bool, list[tuple[float, str]], str]: A tuple containing a boolean indicating success or failure, a - list of formatted children with their proportions, and an error message (if applicable). + A tuple containing a boolean indicating success or failure, a list of formatted children with their + proportions, and an error message (if applicable). Example: # Get children for a hotkey in subnet 1 @@ -1459,7 +1437,7 @@ async def get_children_pending( This method queries the SubtensorModule's PendingChildKeys storage function to get children that are pending approval or in a cooldown period. These are children that have been proposed but not yet finalized. - Arguments: + Parameters: hotkey: The hotkey value. netuid: The netuid value. block: The block number for which the children are to be retrieved. @@ -1508,16 +1486,15 @@ async def get_commitment( This method retrieves the commitment data that a neuron has published to the blockchain. Commitments are used in the commit-reveal mechanism for secure weight setting and other network operations. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. uid: The unique identifier of the neuron. - block: The block number to retrieve the commitment from. If None, the latest block is used. - Default is None. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - str: The commitment data as a string. + The commitment data as a string. Example: # Get commitment for UID 5 in subnet 1 @@ -1555,12 +1532,12 @@ async def get_last_commitment_bonds_reset_block( """ Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. uid: The unique identifier of the neuron. Returns: - Optional[int]: The block number when the bonds were last reset, or None if not found. + The block number when the bonds were last reset, or None if not found. """ metagraph = await self.metagraph(netuid) @@ -1589,15 +1566,14 @@ async def get_all_commitments( This method retrieves all commitment data for all neurons in a specific subnet. This is useful for analyzing the commit-reveal patterns across an entire subnet. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. - block: The block number to retrieve the commitment from. If None, the latest block is used. - Default is None. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - dict[str, str]: A mapping of the ss58:commitment with the commitment as a string. + A mapping of the ss58:commitment with the commitment as a string. Example: # Get all commitments for subnet 1 @@ -1635,15 +1611,15 @@ async def get_revealed_commitment_by_hotkey( ) -> Optional[tuple[tuple[int, str], ...]]: """Returns hotkey related revealed commitment for a given netuid. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. - block: The block number to retrieve the commitment from. Default is ``None``. hotkey_ss58_address: The ss58 address of the committee member. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - result (tuple[int, str): A tuple of reveal block and commitment message. + A tuple of reveal block and commitment message. """ if not is_valid_ss58_address(address=hotkey_ss58_address): raise ValueError(f"Invalid ss58 address {hotkey_ss58_address} provided.") @@ -1668,13 +1644,13 @@ async def get_revealed_commitment( ) -> Optional[tuple[tuple[int, str], ...]]: """Returns uid related revealed commitment for a given netuid. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. uid: The neuron uid to retrieve the commitment from. block: The block number to retrieve the commitment from. Default is ``None``. Returns: - result (Optional[tuple[int, str]]: A tuple of reveal block and commitment message. + A tuple of reveal block and commitment message. Example of result: ( (12, "Alice message 1"), (152, "Alice message 2") ) @@ -1702,11 +1678,11 @@ async def get_all_revealed_commitments( ) -> dict[str, tuple[tuple[int, str], ...]]: """Returns all revealed commitments for a given netuid. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. - block: The block number to retrieve the commitment from. Default is ``None``. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: result: A dictionary of all revealed commitments in view {ss58_address: (reveal block, commitment message)}. @@ -1745,11 +1721,11 @@ async def get_current_weight_commit_info( """ Retrieves CRV3 weight commit information for a specific subnet. - Arguments: + Parameters: netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. Default is ``None``. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: A list of commit details, where each item contains: @@ -1786,11 +1762,11 @@ async def get_current_weight_commit_info_v2( """ Retrieves CRV3 weight commit information for a specific subnet. - Arguments: + Parameters: netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. Default is ``None``. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: A list of commit details, where each item contains: @@ -1824,14 +1800,14 @@ async def get_delegate_by_hotkey( Retrieves detailed information about a delegate neuron based on its hotkey. This function provides a comprehensive view of the delegate's status, including its stakes, nominators, and reward distribution. - Arguments: + Parameters: hotkey_ss58: The ``SS58`` address of the delegate's hotkey. - block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - Optional[DelegateInfo]: Detailed information about the delegate neuron, ``None`` if not found. + Detailed information about the delegate neuron, ``None`` if not found. This function is essential for understanding the roles and influence of delegate neurons within the Bittensor network's consensus and governance structures. @@ -1860,14 +1836,13 @@ async def get_delegate_identities( """ Fetches delegates identities from the chain. - Arguments: - block: The blockchain block number for the query. - block_hash: the hash of the blockchain block for the query - reuse_block: Whether to reuse the last-used blockchain block hash. + Parameters: + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: Dict {ss58: ChainIdentity, ...} - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) identities = await self.substrate.query_map( @@ -1895,11 +1870,11 @@ async def get_delegate_take( Retrieves 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: hotkey_ss58: The ``SS58`` address of the neuron's hotkey. - block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: float: The delegate take percentage. @@ -1928,11 +1903,11 @@ async def get_delegated( Retrieves a list of delegates and their associated stakes for a given coldkey. This function identifies the delegates that a specific account has staked tokens on. - Arguments: + Parameters: coldkey_ss58: The ``SS58`` address of the account's coldkey. - block: The blockchain block number for the query. - block_hash: The hash of the blockchain block number for the query. - reuse_block: Whether to reuse the last-used blockchain block hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: A list containing the delegated information for the specified coldkey. @@ -1964,10 +1939,10 @@ async def get_delegates( """ Fetches all delegates on the chain - Arguments: - block: The blockchain block number for the query. - block_hash: hash of the blockchain block number for the query. - reuse_block: whether to reuse the last-used block hash. + Parameters: + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: List of DelegateInfo objects, or an empty list if there are no delegates. @@ -1996,7 +1971,7 @@ async def get_existential_deposit( The existential deposit is the minimum amount of TAO required for an account to exist on the blockchain. Accounts with balances below this threshold can be reaped to conserve network resources. - Arguments: + Parameters: block: The blockchain block number for the query. block_hash: Block hash at which to query the deposit amount. If ``None``, the current block is used. reuse_block: Whether to reuse the last-used blockchain block hash. @@ -2032,7 +2007,7 @@ async def get_hotkey_owner( This function queries the blockchain for the owner of the provided hotkey. If the hotkey does not exist at the specified block hash, it returns None. - Arguments: + Parameters: hotkey_ss58: The SS58 address of the hotkey. block: The blockchain block number for the query. block_hash: The hash of the block at which to check the hotkey ownership. @@ -2091,7 +2066,7 @@ async def get_metagraph_info( including detailed information on all the nodes (neurons) such as subnet validator stakes and subnet weights in the subnet. Metagraph aids in calculating emissions. - Arguments: + Parameters: netuid: The unique identifier of the subnet to query. field_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. If not provided, all available fields will be returned. @@ -2203,7 +2178,7 @@ async def get_netuids_for_hotkey( Retrieves a list of subnet UIDs (netuids) for which a given hotkey is a member. This function identifies the specific subnets within the Bittensor network where the neuron associated with the hotkey is active. - Arguments: + Parameters: hotkey_ss58: The ``SS58`` address of the neuron's hotkey. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number at which to perform the query. @@ -2236,10 +2211,10 @@ async def get_neuron_certificate( reuse_block: bool = False, ) -> Optional[Certificate]: """ - Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a - specified subnet (netuid) of the Bittensor network. + Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a specified + subnet (netuid) of the Bittensor network. - Arguments: + Parameters: hotkey: The hotkey to query. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. @@ -2281,7 +2256,7 @@ async def get_all_neuron_certificates( """ Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. block_hash: The hash of the block to retrieve the parameter from. Do not specify if using block or @@ -2318,13 +2293,12 @@ async def get_liquidity_list( Retrieves all liquidity positions for the given wallet on a specified subnet (netuid). Calculates associated fee rewards based on current global and tick-level fee data. - Arguments: + Parameters: wallet: Wallet instance to fetch positions for. netuid: Subnet unique id. - block: The blockchain block number for the query. - 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. + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. Returns: List of liquidity positions, or None if subnet does not exist. @@ -2503,7 +2477,7 @@ async def get_neuron_for_pubkey_and_subnet( (netuid). This function provides detailed neuron information for a particular subnet within the Bittensor network. - Arguments: + Parameters: hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. @@ -2511,8 +2485,7 @@ async def get_neuron_for_pubkey_and_subnet( reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - Optional[bittensor.core.chain_data.neuron_info.NeuronInfo]: Detailed information about the neuron if found, - ``None`` otherwise. + Detailed information about the neuron if found, ``None`` otherwise. This function is crucial for accessing specific neuron data and understanding its status, stake, and other attributes within a particular subnet of the Bittensor ecosystem. @@ -2548,7 +2521,7 @@ async def get_next_epoch_start_block( If ``block`` is not provided, the current chain block will be used. Epochs are determined based on the subnet's tempo (i.e., blocks per epoch). The result is the block number at which the next epoch will begin. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The reference block to calculate from. If None, uses the current chain block height. block_hash: The blockchain block number at which to perform the query. @@ -2583,14 +2556,14 @@ async def get_owned_hotkeys( """ Retrieves all hotkeys owned by a specific coldkey address. - Arguments: + Parameters: coldkey_ss58: The SS58 address of the coldkey to query. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number for the query. reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - list[str]: A list of hotkey SS58 addresses owned by the coldkey. + A list of hotkey SS58 addresses owned by the coldkey. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) owned_hotkeys = await self.substrate.query( @@ -2615,7 +2588,7 @@ async def get_stake( """ Returns the stake under a coldkey - hotkey pairing. - Arguments: + Parameters: hotkey_ss58: The SS58 address of the hotkey. coldkey_ss58: The SS58 address of the coldkey. netuid: The subnet ID. @@ -2674,7 +2647,7 @@ async def get_stake_add_fee( """ Calculates the fee for adding new stake to a hotkey. - Arguments: + Parameters: amount: Amount of stake to add in TAO netuid: Netuid of subnet coldkey_ss58: SS58 address of source coldkey @@ -2757,12 +2730,11 @@ async def get_subnet_info( Retrieves detailed information about subnet within the Bittensor network. This function provides comprehensive data on subnet, including its characteristics and operational parameters. - Arguments: + Parameters: netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the stake 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. + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. Returns: SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet. @@ -2822,11 +2794,10 @@ async def get_subnet_prices( ) -> dict[int, Balance]: """Gets the current Alpha price in TAO for a specified subnet. - Arguments: - block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the stake 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. + Parameters: + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. Returns: dict: @@ -2909,7 +2880,7 @@ async def get_unstake_fee( """ Calculates the fee for unstaking from a hotkey. - Arguments: + Parameters: amount: Amount of stake to unstake in TAO netuid: Netuid of subnet coldkey_ss58: SS58 address of source coldkey @@ -2938,7 +2909,7 @@ async def get_stake_movement_fee( """ Calculates the fee for moving stake between hotkeys/subnets/coldkeys. - Arguments: + Parameters: amount: Amount of stake to move in TAO origin_netuid: Netuid of source subnet origin_hotkey_ss58: SS58 address of source hotkey @@ -2967,14 +2938,13 @@ async def get_stake_for_coldkey_and_hotkey( """ Retrieves all coldkey-hotkey pairing stake across specified (or all) subnets - Arguments: + Parameters: coldkey_ss58: The SS58 address of the coldkey. hotkey_ss58: The SS58 address of the hotkey. netuids: The subnet IDs to query for. Set to ``None`` for all subnets. - block: The block number at which to query the stake information. - block_hash: The hash of the block to retrieve the stake 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. + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. Returns: A {netuid: StakeInfo} pairing of all stakes across all subnets. @@ -3014,7 +2984,7 @@ async def get_stake_for_coldkey( """ Retrieves the stake information for a given coldkey. - Arguments: + Parameters: coldkey_ss58: The SS58 address of the coldkey. block: The block number at which to query the stake information. block_hash: The hash of the blockchain block number for the query. @@ -3051,15 +3021,12 @@ async def get_stake_for_hotkey( """ Retrieves the stake information for a given hotkey. - Arguments: + Parameters: hotkey_ss58: The SS58 address of the hotkey. netuid: The subnet ID to query for. - block: The block number at which to query the stake information. Do not specify if also specifying - block_hash or reuse_block. - block_hash: The hash of the blockchain block number for the query. Do not specify if also specifying block - or reuse_block. - reuse_block: Whether to reuse for this query the last-used block. Do not specify if also specifying block - or block_hash. + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. """ hotkey_alpha_query = await self.query_subtensor( name="TotalHotkeyAlpha", @@ -3084,15 +3051,12 @@ async def get_stake_operations_fee( ): """Returns fee for any stake operation in specified subnet. - Args: + Parameters: amount: Amount of stake to add in Alpha/TAO. netuid: Netuid of subnet. - block: The block number at which to query the stake information. Do not specify if also specifying - block_hash or reuse_block. - block_hash: The hash of the blockchain block number for the query. Do not specify if also specifying block - or reuse_block. - reuse_block: Whether to reuse for this query the last-used block. Do not specify if also specifying block - or block_hash. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: The calculated stake fee as a Balance object. @@ -3118,13 +3082,11 @@ async def get_stake_weight( """ Retrieves the stake weight for all hotkeys in a given subnet. - Arguments: + Parameters: netuid: Netuid of subnet. - block: Block number at which to perform the calculation. - block_hash: The hash of the blockchain block number for the query. Do not specify if also specifying block - or reuse_block. - reuse_block: Whether to reuse for this query the last-used block. Do not specify if also specifying block - or block_hash. + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. Returns: A list of stake weights for all hotkeys in the specified subnet. @@ -3150,7 +3112,7 @@ async def get_subnet_burn_cost( Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the amount of Tao that needs to be locked or burned to establish a new - Arguments: + Parameters: block: The blockchain block number for the query. block_hash: The blockchain block_hash of the block id. reuse_block: Whether to reuse the last-used block hash. @@ -3185,7 +3147,7 @@ async def get_subnet_hyperparameters( Retrieves the hyperparameters for a specific subnet within the Bittensor network. These hyperparameters define the operational settings and rules governing the subnet's behavior. - Arguments: + Parameters: netuid: The network UID of the subnet to query. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number for the query. @@ -3229,7 +3191,7 @@ async def get_subnets( """ Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network. - Arguments: + Parameters: block: The blockchain block number for the query. block_hash: The hash of the block to retrieve the subnet unique identifiers from. reuse_block: Whether to reuse the last-used block hash. @@ -3263,13 +3225,13 @@ async def get_total_subnets( """ Retrieves the total number of subnets within the Bittensor network as of a specific blockchain block. - Arguments: + Parameters: block: The blockchain block number for the query. block_hash: The blockchain block_hash representation of block id. reuse_block: Whether to reuse the last-used block hash. Returns: - Optional[str]: The total number of subnets in the network. + The total number of subnets in the network. Understanding the total number of subnets is essential for assessing the network's growth and the extent of its decentralized infrastructure. @@ -3292,7 +3254,7 @@ async def get_transfer_fee( function simulates the transfer to estimate the associated cost, taking into account the current network conditions and transaction complexity. - Arguments: + Parameters: wallet: The wallet from which the transfer is initiated. dest: The ``SS58`` address of the destination account. value: The amount of tokens to be transferred, specified as a Balance object, or in Tao (float) or Rao @@ -3340,7 +3302,7 @@ async def get_vote_data( Retrieves the voting data for a specific proposal on the Bittensor blockchain. This data includes information about how senate members have voted on the proposal. - Arguments: + Parameters: proposal_hash: The hash of the proposal for which voting data is requested. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number to query the voting data. @@ -3377,7 +3339,7 @@ async def get_uid_for_hotkey_on_subnet( """ Retrieves the unique identifier (UID) for a neuron's hotkey on a specific subnet. - Arguments: + Parameters: hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. @@ -3385,7 +3347,7 @@ async def get_uid_for_hotkey_on_subnet( reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - Optional[int]: The UID of the neuron if it is registered on the subnet, ``None`` otherwise. + The UID of the neuron if it is registered on the subnet, ``None`` otherwise. The UID is a critical identifier within the network, linking the neuron's hotkey to its operational and governance activities on a particular subnet. @@ -3412,7 +3374,7 @@ async def filter_netuids_by_registered_hotkeys( """ Filters a given list of all netuids for certain specified netuids and hotkeys - Arguments: + Parameters: all_netuids: A list of netuids to filter. filter_for_netuids: A subset of all_netuids to filter from the main list. all_hotkeys: Hotkeys to filter from the main list. @@ -3469,14 +3431,14 @@ async def immunity_period( Retrieves the 'ImmunityPeriod' hyperparameter for a specific subnet. This parameter defines the duration during which new neurons are protected from certain network penalties or restrictions. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. block_hash: The blockchain block_hash representation of the block id. reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - Optional[int]: The value of the 'ImmunityPeriod' hyperparameter if the subnet exists, ``None`` otherwise. + The value of the 'ImmunityPeriod' hyperparameter if the subnet exists, ``None`` otherwise. The 'ImmunityPeriod' is a critical aspect of the network's governance system, ensuring that new participants have a grace period to establish themselves and contribute to the network without facing immediate punitive @@ -3550,7 +3512,7 @@ async def is_hotkey_delegate( Determines whether a given hotkey (public key) is a delegate on the Bittensor network. This function checks if the neuron associated with the hotkey is part of the network's delegation system. - Arguments: + Parameters: hotkey_ss58: The SS58 address of the neuron's hotkey. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number for the query. @@ -3581,15 +3543,12 @@ async def is_hotkey_registered( any subnet or specifically on a specified subnet. This function checks the registration status of a neuron identified by its hotkey, which is crucial for validating its participation and activities within the network. - Arguments: + Parameters: hotkey_ss58: The SS58 address of the neuron's hotkey. - netuid: The unique identifier of the subnet to check the registration. If ``None``, the - registration is checked across all subnets. - block: The blockchain block number at which to perform the query. - block_hash: The blockchain block_hash representation of the block id. Do not specify if using block or - reuse_block. - reuse_block: Whether to reuse the last-used blockchain block hash. Do not set if using block_hash or - reuse_block. + netuid: The unique identifier of the subnet to check the registration. + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. Returns: bool: ``True`` if the hotkey is registered in the specified context (either any subnet or a specific subnet), @@ -3618,7 +3577,7 @@ async def is_hotkey_registered_any( """ Checks if a neuron's hotkey is registered on any subnet within the Bittensor network. - Arguments: + Parameters: hotkey_ss58: The ``SS58`` address of the neuron's hotkey. block: The blockchain block number for the query. block_hash: The blockchain block_hash representation of block id. @@ -3663,7 +3622,7 @@ async def is_subnet_active( ) -> bool: """Verify if subnet with provided netuid is active. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. block_hash: The blockchain block_hash representation of block id. @@ -3705,15 +3664,15 @@ async def max_weight_limit( """ Returns network MaxWeightsLimit hyperparameter. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. block_hash: The blockchain block_hash representation of block id. reuse_block: Whether to reuse the last-used block hash. Returns: - Optional[float]: The value of the MaxWeightsLimit hyperparameter, or ``None`` if the subnetwork does not - exist or the parameter is not found. + The value of the MaxWeightsLimit hyperparameter, or ``None`` if the subnetwork does not exist or the + parameter is not found. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) call = await self.get_hyperparameter( @@ -3770,15 +3729,15 @@ async def min_allowed_weights( """ Returns network MinAllowedWeights hyperparameter. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. block_hash: The blockchain block_hash representation of block id. reuse_block: Whether to reuse the last-used block hash. Returns: - Optional[int]: The value of the MinAllowedWeights hyperparameter, or ``None`` if the subnetwork does not - exist or the parameter is not found. + The value of the MinAllowedWeights hyperparameter, or ``None`` if the subnetwork does not exist or the + parameter is not found. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) call = await self.get_hyperparameter( @@ -3802,7 +3761,7 @@ async def neuron_for_uid( specified subnet (netuid) of the Bittensor network. This function provides a comprehensive view of a neuron's attributes, including its stake, rank, and operational status. - Arguments: + Parameters: uid: The unique identifier of the neuron. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. @@ -3844,7 +3803,7 @@ async def neurons( This function provides a snapshot of the subnet's neuron population, including each neuron's attributes and network interactions. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number for the query. @@ -3882,7 +3841,7 @@ async def neurons_lite( This function provides a streamlined view of the neurons, focusing on key attributes such as stake and network participation. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number for the query. @@ -3920,9 +3879,8 @@ async def query_identity( detailed identity information about a specific neuron, which is a crucial aspect of the network's decentralized identity and governance system. - Arguments: - coldkey_ss58: The coldkey used to query the neuron's identity (technically the neuron's coldkey SS58 - address). + Parameters: + coldkey_ss58: Coldkey used to query the neuron's identity (technically the neuron's coldkey SS58 address). block: The blockchain block number for the query. block_hash: The hash of the blockchain block number at which to perform the query. reuse_block: Whether to reuse the last-used blockchain block hash. @@ -3970,14 +3928,14 @@ async def recycle( Retrieves the 'Burn' hyperparameter for a specified subnet. The 'Burn' parameter represents the amount of Tao that is effectively recycled within the Bittensor network. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number for the query. reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - Optional[Balance]: The value of the 'Burn' hyperparameter if the subnet exists, ``None`` otherwise. + The value of the 'Burn' hyperparameter if the subnet exists, ``None`` otherwise. Understanding the 'Burn' rate is essential for analyzing the network registration usage, particularly how it is correlated with user activity and the overall cost of participation in a given subnet. @@ -4001,14 +3959,14 @@ async def subnet( """ Retrieves the subnet information for a single subnet in the Bittensor network. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The block number to get the subnets at. block_hash: The hash of the blockchain block number for the query. reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - Optional[DynamicInfo]: A DynamicInfo object, containing detailed information about a subnet. + A DynamicInfo object, containing detailed information about a subnet. """ block_hash = await self.determine_block_hash( block=block, block_hash=block_hash, reuse_block=reuse_block @@ -4046,7 +4004,7 @@ async def subnet_exists( """ Checks if a subnet with the specified unique identifier (netuid) exists within the Bittensor network. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number at which to check the subnet existence. @@ -4078,15 +4036,15 @@ async def subnetwork_n( """ Returns network SubnetworkN hyperparameter. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number at which to check the subnet existence. reuse_block: Whether to reuse the last-used block hash. Returns: - Optional[int]: The value of the SubnetworkN hyperparameter, or ``None`` if the subnetwork does not exist or - the parameter is not found. + The value of the SubnetworkN hyperparameter, or ``None`` if the subnetwork does not exist or the parameter + is not found. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) call = await self.get_hyperparameter( @@ -4107,15 +4065,15 @@ async def tempo( """ Returns network Tempo hyperparameter. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number at which to check the subnet existence. reuse_block: Whether to reuse the last-used block hash. Returns: - Optional[int]: The value of the Tempo hyperparameter, or ``None`` if the subnetwork does not exist or the - parameter is not found. + The value of the Tempo hyperparameter, or ``None`` if the subnetwork does not exist or the parameter is not + found. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) call = await self.get_hyperparameter( @@ -4136,13 +4094,13 @@ async def tx_rate_limit( Retrieves the transaction rate limit for the Bittensor network as of a specific blockchain block. This rate limit sets the maximum number of transactions that can be processed within a given time frame. - Arguments: + Parameters: block: The blockchain block number for the query. block_hash: The hash of the blockchain block number at which to check the subnet existence. reuse_block: Whether to reuse the last-used block hash. Returns: - Optional[int]: The transaction rate limit of the network, ``None`` if not available. + The transaction rate limit of the network, ``None`` if not available. The transaction rate limit is an essential parameter for ensuring the stability and scalability of the Bittensor network. It helps in managing network load and preventing congestion, thereby maintaining efficient and timely @@ -4154,15 +4112,15 @@ async def tx_rate_limit( ) return getattr(result, "value", None) - async def wait_for_block(self, block: Optional[int] = None): + async def wait_for_block(self, block: Optional[int] = None) -> bool: """ Waits until a specific block is reached on the chain. If no block is specified, waits for the next block. - Arguments: + Parameters: block: The block number to wait for. If ``None``, waits for the next block. Returns: - bool: ``True`` if the target block was reached, ``False`` if timeout occurred. + ``True`` if the target block was reached, ``False`` if timeout occurred. Example: import bittensor as bt @@ -4207,12 +4165,12 @@ async def weights( This function maps each neuron's UID to the weights it assigns to other neurons, reflecting the network's trust and value assignment mechanisms. - Arguments: - netuid: The network UID of the subnet to query. - block: Block number for synchronization, or `None` for the latest block. - block_hash: The hash of the blockchain block for the query. - reuse_block: reuse the last-used blockchain block hash. - mechid: Subnet mechanism identifier. + Parameters: + netuid: Subnet unique identifier. + mechid: Subnet mechanism unique identifier. + block: The blockchain block number for the query. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: A list of tuples mapping each neuron's UID to its assigned weights. @@ -4246,7 +4204,7 @@ async def weights_rate_limit( """ Returns network WeightsSetRateLimit hyperparameter. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. block_hash: The blockchain block_hash representation of the block id. @@ -4274,12 +4232,10 @@ async def get_timestamp( """ Retrieves the datetime timestamp for a given block. - Arguments: - block: The blockchain block number for the query. Do not specify if specifying block_hash or reuse_block. - block_hash: The blockchain block_hash representation of the block id. Do not specify if specifying block - or reuse_block. - reuse_block: Whether to reuse the last-used blockchain block hash. Do not specify if specifying block or - block_hash. + Parameters: + block: The blockchain block number for the query. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: datetime object for the timestamp of the block. @@ -4303,7 +4259,7 @@ async def get_subnet_owner_hotkey( This function queries the subtensor network to fetch the hotkey of the owner of a subnet specified by its netuid. If no data is found or the query fails, the function returns None. - Arguments: + Parameters: netuid: The network UID of the subnet to fetch the owner's hotkey for. block: The specific block number to query the data from. @@ -4320,7 +4276,7 @@ async def get_subnet_validator_permits( """ Retrieves the list of validator permits for a given subnet as boolean values. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. @@ -4801,7 +4757,7 @@ async def move_stake( """ Moves stake to a different hotkey and/or subnet. - Arguments: + 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. diff --git a/bittensor/core/axon.py b/bittensor/core/axon.py index 76ecc14a4b..ad46604ee7 100644 --- a/bittensor/core/axon.py +++ b/bittensor/core/axon.py @@ -100,9 +100,9 @@ class FastAPIThreadedServer(uvicorn.Server): # do something self.fast_server.stop() - Args: - should_exit (bool): Flag to indicate whether the server should stop running. - is_running (bool): Flag to indicate whether the server is currently running. + Parameters: + should_exit: Flag to indicate whether the server should stop running. + is_running: Flag to indicate whether the server is currently running. The server overrides the default signal handlers to prevent interference with the main application flow and provides methods to start and stop the server in a controlled manner. @@ -274,17 +274,17 @@ def prioritize_my_synapse( synapse: MySynapse ) -> float: subtensor = ... ).start() - Args: - wallet (Optional[bittensor_wallet.Wallet]): Wallet with hotkey and coldkeypub. - config (Optional[bittensor.core.config.Config]): Configuration parameters for the axon. - port (Optional[int]): Port for server binding. - ip (Optional[str]): Binding IP address. - external_ip (Optional[str]): External IP address to broadcast. - external_port (Optional[int]): External port to broadcast. - max_workers (Optional[int]): Number of active threads for request handling. + Parameters: + wallet: Wallet with hotkey and coldkeypub. + config: Configuration parameters for the axon. + port: Port for server binding. + ip: Binding IP address. + external_ip: External IP address to broadcast. + external_port: External port to broadcast. + max_workers: Number of active threads for request handling. Returns: - bittensor.core.axon.Axon: An instance of the axon class configured as per the provided arguments. + An instance of the axon class configured as per the provided arguments. Note: This class is a core part of Bittensor's decentralized network for machine intelligence, @@ -325,15 +325,15 @@ def __init__( ): """Creates a new bittensor.Axon object from passed arguments. - Args: - config (:obj:`Optional[bittensor.core.config.Config]`): bittensor.Axon.config() - wallet (:obj:`Optional[bittensor_wallet.Wallet]`): bittensor wallet with hotkey and coldkeypub. - port (:type:`Optional[int]`): Binding port. - ip (:type:`Optional[str]`): Binding ip. - external_ip (:type:`Optional[str]`): The external ip of the server to broadcast to the network. - external_port (:type:`Optional[int]`): The external port of the server to broadcast to the network. - max_workers (:type:`Optional[int]`): Used to create the threadpool if not passed, specifies the number of - active threads servicing requests. + Parameters: + config: Bittensor.Axon.config() + wallet: Bittensor Wallet with hotkey and coldkeypub. + port: Binding port. + ip: Binding ip. + external_ip: The external ip of the server to broadcast to the network. + external_port: The external port of the server to broadcast to the network. + max_workers: Used to create the threadpool if not passed, specifies the number of active threads servicing + requests. """ # Build and check config. if config is None: @@ -444,18 +444,14 @@ def attach( blacklisting, and prioritizing requests. It's a key part of customizing the server's behavior and ensuring efficient and secure handling of requests within the Bittensor network. - Args: - forward_fn (Callable): Function to be called when the API endpoint is accessed. It should have at least one - argument. - blacklist_fn (Optional[Callable]): Function to filter out undesired requests. It should take the same - arguments as :func:`forward_fn` and return a boolean value. Defaults to ``None``, meaning no blacklist - filter will be used. - priority_fn (Optional[Callable]): Function to rank requests based on their priority. It should take the same - arguments as :func:`forward_fn` and return a numerical value representing the request's priority. - Defaults to ``None``, meaning no priority sorting will be applied. - verify_fn (Optional[Callable]): Function to verify requests. It should take the same arguments as - :func:`forward_fn` and return a boolean value. If ``None``, :func:`self.default_verify` function will be - used. + Parameters: + forward_fn: Function to be called when the API endpoint is accessed. It should have at least one argument. + blacklist_fn: Function to filter out undesired requests. It should take the same arguments as + :func:`forward_fn` and return a boolean value. + priority_fn: Function to rank requests based on their priority. It should take the same arguments as + :func:`forward_fn` and return a numerical value representing the request's priority. + verify_fn: Function to verify requests. It should take the same arguments as :func:`forward_fn` and return a + boolean value. If ``None``, :func:`self.default_verify` function will be used. Note: The methods :func:`forward_fn`, :func:`blacklist_fn`, :func:`priority_fn`, and :func:`verify_fn` should be @@ -468,7 +464,7 @@ def attach( AssertionError: If :func:`verify_fn` does not have the signature: ``verify( synapse: YourSynapse ) -> None``. Returns: - self: Returns the instance of the AxonServer class for potential method chaining. + Returns the instance of the AxonServer class for potential method chaining. Example Usage:: @@ -630,9 +626,9 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: Optional[str] = None) """ Adds AxonServer-specific command-line arguments to the argument parser. - Args: - parser (argparse.ArgumentParser): Argument parser to which the arguments will be added. - prefix (Optional[str]): Prefix to add to the argument names. Defaults to None. + Parameters: + parser: Argument parser to which the arguments will be added. + prefix: Prefix to add to the argument names. Defaults to None. Note: Environment variables are used to define default values for the arguments. @@ -678,7 +674,7 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: Optional[str] = None) # Exception handling for re-parsing arguments pass - async def verify_body_integrity(self, request: "Request"): + async def verify_body_integrity(self, request: "Request") -> dict: """ The ``verify_body_integrity`` method in the Bittensor framework is a key security function within the Axon server's middleware. It is responsible for ensuring the integrity of the body of incoming HTTP @@ -689,12 +685,12 @@ async def verify_body_integrity(self, request: "Request"): that the incoming request payload has not been altered or tampered with during transmission, establishing a level of trust and security between the sender and receiver in the network. - Args: - request (Request): The incoming FastAPI request object containing both headers and the request body. + Parameters: + request: The incoming FastAPI request object containing both headers and the request body. Returns: - dict: Returns the parsed body of the request as a dictionary if all the hash comparisons match, indicating - that the body is intact and has not been tampered with. + Returns the parsed body of the request as a dictionary if all the hash comparisons match, indicating that + the body is intact and has not been tampered with. Raises: JSONResponse: Raises a JSONResponse with a 400 status code if any of the hash comparisons fail, indicating @@ -741,8 +737,8 @@ def check_config(cls, config: "Config"): """ This method checks the configuration for the axon's port and wallet. - Args: - config (bittensor.core.config.Config): The config object holding axon settings. + Parameters: + config: The config object holding axon settings. Raises: AssertionError: If the axon or external ports are not in range [1024, 65535] @@ -842,19 +838,19 @@ def serve( certificate: Optional[Certificate] = None, ) -> "Axon": """ - Serves the Axon on the specified subtensor connection using the configured wallet. This method - registers the Axon with a specific subnet within the Bittensor network, identified by the ``netuid``. - It links the Axon to the broader network, allowing it to participate in the decentralized exchange - of information. + Serves the Axon on the specified subtensor connection using the configured wallet. This method registers the + Axon with a specific subnet within the Bittensor network, identified by the ``netuid``. It links the Axon to the + broader network, allowing it to participate in the decentralized exchange of information. - Args: - netuid (int): The unique identifier of the subnet to register on. This ID is essential for the Axon to + Parameters: + netuid: The unique identifier of the subnet to register on. This ID is essential for the Axon to correctly position itself within the Bittensor network topology. - subtensor (Optional[bittensor.core.subtensor.Subtensor]): The subtensor connection to use for serving. If - not provided, a new connection is established based on default configurations. + subtensor: The subtensor connection to use for serving. If not provided, a new connection is established + based on default configurations. + certificate: Neuron certificate. Returns: - bittensor.core.axon.Axon: The Axon instance that is now actively serving on the specified subtensor. + The Axon instance that is now actively serving on the specified subtensor. Example:: @@ -876,11 +872,11 @@ async def default_verify(self, synapse: "Synapse"): It ensures that the message was not tampered with and was sent by the expected sender. - The :func:`default_verify` method in the Bittensor framework is a critical security function within the - Axon server. It is designed to authenticate incoming messages by verifying their digital - signatures. This verification ensures the integrity of the message and confirms that it was - indeed sent by the claimed sender. The method plays a pivotal role in maintaining the trustworthiness - and reliability of the communication within the Bittensor network. + The :func:`default_verify` method in the Bittensor framework is a critical security function within the Axon + server. It is designed to authenticate incoming messages by verifying their digital signatures. This + verification ensures the integrity of the message and confirms that it was indeed sent by the claimed sender. + The method plays a pivotal role in maintaining the trustworthiness and reliability of the communication within + the Bittensor network. Key Features Security Assurance @@ -909,8 +905,8 @@ async def default_verify(self, synapse: "Synapse"): cornerstone of modern cryptographic security, ensuring that only entities with the correct cryptographic keys can participate in secure communication. - Args: - synapse(bittensor.core.synapse.Synapse): bittensor request synapse. + Parameters: + synapse: bittensor request synapse. Raises: Exception: If the ``receiver_hotkey`` doesn't match with ``self.receiver_hotkey``. @@ -992,9 +988,8 @@ async def default_verify(self, synapse: "Synapse"): def create_error_response(synapse: "Synapse") -> "JSONResponse": """Creates an error response based on the provided synapse object. - Args: - synapse (bittensor.core.synapse.Synapse): The synapse object containing details about the request and the - associated axon. + Parameters: + synapse: The synapse object containing details about the request and the associated axon. Returns: JSONResponse: A JSON response with a status code and content indicating the error message. @@ -1022,15 +1017,14 @@ def log_and_handle_error( """ Logs the error and updates the synapse object with the appropriate error details. - Args: - synapse (bittensor.core.synapse.Synapse): The synapse object to be updated with error information. - exception (Exception): The exception that was raised and needs to be logged and handled. - status_code (Optional[int]): The HTTP status code to be set on the synapse object. Defaults to None. - start_time (Optional[float]): The timestamp marking the start of the processing, used to calculate process time. - Defaults to None. + Parameters: + synapse: The synapse object to be updated with error information. + exception: The exception that was raised and needs to be logged and handled. + status_code: The HTTP status code to be set on the synapse object. Defaults to None. + start_time: The timestamp marking the start of the processing, used to calculate process time. Returns: - Synapse: The updated synapse object with error details. + The updated synapse object with error details. """ if isinstance(exception, SynapseException): synapse = exception.synapse or synapse @@ -1086,31 +1080,30 @@ class AxonMiddleware(BaseHTTPMiddleware): """ The `AxonMiddleware` class is a key component in the Axon server, responsible for processing all incoming requests. - It handles the essential tasks of verifying requests, executing blacklist checks, - running priority functions, and managing the logging of messages and errors. Additionally, the class - is responsible for updating the headers of the response and executing the requested functions. + It handles the essential tasks of verifying requests, executing blacklist checks, running priority functions, and + managing the logging of messages and errors. Additionally, the class is responsible for updating the headers of the + response and executing the requested functions. - This middleware acts as an intermediary layer in request handling, ensuring that each request is - processed according to the defined rules and protocols of the Bittensor network. It plays a pivotal - role in maintaining the integrity and security of the network communication. + This middleware acts as an intermediary layer in request handling, ensuring that each request is processed according + to the defined rules and protocols of the Bittensor network. It plays a pivotal role in maintaining the integrity + and security of the network communication. - Args: - app (FastAPI): An instance of the FastAPI application to which this middleware is attached. - axon (bittensor.core.axon.Axon): The Axon instance that will process the requests. + Parameters: + app: An instance of the FastAPI application to which this middleware is attached. + axon: The Axon instance that will process the requests. - The middleware operates by intercepting incoming requests, performing necessary preprocessing - (like verification and priority assessment), executing the request through the Axon's endpoints, and - then handling any postprocessing steps such as response header updating and logging. + The middleware operates by intercepting incoming requests, performing necessary preprocessing (like verification and + priority assessment), executing the request through the Axon's endpoints, and then handling any postprocessing steps + such as response header updating and logging. """ def __init__(self, app: "AxonMiddleware", axon: "Axon"): """ Initialize the AxonMiddleware class. - Args: - app (bittensor.core.axon.AxonMiddleware): An instance of the application where the middleware processor is - used. - axon (bittensor.core.axon.Axon): The axon instance used to process the requests. + Parameters: + app: An instance of the application where the middleware processor is used. + axon: The axon instance used to process the requests. """ super().__init__(app) self.axon = axon @@ -1119,13 +1112,12 @@ async def dispatch( self, request: "Request", call_next: "RequestResponseEndpoint" ) -> Response: """ - Asynchronously processes incoming HTTP requests and returns the corresponding responses. This - method acts as the central processing unit of the AxonMiddleware, handling each step in the - request lifecycle. + Asynchronously processes incoming HTTP requests and returns the corresponding responses. This method acts as the + central processing unit of the AxonMiddleware, handling each step in the request lifecycle. - Args: - request (Request): The incoming HTTP request to be processed. - call_next (RequestResponseEndpoint): A callable that processes the request and returns a response. + Parameters: + request: The incoming HTTP request to be processed. + call_next: A callable that processes the request and returns a response. Returns: Response: The HTTP response generated after processing the request. @@ -1220,15 +1212,15 @@ async def dispatch( async def preprocess(self, request: "Request") -> "Synapse": """ - Performs the initial processing of the incoming request. This method is responsible for - extracting relevant information from the request and setting up the Synapse object, which - represents the state and context of the request within the Axon server. + Performs the initial processing of the incoming request. This method is responsible for extracting relevant + information from the request and setting up the Synapse object, which represents the state and context of the + request within the Axon server. - Args: - request (Request): The incoming request to be preprocessed. + Parameters: + request: The incoming request to be preprocessed. Returns: - bittensor.core.synapse.Synapse: The Synapse object representing the preprocessed state of the request. + The Synapse object representing the preprocessed state of the request. The preprocessing involves: @@ -1288,11 +1280,11 @@ async def preprocess(self, request: "Request") -> "Synapse": async def verify(self, synapse: "Synapse"): """ - Verifies the authenticity and integrity of the request. This method ensures that the incoming - request meets the predefined security and validation criteria. + Verifies the authenticity and integrity of the request. This method ensures that the incoming request meets the + predefined security and validation criteria. - Args: - synapse (bittensor.core.synapse.Synapse): The Synapse object representing the request. + Parameters: + synapse: The Synapse object representing the request. Raises: Exception: If the verification process fails due to unmet criteria or security concerns. @@ -1345,13 +1337,12 @@ async def verify(self, synapse: "Synapse"): async def blacklist(self, synapse: "Synapse"): """ - Checks if the request should be blacklisted. This method ensures that requests from disallowed - sources or with malicious intent are blocked from processing. This can be extremely useful for - preventing spam or other forms of abuse. The blacklist is a list of keys or identifiers that - are prohibited from accessing certain resources. + Checks if the request should be blacklisted. This method ensures that requests from disallowed sources or with + malicious intent are blocked from processing. This can be extremely useful for preventing spam or other forms of + abuse. The blacklist is a list of keys or identifiers that are prohibited from accessing certain resources. - Args: - synapse (bittensor.core.synapse.Synapse): The Synapse object representing the request. + Parameters: + synapse: The Synapse object representing the request. Raises: Exception: If the request is found in the blacklist. @@ -1404,8 +1395,8 @@ async def priority(self, synapse: "Synapse"): Executes the priority function for the request. This method assesses and assigns a priority level to the request, determining its urgency and importance in the processing queue. - Args: - synapse (bittensor.core.synapse.Synapse): The Synapse object representing the request. + Parameters: + synapse: The Synapse object representing the request. Raises: Exception: If the priority assessment process encounters issues, such as timeouts. @@ -1424,13 +1415,12 @@ async def submit_task( Submits the given priority function to the specified executor for asynchronous execution. The function will run in the provided executor and return the priority value along with the result. - Args: - executor (bittensor.core.threadpool.PriorityThreadPoolExecutor): The executor in which the priority - function will be run. - priority (float): The priority function to be executed. + Parameters: + executor: The executor in which the priority function will be run. + priority: The priority function to be executed. Returns: - tuple: A tuple containing the priority value and the result of the priority function execution. + A tuple containing the priority value and the result of the priority function execution. """ loop = asyncio.get_running_loop() future = loop.run_in_executor(executor, lambda: priority) @@ -1475,10 +1465,10 @@ async def run( Executes the requested function as part of the request processing pipeline. This method calls the next function in the middleware chain to process the request and generate a response. - Args: - synapse (bittensor.core.synapse.Synapse): The Synapse object representing the request. - call_next (RequestResponseEndpoint): The next function in the middleware chain to process requests. - request (Request): The original HTTP request. + Parameters: + synapse: The Synapse object representing the request. + call_next: The next function in the middleware chain to process requests. + request: The original HTTP request. Returns: Response: The HTTP response generated by processing the request. @@ -1513,9 +1503,9 @@ async def synapse_to_response( """ Converts the Synapse object into a JSON response with HTTP headers. - Args: - synapse (bittensor.core.synapse.Synapse): The Synapse object representing the request. - start_time (float): The timestamp when the request processing started. + Parameters: + synapse: The Synapse object representing the request. + start_time: The timestamp when the request processing started. response_override: Instead of serializing the synapse, mutate the provided response object. This is only really useful for StreamingSynapse responses. diff --git a/bittensor/core/chain_data/__init__.py b/bittensor/core/chain_data/__init__.py index 9e1852e1d5..e2aee44a4a 100644 --- a/bittensor/core/chain_data/__init__.py +++ b/bittensor/core/chain_data/__init__.py @@ -34,30 +34,30 @@ ProposalCallData = GenericCall __all__ = [ - AxonInfo, - ChainIdentity, - DelegateInfo, - DelegatedInfo, - DelegateInfoLite, - DynamicInfo, - IPInfo, - MetagraphInfo, - MetagraphInfoEmissions, - MetagraphInfoParams, - MetagraphInfoPool, - NeuronInfo, - NeuronInfoLite, - PrometheusInfo, - ProposalCallData, - ProposalVoteData, - ScheduledColdkeySwapInfo, - SelectiveMetagraphIndex, - StakeInfo, - SubnetHyperparameters, - SubnetIdentity, - SubnetInfo, - SubnetState, - WeightCommitInfo, - decode_account_id, - process_stake_data, + "AxonInfo", + "ChainIdentity", + "DelegateInfo", + "DelegatedInfo", + "DelegateInfoLite", + "DynamicInfo", + "IPInfo", + "MetagraphInfo", + "MetagraphInfoEmissions", + "MetagraphInfoParams", + "MetagraphInfoPool", + "NeuronInfo", + "NeuronInfoLite", + "PrometheusInfo", + "ProposalCallData", + "ProposalVoteData", + "ScheduledColdkeySwapInfo", + "SelectiveMetagraphIndex", + "StakeInfo", + "SubnetHyperparameters", + "SubnetIdentity", + "SubnetInfo", + "SubnetState", + "WeightCommitInfo", + "decode_account_id", + "process_stake_data", ] diff --git a/bittensor/core/chain_data/axon_info.py b/bittensor/core/chain_data/axon_info.py index 7b9e666bc2..8df204e209 100644 --- a/bittensor/core/chain_data/axon_info.py +++ b/bittensor/core/chain_data/axon_info.py @@ -101,12 +101,12 @@ def from_string(cls, json_string: str) -> "AxonInfo": """ Creates an `AxonInfo` object from its string representation using JSON. - Args: - json_string (str): The JSON string representation of the AxonInfo object. + Parameters: + json_string: The JSON string representation of the AxonInfo object. Returns: - AxonInfo: An instance of AxonInfo created from the JSON string. If decoding fails, returns a default - `AxonInfo` object with default values. + An instance of AxonInfo created from the JSON string. If decoding fails, returns a default AxonInfo` object + with default values. Raises: json.JSONDecodeError: If there is an error in decoding the JSON string. @@ -129,11 +129,11 @@ def from_neuron_info(cls, neuron_info: dict) -> "AxonInfo": """ Converts a dictionary to an `AxonInfo` object. - Args: - neuron_info (dict): A dictionary containing the neuron information. + Parameters: + neuron_info: A dictionary containing the neuron information. Returns: - instance (AxonInfo): An instance of AxonInfo created from the dictionary. + An instance of AxonInfo created from the dictionary. """ return cls( version=neuron_info["axon_info"]["version"], diff --git a/bittensor/core/chain_data/delegate_info.py b/bittensor/core/chain_data/delegate_info.py index c0a595bf39..cfc4e50654 100644 --- a/bittensor/core/chain_data/delegate_info.py +++ b/bittensor/core/chain_data/delegate_info.py @@ -12,13 +12,13 @@ class DelegateInfoBase(InfoBase): """Base class containing common delegate information fields. Attributes: - hotkey_ss58 (str): Hotkey of delegate. - owner_ss58 (str): Coldkey of owner. - take (float): Take of the delegate as a percentage. - validator_permits (list[int]): List of subnets that the delegate is allowed to validate on. - registrations (list[int]): List of subnets that the delegate is registered on. - return_per_1000 (Balance): Return per 1000 tao of the delegate over a day. - total_daily_return (Balance): Total daily return of the delegate. + hotkey_ss58: Hotkey of delegate. + owner_ss58: Coldkey of owner. + take: Take of the delegate as a percentage. + validator_permits: List of subnets that the delegate is allowed to validate on. + registrations: List of subnets that the delegate is registered on. + return_per_1000: Return per 1000 tao of the delegate over a day. + total_daily_return: Total daily return of the delegate. """ hotkey_ss58: str # Hotkey of delegate @@ -38,8 +38,8 @@ class DelegateInfo(DelegateInfoBase): Dataclass for delegate information. Additional Attributes: - total_stake (dict[int, Balance]): Total stake of the delegate mapped by netuid. - nominators (dict[str, dict[int, Balance]]): Mapping of nominator SS58 addresses to their stakes per subnet. + total_stake: Total stake of the delegate mapped by netuid. + nominators: Mapping of nominator SS58 addresses to their stakes per subnet. """ total_stake: dict[int, Balance] # Total stake of the delegate by netuid and stake @@ -84,12 +84,11 @@ def _from_dict(cls, decoded: dict) -> Optional["DelegateInfo"]: @dataclass class DelegatedInfo(DelegateInfoBase): """ - Dataclass for delegated information. This class represents a delegate's information - specific to a particular subnet. + Dataclass for delegated information. This class represents a delegate's information specific to a particular subnet. Additional Attributes: - netuid (int): Network ID of the subnet. - stake (Balance): Stake amount for this specific delegation. + netuid: Network ID of the subnet. + stake: Stake amount for this specific delegation. """ netuid: int diff --git a/bittensor/core/chain_data/delegate_info_lite.py b/bittensor/core/chain_data/delegate_info_lite.py index 1138696314..aa61f44abd 100644 --- a/bittensor/core/chain_data/delegate_info_lite.py +++ b/bittensor/core/chain_data/delegate_info_lite.py @@ -11,15 +11,15 @@ class DelegateInfoLite(InfoBase): """ Dataclass for `DelegateLiteInfo`. This is a lighter version of :func:``DelegateInfo``. - Args: - delegate_ss58 (str): Hotkey of the delegate for which the information is being fetched. - take (float): Take of the delegate as a percentage. - nominators (int): Count of the nominators of the delegate. - owner_ss58 (str): Coldkey of the owner. - registrations (list[int]): List of subnets that the delegate is registered on. - validator_permits (list[int]): List of subnets that the delegate is allowed to validate on. - return_per_1000 (int): Return per 1000 TAO, for the delegate over a day. - total_daily_return (int): Total daily return of the delegate. + Parameters: + delegate_ss58: Hotkey of the delegate for which the information is being fetched. + take: Take of the delegate as a percentage. + nominators: Count of the nominators of the delegate. + owner_ss58: Coldkey of the owner. + registrations: List of subnets that the delegate is registered on. + validator_permits: List of subnets that the delegate is allowed to validate on. + return_per_1000: Return per 1000 TAO, for the delegate over a day. + total_daily_return: Total daily return of the delegate. """ delegate_ss58: str # Hotkey of delegate diff --git a/bittensor/core/chain_data/dynamic_info.py b/bittensor/core/chain_data/dynamic_info.py index 416dfd29c4..4d80d79dce 100644 --- a/bittensor/core/chain_data/dynamic_info.py +++ b/bittensor/core/chain_data/dynamic_info.py @@ -1,6 +1,6 @@ """ -This module defines the `DynamicInfo` data class and associated methods for handling and decoding -dynamic information in the Bittensor network. +This module defines the `DynamicInfo` data class and associated methods for handling and decoding dynamic information in +the Bittensor network. """ from dataclasses import dataclass @@ -148,7 +148,7 @@ def tao_to_alpha_with_slippage( """ Returns an estimate of how much Alpha would a staker receive if they stake their tao using the current pool state. - Arguments: + Parameters: tao: Amount of TAO to stake. percentage: percentage @@ -203,7 +203,7 @@ def alpha_to_tao_with_slippage( """ Returns an estimate of how much TAO would a staker receive if they unstake their alpha using the current pool state. - Args: + Parameters: alpha: Amount of Alpha to stake. percentage: percentage diff --git a/bittensor/core/chain_data/info_base.py b/bittensor/core/chain_data/info_base.py index e2718619af..722f9b32ee 100644 --- a/bittensor/core/chain_data/info_base.py +++ b/bittensor/core/chain_data/info_base.py @@ -1,19 +1,15 @@ from dataclasses import dataclass -from typing import Any, TypeVar +from typing import Any, Self from bittensor.core.errors import SubstrateRequestException -# NOTE: once Python 3.10+ is required, we can use `typing.Self` instead of this for better ide integration and type hinting. -# This current generic does not play so nice with the inherited type hinting. -T = TypeVar("T", bound="InfoBase") - @dataclass class InfoBase: """Base dataclass for info objects.""" @classmethod - def from_dict(cls, decoded: dict) -> T: + def from_dict(cls, decoded: dict) -> Self: try: return cls._from_dict(decoded) except KeyError as e: @@ -22,9 +18,9 @@ def from_dict(cls, decoded: dict) -> T: ) @classmethod - def list_from_dicts(cls, any_list: list[Any]) -> list[T]: + def list_from_dicts(cls, any_list: list[Any]) -> list[Self]: return [cls.from_dict(any_) for any_ in any_list] @classmethod - def _from_dict(cls, decoded: dict) -> T: + def _from_dict(cls, decoded: dict) -> Self: return cls(**decoded) diff --git a/bittensor/core/chain_data/ip_info.py b/bittensor/core/chain_data/ip_info.py index 02cde8f905..5625bd41b0 100644 --- a/bittensor/core/chain_data/ip_info.py +++ b/bittensor/core/chain_data/ip_info.py @@ -11,9 +11,9 @@ class IPInfo: Dataclass representing IP information. Attributes: - ip (str): The IP address as a string. - ip_type (int): The type of the IP address (e.g., IPv4, IPv6). - protocol (int): The protocol associated with the IP (e.g., TCP, UDP). + ip: The IP address as a string. + ip_type: The type of the IP address (e.g., IPv4, IPv6). + protocol: The protocol associated with the IP (e.g., TCP, UDP). """ ip: str diff --git a/bittensor/core/chain_data/neuron_info.py b/bittensor/core/chain_data/neuron_info.py index 0cf917a5fb..54d99d7eb0 100644 --- a/bittensor/core/chain_data/neuron_info.py +++ b/bittensor/core/chain_data/neuron_info.py @@ -18,29 +18,29 @@ class NeuronInfo(InfoBase): """Represents the metadata of a neuron including keys, UID, stake, rankings, and other attributes. Attributes: - hotkey (str): The hotkey associated with the neuron. - coldkey (str): The coldkey associated with the neuron. - uid (int): The unique identifier for the neuron. - netuid (int): The network unique identifier for the neuron. - active (int): The active status of the neuron. - stake (Balance): The balance staked to this neuron. - stake_dict (dict[str, Balance]): A dictionary mapping coldkey to the amount staked. - total_stake (Balance): The total amount of stake. - rank (float): The rank score of the neuron. - emission (float): The emission rate. - incentive (float): The incentive value. - consensus (float): The consensus score. - trust (float): The trust score. - validator_trust (float): The validation trust score. - dividends (float): The dividends value. - last_update (int): The timestamp of the last update. - validator_permit (bool): Validator permit status. - weights (list[tuple[int]]): List of weights associated with the neuron. - bonds (list[list[int]]): List of bonds associated with the neuron. - pruning_score (int): The pruning score of the neuron. - prometheus_info (Optional[PrometheusInfo]): Information related to Prometheus. - axon_info (Optional[AxonInfo]): Information related to Axon. - is_null (bool): Indicator if this is a null neuron. + hotkey: The hotkey associated with the neuron. + coldkey: The coldkey associated with the neuron. + uid: The unique identifier for the neuron. + netuid: The network unique identifier for the neuron. + active: The active status of the neuron. + stake: The balance staked to this neuron. + stake_dict: A dictionary mapping coldkey to the amount staked. + total_stake: The total amount of stake. + rank: The rank score of the neuron. + emission: The emission rate. + incentive: The incentive value. + consensus: The consensus score. + trust: The trust score. + validator_trust: The validation trust score. + dividends: The dividends value. + last_update: The timestamp of the last update. + validator_permit: Validator permit status. + weights: List of weights associated with the neuron. + bonds: List of bonds associated with the neuron. + pruning_score: The pruning score of the neuron. + prometheus_info: Information related to Prometheus. + axon_info: Information related to Axon. + is_null: Indicator if this is a null neuron. """ hotkey: str @@ -78,15 +78,15 @@ def from_weights_bonds_and_neuron_lite( """ Creates an instance of NeuronInfo from NeuronInfoLite and dictionaries of weights and bonds. - Args: - neuron_lite (NeuronInfoLite): A lite version of the neuron containing basic attributes. - weights_as_dict (dict[int, list[tuple[int, int]]]): A dictionary where the key is the UID and the value is - a list of weight tuples associated with the neuron. - bonds_as_dict (dict[int, list[tuple[int, int]]]): A dictionary where the key is the UID and the value is a - list of bond tuples associated with the neuron. + Parameters: + neuron_lite: A lite version of the neuron containing basic attributes. + weights_as_dict: A dictionary where the key is the UID and the value is a list of weight tuples associated + with the neuron. + bonds_as_dict: A dictionary where the key is the UID and the value is a list of bond tuples associated with + the neuron. Returns: - NeuronInfo: An instance of NeuronInfo populated with the provided weights and bonds. + An instance of NeuronInfo populated with the provided weights and bonds. """ n_dict = neuron_lite.__dict__ n_dict["weights"] = weights_as_dict.get(neuron_lite.uid, []) diff --git a/bittensor/core/chain_data/neuron_info_lite.py b/bittensor/core/chain_data/neuron_info_lite.py index 5dd60cef82..5b6968a479 100644 --- a/bittensor/core/chain_data/neuron_info_lite.py +++ b/bittensor/core/chain_data/neuron_info_lite.py @@ -15,27 +15,27 @@ class NeuronInfoLite(InfoBase): NeuronInfoLite is a dataclass representing neuron metadata without weights and bonds. Attributes: - hotkey (str): The hotkey string for the neuron. - coldkey (str): The coldkey string for the neuron. - uid (int): A unique identifier for the neuron. - netuid (int): Network unique identifier for the neuron. - active (int): Indicates whether the neuron is active. - stake (Balance): The stake amount associated with the neuron. - stake_dict (dict): Mapping of coldkey to the amount staked to this Neuron. - total_stake (Balance): Total amount of the stake. - rank (float): The rank of the neuron. - emission (float): The emission value of the neuron. - incentive (float): The incentive value of the neuron. - consensus (float): The consensus value of the neuron. - trust (float): Trust value of the neuron. - validator_trust (float): Validator trust value of the neuron. - dividends (float): Dividends associated with the neuron. - last_update (int): Timestamp of the last update. - validator_permit (bool): Indicates if the neuron has a validator permit. - prometheus_info (Optional[PrometheusInfo]): Prometheus information associated with the neuron. - axon_info (Optional[AxonInfo]): Axon information associated with the neuron. - pruning_score (int): The pruning score of the neuron. - is_null (bool): Indicates whether the neuron is null. + hotkey: The hotkey string for the neuron. + coldkey: The coldkey string for the neuron. + uid: A unique identifier for the neuron. + netuid: Network unique identifier for the neuron. + active: Indicates whether the neuron is active. + stake: The stake amount associated with the neuron. + stake_dict: Mapping of coldkey to the amount staked to this Neuron. + total_stake: Total amount of the stake. + rank: The rank of the neuron. + emission: The emission value of the neuron. + incentive: The incentive value of the neuron. + consensus: The consensus value of the neuron. + trust: Trust value of the neuron. + validator_trust: Validator trust value of the neuron. + dividends: Dividends associated with the neuron. + last_update: Timestamp of the last update. + validator_permit: Indicates if the neuron has a validator permit. + prometheus_info: Prometheus information associated with the neuron. + axon_info: Axon information associated with the neuron. + pruning_score: The pruning score of the neuron. + is_null: Indicates whether the neuron is null. Methods: get_null_neuron: Returns a NeuronInfoLite object representing a null neuron. diff --git a/bittensor/core/chain_data/prometheus_info.py b/bittensor/core/chain_data/prometheus_info.py index 6aac398d72..c0f32a8443 100644 --- a/bittensor/core/chain_data/prometheus_info.py +++ b/bittensor/core/chain_data/prometheus_info.py @@ -11,11 +11,11 @@ class PrometheusInfo(InfoBase): Dataclass representing information related to Prometheus. Attributes: - block (int): The block number associated with the Prometheus data. - version (int): The version of the Prometheus data. - ip (str): The IP address associated with Prometheus. - port (int): The port number for Prometheus. - ip_type (int): The type of IP address (e.g., IPv4, IPv6). + block: The block number associated with the Prometheus data. + version: The version of the Prometheus data. + ip: The IP address associated with Prometheus. + port: The port number for Prometheus. + ip_type: The type of IP address (e.g., IPv4, IPv6). """ block: int diff --git a/bittensor/core/chain_data/scheduled_coldkey_swap_info.py b/bittensor/core/chain_data/scheduled_coldkey_swap_info.py index d5717a25fc..991665a77e 100644 --- a/bittensor/core/chain_data/scheduled_coldkey_swap_info.py +++ b/bittensor/core/chain_data/scheduled_coldkey_swap_info.py @@ -14,9 +14,9 @@ class ScheduledColdkeySwapInfo(InfoBase): The `ScheduledColdkeySwapInfo` class is a dataclass representing information about scheduled cold key swaps. Attributes: - old_coldkey (str): The old cold key before the swap. - new_coldkey (str): The new cold key after the swap. - arbitration_block (int): The block number at which the arbitration of the swap will take place. + old_coldkey: The old cold key before the swap. + new_coldkey: The new cold key after the swap. + arbitration_block: The block number at which the arbitration of the swap will take place. """ old_coldkey: str diff --git a/bittensor/core/chain_data/stake_info.py b/bittensor/core/chain_data/stake_info.py index ade4515743..4f52ddfe2f 100644 --- a/bittensor/core/chain_data/stake_info.py +++ b/bittensor/core/chain_data/stake_info.py @@ -11,9 +11,9 @@ class StakeInfo(InfoBase): Dataclass for representing stake information linked to hotkey and coldkey pairs. Attributes: - hotkey_ss58 (str): The SS58 encoded hotkey address. - coldkey_ss58 (str): The SS58 encoded coldkey address. - stake (Balance): The stake associated with the hotkey-coldkey pair, represented as a Balance object. + hotkey_ss58: The SS58 encoded hotkey address. + coldkey_ss58: The SS58 encoded coldkey address. + stake: The stake associated with the hotkey-coldkey pair, represented as a Balance object. """ hotkey_ss58: str # Hotkey address diff --git a/bittensor/core/chain_data/subnet_hyperparameters.py b/bittensor/core/chain_data/subnet_hyperparameters.py index 206e44e7d4..5317bad9e3 100644 --- a/bittensor/core/chain_data/subnet_hyperparameters.py +++ b/bittensor/core/chain_data/subnet_hyperparameters.py @@ -9,39 +9,39 @@ class SubnetHyperparameters(InfoBase): This class represents the hyperparameters for a subnet. Attributes: - rho (int): The rate of decay of some value. - kappa (int): A constant multiplier used in calculations. - immunity_period (int): The period during which immunity is active. - min_allowed_weights (int): Minimum allowed weights. - max_weight_limit (float): Maximum weight limit. - tempo (int): The tempo or rate of operation. - min_difficulty (int): Minimum difficulty for some operations. - max_difficulty (int): Maximum difficulty for some operations. - weights_version (int): The version number of the weights used. - weights_rate_limit (int): Rate limit for processing weights. - adjustment_interval (int): Interval at which adjustments are made. - activity_cutoff (int): Activity cutoff threshold. - registration_allowed (bool): Indicates if registration is allowed. - target_regs_per_interval (int): Target number of registrations per interval. - min_burn (int): Minimum burn value. - max_burn (int): Maximum burn value. - bonds_moving_avg (int): Moving average of bonds. - max_regs_per_block (int): Maximum number of registrations per block. - serving_rate_limit (int): Limit on the rate of service. - max_validators (int): Maximum number of validators. - adjustment_alpha (int): Alpha value for adjustments. - difficulty (int): Difficulty level. - commit_reveal_period (int): Interval for commit-reveal weights. - commit_reveal_weights_enabled (bool): Flag indicating if commit-reveal weights are enabled. - alpha_high (int): High value of alpha. - alpha_low (int): Low value of alpha. - liquid_alpha_enabled (bool): Flag indicating if liquid alpha is enabled. - alpha_sigmoid_steepness (float): - yuma_version (int): Version of yuma. - subnet_is_active (bool): Indicates if subnet is active after START CALL. - transfers_enabled (bool): Flag indicating if transfers are enabled. - bonds_reset_enabled (bool): Flag indicating if bonds are reset enabled. - user_liquidity_enabled (bool): Flag indicating if user liquidity is enabled. + rho: The rate of decay of some value. + kappa: A constant multiplier used in calculations. + immunity_period: The period during which immunity is active. + min_allowed_weights: Minimum allowed weights. + max_weight_limit: Maximum weight limit. + tempo: The tempo or rate of operation. + min_difficulty: Minimum difficulty for some operations. + max_difficulty: Maximum difficulty for some operations. + weights_version: The version number of the weights used. + weights_rate_limit: Rate limit for processing weights. + adjustment_interval: Interval at which adjustments are made. + activity_cutoff: Activity cutoff threshold. + registration_allowed: Indicates if registration is allowed. + target_regs_per_interval: Target number of registrations per interval. + min_burn: Minimum burn value. + max_burn: Maximum burn value. + bonds_moving_avg: Moving average of bonds. + max_regs_per_block: Maximum number of registrations per block. + serving_rate_limit: Limit on the rate of service. + max_validators: Maximum number of validators. + adjustment_alpha: Alpha value for adjustments. + difficulty: Difficulty level. + commit_reveal_period: Interval for commit-reveal weights. + commit_reveal_weights_enabled: Flag indicating if commit-reveal weights are enabled. + alpha_high: High value of alpha. + alpha_low: Low value of alpha. + liquid_alpha_enabled: Flag indicating if liquid alpha is enabled. + alpha_sigmoid_steepness: Sigmoid steepness parameter for converting miner-validator alignment into alpha. + yuma_version: Version of yuma. + subnet_is_active: Indicates if subnet is active after START CALL. + transfers_enabled: Flag indicating if transfers are enabled. + bonds_reset_enabled: Flag indicating if bonds are reset enabled. + user_liquidity_enabled: Flag indicating if user liquidity is enabled. """ rho: int diff --git a/bittensor/core/chain_data/subnet_state.py b/bittensor/core/chain_data/subnet_state.py index caf01bfc2c..95a38536e3 100644 --- a/bittensor/core/chain_data/subnet_state.py +++ b/bittensor/core/chain_data/subnet_state.py @@ -1,6 +1,6 @@ """ -This module defines the `SubnetState` data class and associated methods for handling and decoding -subnetwork states in the Bittensor network. +This module defines the `SubnetState` data class and associated methods for handling and decoding subnetwork states in +the Bittensor network. """ from dataclasses import dataclass diff --git a/bittensor/core/chain_data/utils.py b/bittensor/core/chain_data/utils.py index 2916581b1b..621477ad21 100644 --- a/bittensor/core/chain_data/utils.py +++ b/bittensor/core/chain_data/utils.py @@ -43,14 +43,14 @@ def from_scale_encoding( """ Decodes input_ data from SCALE encoding based on the specified type name and modifiers. - Args: - input_ (Union[List[int], bytes, ScaleBytes]): The input_ data to decode. - type_name (ChainDataType): The type of data being decoded. - is_vec (bool): Whether the data is a vector of the specified type. Default is ``False``. - is_option (bool): Whether the data is an optional value of the specified type. Default is ``False``. + Parameters: + input_: The input_ data to decode. + type_name: The type of data being decoded. + is_vec: Whether the data is a vector of the specified type. + is_option: Whether the data is an optional value of the specified type. Returns: - Optional[dict]: The decoded data as a dictionary, or ``None`` if the decoding fails. + The decoded data as a dictionary, or ``None`` if the decoding fails. """ type_string = type_name.name if type_name == ChainDataType.DelegatedInfo: @@ -70,12 +70,12 @@ def from_scale_encoding_using_type_string( """ Decodes SCALE encoded data to a dictionary based on the provided type string. - Args: - input_ (Union[List[int], bytes, ScaleBytes]): The SCALE encoded input data. - type_string (str): The type string defining the structure of the data. + Parameters: + input_: The SCALE encoded input data. + type_string: The type string defining the structure of the data. Returns: - Optional[dict]: The decoded data as a dictionary, or ``None`` if the decoding fails. + The decoded data as a dictionary, or ``None`` if the decoding fails. Raises: TypeError: If the input_ is not a list[int], bytes, or ScaleBytes. @@ -105,8 +105,8 @@ def decode_account_id(account_id_bytes: Union[bytes, str]) -> str: """ Decodes an AccountId from bytes to a Base64 string using SS58 encoding. - Args: - account_id_bytes (bytes): The AccountId in bytes that needs to be decoded. + Parameters: + account_id_bytes: The AccountId in bytes that needs to be decoded. Returns: str: The decoded AccountId as a Base64 string. @@ -122,8 +122,8 @@ def process_stake_data(stake_data: list) -> dict: """ Processes stake data to decode account IDs and convert stakes from rao to Balance objects. - Args: - stake_data (list): A list of tuples where each tuple contains an account ID in bytes and a stake in rao. + Parameters: + stake_data: A list of tuples where each tuple contains an account ID in bytes and a stake in rao. Returns: dict: A dictionary with account IDs as keys and their corresponding Balance objects as values. @@ -146,8 +146,8 @@ def decode_block(data: bytes) -> int: """ Decode the block data from the given input if it is not None. - Arguments: - data (bytes): The block data to decode. + Parameters: + data: The block data to decode. Returns: int: The decoded block. @@ -159,11 +159,11 @@ def decode_revealed_commitment(encoded_data) -> tuple[int, str]: """ Decode the revealed commitment data from the given input if it is not None. - Arguments: - encoded_data (tuple[bytes, int]): A tuple containing the revealed message and the block number. + Parameters: + encoded_data: A tuple containing the revealed message and the block number. Returns: - tuple[int, str]: A tuple containing the revealed block number and decoded commitment message. + A tuple containing the revealed block number and decoded commitment message. """ def scale_decode_offset(data: bytes) -> int: diff --git a/bittensor/core/chain_data/weight_commit_info.py b/bittensor/core/chain_data/weight_commit_info.py index b8555eed68..814ef54aca 100644 --- a/bittensor/core/chain_data/weight_commit_info.py +++ b/bittensor/core/chain_data/weight_commit_info.py @@ -25,8 +25,8 @@ def from_vec_u8(cls, data: tuple) -> tuple[str, str, int]: """ Creates a WeightCommitInfo instance - Args: - data (tuple): Tuple containing ((AccountId,), (commit_data,), round_number) + Parameters: + data: Tuple containing ((AccountId,), (commit_data,), round_number) Returns: WeightCommitInfo: A new instance with the decoded data @@ -48,8 +48,8 @@ def from_vec_u8_v2(cls, data: tuple) -> tuple[str, int, str, int]: """ Creates a WeightCommitInfo instance - Args: - data (tuple): Tuple containing ((AccountId,), (commit_block, ) (commit_data,), round_number) + Parameters: + data: Tuple containing ((AccountId,), (commit_block, ) (commit_data,), round_number) Returns: WeightCommitInfo: A new instance with the decoded data diff --git a/bittensor/core/config.py b/bittensor/core/config.py index 2fb3d8495b..3d9c105ace 100644 --- a/bittensor/core/config.py +++ b/bittensor/core/config.py @@ -20,7 +20,7 @@ import os import sys from copy import deepcopy -from typing import Any, TypeVar, Type, Optional +from typing import Any, Optional, Self import yaml from munch import DefaultMunch @@ -236,13 +236,10 @@ def _add_default_arguments(self, parser: argparse.ArgumentParser) -> None: pass -T = TypeVar("T", bound="DefaultConfig") - - class DefaultConfig(Config): """A Config with a set of default values.""" @classmethod - def default(cls: Type[T]) -> T: + def default(cls: Self) -> Self: """Get default config.""" raise NotImplementedError("Function default is not implemented.") diff --git a/bittensor/core/dendrite.py b/bittensor/core/dendrite.py index 5dcc509e16..d785e3bfb8 100644 --- a/bittensor/core/dendrite.py +++ b/bittensor/core/dendrite.py @@ -51,11 +51,10 @@ class DendriteMixin: HTTP requests to the network servers. It also provides functionalities such as logging network requests and processing server responses. - Args: - keypair (Option[Union[bittensor_wallet.Wallet, bittensor_wallet.Keypair]]): The wallet or keypair used for - signing messages. - external_ip (str): The external IP address of the local system. - synapse_history (list): A list of Synapse objects representing the historical responses. + Parameters: + keypair: The wallet or keypair used for signing messages. + external_ip: The external IP address of the local system. + synapse_history: A list of Synapse objects representing the historical responses. Methods: __str__(): Returns a string representation of the Dendrite object. @@ -102,10 +101,8 @@ def __init__(self, wallet: Optional[Union["Wallet", "Keypair"]] = None): """ Initializes the Dendrite object, setting up essential properties. - Args: - wallet (Optional[Union[bittensor_wallet.Wallet, bittensor_wallet.Keypair]]): The user's wallet or keypair - used for signing messages. Defaults to ``None``, in which case a new - :func:`bittensor_wallet.Wallet().hotkey` is generated and used. + Parameters: + wallet: The user's wallet or keypair used for signing messages. """ # Initialize the parent class super(DendriteMixin, self).__init__() @@ -171,7 +168,7 @@ def close_session(self, using_new_loop: bool = False): resources like open connections and internal buffers. It is crucial for preventing resource leakage and should be called when the dendrite instance is no longer in use, especially in synchronous contexts. - Arguments: + Parameters: using_new_loop: A flag to determine whether this has been called with a new event loop rather than the default. This will indicate whether to close this event loop at the end of this call. @@ -225,11 +222,11 @@ def _get_endpoint_url(self, target_axon, request_name): """ Constructs the endpoint URL for a network request to a target axon. - This internal method generates the full HTTP URL for sending a request to the specified axon. The - URL includes the IP address and port of the target axon, along with the specific request name. It - differentiates between requests to the local system (using '0.0.0.0') and external systems. + This internal method generates the full HTTP URL for sending a request to the specified axon. The URL includes + the IP address and port of the target axon, along with the specific request name. It differentiates between + requests to the local system (using '0.0.0.0') and external systems. - Args: + Parameters: target_axon: The target axon object containing IP and port information. request_name: The specific name of the request being made. @@ -250,8 +247,8 @@ def log_exception(self, exception: Exception): This method generates a unique UUID for the error, extracts the error type, and logs the error message using Bittensor's logging system. - Args: - exception (Exception): The exception object to be logged. + Parameters: + exception: The exception object to be logged. Returns: None @@ -270,18 +267,19 @@ def process_error_message( exception: Exception, ) -> Union["Synapse", "StreamingSynapse"]: """ - Handles exceptions that occur during network requests, updating the synapse with appropriate status codes and messages. + Handles exceptions that occur during network requests, updating the synapse with appropriate status codes and + messages. - This method interprets different types of exceptions and sets the corresponding status code and - message in the synapse object. It covers common network errors such as connection issues and timeouts. + This method interprets different types of exceptions and sets the corresponding status code and message in the + synapse object. It covers common network errors such as connection issues and timeouts. - Args: - synapse (bittensor.core.synapse.Synapse): The synapse object associated with the request. - request_name (str): The name of the request during which the exception occurred. - exception (Exception): The exception object caught during the request. + Parameters: + synapse: The synapse object associated with the request. + request_name: The name of the request during which the exception occurred. + exception: The exception object caught during the request. Returns: - Synapse (bittensor.core.synapse.Synapse): The updated synapse object with the error status code and message. + Synapse: The updated synapse object with the error status code and message. Note: This method updates the synapse object in-place. @@ -323,8 +321,8 @@ def _log_outgoing_request(self, synapse: "Synapse"): import bittensor bittensor.debug() - Args: - synapse (bittensor.core.synapse.Synapse): The synapse object representing the request being sent. + Parameters: + synapse: The synapse object representing the request being sent. """ if synapse.axon is not None: logging.trace( @@ -339,8 +337,8 @@ def _log_incoming_response(self, synapse: "Synapse"): including the size of the response, synapse name, axon details, status code, and status message. This logging is vital for troubleshooting and understanding the network interactions in Bittensor. - Args: - synapse (bittensor.core.synapse.Synapse): The synapse object representing the received response. + Parameters: + synapse: The synapse object representing the received response. """ if synapse.axon is not None and synapse.dendrite is not None: logging.trace( @@ -361,17 +359,14 @@ def query( Cleanup is automatically handled and sessions are closed upon completed requests. - Args: - axons (Union[list[Union[bittensor.core.chain_data.axon_info.AxonInfo, 'bittensor.core.axon.Axon']], - Union['bittensor.core.chain_data.axon_info.AxonInfo', 'bittensor.core.axon.Axon']]): The list of target - Axon information. - synapse (Optional[bittensor.core.synapse.Synapse]): The Synapse object. Defaults to :func:`Synapse()`. - timeout (Optional[float]): The request timeout duration in seconds. Defaults to ``12.0`` seconds. + Parameters: + axons: The list of target Axon information. + synapse: The Synapse object. Defaults to :func:`Synapse()`. + timeout: The request timeout duration in seconds. Defaults to ``12.0`` seconds. Returns: - Union[bittensor.core.synapse.Synapse, list[bittensor.core.synapse.Synapse]]: If a single target axon is - provided, returns the response from that axon. If multiple target axons are provided, returns a list of - responses from all target axons. + If a single target axon is provided, returns the response from that axon. If multiple target axons are + provided, returns a list of responses from all target axons. """ if event_loop_is_running(): warnings.warn( @@ -408,14 +403,13 @@ async def forward( """ Asynchronously sends requests to one or multiple Axons and collates their responses. - This function acts as a bridge for sending multiple requests concurrently or sequentially - based on the provided parameters. It checks the type of the target Axons, preprocesses - the requests, and then sends them off. After getting the responses, it processes and - collates them into a unified format. + This function acts as a bridge for sending multiple requests concurrently or sequentially based on the provided + parameters. It checks the type of the target Axons, preprocesses the requests, and then sends them off. After + getting the responses, it processes and collates them into a unified format. - When querying an Axon that sends a single response, this function returns a Synapse object - containing the response data. If multiple Axons are queried, a list of Synapse objects is - returned, each containing the response from the corresponding Axon. + When querying an Axon that sends a single response, this function returns a Synapse object containing the + response data. If multiple Axons are queried, a list of Synapse objects is returned, each containing the + response from the corresponding Axon. For example:: @@ -441,22 +435,17 @@ async def forward( # Process each chunk here print(chunk) - Args: - axons (Union[list[Union[bittensor.core.chain_data.axon_info.AxonInfo, bittensor.core.axon.Axon]], - Union[bittensor.core.chain_data.axon_info.AxonInfo, bittensor.core.axon.Axon]]): The target Axons to - send requests to. Can be a single Axon or a list of Axons. - synapse (bittensor.core.synapse.Synapse): The Synapse object encapsulating the data. Defaults to a new - :func:`Synapse` instance. - timeout (float): Maximum duration to wait for a response from an Axon in seconds. Defaults to ``12.0``. - deserialize (bool): Determines if the received response should be deserialized. Defaults to ``True``. - run_async (bool): If ``True``, sends requests concurrently. Otherwise, sends requests sequentially. - Defaults to ``True``. - streaming (bool): Indicates if the response is expected to be in streaming format. Defaults to ``False``. + Parameters: + axons: The target Axons to send requests to. Can be a single Axon or a list of Axons. + synapse: The Synapse object encapsulating the data. + timeout: Maximum duration to wait for a response from an Axon in seconds. + deserialize: Determines if the received response should be deserialized. + run_async: If ``True``, sends requests concurrently. Otherwise, sends requests sequentially. + streaming: Indicates if the response is expected to be in streaming format. Returns: - Union[AsyncGenerator, bittensor.core.synapse.Synapse, list[bittensor.core.synapse.Synapse]]: If a single - `Axon` is targeted, returns its response. - If multiple Axons are targeted, returns a list of their responses. + If a single `Axon` is targeted, returns its response. If multiple Axons are targeted, returns a list of + their responses. """ is_list = True # If a single axon is provided, wrap it in a list for uniform processing @@ -476,42 +465,41 @@ async def query_all_axons( is_stream: bool, ) -> Union["AsyncGenerator[Any, Any]", "Synapse", "StreamingSynapse"]: """ - Handles the processing of requests to all targeted axons, accommodating both streaming and non-streaming responses. + Handles the processing of requests to all targeted axons, accommodating both streaming and non-streaming + responses. This function manages the concurrent or sequential dispatch of requests to a list of axons. It utilizes the ``is_stream`` parameter to determine the mode of response handling (streaming or non-streaming). For each axon, it calls ``single_axon_response`` and aggregates the responses. - Args: - is_stream (bool): Flag indicating whether the axon responses are expected to be streamed. - If ``True``, responses are handled in streaming mode. + Parameters: + is_stream: Flag indicating whether the axon responses are expected to be streamed. If ``True``, + responses are handled in streaming mode. Returns: - list[Union[AsyncGenerator, bittensor.core.synapse.Synapse, bittensor.core.stream.StreamingSynapse]]: - A list containing the responses from each axon. The type of each response depends on the streaming - mode and the type of synapse used. + A list containing the responses from each axon. The type of each response depends on the streaming mode + and the type of synapse used. """ async def single_axon_response( target_axon: Union["AxonInfo", "Axon"], ) -> Union["AsyncGenerator[Any, Any]", "Synapse", "StreamingSynapse"]: """ - Manages the request and response process for a single axon, supporting both streaming and non-streaming modes. + Manages the request and response process for a single axon, supporting both streaming and non-streaming + modes. This function is responsible for initiating a request to a single axon. Depending on the ``is_stream`` flag, it either uses ``call_stream`` for streaming responses or ``call`` for standard responses. The function handles the response processing, catering to the specifics of streaming or non-streaming data. - Args: - target_axon (Union[bittensor.core.chain_data.axon_info.AxonInfo, bittensor.core.axon.Axon): The - target axon object to which the request is to be sent. This object contains the necessary - information like IP address and port to formulate the request. + Parameters: + target_axon: The target axon object to which the request is to be sent. This object contains the + necessary information like IP address and port to formulate the request. Returns: - Union[AsyncGenerator, bittensor.core.synapse.Synapse, bittensor.core.stream.StreamingSynapse]: The - response from the targeted axon. In streaming mode, an AsyncGenerator is returned, yielding data - chunks. In non-streaming mode, a Synapse or StreamingSynapse object is returned containing the - response. + The response from the targeted axon. In streaming mode, an AsyncGenerator is returned, yielding data + chunks. In non-streaming mode, a Synapse or StreamingSynapse object is returned containing the + response. """ if is_stream: # If in streaming mode, return the async_generator @@ -558,13 +546,11 @@ async def call( This function establishes a connection with a specified Axon, sends the encapsulated data through the Synapse object, waits for a response, processes it, and then returns the updated Synapse object. - Args: - target_axon (Union[bittensor.core.chain_data.axon_info.AxonInfo, bittensor.core.axon.Axon]): The target Axon - to send the request to. - synapse (bittensor.core.synapse.Synapse): The Synapse object encapsulating the data. Defaults to a new - :func:`Synapse` instance. - timeout (float): Maximum duration to wait for a response from the Axon in seconds. Defaults to ``12.0``. - deserialize (bool): Determines if the received response should be deserialized. Defaults to ``True``. + Parameters: + target_axon: The target Axon to send the request to. + synapse: The Synapse object encapsulating the data. + timeout: Maximum duration to wait for a response from the Axon in seconds. + deserialize: Determines if the received response should be deserialized. Returns: bittensor.core.synapse.Synapse: The Synapse object, updated with the response data from the Axon. @@ -629,14 +615,11 @@ async def call_stream( useful for processing large responses piece by piece without waiting for the entire data to be transmitted. - Args: - target_axon (Union[bittensor.core.chain_data.axon_info.AxonInfo, bittensor.core.axon.Axon]): The target Axon - to send the request to. - synapse (bittensor.core.synapse.Synapse): The Synapse object encapsulating the data. Defaults to a new - :func:`Synapse` instance. - timeout (float): Maximum duration to wait for a response (or a chunk of the response) from the Axon in - seconds. Defaults to ``12.0``. - deserialize (bool): Determines if each received chunk should be deserialized. Defaults to ``True``. + Parameters: + target_axon: The target Axon to send the request to. + synapse: The Synapse object encapsulating the data. + timeout: Maximum duration to wait for a response (or a chunk of the response) from the Axon in seconds. + deserialize: Determines if each received chunk should be deserialized. Yields: object: Each yielded object contains a chunk of the arbitrary response data from the Axon. @@ -708,13 +691,13 @@ def preprocess_synapse_for_request( Preprocesses the synapse for making a request. This includes building headers for Dendrite and Axon and signing the request. - Args: - target_axon_info (bittensor.core.chain_data.axon_info.AxonInfo): The target axon information. - synapse (bittensor.core.synapse.Synapse): The synapse object to be preprocessed. - timeout (float): The request timeout duration in seconds. Defaults to ``12.0`` seconds. + Parameters: + target_axon_info: The target axon information. + synapse: The synapse object to be preprocessed. + timeout: The request timeout duration in seconds. Defaults to ``12.0`` seconds. Returns: - bittensor.core.synapse.Synapse: The preprocessed synapse. + The preprocessed synapse. """ # Set the timeout for the synapse synapse.timeout = timeout @@ -749,10 +732,10 @@ def process_server_response( Processes the server response, updates the local synapse state with the server's state and merges headers set by the server. - Args: - server_response (object): The `aiohttp `_ response object from the server. - json_response (dict): The parsed JSON response from the server. - local_synapse (bittensor.core.synapse.Synapse): The local synapse object to be updated. + Parameters: + server_response: The `aiohttp `_ response object from the server. + json_response: The parsed JSON response from the server. + local_synapse: The local synapse object to be updated. Raises: None: But errors in attribute setting are silently ignored. @@ -842,11 +825,10 @@ async def __aexit__(self, exc_type, exc_value, traceback): Ensures proper cleanup when exiting the ``async with`` context. This method will close the `aiohttp `_ client session asynchronously, releasing any tied resources. - Args: - exc_type (Type[BaseException]): The type of exception that was raised. - exc_value (BaseException): The instance of exception that was raised. - traceback (TracebackType): A traceback object encapsulating the call stack at the point where the exception - was raised. + Parameters: + exc_type : The type of exception that was raised. + exc_value: The instance of exception that was raised. + traceback: A traceback object encapsulating the call stack at the point where the exception was raised. Usage:: import bittensor diff --git a/bittensor/core/errors.py b/bittensor/core/errors.py index e52e994fcd..15eb9e0446 100644 --- a/bittensor/core/errors.py +++ b/bittensor/core/errors.py @@ -64,147 +64,99 @@ class ChainTransactionError(ChainError): class DelegateTakeTooHigh(ChainTransactionError): - """ - Delegate take is too high. - """ + """Delegate take is too high.""" class DelegateTakeTooLow(ChainTransactionError): - """ - Delegate take is too low. - """ + """Delegate take is too low.""" class DuplicateChild(ChainTransactionError): - """ - Duplicate child when setting children. - """ + """Duplicate child when setting children.""" class HotKeyAccountNotExists(ChainTransactionError): - """ - The hotkey does not exist. - """ + """The hotkey does not exist.""" class IdentityError(ChainTransactionError): - """ - Error raised when an identity transaction fails. - """ + """Error raised when an identity transaction fails.""" class InvalidChild(ChainTransactionError): - """ - Attempting to set an invalid child for a hotkey on a network. - """ + """Attempting to set an invalid child for a hotkey on a network.""" class MetadataError(ChainTransactionError): - """ - Error raised when metadata commitment transaction fails. - """ + """Error raised when metadata commitment transaction fails.""" class NominationError(ChainTransactionError): - """ - Error raised when a nomination transaction fails. - """ + """Error raised when a nomination transaction fails.""" class NonAssociatedColdKey(ChainTransactionError): - """ - Request to stake, unstake or subscribe is made by a coldkey that is not associated with the hotkey account. - """ + """Request to stake, unstake or subscribe is made by a coldkey that is not associated with the hotkey account.""" class NotEnoughStakeToSetChildkeys(ChainTransactionError): - """ - The parent hotkey doesn't have enough own stake to set childkeys. - """ + """The parent hotkey doesn't have enough own stake to set childkeys.""" class NotRegisteredError(ChainTransactionError): - """ - Error raised when a neuron is not registered, and the transaction requires it to be. - """ + """Error raised when a neuron is not registered, and the transaction requires it to be.""" class ProportionOverflow(ChainTransactionError): - """ - Proportion overflow when setting children. - """ + """Proportion overflow when setting children.""" class RegistrationError(ChainTransactionError): - """ - Error raised when a neuron registration transaction fails. - """ + """Error raised when a neuron registration transaction fails.""" class RegistrationNotPermittedOnRootSubnet(ChainTransactionError): - """ - Operation is not permitted on the root subnet. - """ + """Operation is not permitted on the root subnet.""" class StakeError(ChainTransactionError): - """ - Error raised when a stake transaction fails. - """ + """Error raised when a stake transaction fails.""" class NotDelegateError(StakeError): - """ - Error raised when a hotkey you are trying to stake to is not a delegate. - """ + """Error raised when a hotkey you are trying to stake to is not a delegate.""" class SubnetNotExists(ChainTransactionError): - """ - The subnet does not exist. - """ + """The subnet does not exist.""" class TakeError(ChainTransactionError): - """ - Error raised when an increase / decrease take transaction fails. - """ + """Error raised when an increase / decrease take transaction fails.""" class TransferError(ChainTransactionError): - """ - Error raised when a transfer transaction fails. - """ + """Error raised when a transfer transaction fails.""" class TooManyChildren(ChainTransactionError): - """ - Too many children MAX 5. - """ + """Too many children MAX 5.""" class TxRateLimitExceeded(ChainTransactionError): - """ - Default transaction rate limit exceeded. - """ + """Default transaction rate limit exceeded.""" class DelegateTxRateLimitExceeded(TxRateLimitExceeded): - """ - A transactor exceeded the rate limit for delegate transaction. - """ + """A transactor exceeded the rate limit for delegate transaction.""" class UnstakeError(ChainTransactionError): - """ - Error raised when an unstake transaction fails. - """ + """Error raised when an unstake transaction fails.""" class ChainQueryError(ChainError): - """ - Error for any chain query related errors. - """ + """Error for any chain query related errors.""" class InvalidRequestNameError(Exception): diff --git a/bittensor/core/extrinsics/asyncex/root.py b/bittensor/core/extrinsics/asyncex/root.py index c6e76546ac..88a7f13c4d 100644 --- a/bittensor/core/extrinsics/asyncex/root.py +++ b/bittensor/core/extrinsics/asyncex/root.py @@ -17,13 +17,13 @@ async def _get_limits(subtensor: "AsyncSubtensor") -> tuple[int, float]: These values are fetched asynchronously using `asyncio.gather` to run both requests concurrently. - Args: - subtensor (AsyncSubtensor): The AsyncSubtensor object used to interface with the network's substrate node. + Parameters: + subtensor: The AsyncSubtensor instance. Returns: - tuple[int, float]: A tuple containing: - - `min_allowed_weights` (int): The minimum allowed weights. - - `max_weight_limit` (float): The maximum weight limit, normalized to a float value. + tuple[int, float]: + - `min_allowed_weights`: The minimum allowed weights. + - `max_weight_limit`: The maximum weight limit, normalized to a float value. """ # Get weight restrictions. maw, mwl = await asyncio.gather( @@ -46,7 +46,7 @@ async def root_register_extrinsic( """ Registers the neuron to the root network. - Arguments: + 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 diff --git a/bittensor/core/extrinsics/asyncex/utils.py b/bittensor/core/extrinsics/asyncex/utils.py index d103b38f0b..407c80c024 100644 --- a/bittensor/core/extrinsics/asyncex/utils.py +++ b/bittensor/core/extrinsics/asyncex/utils.py @@ -20,7 +20,7 @@ async def get_extrinsic_fee( """ Get extrinsic fee for a given extrinsic call and keypair for a given SN's netuid. - Arguments: + Parameters: subtensor: The Subtensor instance. netuid: The SN's netuid. call: The extrinsic call. diff --git a/bittensor/core/extrinsics/root.py b/bittensor/core/extrinsics/root.py index 9117832efd..3458444b70 100644 --- a/bittensor/core/extrinsics/root.py +++ b/bittensor/core/extrinsics/root.py @@ -17,13 +17,13 @@ def _get_limits(subtensor: "Subtensor") -> tuple[int, float]: These values are fetched asynchronously using `asyncio.gather` to run both requests concurrently. - Args: - subtensor (Subtensor): The AsyncSubtensor object used to interface with the network's substrate node. + Parameters: + subtensor: The AsyncSubtensor instance. Returns: - tuple[int, float]: A tuple containing: - - `min_allowed_weights` (int): The minimum allowed weights. - - `max_weight_limit` (float): The maximum weight limit, normalized to a float value. + tuple[int, float]: + - `min_allowed_weights`: The minimum allowed weights. + - `max_weight_limit`: The maximum weight limit, normalized to a float value. """ # Get weight restrictions. maw = subtensor.get_hyperparameter("MinAllowedWeights", netuid=0) diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 4cafb2acef..493dbf6fdf 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -26,14 +26,14 @@ def get_old_stakes( netuids associated with the wallet's coldkey. If no match is found for a particular hotkey and netuid combination, a default balance of zero is returned. - Args: + Parameters: wallet: The wallet containing the coldkey to compare with stake data. hotkey_ss58s: List of hotkey SS58 addresses for which stakes are retrieved. netuids: List of network unique identifiers (netuids) corresponding to the hotkeys. all_stakes: A collection of all staking information to search through. Returns: - list[Balance]: A list of Balances, each representing the stake for a given hotkey and netuid. + A list of Balances, each representing the stake for a given hotkey and netuid. """ stake_lookup = { (stake.hotkey_ss58, stake.coldkey_ss58, stake.netuid): stake.stake @@ -57,7 +57,7 @@ def get_extrinsic_fee( """ Get extrinsic fee for a given extrinsic call and keypair for a given SN's netuid. - Arguments: + Parameters: subtensor: The Subtensor instance. call: The extrinsic call. keypair: The keypair associated with the extrinsic. diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 2bc0cef868..0231411df5 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -99,14 +99,14 @@ def get_save_dir( """ Returns a directory path given ``network`` and ``netuid`` inputs. - Args: - network (str): Network name. - netuid (int): Network UID. + Parameters: + network: Network name. + netuid: Network UID. root_dir: list to the file path for the root directory of your metagraph saves (i.e. ['/', 'tmp', 'metagraphs'], defaults to ["~", ".bittensor", "metagraphs"] Returns: - str: Directory path. + Directory path. """ _root_dir = root_dir or ["~", ".bittensor", "metagraphs"] return os.path.expanduser( @@ -122,11 +122,11 @@ def latest_block_path(dir_path: str) -> str: """ Get the latest block path from the provided directory path. - Args: - dir_path (str): Directory path. + Parameters: + dir_path: Directory path. Returns: - str: Latest block path. + Latest block path. """ latest_block = -1 latest_file_full_path = None @@ -182,9 +182,9 @@ class MetagraphMixin(ABC): `_. These attributes govern how neurons interact, how they are incentivized, and their roles within the network's decision-making processes. - Args: - netuid (int): A unique identifier that distinguishes between different instances or versions of the Bittensor network. - network (str): The name of the network, signifying specific configurations or iterations within the Bittensor ecosystem. + Parameters: + netuid: A unique identifier that distinguishes between different instances or versions of the Bittensor network. + network: The name of the network, signifying specific configurations or iterations within the Bittensor ecosystem. Attributes: version (NDArray): The version number of the network, integral for tracking network updates. @@ -208,8 +208,8 @@ class MetagraphMixin(ABC): axons (List): Details about each neuron's axon, critical for facilitating network communication. The metagraph plays a pivotal role in Bittensor's decentralized AI operations, influencing everything from data - propagation to reward distribution. It embodies the principles of decentralized governance and collaborative - intelligence, ensuring that the network remains adaptive, secure, and efficient. + propagation to reward distribution. It embodies the principles of decentralized governance and collaborative + intelligence, ensuring that the network remains adaptive, secure, and efficient. Example: Initializing the metagraph to represent the current state of the Bittensor network:: @@ -537,7 +537,7 @@ def __init__( provided arguments. This method is the entry point for creating a metagraph object, which is a central component in representing the state of the Bittensor network. - Args: + Parameters: netuid: The unique identifier for the network, distinguishing this instance of the metagraph within potentially multiple network configurations. network: The name of the network, which can indicate specific configurations or versions of the Bittensor @@ -659,7 +659,7 @@ def _create_tensor(data, dtype) -> Tensor: Creates a numpy array with the given data and data type. This method is a utility function used internally to encapsulate data into a np.array, making it compatible with the metagraph's numpy model structure. - Args: + Parameters: data: The data to be included in the tensor. This could be any numeric data, like stakes, ranks, etc. dtype: The data type for the tensor, typically a numpy data type like ``np.float32`` or ``np.int64``. @@ -684,7 +684,7 @@ def _process_weights_or_bonds(self, data, attribute: str) -> Tensor: transformation of neuron connection data (``weights`` or ``bonds``) from a list or other unstructured format into a tensor that can be utilized within the metagraph model. - Args: + Parameters: data: The raw weights or bonds data to be processed. This data typically comes from the subtensor. attribute: A string indicating whether the data is ``weights`` or ``bonds``, which determines the specific processing steps to be applied. @@ -746,8 +746,8 @@ def _set_metagraph_attributes(self, block: int): method updates parameters like the number of neurons, block number, stakes, trusts, ranks, and other neuron-specific information. - Args: - block (int): The block number for which the metagraph attributes need to be set. + Parameters: + block: The block number for which the metagraph attributes need to be set. Internal Usage: Used internally during the sync process to update the metagraph's attributes:: @@ -811,12 +811,12 @@ def save(self, root_dir: Optional[list[str]] = None) -> "MetagraphMixin": state of the network's metagraph, which can later be reloaded or analyzed. The save operation includes all neuron attributes and parameters, ensuring a complete snapshot of the metagraph's state. - Args: - root_dir: list to the file path for the root directory of your metagraph saves - (i.e. ['/', 'tmp', 'metagraphs'], defaults to ["~", ".bittensor", "metagraphs"] + Parameters: + root_dir: list to the file path for the root directory of your metagraph saves (i.e. ['/', 'tmp', + 'metagraphs'], defaults to ["~", ".bittensor", "metagraphs"] Returns: - metagraph (bittensor.core.metagraph.Metagraph): The metagraph instance after saving its state. + metagraph: The metagraph instance after saving its state. Example: Save the current state of the metagraph to the default directory:: @@ -864,13 +864,12 @@ def load(self, root_dir: Optional[list[str]] = None) -> None: metagraph's current ``network`` and ``netuid`` properties. This abstraction simplifies the process of loading the metagraph's state for the user, requiring no direct path specifications. - Args: - root_dir: list to the file path for the root directory of your metagraph saves - (i.e. ['/', 'tmp', 'metagraphs'], defaults to ["~", ".bittensor", "metagraphs"] + Parameters: + root_dir: list to the file path for the root directory of your metagraph saves (i.e. ['/', 'tmp', + 'metagraphs'], defaults to ["~", ".bittensor", "metagraphs"] Returns: - metagraph (bittensor.core.metagraph.Metagraph): The metagraph instance after loading its state from the - default directory. + metagraph: The metagraph instance after loading its state from the default directory. Example: Load the metagraph state from the last saved snapshot in the default directory:: @@ -899,13 +898,12 @@ def load_from_path(self, dir_path: str) -> "AsyncMetagraph": including neuron attributes and parameters from this file. This ensures that the metagraph is accurately reconstituted to reflect the network state at the time of the saved block. - Args: - dir_path (str): The directory path where the metagraph's state files are stored. This path should contain - one or more saved state files, typically named in a format that includes the block number. + Parameters: + dir_path: The directory path where the metagraph's state files are stored. This path should contain one or + more saved state files, typically named in a format that includes the block number. Returns: - metagraph (bittensor.core.metagraph.AsyncMetagraph): The metagraph instance after loading its state from the - specified directory path. + metagraph: The metagraph instance after loading its state from the specified directory path. Example: Load the metagraph state from a specific directory:: @@ -950,9 +948,8 @@ def _apply_metagraph_info_mixin(self, metagraph_info: "MetagraphInfo"): """ Updates the attributes of the current object with data from a provided MetagraphInfo instance. - Args: - metagraph_info (MetagraphInfo): An instance of the MetagraphInfo class containing the data to be applied to - the current object. + Parameters: + metagraph_info: An instance of the MetagraphInfo class containing the data to be applied to the current obj. """ self.name = metagraph_info.name self.symbol = metagraph_info.symbol @@ -1137,11 +1134,11 @@ def load_from_path(self, dir_path: str) -> "MetagraphMixin": """ Loads the metagraph state from a specified directory path. - Args: - dir_path (str): The directory path where the state file is located. + Parameters: + dir_path: The directory path where the state file is located. Returns: - metagraph (bittensor.core.metagraph.AsyncMetagraph): The current metagraph instance with the loaded state. + metagraph: The current metagraph instance with the loaded state. Example: @@ -1263,12 +1260,11 @@ def load_from_path(self, dir_path: str) -> "MetagraphMixin": """ Loads the state of the Metagraph from a specified directory path. - Args: - dir_path (str): The directory path where the metagraph's state file is located. + Parameters: + dir_path: The directory path where the metagraph's state file is located. Returns: - metagraph (:func:`bittensor.core.metagraph.AsyncMetagraph`): An instance of the Metagraph with the state - loaded from the file. + metagraph: An instance of the Metagraph with the state loaded from the file. Raises: pickle.UnpicklingError: If there is an error unpickling the state file. @@ -1367,15 +1363,13 @@ async def sync( reflect the latest data from the network, ensuring the metagraph represents the most current state of the network. - Args: - block (Optional[int]): A specific block number to synchronize with. If None, the metagraph syncs with the - latest block. This allows for historical analysis or specific state examination of the network. - lite (Optional[bool]): If True, a lite version of the metagraph is used for quicker synchronization. This is - beneficial when full detail is not necessary, allowing for reduced computational and time overhead. - Defaults to `True`. - subtensor (Optional[bittensor.core.subtensor.Subtensor]): An instance of the subtensor class from Bittensor, - providing an interface to the underlying blockchain data. If provided, this instance is used for data - retrieval during synchronization. + Parameters: + block: A specific block number to synchronize with. If None, the metagraph syncs with the latest block. This + allows for historical analysis or specific state examination of the network. + lite: If True, a lite version of the metagraph is used for quicker synchronization. This is beneficial when + full detail is not necessary, allowing for reduced computational and time overhead. + subtensor: An instance of the subtensor class from Bittensor, providing an interface to the underlying + blockchain data. If provided, this instance is used for data retrieval during synchronization. Example: Sync the metagraph with the latest block from the subtensor, using the lite version for efficiency:: @@ -1453,13 +1447,12 @@ async def _initialize_subtensor( If no subtensor is provided, this method is responsible for creating a new instance of the subtensor, configured according to the current network settings. - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance provided for - initialization. If ``None``, a new subtensor instance is created using the current network configuration. + Parameters: + subtensor: The subtensor instance provided for initialization. If ``None``, a new subtensor instance is + created using the current network configuration. Returns: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The initialized subtensor instance, ready to be - used for syncing the metagraph. + subtensor: The initialized subtensor instance, ready to be used for syncing the metagraph. Internal Usage: Used internally during the sync process to ensure a valid subtensor instance is available:: @@ -1487,13 +1480,11 @@ async def _assign_neurons( This method is responsible for fetching and setting the neuron data in the metagraph, which includes neuron attributes like UID, stake, trust, and other relevant information. - Args: - block (int): The block number for which the neuron data needs to be fetched. If ``None``, the latest block - data is used. - lite (bool): A boolean flag indicating whether to use a lite version of the neuron data. The lite version - typically includes essential information and is quicker to fetch and process. - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for fetching neuron - data from the network. + Parameters: + block: The block number for which the neuron data needs to be fetched. + lite: A boolean flag indicating whether to use a lite version of the neuron data. The lite version typically + includes essential information and is quicker to fetch and process. + subtensor: The subtensor instance used for fetching neuron data from the network. Internal Usage: Used internally during the sync process to fetch and set neuron data:: @@ -1518,7 +1509,7 @@ async def _set_weights_and_bonds(self, subtensor: "AsyncSubtensor", block: int): processing the raw weight and bond data obtained from the network and converting it into a structured format suitable for the metagraph model. - Args: + Parameters: subtensor: The subtensor instance used for fetching weights and bonds data. If ``None``, the weights and bonds are not updated. @@ -1550,11 +1541,10 @@ async def _process_root_weights( Specifically processes the root weights data for the metagraph. This method is similar to :func:`_process_weights_or_bonds` but is tailored for processing root weights, which have a different structure and significance in the network. - Args: - data (list): The raw root weights data to be processed. - attribute (str): A string indicating the attribute type, here it's typically ``weights``. - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for additional data - and context needed in processing. + Parameters: + data: The raw root weights data to be processed. + attribute: A string indicating the attribute type, here it's typically ``weights``. + subtensor: The subtensor instance used for additional data and context needed in processing. Returns: A tensor parameter encapsulating the processed root weights data. @@ -1689,15 +1679,13 @@ def sync( reflect the latest data from the network, ensuring the metagraph represents the most current state of the network. - Args: - block (Optional[int]): A specific block number to synchronize with. If None, the metagraph syncs with the - latest block. This allows for historical analysis or specific state examination of the network. - lite (Optional[bool]): If True, a lite version of the metagraph is used for quicker synchronization. This is - beneficial when full detail is not necessary, allowing for reduced computational and time overhead. - Defaults to `True`. - subtensor (Optional[bittensor.core.subtensor.Subtensor]): An instance of the subtensor class from Bittensor, - providing an interface to the underlying blockchain data. If provided, this instance is used for data - retrieval during synchronization. + Parameters: + block: A specific block number to synchronize with. If None, the metagraph syncs with the latest block. This + allows for historical analysis or specific state examination of the network. + lite: If True, a lite version of the metagraph is used for quicker synchronization. This is beneficial when + full detail is not necessary, allowing for reduced computational and time overhead. + subtensor: An instance of the subtensor class from Bittensor, providing an interface to the underlying + blockchain data. If provided, this instance is used for data retrieval during synchronization. Example: Sync the metagraph with the latest block from the subtensor, using the lite version for efficiency:: @@ -1775,13 +1763,12 @@ def _initialize_subtensor(self, subtensor: "Subtensor") -> "Subtensor": If no subtensor is provided, this method is responsible for creating a new instance of the subtensor, configured according to the current network settings. - Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor instance provided for - initialization. If ``None``, a new subtensor instance is created using the current network configuration. + Parameters: + subtensor: The subtensor instance provided for initialization. If ``None``, a new subtensor instance is + created using the current network configuration. Returns: - subtensor (bittensor.core.subtensor.Subtensor): The initialized subtensor instance, ready to be - used for syncing the metagraph. + The initialized subtensor instance, ready to be used for syncing the metagraph. Internal Usage: Used internally during the sync process to ensure a valid subtensor instance is available:: @@ -1808,13 +1795,11 @@ def _assign_neurons(self, block: int, lite: bool, subtensor: "Subtensor"): This method is responsible for fetching and setting the neuron data in the metagraph, which includes neuron attributes like UID, stake, trust, and other relevant information. - Args: - block (int): The block number for which the neuron data needs to be fetched. If ``None``, the latest block - data is used. - lite (bool): A boolean flag indicating whether to use a lite version of the neuron data. The lite version - typically includes essential information and is quicker to fetch and process. - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for fetching neuron - data from the network. + Parameters: + block: The block number for which the neuron data needs to be fetched. + lite: A boolean flag indicating whether to use a lite version of the neuron data. The lite version typically + includes essential information and is quicker to fetch and process. + subtensor: The subtensor instance used for fetching neuron data from the network. Internal Usage: Used internally during the sync process to fetch and set neuron data:: @@ -1839,7 +1824,7 @@ def _set_weights_and_bonds(self, block: int, subtensor: "Subtensor"): processing the raw weight and bond data obtained from the network and converting it into a structured format suitable for the metagraph model. - Args: + Parameters: subtensor: The subtensor instance used for fetching weights and bonds data. If ``None``, the weights and bonds are not updated. @@ -1867,11 +1852,10 @@ def _process_root_weights( Specifically processes the root weights data for the metagraph. This method is similar to :func:`_process_weights_or_bonds` but is tailored for processing root weights, which have a different structure and significance in the network. - Args: - data (list): The raw root weights data to be processed. - attribute (str): A string indicating the attribute type, here it's typically ``weights``. - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance used for additional data - and context needed in processing. + Parameters: + data: The raw root weights data to be processed. + attribute: A string indicating the attribute type, here it's typically ``weights``. + subtensor: The subtensor instance used for additional data and context needed in processing. Returns: A tensor parameter encapsulating the processed root weights data. diff --git a/bittensor/core/stream.py b/bittensor/core/stream.py index 628a459501..52dc7f2ccf 100644 --- a/bittensor/core/stream.py +++ b/bittensor/core/stream.py @@ -61,11 +61,10 @@ def __init__( """ Initializes the BTStreamingResponse with the given token streamer model. - Args: - model (bittensor.core.stream.BTStreamingResponseModel): A BTStreamingResponseModel instance containing - the token streamer callable, which is responsible for generating the content of the response. - synapse (bittensor.core.stream.StreamingSynapse): The response Synapse to be used to update the response - headers etc. + Parameters: + model: A BTStreamingResponseModel instance containing the token streamer callable, which is responsible + for generating the content of the response. + synapse: The response Synapse to be used to update the response headers etc. **kwargs: Additional keyword arguments passed to the parent StreamingResponse class. """ super().__init__(content=iter(()), **kwargs) @@ -80,8 +79,8 @@ async def stream_response(self, send: "Send"): content type for event-streaming. It then calls the token streamer to generate the content and sends the response body to the client. - Args: - send (starlette.types.Send): A callable to send the response, provided by the ASGI server. + Parameters: + send: A callable to send the response, provided by the ASGI server. """ headers = [(b"content-type", b"text/event-stream")] + self.raw_headers @@ -101,11 +100,10 @@ async def __call__(self, scope: "Scope", receive: "Receive", send: "Send"): This method is part of the ASGI interface and is called by the ASGI server to handle the request and send the response. It delegates to the :func:`stream_response` method to perform the actual streaming process. - Args: - scope (starlette.types.Scope): The scope of the request, containing information about the client, - server, and request itself. - receive (starlette.types.Receive): A callable to receive the request, provided by the ASGI server. - send (starlette.types.Send): A callable to send the response, provided by the ASGI server. + Parameters: + scope: The scope of the request, containing information about the client, server, and request itself. + receive: A callable to receive the request, provided by the ASGI server. + send: A callable to send the response, provided by the ASGI server. """ await self.stream_response(send) @@ -113,12 +111,12 @@ async def __call__(self, scope: "Scope", receive: "Receive", send: "Send"): async def process_streaming_response(self, response: "ClientResponse"): """ Abstract method that must be implemented by the subclass. - This method should provide logic to handle the streaming response, such as parsing and accumulating data. - It is called as the response is being streamed from the network, and should be implemented to handle the - specific streaming data format and requirements of the subclass. + This method should provide logic to handle the streaming response, such as parsing and accumulating data. It is + called as the response is being streamed from the network, and should be implemented to handle the specific + streaming data format and requirements of the subclass. - Args: - response (aiohttp.ClientResponse): The response object to be processed, typically containing chunks of data. + Parameters: + The response object to be processed, typically containing chunks of data. """ ... @@ -130,8 +128,8 @@ def extract_response_json(self, response: "ClientResponse") -> dict: It is called after the response has been processed and is responsible for retrieving structured data that can be used by the application. - Args: - response (aiohttp.ClientResponse): The response object from which to extract JSON data. + Parameters: + The response object from which to extract JSON data. """ def create_streaming_response( @@ -143,13 +141,12 @@ def create_streaming_response( The token streamer should be implemented to generate the content of the response according to the specific requirements of the subclass. - Args: - token_streamer (Callable[[starlette.types.Send], Awaitable[None]]): A callable that takes a send function - and returns an awaitable. It's responsible for generating the content of the response. + Parameters: + token_streamer: A callable that takes a send function and returns an awaitable. It's responsible for + generating the content of the response. Returns: - BTStreamingResponse (bittensor.core.stream.StreamingSynapse.BTStreamingResponse): The streaming response - object, ready to be sent to the client. + The streaming response object, ready to be sent to the client. """ model_instance = BTStreamingResponseModel(token_streamer=token_streamer) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 25f53bbe31..ea3c72dca0 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -152,17 +152,15 @@ def __init__( """ Initializes an instance of the Subtensor class. - Arguments: + Parameters: network: The network name or type to connect to. config: Configuration object for the AsyncSubtensor instance. log_verbose: Enables or disables verbose logging. fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. - Defaults to `None`. - retry_forever: Whether to retry forever on connection errors. Defaults to `False`. + retry_forever: Whether to retry forever on connection errors. _mock: Whether this is a mock instance. Mainly just for use in testing. archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases - where you are requesting a block that is too old for your current (presumably lite) node. Defaults to - `None` + where you are requesting a block that is too old for your current (presumably lite) node. Raises: Any exceptions raised during the setup, configuration, or connection process. @@ -209,17 +207,15 @@ def _get_substrate( ) -> Union[SubstrateInterface, RetrySyncSubstrate]: """Creates the Substrate instance based on provided arguments. - Arguments: - fallback_endpoints: List of fallback chains endpoints to use if main network isn't available. Defaults to - `None`. - retry_forever: Whether to retry forever on connection errors. Defaults to `False`. + Parameters: + fallback_endpoints: List of fallback chains endpoints to use if main network isn't available. + retry_forever: Whether to retry forever on connection errors. _mock: Whether this is a mock instance. Mainly just for use in testing. archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases - where you are requesting a block that is too old for your current (presumably lite) node. Defaults to - `None` + where you are requesting a block that is too old for your current (presumably lite) node. Returns: - the instance of the SubstrateInterface or RetrySyncSubstrate class. + The instance of the SubstrateInterface or RetrySyncSubstrate class. """ if fallback_endpoints or retry_forever or archive_endpoints: return RetrySyncSubstrate( @@ -249,20 +245,20 @@ def query_constant( ) -> Optional["ScaleObj"]: """ Retrieves a constant from the specified module on the Bittensor blockchain. This function is used to access - fixed parameters or values defined within the blockchain's modules, which are essential for understanding - the network's configuration and rules. + fixed parameters or values defined within the blockchain's modules, which are essential for understanding the + network's configuration and rules. - Args: + Parameters: module_name: The name of the module containing the constant. constant_name: The name of the constant to retrieve. block: The blockchain block number at which to query the constant. Returns: - Optional[async_substrate_interface.types.ScaleObj]: The value of the constant if found, `None` otherwise. + The value of the constant if found, `None` otherwise. Constants queried through this function can include critical network parameters such as inflation rates, - consensus rules, or validation thresholds, providing a deeper understanding of the Bittensor network's - operational parameters. + consensus rules, or validation thresholds, providing a deeper understanding of the Bittensor network's + operational parameters. """ return self.substrate.get_constant( module_name=module_name, @@ -274,25 +270,24 @@ def query_map( self, module: str, name: str, - block: Optional[int] = None, params: Optional[list] = None, + block: Optional[int] = None, ) -> "QueryMapResult": """ Queries map storage from any module on the Bittensor blockchain. This function retrieves data structures that - represent key-value mappings, essential for accessing complex and structured data within the blockchain - modules. + represent key-value mappings, essential for accessing complex and structured data within the blockchain modules. - Args: + Parameters: module: The name of the module from which to query the map storage. name: The specific storage function within the module to query. - block: The blockchain block number at which to perform the query. params: Parameters to be passed to the query. + block: The blockchain block number at which to perform the query. Returns: - result: A data structure representing the map storage if found, `None` otherwise. + A data structure representing the map storage if found, `None` otherwise. This function is particularly useful for retrieving detailed and structured data from various blockchain - modules, offering insights into the network's state and the relationships between its different components. + modules, offering insights into the network's state and the relationships between its different components. """ result = self.substrate.query_map( module=module, @@ -303,22 +298,25 @@ def query_map( return result def query_map_subtensor( - self, name: str, block: Optional[int] = None, params: Optional[list] = None + self, + name: str, + params: Optional[list] = None, + block: Optional[int] = None, ) -> "QueryMapResult": """ Queries map storage from the Subtensor module on the Bittensor blockchain. This function is designed to retrieve - a map-like data structure, which can include various neuron-specific details or network-wide attributes. + a map-like data structure, which can include various neuron-specific details or network-wide attributes. - Args: + Parameters: name: The name of the map storage function to query. - block: The blockchain block number at which to perform the query. params: A list of parameters to pass to the query function. + block: The blockchain block number at which to perform the query. Returns: An object containing the map-like data structure, or `None` if not found. This function is particularly useful for analyzing and understanding complex network structures and - relationships within the Bittensor ecosystem, such as interneuronal connections and stake distributions. + relationships within the Bittensor ecosystem, such as interneuronal connections and stake distributions. """ return self.substrate.query_map( module="SubtensorModule", @@ -331,19 +329,19 @@ def query_module( self, module: str, name: str, - block: Optional[int] = None, params: Optional[list] = None, + block: Optional[int] = None, ) -> Optional[Union["ScaleObj", Any, FixedPoint]]: """ Queries any module storage on the Bittensor blockchain with the specified parameters and block number. This - function is a generic query interface that allows for flexible and diverse data retrieval from various - blockchain modules. + function is a generic query interface that allows for flexible and diverse data retrieval from various + blockchain modules. - Args: - module (str): The name of the module from which to query data. - name (str): The name of the storage function within the module. - block (Optional[int]): The blockchain block number at which to perform the query. - params (Optional[list[object]]): A list of parameters to pass to the query function. + Parameters: + module: The name of the module from which to query data. + name: The name of the storage function within the module. + block: The blockchain block number at which to perform the query. + params: A list of parameters to pass to the query function. Returns: An object containing the requested data if found, `None` otherwise. @@ -367,10 +365,10 @@ def query_runtime_api( ) -> Any: """ Queries the runtime API of the Bittensor blockchain, providing a way to interact with the underlying runtime and - retrieve data encoded in Scale Bytes format. This function is essential for advanced users who need to - interact with specific runtime methods and decode complex data types. + retrieve data encoded in Scale Bytes format. This function is essential for advanced users who need to interact + with specific runtime methods and decode complex data types. - Args: + Parameters: runtime_api: The name of the runtime API to query. method: The specific method within the runtime API to call. params: The parameters to pass to the method call. @@ -380,7 +378,7 @@ def query_runtime_api( The Scale Bytes encoded result from the runtime API call, or `None` if the call fails. This function enables access to the deeper layers of the Bittensor blockchain, allowing for detailed and - specific interactions with the network's runtime environment. + specific interactions with the network's runtime environment. """ block_hash = self.determine_block_hash(block) result = self.substrate.runtime_call(runtime_api, method, params, block_hash) @@ -388,22 +386,25 @@ def query_runtime_api( return result.value def query_subtensor( - self, name: str, block: Optional[int] = None, params: Optional[list] = None + self, + name: str, + params: Optional[list] = None, + block: Optional[int] = None, ) -> Optional[Union["ScaleObj", Any]]: """ Queries named storage from the Subtensor module on the Bittensor blockchain. This function is used to retrieve - specific data or parameters from the blockchain, such as stake, rank, or other neuron-specific attributes. + specific data or parameters from the blockchain, such as stake, rank, or other neuron-specific attributes. - Args: + Parameters: name: The name of the storage function to query. - block: The blockchain block number at which to perform the query. params: A list of parameters to pass to the query function. + block: The blockchain block number at which to perform the query. Returns: - query_response: An object containing the requested data. + An object containing the requested data. This query function is essential for accessing detailed information about the network and its neurons, providing - valuable insights into the state and dynamics of the Bittensor ecosystem. + valuable insights into the state and dynamics of the Bittensor ecosystem. """ return self.substrate.query( module="SubtensorModule", @@ -417,18 +418,18 @@ def state_call( ) -> dict[Any, Any]: """ Makes a state call to the Bittensor blockchain, allowing for direct queries of the blockchain's state. This - function is typically used for advanced queries that require specific method calls and data inputs. + function is typically used for advanced queries that require specific method calls and data inputs. - Args: + Parameters: method: The method name for the state call. data: The data to be passed to the method. block: The blockchain block number at which to perform the state call. Returns: - result (dict[Any, Any]): The result of the rpc call. + The result of the rpc call. The state call function provides a more direct and flexible way of querying blockchain data, useful for specific - use cases where standard queries are insufficient. + use cases where standard queries are insufficient. """ block_hash = self.determine_block_hash(block) return self.substrate.rpc_request( @@ -445,11 +446,11 @@ def all_subnets(self, block: Optional[int] = None) -> Optional[list["DynamicInfo """ Retrieves the subnet information for all subnets in the network. - Args: - block (Optional[int]): The block number to query the subnet information from. + Parameters: + block: The block number to query the subnet information from. Returns: - Optional[DynamicInfo]: A list of DynamicInfo objects, each containing detailed information about a subnet. + A list of DynamicInfo objects, each containing detailed information about a subnet. """ block_hash = self.determine_block_hash(block=block) @@ -475,8 +476,8 @@ def blocks_since_last_step( ) -> Optional[int]: """Returns number of blocks since the last epoch of the subnet. - Arguments: - netuid (int): The unique identifier of the subnetwork. + Parameters: + netuid: The unique identifier of the subnetwork. block: the block number for this query. Returns: @@ -491,13 +492,12 @@ def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int]: """ Returns the number of blocks since the last update for a specific UID in the subnetwork. - Arguments: - netuid (int): The unique identifier of the subnetwork. - uid (int): The unique identifier of the neuron. + Parameters: + netuid: The unique identifier of the subnetwork. + uid: The unique identifier of the neuron. Returns: - Optional[int]: The number of blocks since the last update, or ``None`` if the subnetwork or UID does not - exist. + The number of blocks since the last update, or ``None`` if the subnetwork or UID does not exist. """ call = self.get_hyperparameter(param_name="LastUpdate", netuid=netuid) return None if not call else (self.get_current_block() - int(call[uid])) @@ -546,7 +546,7 @@ def commit_reveal_enabled( """ Check if the commit-reveal mechanism is enabled for a given network at a specific block. - Arguments: + Parameters: netuid: The network identifier for which to check the commit-reveal mechanism. block: The block number to query. @@ -563,17 +563,17 @@ def difficulty(self, netuid: int, block: Optional[int] = None) -> Optional[int]: Retrieves the 'Difficulty' hyperparameter for a specified subnet in the Bittensor network. This parameter is instrumental in determining the computational challenge required for neurons to participate in - consensus and validation processes. + consensus and validation processes. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. Returns: - Optional[int]: The value of the 'Difficulty' hyperparameter if the subnet exists, ``None`` otherwise. + The value of the 'Difficulty' hyperparameter if the subnet exists, ``None`` otherwise. The 'Difficulty' parameter directly impacts the network's security and integrity by setting the computational - effort required for validating transactions and participating in the network's consensus mechanism. + effort required for validating transactions and participating in the network's consensus mechanism. """ call = self.get_hyperparameter( param_name="Difficulty", netuid=netuid, block=block @@ -586,7 +586,7 @@ def does_hotkey_exist(self, hotkey_ss58: str, block: Optional[int] = None) -> bo """ Returns true if the hotkey is known by the chain and there are accounts. - Args: + Parameters: hotkey_ss58: The SS58 address of the hotkey. block: the block number for this query. @@ -611,7 +611,7 @@ def get_admin_freeze_window(self, block: Optional[int] = None) -> int: """ Returns the number of blocks when dependent transactions will be frozen for execution. - Arguments: + Parameters: block: The block number for which the children are to be retrieved. Returns: @@ -627,16 +627,16 @@ def get_admin_freeze_window(self, block: Optional[int] = None) -> int: def get_all_subnets_info(self, block: Optional[int] = None) -> list["SubnetInfo"]: """ Retrieves detailed information about all subnets within the Bittensor network. This function provides - comprehensive data on each subnet, including its characteristics and operational parameters. + comprehensive data on each subnet, including its characteristics and operational parameters. - Arguments: + Parameters: block: The blockchain block number for the query. Returns: - list[SubnetInfo]: A list of SubnetInfo objects, each containing detailed information about a subnet. + A list of SubnetInfo objects, each containing detailed information about a subnet. Gaining insights into the subnets' details assists in understanding the network's composition, the roles of - different subnets, and their unique features. + different subnets, and their unique features. """ result = self.query_runtime_api( runtime_api="SubnetInfoRuntimeApi", @@ -692,9 +692,9 @@ def get_balance(self, address: str, block: Optional[int] = None) -> Balance: """ Retrieves the balance for given coldkey. Always in TAO. - Arguments: + Parameters: address: coldkey address. - block (Optional[int]): The blockchain block number for the query. + block: The blockchain block number for the query. Returns: Balance object in TAO. @@ -715,9 +715,9 @@ def get_balances( """ Retrieves the balance for given coldkey(s) - Arguments: - addresses (str): coldkey addresses(s). - block (Optional[int]): The blockchain block number for the query. + Parameters: + addresses: coldkey addresses(s). + block: The blockchain block number for the query. Returns: Dict of {address: Balance objects}. @@ -760,17 +760,17 @@ def _get_block_hash(self, block_id: int): def get_block_hash(self, block: Optional[int] = None) -> str: """ Retrieves the hash of a specific block on the Bittensor blockchain. The block hash is a unique identifier - representing the cryptographic hash of the block's content, ensuring its integrity and immutability. + representing the cryptographic hash of the block's content, ensuring its integrity and immutability. - Arguments: - block (int): The block number for which the hash is to be retrieved. + Parameters: + block: The block number for which the hash is to be retrieved. Returns: str: The cryptographic hash of the specified block. The block hash is a fundamental aspect of blockchain technology, providing a secure reference to each block's - data. It is crucial for verifying transactions, ensuring data consistency, and maintaining the - trustworthiness of the blockchain. + data. It is crucial for verifying transactions, ensuring data consistency, and maintaining the trustworthiness + of the blockchain. """ if block is not None: return self._get_block_hash(block) @@ -809,9 +809,9 @@ def get_hyperparameter( """ Retrieves a specified hyperparameter for a specific subnet. - Arguments: - param_name (str): The name of the hyperparameter to retrieve. - netuid (int): The unique identifier of the subnet. + Parameters: + param_name: The name of the hyperparameter to retrieve. + netuid: The unique identifier of the subnet. block: the block number at which to retrieve the hyperparameter. Returns: @@ -836,9 +836,9 @@ def get_parents( ) -> list[tuple[float, str]]: """ This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys - storage function to get the children and formats them before returning as a tuple. + storage function to get the children and formats them before returning as a tuple. - Arguments: + Parameters: hotkey: The child hotkey SS58. netuid: The netuid. block: The block number for which the children are to be retrieved. @@ -868,12 +868,12 @@ def get_children( ) -> tuple[bool, list[tuple[float, str]], str]: """ This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys - storage function to get the children and formats them before returning as a tuple. + storage function to get the children and formats them before returning as a tuple. - Arguments: - hotkey (str): The hotkey value. - netuid (int): The netuid value. - block (Optional[int]): The block number for which the children are to be retrieved. + Parameters: + hotkey: The hotkey value. + netuid: The netuid value. + block: The block number for which the children are to be retrieved. Returns: A tuple containing a boolean indicating success or failure, a list of formatted children, and an error @@ -912,14 +912,14 @@ def get_children_pending( This method retrieves the pending children of a given hotkey and netuid. It queries the SubtensorModule's PendingChildKeys storage function. - Arguments: - hotkey (str): The hotkey value. - netuid (int): The netuid value. - block (Optional[int]): The block number for which the children are to be retrieved. + Parameters: + hotkey: The hotkey value. + netuid: The netuid value. + block: The block number for which the children are to be retrieved. Returns: - list[tuple[float, str]]: A list of children with their proportions. - int: The cool-down block number. + - A list of children with their proportions. + - The cool-down block number. """ children, cooldown = self.substrate.query( @@ -944,14 +944,13 @@ def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) -> """ Retrieves the on-chain commitment for a specific neuron in the Bittensor network. - Arguments: - netuid (int): The unique identifier of the subnetwork. - uid (int): The unique identifier of the neuron. - block (Optional[int]): The block number to retrieve the commitment from. If None, the latest block is used. - Default is ``None``. + Parameters: + netuid: The unique identifier of the subnetwork. + uid: The unique identifier of the neuron. + block: The block number to retrieve the commitment from. If None, the latest block is used. Returns: - str: The commitment data as a string. + The commitment data as a string. """ metagraph = self.metagraph(netuid) try: @@ -975,12 +974,12 @@ def get_last_commitment_bonds_reset_block( """ Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. - Arguments: - netuid (int): The unique identifier of the subnetwork. - uid (int): The unique identifier of the neuron. + Parameters: + netuid: The unique identifier of the subnetwork. + uid: The unique identifier of the neuron. Returns: - Optional[int]: The block number when the bonds were last reset, or None if not found. + The block number when the bonds were last reset, or None if not found. """ metagraph = self.metagraph(netuid) @@ -1023,13 +1022,13 @@ def get_revealed_commitment_by_hotkey( ) -> Optional[tuple[tuple[int, str], ...]]: """Returns hotkey related revealed commitment for a given netuid. - Arguments: - netuid (int): The unique identifier of the subnetwork. - hotkey_ss58_address (str): The ss58 address of the committee member. - block (Optional[int]): The block number to retrieve the commitment from. Default is ``None``. + Parameters: + netuid: The unique identifier of the subnetwork. + hotkey_ss58_address: The ss58 address of the committee member. + block: The block number to retrieve the commitment from. Default is ``None``. Returns: - result (tuple[int, str): A tuple of reveal block and commitment message. + A tuple of reveal block and commitment message. """ if not is_valid_ss58_address(address=hotkey_ss58_address): raise ValueError(f"Invalid ss58 address {hotkey_ss58_address} provided.") @@ -1052,13 +1051,13 @@ def get_revealed_commitment( ) -> Optional[tuple[tuple[int, str], ...]]: """Returns uid related revealed commitment for a given netuid. - Arguments: - netuid (int): The unique identifier of the subnetwork. - uid (int): The neuron uid to retrieve the commitment from. - block (Optional[int]): The block number to retrieve the commitment from. Default is ``None``. + Parameters: + netuid: The unique identifier of the subnetwork. + uid: The neuron uid to retrieve the commitment from. + block: The block number to retrieve the commitment from. Default is ``None``. Returns: - result (Optional[tuple[int, str]]: A tuple of reveal block and commitment message. + A tuple of reveal block and commitment message. Example of result: ( (12, "Alice message 1"), (152, "Alice message 2") ) @@ -1082,13 +1081,12 @@ def get_all_revealed_commitments( ) -> dict[str, tuple[tuple[int, str], ...]]: """Returns all revealed commitments for a given netuid. - Arguments: - netuid (int): The unique identifier of the subnetwork. - block (Optional[int]): The block number to retrieve the commitment from. Default is ``None``. + Parameters: + netuid: The unique identifier of the subnetwork. + block: The block number to retrieve the commitment from. Default is ``None``. Returns: - result (dict): A dictionary of all revealed commitments in view - {ss58_address: (reveal block, commitment message)}. + result: A dictionary of all revealed commitments in view {ss58_address: (reveal block, commitment message)}. Example of result: { @@ -1118,9 +1116,9 @@ def get_current_weight_commit_info( """ Retrieves CRV3 weight commit information for a specific subnet. - Arguments: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. Default is ``None``. + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Default is ``None``. Returns: A list of commit details, where each item contains: @@ -1129,7 +1127,6 @@ def get_current_weight_commit_info( - reveal_round: The round when the commitment was revealed. The list may be empty if there are no commits found. - """ deprecated_message( message="The method `get_current_weight_commit_info` is deprecated and will be removed in version 10.0.0. " @@ -1152,9 +1149,9 @@ def get_current_weight_commit_info_v2( """ Retrieves CRV3 weight commit information for a specific subnet. - Arguments: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. Default is ``None``. + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Default is ``None``. Returns: A list of commit details, where each item contains: @@ -1163,7 +1160,7 @@ def get_current_weight_commit_info_v2( - commit_message: The commit message. - reveal_round: The round when the commitment was revealed. - The list may be empty if there are no commits found. + The list may be empty if there are no commits found. """ result = self.substrate.query_map( module="SubtensorModule", @@ -1180,17 +1177,17 @@ def get_delegate_by_hotkey( ) -> Optional["DelegateInfo"]: """ Retrieves detailed information about a delegate neuron based on its hotkey. This function provides a - comprehensive view of the delegate's status, including its stakes, nominators, and reward distribution. + comprehensive view of the delegate's status, including its stakes, nominators, and reward distribution. - Arguments: - hotkey_ss58 (str): The ``SS58`` address of the delegate's hotkey. - block (Optional[int]): The blockchain block number for the query. + Parameters: + hotkey_ss58: The ``SS58`` address of the delegate's hotkey. + block: The blockchain block number for the query. Returns: - Optional[DelegateInfo]: Detailed information about the delegate neuron, ``None`` if not found. + Detailed information about the delegate neuron, ``None`` if not found. This function is essential for understanding the roles and influence of delegate neurons within the Bittensor - network's consensus and governance structures. + network's consensus and governance structures. """ result = self.query_runtime_api( @@ -1211,8 +1208,8 @@ def get_delegate_identities( """ Fetches delegates identities from the chain. - Arguments: - block (Optional[int]): The blockchain block number for the query. + Parameters: + block: The blockchain block number for the query. Returns: Dict {ss58: ChainIdentity, ...} @@ -1234,17 +1231,17 @@ def get_delegate_identities( def get_delegate_take(self, hotkey_ss58: str, block: Optional[int] = None) -> float: """ Retrieves 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. + percentage of rewards that the delegate claims from its nominators' stakes. - Arguments: - hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. - block (Optional[int]): The blockchain block number for the query. + Parameters: + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + block: The blockchain block number for the query. Returns: float: The delegate take percentage. The delegate take is a critical parameter in the network's incentive structure, influencing the distribution of - rewards among neurons and their nominators. + rewards among neurons and their nominators. """ result = self.query_subtensor( name="Delegates", @@ -1261,15 +1258,15 @@ def get_delegated( Retrieves a list of delegates and their associated stakes for a given coldkey. This function identifies the delegates that a specific account has staked tokens on. - Arguments: - coldkey_ss58 (str): The `SS58` address of the account's coldkey. - block (Optional[int]): The blockchain block number for the query. + Parameters: + coldkey_ss58: The `SS58` address of the account's coldkey. + block: The blockchain block number for the query. Returns: A list containing the delegated information for the specified coldkey. This function is important for account holders to understand their stake allocations and their involvement in - the network's delegation and consensus mechanisms. + the network's delegation and consensus mechanisms. """ result = self.query_runtime_api( @@ -1288,8 +1285,8 @@ def get_delegates(self, block: Optional[int] = None) -> list["DelegateInfo"]: """ Fetches all delegates on the chain - Arguments: - block (Optional[int]): The blockchain block number for the query. + Parameters: + block: The blockchain block number for the query. Returns: List of DelegateInfo objects, or an empty list if there are no delegates. @@ -1311,14 +1308,14 @@ def get_existential_deposit(self, block: Optional[int] = None) -> Optional[Balan The existential deposit is the minimum amount of TAO required for an account to exist on the blockchain. Accounts with balances below this threshold can be reaped to conserve network resources. - Arguments: - block (Optional[int]): The blockchain block number for the query. + Parameters: + block: The blockchain block number for the query. Returns: The existential deposit amount. Always in TAO. The existential deposit is a fundamental economic parameter in the Bittensor network, ensuring efficient use of - storage and preventing the proliferation of dust accounts. + storage and preventing the proliferation of dust accounts. """ result = self.substrate.get_constant( module_name="Balances", @@ -1337,14 +1334,14 @@ def get_hotkey_owner( """ Retrieves the owner of the given hotkey at a specific block hash. This function queries the blockchain for the owner of the provided hotkey. If the hotkey does not exist at the - specified block hash, it returns None. + specified block hash, it returns None. - Arguments: - hotkey_ss58 (str): The SS58 address of the hotkey. - block (Optional[int]): The blockchain block number for the query. + Parameters: + hotkey_ss58: The SS58 address of the hotkey. + block: The blockchain block number for the query. Returns: - Optional[str]: The SS58 address of the owner if the hotkey exists, or None if it doesn't. + The SS58 address of the owner if the hotkey exists, or None if it doesn't. """ hk_owner_query = self.substrate.query( module="SubtensorModule", @@ -1382,7 +1379,7 @@ def get_metagraph_info( """ Retrieves full or partial metagraph information for the specified subnet mechanism (netuid, mechid). - Arguments: + Parameters: netuid: Subnet unique identifier. field_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. If not provided, all available fields will be returned. @@ -1480,9 +1477,9 @@ def get_netuids_for_hotkey( Retrieves a list of subnet UIDs (netuids) for which a given hotkey is a member. This function identifies the specific subnets within the Bittensor network where the neuron associated with the hotkey is active. - Arguments: - hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. - block (Optional[int]): The blockchain block number for the query. + Parameters: + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + block: The blockchain block number for the query. Returns: A list of netuids where the neuron is a member. @@ -1504,10 +1501,10 @@ def get_neuron_certificate( self, hotkey: str, netuid: int, block: Optional[int] = None ) -> Optional[Certificate]: """ - Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a - specified subnet (netuid) of the Bittensor network. + Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a specified + subnet (netuid) of the Bittensor network. - Arguments: + Parameters: hotkey: The hotkey to query. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. @@ -1537,7 +1534,7 @@ def get_all_neuron_certificates( """ Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. @@ -1567,7 +1564,7 @@ def get_liquidity_list( Retrieves all liquidity positions for the given wallet on a specified subnet (netuid). Calculates associated fee rewards based on current global and tick-level fee data. - Args: + Parameters: wallet: Wallet instance to fetch positions for. netuid: Subnet unique id. block: The blockchain block number for the query. @@ -1735,20 +1732,19 @@ def get_neuron_for_pubkey_and_subnet( ) -> Optional["NeuronInfo"]: """ Retrieves information about a neuron based on its public key (hotkey SS58 address) and the specific subnet UID - (netuid). This function provides detailed neuron information for a particular subnet within the Bittensor - network. + (netuid). This function provides detailed neuron information for a particular subnet within the Bittensor + network. - Arguments: - hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. + Parameters: + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Returns: - Optional[bittensor.core.chain_data.neuron_info.NeuronInfo]: Detailed information about the neuron if found, - ``None`` otherwise. + Optional: Detailed information about the neuron if found, ``None`` otherwise. This function is crucial for accessing specific neuron data and understanding its status, stake, and other - attributes within a particular subnet of the Bittensor ecosystem. + attributes within a particular subnet of the Bittensor ecosystem. """ block_hash = self.determine_block_hash(block) uid = self.substrate.query( @@ -1782,10 +1778,9 @@ def get_next_epoch_start_block( determined based on the subnet's tempo (i.e., blocks per epoch). The result is the block number at which the next epoch will begin. - Args: - netuid (int): The unique identifier of the subnet. - block (Optional[int], optional): The reference block to calculate from. - If None, uses the current chain block height. + Parameters: + netuid: The unique identifier of the subnet. + block: The reference block to calculate from. If None, uses the current chain block height. Returns: int: The block number at which the next epoch will start. @@ -1807,10 +1802,10 @@ def get_owned_hotkeys( """ Retrieves all hotkeys owned by a specific coldkey address. - Args: - coldkey_ss58 (str): The SS58 address of the coldkey to query. - block (int): The blockchain block number for the query. - reuse_block (bool): Whether to reuse the last-used blockchain block hash. + Parameters: + coldkey_ss58: The SS58 address of the coldkey to query. + block: The blockchain block number for the query. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: list[str]: A list of hotkey SS58 addresses owned by the coldkey. @@ -1836,12 +1831,11 @@ def get_stake( Returns the amount of Alpha staked by a specific coldkey to a specific hotkey within a given subnet. This function retrieves the delegated stake balance, referred to as the 'Alpha' value. - Args: + Parameters: coldkey_ss58: The SS58 address of the coldkey that delegated the stake. This address owns the stake. hotkey_ss58: The ss58 address of the hotkey which the stake is on. netuid: The unique identifier of the subnet to query. - block: The specific block number at which to retrieve the stake information. If None, the current stake at - the latest block is returned. Defaults to ``None``. + block: The specific block number at which to retrieve the stake information. Returns: An object representing the amount of Alpha (TAO ONLY if the subnet's netuid is 0) currently staked from the @@ -1893,7 +1887,7 @@ def get_stake_add_fee( """ Calculates the fee for adding new stake to a hotkey. - Args: + Parameters: amount: Amount of stake to add in TAO netuid: Netuid of subnet coldkey_ss58: SS58 address of coldkey @@ -1960,7 +1954,7 @@ def get_subnet_info( Retrieves detailed information about subnet within the Bittensor network. This function provides comprehensive data on subnet, including its characteristics and operational parameters. - Arguments: + Parameters: netuid: The unique identifier of the subnet. block: The blockchain block number for the query. @@ -1968,7 +1962,7 @@ def get_subnet_info( SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet. Gaining insights into the subnet's details assists in understanding the network's composition, the roles of - different subnets, and their unique features. + different subnets, and their unique features. """ result = self.query_runtime_api( runtime_api="SubnetInfoRuntimeApi", @@ -2013,7 +2007,7 @@ def get_subnet_prices( ) -> dict[int, Balance]: """Gets the current Alpha price in TAO for a specified subnet. - Args: + Parameters: block: The blockchain block number for the query. Default to `None`. Returns: @@ -2088,7 +2082,7 @@ def get_unstake_fee( """ Calculates the fee for unstaking from a hotkey. - Args: + Parameters: amount: Amount of stake to unstake in TAO netuid: Netuid of subnet coldkey_ss58: SS58 address of coldkey @@ -2115,7 +2109,7 @@ def get_stake_movement_fee( """ Calculates the fee for moving stake between hotkeys/subnets/coldkeys. - Args: + Parameters: amount: Amount of stake to move in TAO origin_netuid: Netuid of origin subnet origin_hotkey_ss58: SS58 address of origin hotkey @@ -2142,11 +2136,11 @@ def get_stake_for_coldkey_and_hotkey( """ Retrieves all coldkey-hotkey pairing stake across specified (or all) subnets - Arguments: - coldkey_ss58 (str): The SS58 address of the coldkey. - hotkey_ss58 (str): The SS58 address of the hotkey. - netuids (Optional[list[int]]): The subnet IDs to query for. Set to `None` for all subnets. - block (Optional[int]): The block number at which to query the stake information. + Parameters: + coldkey_ss58: The SS58 address of the coldkey. + hotkey_ss58: The SS58 address of the hotkey. + netuids: The subnet IDs to query for. Set to `None` for all subnets. + block: The block number at which to query the stake information. Returns: A {netuid: StakeInfo} pairing of all stakes across all subnets. @@ -2175,12 +2169,12 @@ def get_stake_for_coldkey( """ Retrieves the stake information for a given coldkey. - Args: - coldkey_ss58 (str): The SS58 address of the coldkey. - block (Optional[int]): The block number at which to query the stake information. + Parameters: + coldkey_ss58 The SS58 address of the coldkey. + block: The block number at which to query the stake information. Returns: - Optional[list[StakeInfo]]: A list of StakeInfo objects, or ``None`` if no stake information is found. + OA list of StakeInfo objects, or ``None`` if no stake information is found. """ result = self.query_runtime_api( runtime_api="StakeInfoRuntimeApi", @@ -2202,11 +2196,10 @@ def get_stake_for_hotkey( """ Retrieves the stake information for a given hotkey. - Args: + Parameters: hotkey_ss58: The SS58 address of the hotkey. netuid: The subnet ID to query for. - block: The block number at which to query the stake information. Do not specify if also specifying - block_hash or reuse_block + block: The block number at which to query the stake information. """ hotkey_alpha_query = self.query_subtensor( name="TotalHotkeyAlpha", params=[hotkey_ss58, netuid], block=block @@ -2226,7 +2219,7 @@ def get_stake_operations_fee( ): """Returns fee for any stake operation in specified subnet. - Args: + Parameters: amount: Amount of stake to add in Alpha/TAO. netuid: Netuid of subnet. block: Block number at which to perform the calculation. @@ -2247,7 +2240,7 @@ def get_stake_weight(self, netuid: int, block: Optional[int] = None) -> list[flo """ Retrieves the stake weight for all hotkeys in a given subnet. - Arguments: + Parameters: netuid: Netuid of subnet. block: Block number at which to perform the calculation. @@ -2266,16 +2259,16 @@ def get_stake_weight(self, netuid: int, block: Optional[int] = None) -> list[flo def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[Balance]: """ Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the - amount of Tao that needs to be locked or burned to establish a new subnet. + amount of Tao that needs to be locked or burned to establish a new subnet. - Arguments: - block (Optional[int]): The blockchain block number for the query. + Parameters: + block: The blockchain block number for the query. Returns: int: The burn cost for subnet registration. - The subnet burn cost is an important economic parameter, reflecting the network's mechanisms for controlling - the proliferation of subnets and ensuring their commitment to the network's long-term viability. + The subnet burn cost is an important economic parameter, reflecting the network's mechanisms for controlling the + proliferation of subnets and ensuring their commitment to the network's long-term viability. """ lock_cost = self.query_runtime_api( runtime_api="SubnetRegistrationRuntimeApi", @@ -2294,17 +2287,17 @@ def get_subnet_hyperparameters( ) -> Optional[Union[list, "SubnetHyperparameters"]]: """ Retrieves the hyperparameters for a specific subnet within the Bittensor network. These hyperparameters define - the operational settings and rules governing the subnet's behavior. + the operational settings and rules governing the subnet's behavior. - Arguments: - netuid (int): The network UID of the subnet to query. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The network UID of the subnet to query. + block: The blockchain block number for the query. Returns: The subnet's hyperparameters, or `None` if not available. Understanding the hyperparameters is crucial for comprehending how subnets are configured and managed, and how - they interact with the network's consensus and incentive mechanisms. + they interact with the network's consensus and incentive mechanisms. """ result = self.query_runtime_api( runtime_api="SubnetInfoRuntimeApi", @@ -2333,8 +2326,8 @@ def get_subnets(self, block: Optional[int] = None) -> UIDs: """ Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network. - Arguments: - block (Optional[int]): The blockchain block number for the query. + Parameters: + block: The blockchain block number for the query. Returns: A list of subnet netuids. @@ -2358,14 +2351,14 @@ def get_total_subnets(self, block: Optional[int] = None) -> Optional[int]: """ Retrieves the total number of subnets within the Bittensor network as of a specific blockchain block. - Arguments: - block (Optional[int]): The blockchain block number for the query. + Parameters: + block: The blockchain block number for the query. Returns: - Optional[str]: The total number of subnets in the network. + The total number of subnets in the network. Understanding the total number of subnets is essential for assessing the network's growth and the extent of its - decentralized infrastructure. + decentralized infrastructure. """ result = self.substrate.query( module="SubtensorModule", @@ -2384,24 +2377,22 @@ def get_transfer_fee( ) -> Balance: """ Calculates the transaction fee for transferring tokens from a wallet to a specified destination address. This - function simulates the transfer to estimate the associated cost, taking into account the current network - conditions and transaction complexity. - - Arguments: - wallet (bittensor_wallet.Wallet): The wallet from which the transfer is initiated. - dest (str): The ``SS58`` address of the destination account. - value (Union[bittensor.utils.balance.Balance, float, int]): The amount of tokens to be transferred, - specified as a Balance object, or in Tao (float) or Rao (int) units. + function simulates the transfer to estimate the associated cost, taking into account the current network + conditions and transaction complexity. + + Parameters: + wallet: The wallet from which the transfer is initiated. + dest: The ``SS58`` address of the destination account. + value: The amount of tokens to be transferred, specified as a Balance object, or in Tao or Rao units. keep_alive: Whether the transfer fee should be calculated based on keeping the wallet alive (existential deposit) or not. Returns: - bittensor.utils.balance.Balance: The estimated transaction fee for the transfer, represented as a Balance - object. + The estimated transaction fee for the transfer, represented as a Balance object. Estimating the transfer fee is essential for planning and executing token transactions, ensuring that the wallet - has sufficient funds to cover both the transfer amount and the associated costs. This function provides a - crucial tool for managing financial operations within the Bittensor network. + has sufficient funds to cover both the transfer amount and the associated costs. This function provides a + crucial tool for managing financial operations within the Bittensor network. """ if value is not None: value = check_and_convert_to_balance(value) @@ -2429,17 +2420,17 @@ def get_vote_data( ) -> Optional["ProposalVoteData"]: """ Retrieves the voting data for a specific proposal on the Bittensor blockchain. This data includes information - about how senate members have voted on the proposal. + about how senate members have voted on the proposal. - Arguments: - proposal_hash (str): The hash of the proposal for which voting data is requested. - block (Optional[int]): The blockchain block number for the query. + Parameters: + proposal_hash: The hash of the proposal for which voting data is requested. + block: The blockchain block number for the query. Returns: An object containing the proposal's voting data, or `None` if not found. This function is important for tracking and understanding the decision-making processes within the Bittensor - network, particularly how proposals are received and acted upon by the governing body. + network, particularly how proposals are received and acted upon by the governing body. """ vote_data: dict[str, Any] = self.substrate.query( module="Triumvirate", @@ -2459,16 +2450,16 @@ def get_uid_for_hotkey_on_subnet( """ Retrieves the unique identifier (UID) for a neuron's hotkey on a specific subnet. - Arguments: - hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. + Parameters: + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Returns: - Optional[int]: The UID of the neuron if it is registered on the subnet, ``None`` otherwise. + The UID of the neuron if it is registered on the subnet, ``None`` otherwise. The UID is a critical identifier within the network, linking the neuron's hotkey to its operational and - governance activities on a particular subnet. + governance activities on a particular subnet. """ result = self.substrate.query( module="SubtensorModule", @@ -2488,11 +2479,11 @@ def filter_netuids_by_registered_hotkeys( """ Filters a given list of all netuids for certain specified netuids and hotkeys - Arguments: - all_netuids (Iterable[int]): A list of netuids to filter. - filter_for_netuids (Iterable[int]): A subset of all_netuids to filter from the main list. - all_hotkeys (Iterable[Wallet]): Hotkeys to filter from the main list. - block (Optional[int]): The blockchain block number for the query. + Parameters: + all_netuids: A list of netuids to filter. + filter_for_netuids: A subset of all_netuids to filter from the main list. + all_hotkeys: Hotkeys to filter from the main list. + block: The blockchain block number for the query. Returns: The filtered list of netuids. @@ -2534,18 +2525,18 @@ def immunity_period( ) -> Optional[int]: """ Retrieves the 'ImmunityPeriod' hyperparameter for a specific subnet. This parameter defines the duration during - which new neurons are protected from certain network penalties or restrictions. + which new neurons are protected from certain network penalties or restrictions. - Args: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Returns: - Optional[int]: The value of the 'ImmunityPeriod' hyperparameter if the subnet exists, ``None`` otherwise. + The value of the 'ImmunityPeriod' hyperparameter if the subnet exists, ``None`` otherwise. The 'ImmunityPeriod' is a critical aspect of the network's governance system, ensuring that new participants - have a grace period to establish themselves and contribute to the network without facing immediate - punitive actions. + have a grace period to establish themselves and contribute to the network without facing immediate punitive + actions. """ call = self.get_hyperparameter( param_name="ImmunityPeriod", netuid=netuid, block=block @@ -2590,17 +2581,17 @@ def is_fast_blocks(self): def is_hotkey_delegate(self, hotkey_ss58: str, block: Optional[int] = None) -> bool: """ Determines whether a given hotkey (public key) is a delegate on the Bittensor network. This function checks if - the neuron associated with the hotkey is part of the network's delegation system. + the neuron associated with the hotkey is part of the network's delegation system. - Arguments: - hotkey_ss58 (str): The SS58 address of the neuron's hotkey. - block (Optional[int]): The blockchain block number for the query. + Parameters: + hotkey_ss58: The SS58 address of the neuron's hotkey. + block: The blockchain block number for the query. Returns: `True` if the hotkey is a delegate, `False` otherwise. Being a delegate is a significant status within the Bittensor network, indicating a neuron's involvement in - consensus and governance processes. + consensus and governance processes. """ delegates = self.get_delegates(block) return hotkey_ss58 in [info.hotkey_ss58 for info in delegates] @@ -2613,23 +2604,22 @@ def is_hotkey_registered( ) -> bool: """ Determines whether a given hotkey (public key) is registered in the Bittensor network, either globally across - any subnet or specifically on a specified subnet. This function checks the registration status of a neuron - identified by its hotkey, which is crucial for validating its participation and activities within the - network. + any subnet or specifically on a specified subnet. This function checks the registration status of a neuron + identified by its hotkey, which is crucial for validating its participation and activities within the network. - Args: + Parameters: hotkey_ss58: The SS58 address of the neuron's hotkey. - netuid: The unique identifier of the subnet to check the registration. If `None`, the - registration is checked across all subnets. + netuid: The unique identifier of the subnet to check the registration. If `None`, the registration is + checked across all subnets. block: The blockchain block number at which to perform the query. Returns: - bool: `True` if the hotkey is registered in the specified context (either any subnet or a specific subnet), + `True` if the hotkey is registered in the specified context (either any subnet or a specific subnet), `False` otherwise. This function is important for verifying the active status of neurons in the Bittensor network. It aids in - understanding whether a neuron is eligible to participate in network processes such as consensus, - validation, and incentive distribution based on its registration status. + understanding whether a neuron is eligible to participate in network processes such as consensus, validation, + and incentive distribution based on its registration status. """ if netuid is None: return self.is_hotkey_registered_any(hotkey_ss58, block) @@ -2644,12 +2634,12 @@ def is_hotkey_registered_any( """ Checks if a neuron's hotkey is registered on any subnet within the Bittensor network. - Arguments: - hotkey_ss58 (str): The ``SS58`` address of the neuron's hotkey. - block (Optional[int]): The blockchain block number for the query. + Parameters: + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + block: The blockchain block number for the query. Returns: - bool: ``True`` if the hotkey is registered on any subnet, False otherwise. + ``True`` if the hotkey is registered on any subnet, False otherwise. This function is essential for determining the network-wide presence and participation of a neuron. """ @@ -2668,9 +2658,9 @@ def is_hotkey_registered_on_subnet( def is_subnet_active(self, netuid: int, block: Optional[int] = None) -> bool: """Verify if subnet with provided netuid is active. - Args: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Returns: True if subnet is active, False otherwise. @@ -2702,13 +2692,13 @@ def max_weight_limit( """ Returns network MaxWeightsLimit hyperparameter. - Args: - netuid (int): The unique identifier of the subnetwork. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnetwork. + block: The blockchain block number for the query. Returns: - Optional[float]: The value of the MaxWeightsLimit hyperparameter, or ``None`` if the subnetwork does not - exist or the parameter is not found. + The value of the MaxWeightsLimit hyperparameter, or ``None`` if the subnetwork does not exist or the + parameter is not found. """ call = self.get_hyperparameter( param_name="MaxWeightsLimit", netuid=netuid, block=block @@ -2757,13 +2747,13 @@ def min_allowed_weights( """ Returns network MinAllowedWeights hyperparameter. - Args: - netuid (int): The unique identifier of the subnetwork. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnetwork. + block: The blockchain block number for the query. Returns: - Optional[int]: The value of the MinAllowedWeights hyperparameter, or ``None`` if the subnetwork does not - exist or the parameter is not found. + The value of the MinAllowedWeights hyperparameter, or ``None`` if the subnetwork does not exist or the + parameter is not found. """ call = self.get_hyperparameter( param_name="MinAllowedWeights", netuid=netuid, block=block @@ -2775,19 +2765,19 @@ def neuron_for_uid( ) -> "NeuronInfo": """ Retrieves detailed information about a specific neuron identified by its unique identifier (UID) within a - specified subnet (netuid) of the Bittensor network. This function provides a comprehensive view of a - neuron's attributes, including its stake, rank, and operational status. + specified subnet (netuid) of the Bittensor network. This function provides a comprehensive view of a neuron's + attributes, including its stake, rank, and operational status. - Arguments: - uid (int): The unique identifier of the neuron. - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. + Parameters: + uid: The unique identifier of the neuron. + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Returns: Detailed information about the neuron if found, a null neuron otherwise This function is crucial for analyzing individual neurons' contributions and status within a specific subnet, - offering insights into their roles in the network's consensus and validation mechanisms. + offering insights into their roles in the network's consensus and validation mechanisms. """ if uid is None: return NeuronInfo.get_null_neuron() @@ -2808,17 +2798,17 @@ def neurons(self, netuid: int, block: Optional[int] = None) -> list["NeuronInfo" """ Retrieves a list of all neurons within a specified subnet of the Bittensor network. This function provides a snapshot of the subnet's neuron population, including each neuron's attributes and - network interactions. + network interactions. - Arguments: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Returns: A list of NeuronInfo objects detailing each neuron's characteristics in the subnet. Understanding the distribution and status of neurons within a subnet is key to comprehending the network's - decentralized structure and the dynamics of its consensus and governance processes. + decentralized structure and the dynamics of its consensus and governance processes. """ result = self.query_runtime_api( runtime_api="NeuronInfoRuntimeApi", @@ -2838,17 +2828,17 @@ def neurons_lite( """ Retrieves a list of neurons in a 'lite' format from a specific subnet of the Bittensor network. This function provides a streamlined view of the neurons, focusing on key attributes such as stake and network - participation. + participation. - Arguments: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Returns: A list of simplified neuron information for the subnet. This function offers a quick overview of the neuron population within a subnet, facilitating efficient analysis - of the network's decentralized structure and neuron dynamics. + of the network's decentralized structure and neuron dynamics. """ result = self.query_runtime_api( runtime_api="NeuronInfoRuntimeApi", @@ -2867,23 +2857,22 @@ def query_identity( ) -> Optional[ChainIdentity]: """ Queries the identity of a neuron on the Bittensor blockchain using the given key. This function retrieves - detailed identity information about a specific neuron, which is a crucial aspect of the network's - decentralized identity and governance system. + detailed identity information about a specific neuron, which is a crucial aspect of the network's decentralized + identity and governance system. - Arguments: - coldkey_ss58 (str): The coldkey used to query the neuron's identity (technically the neuron's coldkey SS58 - address). - block (Optional[int]): The blockchain block number for the query. + Parameters: + coldkey_ss58: Coldkey used to query the neuron's identity (technically the neuron's coldkey SS58 address). + block: The blockchain block number for the query. Returns: An object containing the identity information of the neuron if found, ``None`` otherwise. The identity information can include various attributes such as the neuron's stake, rank, and other - network-specific details, providing insights into the neuron's role and status within the Bittensor network. + network-specific details, providing insights into the neuron's role and status within the Bittensor network. Note: See the `Bittensor CLI documentation `_ for supported identity - parameters. + parameters. """ identity_info = cast( dict, @@ -2908,14 +2897,14 @@ def query_identity( def recycle(self, netuid: int, block: Optional[int] = None) -> Optional[Balance]: """ Retrieves the 'Burn' hyperparameter for a specified subnet. The 'Burn' parameter represents the amount of Tao - that is effectively recycled within the Bittensor network. + that is effectively recycled within the Bittensor network. - Args: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Returns: - Optional[Balance]: The value of the 'Burn' hyperparameter if the subnet exists, None otherwise. + The value of the 'Burn' hyperparameter if the subnet exists, None otherwise. Understanding the 'Burn' rate is essential for analyzing the network registration usage, particularly how it is correlated with user activity and the overall cost of participation in a given subnet. @@ -2927,13 +2916,12 @@ def subnet(self, netuid: int, block: Optional[int] = None) -> Optional[DynamicIn """ Retrieves the subnet information for a single subnet in the network. - Args: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The block number to query the subnet information from. + Parameters: + netuid: The unique identifier of the subnet. + block: The block number to query the subnet information from. Returns: - Optional[DynamicInfo]: A DynamicInfo object, containing detailed information about a subnet. - + A DynamicInfo object, containing detailed information about a subnet. """ block_hash = self.determine_block_hash(block=block) @@ -2956,15 +2944,15 @@ def subnet_exists(self, netuid: int, block: Optional[int] = None) -> bool: """ Checks if a subnet with the specified unique identifier (netuid) exists within the Bittensor network. - Arguments: - netuid (int): The unique identifier of the subnet. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. Returns: `True` if the subnet exists, `False` otherwise. - This function is critical for verifying the presence of specific subnets in the network, - enabling a deeper understanding of the network's structure and composition. + This function is critical for verifying the presence of specific subnets in the network, enabling a deeper + understanding of the network's structure and composition. """ result = self.substrate.query( module="SubtensorModule", @@ -2978,13 +2966,13 @@ def subnetwork_n(self, netuid: int, block: Optional[int] = None) -> Optional[int """ Returns network SubnetworkN hyperparameter. - Args: - netuid (int): The unique identifier of the subnetwork. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnetwork. + block: The blockchain block number for the query. Returns: - Optional[int]: The value of the SubnetworkN hyperparameter, or ``None`` if the subnetwork does not exist or - the parameter is not found. + The value of the SubnetworkN hyperparameter, or ``None`` if the subnetwork does not exist or the parameter + is not found. """ call = self.get_hyperparameter( param_name="SubnetworkN", netuid=netuid, block=block @@ -2995,13 +2983,13 @@ def tempo(self, netuid: int, block: Optional[int] = None) -> Optional[int]: """ Returns network Tempo hyperparameter. - Args: - netuid (int): The unique identifier of the subnetwork. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnetwork. + block: The blockchain block number for the query. Returns: - Optional[int]: The value of the Tempo hyperparameter, or ``None`` if the subnetwork does not exist or the - parameter is not found. + The value of the Tempo hyperparameter, or ``None`` if the subnetwork does not exist or the parameter is not + found. """ call = self.get_hyperparameter(param_name="Tempo", netuid=netuid, block=block) return None if call is None else int(call) @@ -3011,11 +2999,11 @@ def tx_rate_limit(self, block: Optional[int] = None) -> Optional[int]: Retrieves the transaction rate limit for the Bittensor network as of a specific blockchain block. This rate limit sets the maximum number of transactions that can be processed within a given time frame. - Args: - block (Optional[int]): The blockchain block number for the query. + Parameters: + block: The blockchain block number for the query. Returns: - Optional[int]: The transaction rate limit of the network, None if not available. + The transaction rate limit of the network, None if not available. The transaction rate limit is an essential parameter for ensuring the stability and scalability of the Bittensor network. It helps in managing network load and preventing congestion, thereby maintaining efficient and @@ -3029,11 +3017,11 @@ def wait_for_block(self, block: Optional[int] = None): Waits until a specific block is reached on the chain. If no block is specified, waits for the next block. - Args: - block (Optional[int]): The block number to wait for. If None, waits for the next block. + Parameters: + block: The block number to wait for. If None, waits for the next block. Returns: - bool: True if the target block was reached, False if timeout occurred. + True if the target block was reached, False if timeout occurred. Example: import bittensor as bt @@ -3073,18 +3061,18 @@ def weights( """ Retrieves the weight distribution set by neurons within a specific subnet of the Bittensor network. This function maps each neuron's UID to the weights it assigns to other neurons, reflecting the network's trust - and value assignment mechanisms. + and value assignment mechanisms. - Arguments: - netuid (int): The network UID of the subnet to query. - block (Optional[int]): Block number for synchronization, or ``None`` for the latest block. + Parameters: + netuid: The network UID of the subnet to query. + block: Block number for synchronization, or ``None`` for the latest block. mechid: Subnet mechanism identifier. Returns: A list of tuples mapping each neuron's UID to its assigned weights. The weight distribution is a key factor in the network's consensus algorithm and the ranking of neurons, - influencing their influence and reward allocation within the subnet. + influencing their influence and reward allocation within the subnet. """ storage_index = get_mechid_storage_index(netuid, mechid) w_map_encoded = self.substrate.query_map( @@ -3103,13 +3091,13 @@ def weights_rate_limit( """ Returns network WeightsSetRateLimit hyperparameter. - Arguments: - netuid (int): The unique identifier of the subnetwork. - block (Optional[int]): The blockchain block number for the query. + Parameters: + netuid: The unique identifier of the subnetwork. + block: The blockchain block number for the query. Returns: - Optional[int]: The value of the WeightsSetRateLimit hyperparameter, or ``None`` if the subnetwork does not - exist or the parameter is not found. + The value of the WeightsSetRateLimit hyperparameter, or ``None`` if the subnetwork does not exist or the + parameter is not found. """ call = self.get_hyperparameter( param_name="WeightsSetRateLimit", netuid=netuid, block=block @@ -3120,7 +3108,7 @@ def get_timestamp(self, block: Optional[int] = None) -> datetime: """ Retrieves the datetime timestamp for a given block - Arguments: + Parameters: block: The blockchain block number for the query. Returns: @@ -3138,7 +3126,7 @@ def get_subnet_owner_hotkey( This function queries the subtensor network to fetch the hotkey of the owner of a subnet specified by its netuid. If no data is found or the query fails, the function returns None. - Arguments: + Parameters: netuid: The network UID of the subnet to fetch the owner's hotkey for. block: The specific block number to query the data from. @@ -3155,7 +3143,7 @@ def get_subnet_validator_permits( """ Retrieves the list of validator permits for a given subnet as boolean values. - Arguments: + Parameters: netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. diff --git a/bittensor/core/subtensor_api/__init__.py b/bittensor/core/subtensor_api/__init__.py index dbf261c341..63bfd137c3 100644 --- a/bittensor/core/subtensor_api/__init__.py +++ b/bittensor/core/subtensor_api/__init__.py @@ -21,16 +21,16 @@ class SubtensorApi: """Subtensor API class. - Arguments: - network: The network to connect to. Defaults to `None` -> "finney". - config: Bittensor configuration object. Defaults to `None`. + Parameters: + network: The network to connect to. + config: Bittensor configuration object. legacy_methods: If `True`, all methods from the Subtensor class will be added to the root level of this class. - fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. Defaults to `None`. - retry_forever: Whether to retry forever on connection errors. Defaults to `False`. + fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. + retry_forever: Whether to retry forever on connection errors. log_verbose: Enables or disables verbose logging. mock: Whether this is a mock instance. Mainly just for use in testing. archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases - where you are requesting a block that is too old for your current (presumably lite) node. Defaults to `None` + where you are requesting a block that is too old for your current (presumably lite) node. websocket_shutdown_timer: Amount of time, in seconds, to wait after the last response from the chain to close the connection. Only applicable to AsyncSubtensor. diff --git a/bittensor/core/synapse.py b/bittensor/core/synapse.py index aa7abbbffe..3983177162 100644 --- a/bittensor/core/synapse.py +++ b/bittensor/core/synapse.py @@ -22,9 +22,9 @@ def get_size(obj: Any, seen: Optional[set] = None) -> int: This function traverses every item of a given object and sums their sizes to compute the total size. - Args: - obj (Any): The object to get the size of. - seen (Optional[set]): Set of object ids that have been calculated. + Parameters: + obj: The object to get the size of. + seen: Set of object ids that have been calculated. Returns: int: The total size of the object. @@ -55,11 +55,11 @@ def cast_int(raw: str) -> int: This function attempts to convert a string to an integer. If the string is ``None``, it simply returns ``None``. - Args: - raw (str): The string to convert. + Parameters: + raw: The string to convert. Returns: - int or None: The converted integer, or ``None`` if the input was ``None``. + The converted integer, or ``None`` if the input was ``None``. """ return int(raw) if raw is not None else raw @@ -71,11 +71,11 @@ def cast_float(raw: str) -> Optional[float]: This function attempts to convert a string to a float. If the string is ``None``, it simply returns ``None``. - Args: - raw (str): The string to convert. + Parameters: + raw: The string to convert. Returns: - float or None: The converted float, or ``None`` if the input was ``None``. + The converted float, or ``None`` if the input was ``None``. """ return float(raw) if raw is not None else raw @@ -97,24 +97,24 @@ class TerminalInfo(BaseModel): designed to be used natively within Synapses, so that you will not need to call this directly, but rather is used as a helper class for Synapses. - Args: - status_code (int): HTTP status code indicating the result of a network request. Essential for identifying the - outcome of network interactions. - status_message (str): Descriptive message associated with the status code, providing additional context about - the request's result. - process_time (float): Time taken by the terminal to process the call, important for performance monitoring and + Parameters: + status_code: HTTP status code indicating the result of a network request. Essential for identifying the outcome + of network interactions. + status_message: Descriptive message associated with the status code, providing additional context about the + request's result. + process_time: Time taken by the terminal to process the call, important for performance monitoring and optimization. - ip (str): IP address of the terminal, crucial for network routing and data transmission. - port (int): Network port used by the terminal, key for establishing network connections. - version (int): Bittensor version running on the terminal, ensuring compatibility between different nodes in the + ip: IP address of the terminal, crucial for network routing and data transmission. + port: Network port used by the terminal, key for establishing network connections. + version: Bittensor version running on the terminal, ensuring compatibility between different nodes in the network. - nonce (int): Unique, monotonically increasing number for each terminal, aiding in identifying and ordering - network interactions. - uuid (str): Unique identifier for the terminal, fundamental for network security and identification. - hotkey (str): Encoded hotkey string of the terminal wallet, important for transaction and identity verification - in the network. - signature (str): Digital signature verifying the tuple of nonce, axon_hotkey, dendrite_hotkey, and uuid, - critical for ensuring data authenticity and security. + nonce: Unique, monotonically increasing number for each terminal, aiding in identifying and ordering network + interactions. + uuid: Unique identifier for the terminal, fundamental for network security and identification. + hotkey: Encoded hotkey string of the terminal wallet, important for transaction and identity verification in the + network. + signature: Digital signature verifying the tuple of nonce, axon_hotkey, dendrite_hotkey, and uuid, critical for + ensuring data authenticity and security. Usage:: @@ -343,15 +343,15 @@ class Synapse(BaseModel): print(synapse.axon.status_code) synapse.axon.status_code = 408 # Timeout - Args: - name (str): HTTP route name, set on :func:`axon.attach`. - timeout (float): Total query length, set by the dendrite terminal. - total_size (int): Total size of request body in bytes. - header_size (int): Size of request header in bytes. - dendrite (:func:`TerminalInfo`): Information about the dendrite terminal. - axon (:func:`TerminalInfo`): Information about the axon terminal. - computed_body_hash (str): Computed hash of the request body. - required_hash_fields (list[str]): Fields required to compute the body hash. + Parameters: + name: HTTP route name, set on :func:`axon.attach`. + timeout: Total query length, set by the dendrite terminal. + total_size: Total size of request body in bytes. + header_size: Size of request header in bytes. + dendrite: Information about the dendrite terminal. + axon: Information about the axon terminal. + computed_body_hash: Computed hash of the request body. + required_hash_fields: Fields required to compute the body hash. Methods: deserialize: Custom deserialization logic for subclasses. @@ -732,17 +732,21 @@ def parse_headers_to_inputs(cls, headers: dict) -> dict: Interprets and transforms a given dictionary of headers into a structured dictionary, facilitating the reconstruction of Synapse objects. - This method is essential for parsing network-transmitted - data back into a Synapse instance, ensuring data consistency and integrity. + This method is essential for parsing network-transmitted data back into a Synapse instance, ensuring data + consistency and integrity. - Process: + Parameters: + headers: The headers dictionary to parse. - 1. Separates headers into categories based on prefixes (``axon``, ``dendrite``, etc.). - 2. Decodes and deserializes ``input_obj`` headers into their original objects. - 3. Assigns simple fields directly from the headers to the input dictionary. + Returns: + A structured dictionary representing the inputs for constructing a Synapse instance. - Example:: + Process: + 1. Separates headers into categories based on prefixes (``axon``, ``dendrite``, etc.). + 2. Decodes and deserializes ``input_obj`` headers into their original objects. + 3. Assigns simple fields directly from the headers to the input dictionary. + Example:: received_headers = { 'bt_header_axon_address': '127.0.0.1', 'bt_header_dendrite_port': '8080', @@ -753,13 +757,7 @@ def parse_headers_to_inputs(cls, headers: dict) -> dict: Note: This is handled automatically when calling :func:`Synapse.from_headers(headers)` and does not need to be - called directly. - - Args: - headers (dict): The headers dictionary to parse. - - Returns: - dict: A structured dictionary representing the inputs for constructing a Synapse instance. + called directly. """ # Initialize the input dictionary with empty sub-dictionaries for 'axon' and 'dendrite' @@ -826,9 +824,15 @@ def from_headers(cls, headers: dict) -> "Synapse": Constructs a new Synapse instance from a given headers dictionary, enabling the re-creation of the Synapse's state as it was prior to network transmission. - This method is a key part of the - deserialization process in the Bittensor network, allowing nodes to accurately reconstruct Synapse - objects from received data. + This method is a key part of the deserialization process in the Bittensor network, allowing nodes to accurately + reconstruct Synapse objects from received data. + + Parameters: + headers: The dictionary of headers containing serialized Synapse information. + + Returns: + A new instance of Synapse, reconstructed from the parsed header information, replicating the original + instance's state. Example:: @@ -839,13 +843,6 @@ def from_headers(cls, headers: dict) -> "Synapse": } synapse = Synapse.from_headers(received_headers) # synapse is a new Synapse instance reconstructed from the received headers - - Args: - headers (dict): The dictionary of headers containing serialized Synapse information. - - Returns: - bittensor.core.synapse.Synapse: A new instance of Synapse, reconstructed from the parsed header information, - replicating the original instance's state. """ # Get the inputs dictionary from the headers diff --git a/bittensor/core/tensor.py b/bittensor/core/tensor.py index 5cafd0986a..4be8e45fd2 100644 --- a/bittensor/core/tensor.py +++ b/bittensor/core/tensor.py @@ -59,11 +59,11 @@ def cast_dtype(raw: Union[None, np.dtype, "torch.dtype", str]) -> Optional[str]: """ Casts the raw value to a string representing the `numpy data type `_, or the `torch data type `_ if using torch. - Args: - raw (Union[None, numpy.dtype, torch.dtype, str]): The raw value to cast. + Parameters: + raw: The raw value to cast. Returns: - str: The string representing the numpy/torch data type. + The string representing the numpy/torch data type. Raises: Exception: If the raw value is of an invalid type. @@ -91,11 +91,11 @@ def cast_shape(raw: Union[None, list[int], str]) -> Optional[Union[str, list]]: """ Casts the raw value to a string representing the tensor shape. - Args: - raw (Union[None, list[int], str]): The raw value to cast. + Parameters: + raw: The raw value to cast. Returns: - str: The string representing the tensor shape. + The string representing the tensor shape. Raises: Exception: If the raw value is of an invalid type or if the list elements are not of type int. @@ -127,10 +127,10 @@ class Tensor(BaseModel): """ Represents a Tensor object. - Args: - buffer (Optional[str]): Tensor buffer data. - dtype (str): Tensor data type. - shape (list[int]): Tensor shape. + Parameters: + buffer: Tensor buffer data. + dtype: Tensor data type. + shape: Tensor shape. """ model_config = ConfigDict(validate_assignment=True) @@ -178,11 +178,11 @@ def serialize(tensor_: Union["np.ndarray", "torch.Tensor"]) -> "Tensor": """ Serializes the given tensor. - Args: - tensor_ (np.array or torch.Tensor): The tensor to serialize. + Parameters: + tensor_: The tensor to serialize. Returns: - :func:`Tensor`: The serialized tensor. + The serialized tensor. Raises: Exception: If the serialization process encounters an error. diff --git a/bittensor/core/threadpool.py b/bittensor/core/threadpool.py index bca3ad014c..3ff099c4a5 100644 --- a/bittensor/core/threadpool.py +++ b/bittensor/core/threadpool.py @@ -135,9 +135,8 @@ def __init__( ): """Initializes a new `ThreadPoolExecutor `_ instance. - Args: - max_workers: The maximum number of threads that can be used to - execute the given calls. + Parameters: + max_workers: The maximum number of threads that can be used to execute the given calls. thread_name_prefix: An optional name prefix to give our threads. initializer: An callable used to initialize worker threads. initargs: A tuple of arguments to pass to the initializer. diff --git a/bittensor/core/timelock.py b/bittensor/core/timelock.py index ed0b7c892e..35c0fc1768 100644 --- a/bittensor/core/timelock.py +++ b/bittensor/core/timelock.py @@ -71,7 +71,7 @@ def encrypt( ) -> tuple[bytes, int]: """Encrypts data using TimeLock Encryption - Arguments: + Parameters: data: Any bytes data to be encrypted. n_blocks: Number of blocks to encrypt. block_time: Time in seconds for each block. Default is `12.0` seconds. @@ -108,7 +108,7 @@ def decrypt( ) -> Optional[Union[bytes, str]]: """Decrypts encrypted data using TimeLock Decryption - Arguments: + Parameters: encrypted_data: Encrypted data to be decrypted. no_errors: If True, no errors will be raised during decryption. return_str: convert decrypted data to string if `True`. Default is `False`. @@ -143,7 +143,7 @@ def wait_reveal_and_decrypt( """ Waits for reveal round and decrypts data using TimeLock Decryption. - Arguments: + Parameters: encrypted_data: Encrypted data to be decrypted. reveal_round: Reveal round to wait for. If None, will be parsed from encrypted data. no_errors: If True, no errors will be raised during decryption. diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 84894d616e..4dae382736 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -74,11 +74,10 @@ def setup_config(network: Optional[str], config: "Config"): 4. Default chain endpoint. 5. Default network. - Arguments: - network (Optional[str]): The name of the Subtensor network. If None, the network and endpoint will be - determined from the `config` object. - config (bittensor.core.config.Config): The configuration object containing the network and chain endpoint - settings. + Parameters: + network: The name of the Subtensor network. If None, the network and endpoint will be determined from the + `config` object. + config: The configuration object containing the network and chain endpoint settings. Returns: tuple: A tuple containing the formatted WebSocket endpoint URL and the evaluated network name. @@ -121,10 +120,9 @@ def add_args(cls, parser: "argparse.ArgumentParser", prefix: Optional[str] = Non """ Adds command-line arguments to the provided ArgumentParser for configuring the Subtensor settings. - Arguments: - parser (argparse.ArgumentParser): The ArgumentParser object to which the Subtensor arguments will be added. - prefix (Optional[str]): An optional prefix for the argument names. If provided, the prefix is prepended to - each argument name. + Parameters: + parser: The ArgumentParser object to which the Subtensor arguments will be added. + prefix: An optional prefix for the argument names. If provided, the prefix is prepended to each argument name. Arguments added: --subtensor.network: The Subtensor network flag. Possible values are 'finney', 'test', 'archive', and diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index bfccfc8771..662193792d 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -135,13 +135,12 @@ def _get_explorer_root_url_by_network_from_map( """ Returns the explorer root url for the given network name from the given network map. - Args: - network(str): The network to get the explorer url for. - network_map(dict[str, str]): The network map to get the explorer url from. + Parameters: + network: The network to get the explorer url for. + network_map: The network map to get the explorer url from. Returns: - The explorer url for the given network. - Or None if the network is not in the network map. + The explorer url for the given network. Or None if the network is not in the network map. """ explorer_urls: Optional[dict[str, str]] = {} for entity_nm, entity_network_map in network_map.items(): @@ -157,14 +156,13 @@ def get_explorer_url_for_network( """ Returns the explorer url for the given block hash and network. - Args: - network(str): The network to get the explorer url for. - block_hash(str): The block hash to get the explorer url for. - network_map(dict[str, dict[str, str]]): The network maps to get the explorer urls from. + Parameters: + network: The network to get the explorer url for. + block_hash: The block hash to get the explorer url for. + network_map: The network maps to get the explorer urls from. Returns: - The explorer url for the given block hash and network. - Or None if the network is not known. + The explorer url for the given block hash and network. Or None if the network is not known. """ explorer_urls: Optional[dict[str, str]] = {} @@ -226,12 +224,12 @@ def format_error_message(error_message: Union[dict, Exception]) -> str: """ Formats an error message from the Subtensor error information for use in extrinsics. - Args: + Parameters: error_message: A dictionary containing the error information from Subtensor, or a SubstrateRequestException - containing dictionary literal args. + containing dictionary literal args. Returns: - str: A formatted error message string. + A formatted error message string. """ err_name = "UnknownError" err_type = "UnknownType" @@ -310,8 +308,8 @@ def is_valid_ss58_address(address: str) -> bool: """ Checks if the given address is a valid ss58 address. - Args: - address(str): The address to check. + Parameters: + address: The address to check. Returns: True if the address is a valid ss58 address for Bittensor, False otherwise. @@ -330,8 +328,8 @@ def _is_valid_ed25519_pubkey(public_key: Union[str, bytes]) -> bool: """ Checks if the given public_key is a valid ed25519 key. - Args: - public_key(Union[str, bytes]): The public_key to check. + Parameters: + public_key: The public_key to check. Returns: True if the public_key is a valid ed25519 key, False otherwise. @@ -360,8 +358,8 @@ def is_valid_bittensor_address_or_public_key(address: Union[str, bytes]) -> bool """ Checks if the given address is a valid destination address. - Args: - address(Union[str, bytes]): The address to check. + Parameters: + address: The address to check. Returns: True if the address is a valid destination address, False otherwise. @@ -403,9 +401,9 @@ def unlock_key( """ Attempts to decrypt a wallet's coldkey or hotkey - Args: - wallet: a Wallet object - unlock_type: the key type, 'coldkey' or 'hotkey' + Parameters: + wallet: Bittensor Wallet instance. + unlock_type: the key type, 'coldkey' or 'hotkey'. raise_error: if False, will return (False, error msg), if True will raise the otherwise-caught exception. Returns: @@ -445,13 +443,12 @@ def determine_chain_endpoint_and_network( ) -> tuple[Optional[str], Optional[str]]: """Determines the chain endpoint and network from the passed network or chain_endpoint. - Arguments: - network (str): The network flag. The choices are: ``finney`` (main network), ``archive`` (archive network - +300 blocks), ``local`` (local running network), ``test`` (test network). + Parameters: + network: The network flag. The choices are: ``finney`` (main network), ``archive`` (archive network +300 blocks), + ``local`` (local running network), ``test`` (test network). Returns: - tuple[Optional[str], Optional[str]]: The network and chain endpoint flag. If passed, overrides the - ``network`` argument. + The network and chain endpoint flag. If passed, overrides the ``network`` argument. """ if network is None: @@ -486,9 +483,9 @@ def get_transfer_fn_params( ) -> tuple[str, dict[str, Union[str, int, bool]]]: """ Helper function to get the transfer call function and call params, depending on the value and keep_alive flag - provided + provided. - Args: + Parameters: amount: the amount of Tao to transfer. `None` if transferring all. destination: the destination SS58 of the transfer keep_alive: whether to enforce a retention of the existential deposit in the account after transfer. diff --git a/bittensor/utils/axon_utils.py b/bittensor/utils/axon_utils.py index b24f4b6be7..3ef8ebbbea 100644 --- a/bittensor/utils/axon_utils.py +++ b/bittensor/utils/axon_utils.py @@ -10,12 +10,12 @@ def allowed_nonce_window_ns( """ Calculates the allowed window for a nonce in nanoseconds. - Args: - current_time_ns (int): The current time in nanoseconds. - synapse_timeout (Optional[float]): The optional timeout for the synapse in seconds. If None, it defaults to 0. + Parameters: + current_time_ns: The current time in nanoseconds. + synapse_timeout: The optional timeout for the synapse in seconds. If None, it defaults to 0. Returns: - int: The allowed nonce window in nanoseconds. + The allowed nonce window in nanoseconds. """ synapse_timeout_ns = (synapse_timeout or 0) * NANOSECONDS_IN_SECOND allowed_window_ns = current_time_ns - ALLOWED_DELTA - synapse_timeout_ns @@ -26,16 +26,16 @@ def calculate_diff_seconds( current_time: int, synapse_timeout: Optional[float], synapse_nonce: int ): """ - Calculates the difference in seconds between the current time and the synapse nonce, - and also returns the allowed delta in seconds. + Calculates the difference in seconds between the current time and the synapse nonce, and also returns the allowed + delta in seconds. - Args: - current_time (int): The current time in nanoseconds. - synapse_timeout (Optional[float]): The optional timeout for the synapse in seconds. - synapse_nonce (int): The nonce value for the synapse in nanoseconds. + Parameters: + current_time: The current time in nanoseconds. + synapse_timeout: The optional timeout for the synapse in seconds. + synapse_nonce: The nonce value for the synapse in nanoseconds. Returns: - tuple: A tuple containing the difference in seconds (float) and the allowed delta in seconds (float). + A tuple containing the difference in seconds (float) and the allowed delta in seconds (float). """ synapse_timeout_ns = (synapse_timeout or 0) * NANOSECONDS_IN_SECOND diff_seconds = (current_time - synapse_nonce) / NANOSECONDS_IN_SECOND diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 99c1a85831..5e3d7d9c56 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -60,8 +60,8 @@ def __init__(self, balance: Union[int, float]): Initialize a Balance object. If balance is an int, it's assumed to be in rao. If balance is a float, it's assumed to be in tao. - Args: - balance: The initial balance, in either rao (if an int) or tao (if a float). + Parameters: + The initial balance, in either rao (if an int) or tao (if a float). """ if isinstance(balance, int): self.rao = balance @@ -276,9 +276,10 @@ def __abs__(self): def from_float(amount: float, netuid: int = 0) -> "Balance": """ Given tao, return :func:`Balance` object with rao(``int``) and tao(``float``), where rao = int(tao*pow(10,9)) - Args: - amount (float): The amount in tao. - netuid (int): The subnet uid for set currency unit. Defaults to `0`. + + Parameters: + amount: The amount in tao. + netuid: The subnet uid for set currency unit. Defaults to `0`. Returns: A Balance object representing the given amount. @@ -291,9 +292,9 @@ def from_tao(amount: float, netuid: int = 0) -> "Balance": """ Given tao, return Balance object with rao(``int``) and tao(``float``), where rao = int(tao*pow(10,9)) - Args: - amount (float): The amount in tao. - netuid (int): The subnet uid for set currency unit. Defaults to `0`. + Parameters: + amount: The amount in tao. + netuid: The subnet uid for set currency unit. Returns: A Balance object representing the given amount. @@ -306,9 +307,9 @@ def from_rao(amount: int, netuid: int = 0) -> "Balance": """ Given rao, return Balance object with rao(``int``) and tao(``float``), where rao = int(tao*pow(10,9)) - Args: - amount (int): The amount in rao. - netuid (int): The subnet uid for set currency unit. Defaults to `0`. + Parameters: + amount: The amount in rao. + netuid: The subnet uid for set currency unit. Returns: A Balance object representing the given amount. diff --git a/bittensor/utils/btlogging/format.py b/bittensor/utils/btlogging/format.py index 989a49b925..6e119a4024 100644 --- a/bittensor/utils/btlogging/format.py +++ b/bittensor/utils/btlogging/format.py @@ -111,12 +111,12 @@ def formatTime(self, record, datefmt: Optional[str] = None) -> str: """ Override formatTime to add milliseconds. - Args: - record (logging.LogRecord): The log record. - datefmt (Optional[str]): The date format string. + Parameters: + record: The log record. + datefmt: The date format string. Returns: - s (str): The formatted time string with milliseconds. + The formatted time string with milliseconds. """ created = self.converter(record.created) @@ -134,11 +134,11 @@ def format(self, record: "logging.LogRecord") -> str: This method saves the original format, applies custom formatting based on the log level and trace flag, replaces text with emojis and colors, and then returns the formatted log record. - Args: - record (logging.LogRecord): The log record. + Parameters: + record: The log record. Returns: - result (str): The formatted log record. + The formatted log record. """ format_orig = self._style._fmt @@ -184,12 +184,12 @@ def formatTime( """ Override formatTime to add milliseconds. - Args: - record (logging.LogRecord): The log record. - datefmt (Optional[str]): The date format string. + Parameters: + record: The log record. + datefmt: The date format string. Returns: - s (str): The formatted time string with milliseconds. + The formatted time string with milliseconds. """ created = self.converter(record.created) @@ -204,11 +204,11 @@ def format(self, record: "logging.LogRecord") -> str: """ Override format to center the level name. - Args: - record (logging.LogRecord): The log record. + Parameters: + record: The log record. Returns: - formatted record (str): The formatted log record. + The formatted log record. """ record.levelname = f"{record.levelname:^10}" return super().format(record) diff --git a/bittensor/utils/btlogging/helpers.py b/bittensor/utils/btlogging/helpers.py index 266a67b25d..86cc4a7504 100644 --- a/bittensor/utils/btlogging/helpers.py +++ b/bittensor/utils/btlogging/helpers.py @@ -13,7 +13,7 @@ def all_loggers() -> Generator["logging.Logger", None, None]: placeholders and other types that are not instances of `Logger`. Yields: - logger (logging.Logger): An active logger instance. + logger: An active logger instance. """ for logger in logging.root.manager.loggerDict.values(): if isinstance(logger, logging.PlaceHolder): @@ -38,7 +38,7 @@ def all_logger_names() -> Generator[str, None, None]: `Logger` instances. It skips placeholders and other types that are not instances of `Logger`. Yields: - name (str): The name of an active logger. + The name of an active logger. """ for name, logger in logging.root.manager.loggerDict.items(): if isinstance(logger, logging.PlaceHolder): @@ -62,7 +62,7 @@ def get_max_logger_name_length() -> int: This function iterates through all active logger names and determines the length of the longest name. Returns: - max_length (int): The length of the longest logger name. + The length of the longest logger name. """ max_length = 0 for name in all_logger_names(): diff --git a/bittensor/utils/btlogging/loggingmachine.py b/bittensor/utils/btlogging/loggingmachine.py index 32ea7315e0..d52003810b 100644 --- a/bittensor/utils/btlogging/loggingmachine.py +++ b/bittensor/utils/btlogging/loggingmachine.py @@ -179,11 +179,11 @@ def _enable_initial_state(self, config): def _extract_logging_config(self, config: "Config") -> dict: """Extract btlogging's config from bittensor config - Args: - config (bittensor.core.config.Config): Bittensor config instance. + Parameters: + config: Bittensor config instance. Returns: - (dict): btlogging's config from Bittensor config or Bittensor config. + Dict represented btlogging's config from Bittensor config or Bittensor config. """ # This is to handle nature of DefaultMunch if getattr(config, "logging", None): @@ -214,8 +214,8 @@ def get_config(self): def set_config(self, config: "Config"): """Set config after initialization, if desired. - Args: - config (bittensor.core.config.Config): Bittensor config instance. + Parameters: + config: Bittensor config instance. """ self._config = self._extract_logging_config(config) if self._config.logging_dir and self._config.record_log: @@ -281,27 +281,25 @@ def _create_file_handler(self, logfile: str): def register_primary_logger(self, name: str): """ - Register a logger as primary logger + Register a logger as primary logger. - This adds a logger to the _primary_loggers set to ensure - it doesn't get disabled when disabling third-party loggers. - A queue handler is also associated with it. + This adds a logger to the _primary_loggers set to ensure it doesn't get disabled when disabling third-party + loggers. A queue handler is also associated with it. - Args: - name (str): the name for primary logger. + Parameters: + name: the name for primary logger. """ self._primary_loggers.add(name) self._initialize_bt_logger(name) def deregister_primary_logger(self, name: str): """ - De-registers a primary logger + De-registers a primary logger. - This function removes the logger from the _primary_loggers - set and deinitializes its queue handler + This function removes the logger from the _primary_loggers set and deinitializes its queue handler - Args: - name (str): the name of primary logger. + Parameters: + name: the name of primary logger. """ self._primary_loggers.remove(name) self._deinitialize_bt_logger(name) diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index f71ef909c6..de3399aaa5 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -144,8 +144,9 @@ def trace(on: bool = True): """ Enables or disables trace logging. - Args: - on (bool): If True, enables trace logging. If False, disables trace logging. + + Parameters: + on: If True, enables trace logging. If False, disables trace logging. """ logging.set_trace(on) @@ -153,8 +154,9 @@ def trace(on: bool = True): def debug(on: bool = True): """ Enables or disables debug logging. - Args: - on (bool): If True, enables debug logging. If False, disables debug logging. + + Parameters: + on: If True, enables debug logging. If False, disables debug logging. """ logging.set_debug(on) @@ -162,8 +164,9 @@ def debug(on: bool = True): def warning(on: bool = True): """ Enables or disables warning logging. - Args: - on (bool): If True, enables warning logging. If False, disables warning logging and sets default (WARNING) level. + + Parameters: + on: If True, enables warning logging. If False, disables warning logging and sets default (WARNING) level. """ logging.set_warning(on) @@ -171,8 +174,9 @@ def warning(on: bool = True): def info(on: bool = True): """ Enables or disables info logging. - Args: - on (bool): If True, enables info logging. If False, disables info logging and sets default (WARNING) level. + + Parameters: + on: If True, enables info logging. If False, disables info logging and sets default (WARNING) level. """ logging.set_info(on) diff --git a/bittensor/utils/liquidity.py b/bittensor/utils/liquidity.py index 55e206225c..b32fab355c 100644 --- a/bittensor/utils/liquidity.py +++ b/bittensor/utils/liquidity.py @@ -31,7 +31,7 @@ def to_token_amounts( ) -> tuple[Balance, Balance]: """Convert a position to token amounts. - Arguments: + Parameters: current_subnet_price: current subnet price in Alpha. Returns: diff --git a/bittensor/utils/networking.py b/bittensor/utils/networking.py index e01012d8b7..4558907a73 100644 --- a/bittensor/utils/networking.py +++ b/bittensor/utils/networking.py @@ -16,11 +16,11 @@ class ExternalIPNotFound(Exception): def int_to_ip(int_val: int) -> str: """Maps an integer to a unique ip-string - Arguments: - int_val (int): The integer representation of an ip. Must be in the range (0, 3.4028237e+38). + Parameters: + int_val: The integer representation of an ip. Must be in the range (0, 3.4028237e+38). Returns: - str_val (str): The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 + str_val: The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 """ return str(netaddr.IPAddress(int_val)) @@ -28,11 +28,11 @@ def int_to_ip(int_val: int) -> str: def ip_to_int(str_val: str) -> int: """Maps an ip-string to a unique integer. - Arguments: - str_val (str): The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 + Parameters: + str_val: The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 Returns: - int_val (int): The integer representation of an ip. Must be in the range (0, 3.4028237e+38). + int_val: The integer representation of an ip. Must be in the range (0, 3.4028237e+38). """ return int(netaddr.IPAddress(str_val)) @@ -40,11 +40,11 @@ def ip_to_int(str_val: str) -> int: def ip_version(str_val: str) -> int: """Returns the ip version (IPV4 or IPV6). - Arguments: - str_val (str): The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 + Parameters: + str_val: The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 Returns: - int_val (int): The ip version (Either 4 or 6 for IPv4/IPv6) + int_val: The ip version (Either 4 or 6 for IPv4/IPv6) """ return int(netaddr.IPAddress(str_val).version) @@ -124,12 +124,11 @@ def get_formatted_ws_endpoint_url(endpoint_url: Optional[str]) -> Optional[str]: """ Returns a formatted websocket endpoint url. - Arguments: - endpoint_url (Optional[str]): The endpoint url to format. + Parameters: + endpoint_url: The endpoint url to format. Returns: - formatted_endpoint_url (Optional[str]): The formatted endpoint url. In the form of ws:// or - wss:// + formatted_endpoint_url: The formatted endpoint url. In the form of ws:// or wss:// Note: The port (or lack thereof) is left unchanged. """ diff --git a/bittensor/utils/registration/__init__.py b/bittensor/utils/registration/__init__.py index ea527e4dc3..ddcc7a1248 100644 --- a/bittensor/utils/registration/__init__.py +++ b/bittensor/utils/registration/__init__.py @@ -10,12 +10,12 @@ from bittensor.utils.registration.async_pow import create_pow_async __all__ = [ - create_pow, - legacy_torch_api_compat, - log_no_torch_error, - torch, - use_torch, - LazyLoadedTorch, - POWSolution, - create_pow_async, + "create_pow", + "create_pow_async", + "legacy_torch_api_compat", + "log_no_torch_error", + "torch", + "use_torch", + "LazyLoadedTorch", + "POWSolution", ] diff --git a/bittensor/utils/registration/async_pow.py b/bittensor/utils/registration/async_pow.py index fb8b9bef7d..a6d1c67c99 100644 --- a/bittensor/utils/registration/async_pow.py +++ b/bittensor/utils/registration/async_pow.py @@ -32,10 +32,9 @@ async def _get_block_with_retry( """ Gets the current block number, difficulty, and block hash from the substrate node. - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor object to use to get the block number, - difficulty, and block hash. - netuid (int): The netuid of the network to get the block number, difficulty, and block hash from. + Parameters: + subtensor: The subtensor object to use to get the block number, difficulty, and block hash. + netuid: The netuid of the network to get the block number, difficulty, and block hash from. Returns: The current block number, difficulty of the subnet, block hash @@ -82,18 +81,18 @@ async def _check_for_newest_block_and_update( """ Check for the newest block and update block-related information and states across solvers if a new block is detected. - Args: - subtensor (AsyncSubtensor): The subtensor instance interface. - netuid (int): The network UID for the blockchain. - old_block_number (int): The previously known block number. - hotkey_bytes (bytes): The bytes representation of the hotkey. - curr_diff (Array): The current difficulty level. - curr_block (Array): The current block information. - curr_block_num (Value): The current block number. - update_curr_block_ (Callable): Function to update current block information. - check_block (Lock): Lock object for synchronizing block checking. - solvers (list[Solver]): List of solvers to notify of new blocks. - curr_stats (RegistrationStatistics): Current registration statistics to update. + Parameters: + subtensor: The subtensor instance interface. + netuid: The network UID for the blockchain. + old_block_number: The previously known block number. + hotkey_bytes: The bytes representation of the hotkey. + curr_diff: The current difficulty level. + curr_block: The current block information. + curr_block_num: The current block number. + update_curr_block_: Function to update current block information. + check_block: Lock object for synchronizing block checking. + solvers: List of solvers to notify of new blocks. + curr_stats: Current registration statistics to update. Returns: int: The updated block number which is the same as the new block @@ -359,19 +358,17 @@ async def _solve_for_difficulty_fast_cuda( """ Solves the registration fast using CUDA - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor object to use to get the block number, - difficulty, and block hash. - wallet (bittensor_wallet.Wallet): The wallet to register - netuid (int): The netuid of the subnet to register to. - output_in_place (bool): If true, prints the output in place, otherwise prints to new lines - update_interval (int): The number of nonces to try before checking for more blocks - tpb (int): The number of threads per block. CUDA param that should match the GPU capability - dev_id (Union[list[int], int]): The CUDA device IDs to execute the registration on, either a single device or a - list of devices - n_samples (int): The number of samples of the hash_rate to keep for the EWMA - alpha_ (float): The alpha for the EWMA for the hash_rate calculation - log_verbose (bool): If true, prints more verbose logging of the registration metrics. + Parameters: + subtensor: The subtensor object to use to get the block number, difficulty, and block hash. + wallet: The wallet to register + netuid: The netuid of the subnet to register to. + output_in_place: If true, prints the output in place, otherwise prints to new lines + update_interval: The number of nonces to try before checking for more blocks + tpb: The number of threads per block. CUDA param that should match the GPU capability + dev_id: The CUDA device IDs to execute the registration on, either a single device or a list of devices + n_samples: The number of samples of the hash_rate to keep for the EWMA + alpha_: The alpha for the EWMA for the hash_rate calculation + log_verbose: If true, prints more verbose logging of the registration metrics. Note: The hash rate is calculated as an exponentially weighted moving average in order to make the measure more robust. @@ -428,22 +425,21 @@ async def _solve_for_difficulty_fast( """ Solves the POW for registration using multiprocessing. - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor object to use to get the block number, - difficulty, and block hash. - wallet (bittensor_wallet.Wallet): wallet to use for registration. - netuid (int): The netuid of the subnet to register to. - output_in_place (bool): If true, prints the status in place. Otherwise, prints the status on a new line. - num_processes (Optional[int]): Number of processes to use. - update_interval (Optional[int]): Number of nonces to solve before updating block information. - n_samples (int): The number of samples of the hash_rate to keep for the EWMA - alpha_ (float): The alpha for the EWMA for the hash_rate calculation - log_verbose (bool): If true, prints more verbose logging of the registration metrics. + Parameters: + subtensor: The subtensor object to use to get the block number, difficulty, and block hash. + wallet: wallet to use for registration. + netuid: The netuid of the subnet to register to. + output_in_place: If true, prints the status in place. Otherwise, prints the status on a new line. + num_processes: Number of processes to use. + update_interval: Number of nonces to solve before updating block information. + n_samples: The number of samples of the hash_rate to keep for the EWMA + alpha_: The alpha for the EWMA for the hash_rate calculation + log_verbose: If true, prints more verbose logging of the registration metrics. Notes: The hash rate is calculated as an exponentially weighted moving average in order to make the measure more robust. We can also modify the update interval to do smaller blocks of work, while still updating the block information - after a different number of nonces, to increase the transparency of the process while still keeping the speed. + after a different number of nonces, to increase the transparency of the process while still keeping the speed. """ if not num_processes: # get the number of allowed processes for this process @@ -490,20 +486,20 @@ async def create_pow_async( """ Creates a proof of work for the given subtensor and wallet. - Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance. - wallet (bittensor_wallet.Wallet): The wallet to create a proof of work for. - netuid (int): The netuid for the subnet to create a proof of work for. - 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. - cuda (bool): If true, uses CUDA to solve the proof of work. - dev_id (Union[list[int], int]): The CUDA device id(s) to use. If cuda is true and dev_id is a list, then - multiple CUDA devices will be used to solve the proof of work. - tpb (int): The number of threads per block to use when solving the proof of work. Should be a multiple of 32. - num_processes (int): The number of processes to use when solving the proof of work. If None, then the number of + Parameters: + subtensor: The subtensor instance. + wallet: The wallet to create a proof of work for. + netuid: The netuid for the subnet to create a proof of work for. + 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, uses CUDA to solve the proof of work. + dev_id: The CUDA device id(s) to use. If cuda is true and dev_id is a list, then multiple CUDA devices will be + used to solve the proof of work. + tpb: The number of threads per block to use when solving the proof of work. Should be a multiple of 32. + num_processes: The number of processes to use when solving the proof of work. If None, then the number of processes is equal to the number of CPU cores. - update_interval (int): The number of nonces to run before checking for a new block. - log_verbose (bool): If true, prints the progress of the proof of work more verbosely. + update_interval: The number of nonces to run before checking for a new block. + log_verbose: If true, prints the progress of the proof of work more verbosely. Returns: The proof of work solution or None if the wallet is already registered or there is a different error. diff --git a/bittensor/utils/registration/pow.py b/bittensor/utils/registration/pow.py index 078c73d09f..dfbd9b8fc3 100644 --- a/bittensor/utils/registration/pow.py +++ b/bittensor/utils/registration/pow.py @@ -32,11 +32,11 @@ def legacy_torch_api_compat(func): """ Convert function operating on numpy Input&Output to legacy torch Input&Output API if `use_torch()` is True. - Args: - func (function): Function with numpy Input/Output to be decorated. + Parameters: + func: Function with numpy Input/Output to be decorated. Returns: - decorated (function): Decorated function. + decorated: Decorated function. """ @functools.wraps(func) @@ -115,9 +115,9 @@ def _create_seal_hash(block_and_hotkey_hash_bytes: bytes, nonce: int) -> bytes: block and hotkey hash bytes. The result is then hashed using SHA-256 followed by the Keccak-256 algorithm to produce the final seal hash. - Args: - block_and_hotkey_hash_bytes (bytes): The combined hash bytes of the block and hotkey. - nonce (int): The nonce value used for hashing. + Parameters: + block_and_hotkey_hash_bytes: The combined hash bytes of the block and hotkey. + nonce: The nonce value used for hashing. Returns: The resulting seal hash. @@ -185,28 +185,28 @@ class _SolverBase(mp.Process): """ A process that solves the registration PoW problem. - Args: - proc_num (int): The number of the process being created. - num_proc (int): The total number of processes running. - update_interval (int): The number of nonces to try to solve before checking for a new block. - finished_queue (multiprocessing.Queue): The queue to put the process number when a process finishes each - update_interval. Used for calculating the average time per update_interval across all processes. - solution_queue (multiprocessing.Queue): The queue to put the solution the process has found during the pow solve. - stopEvent (multiprocessing.Event): The event to set by the main process when all the solver processes should - stop. The solver process will check for the event after each update_interval. The solver process will stop - when the event is set. Used to stop the solver processes when a solution is found. - curr_block (multiprocessing.Array): The array containing this process's current block hash. The main process - will set the array to the new block hash when a new block is finalized in the network. The solver process - will get the new block hash from this array when newBlockEvent is set. - curr_block_num (multiprocessing.Value): The value containing this process's current block number. The main - process will set the value to the new block number when a new block is finalized in the network. The - solver process will get the new block number from this value when newBlockEvent is set. - curr_diff (multiprocessing.Array): The array containing this process's current difficulty. The main process will - set the array to the new difficulty when a new block is finalized in the network. The solver process will - get the new difficulty from this array when newBlockEvent is set. - check_block (multiprocessing.Lock): The lock to prevent this process from getting the new block data while the - main process is updating the data. - limit (int): The limit of the pow solve for a valid solution. + Parameters: + proc_num: The number of the process being created. + num_proc: The total number of processes running. + update_interval: The number of nonces to try to solve before checking for a new block. + finished_queue: The queue to put the process number when a process finishes each update_interval. Used for + calculating the average time per update_interval across all processes. + solution_queue: The queue to put the solution the process has found during the pow solve. + stopEvent: The event to set by the main process when all the solver processes should stop. The solver process + will check for the event after each update_interval. The solver process will stop when the event is set. + Used to stop the solver processes when a solution is found. + curr_block: The array containing this process's current block hash. The main process will set the array to the + new block hash when a new block is finalized in the network. The solver process will get the new block hash + from this array when newBlockEvent is set. + curr_block_num: The value containing this process's current block number. The main process will set the value to + the new block number when a new block is finalized in the network. The solver process will get the new block + number from this value when newBlockEvent is set. + curr_diff: The array containing this process's current difficulty. The main process will set the array to the + new difficulty when a new block is finalized in the network. The solver process will get the new difficulty + from this array when newBlockEvent is set. + check_block: The lock to prevent this process from getting the new block data while the main process is updating + the data. + limit: The limit of the pow solve for a valid solution. """ proc_num: int @@ -471,7 +471,7 @@ def update_curr_block( This function updates the current block and its difficulty in a thread-safe manner. It sets the current block number, hashes the block with the hotkey, updates the current block bytes, and packs the difficulty. - Arguments: + Parameters: curr_diff: Shared array to store the current difficulty. curr_block: Shared array to store the current block data. curr_block_num: Shared value to store the current block number. @@ -609,22 +609,21 @@ def _solve_for_difficulty_fast( """ Solves the POW for registration using multiprocessing. - Args: - subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance. - wallet (bittensor_wallet.Wallet): wallet to use for registration. - netuid (int): The netuid of the subnet to register to. - output_in_place (bool): If true, prints the status in place. Otherwise, prints the status on a new line. - num_processes (int): Number of processes to use. - update_interval (int): Number of nonces to solve before updating block information. - n_samples (int): The number of samples of the hash_rate to keep for the EWMA. - alpha_ (float): The alpha for the EWMA for the hash_rate calculation. - log_verbose (bool): If true, prints more verbose logging of the registration metrics. - - Note: The hash rate is calculated as an exponentially weighted moving average in order to make the measure more - robust. - Note: We can also modify the update interval to do smaller blocks of work, while still updating the block - information after a different number of nonces, to increase the transparency of the process while still - keeping the speed. + Parameters: + subtensor: Subtensor instance. + wallet: wallet to use for registration. + netuid: The netuid of the subnet to register to. + output_in_place: If true, prints the status in place. Otherwise, prints the status on a new line. + num_processes: Number of processes to use. + update_interval: Number of nonces to solve before updating block information. + n_samples: The number of samples of the hash_rate to keep for the EWMA. + alpha_: The alpha for the EWMA for the hash_rate calculation. + log_verbose: If true, prints more verbose logging of the registration metrics. + + Note: + The hash rate is calculated as an exponentially weighted moving average in order to make the measure more robust. + We can also modify the update interval to do smaller blocks of work, while still updating the block information + after a different number of nonces, to increase the transparency of the process while still keeping the speed. """ if num_processes is None: # get the number of allowed processes for this process @@ -800,15 +799,15 @@ def _get_block_with_retry(subtensor: "Subtensor", netuid: int) -> tuple[int, int """ Gets the current block number, difficulty, and block hash from the substrate node. - Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor instance. - netuid (int): The netuid of the network to get the block number, difficulty, and block hash from. + Parameters: + subtensor: The subtensor instance. + netuid: The netuid of the network to get the block number, difficulty, and block hash from. Returns: tuple[int, int, bytes] - block_number (int): The current block number. - difficulty (int): The current difficulty of the subnet. - block_hash (bytes): The current block hash. + - block_number: The current block number. + - difficulty: The current difficulty of the subnet. + - block_hash: The current block hash. Raises: Exception: If the block hash is None. @@ -842,21 +841,21 @@ def _check_for_newest_block_and_update( """ Checks for a new block and updates the current block information if a new block is found. - Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor object to use for getting the current block. - netuid (int): The netuid to use for retrieving the difficulty. - old_block_number (int): The old block number to check against. - hotkey_bytes (bytes): The bytes of the hotkey's pubkey. - curr_diff (multiprocessing.Array): The current difficulty as a multiprocessing array. - curr_block (multiprocessing.Array): Where the current block is stored as a multiprocessing array. - curr_block_num (multiprocessing.Value): Where the current block number is stored as a multiprocessing value. - update_curr_block_ (typing.Callable): A function that updates the current block. - check_block (multiprocessing.Lock): A mp lock that is used to check for a new block. - solvers (list[bittensor.utils.registration.Solver]): A list of solvers to update the current block for. - curr_stats (bittensor.utils.registration.RegistrationStatistics): The current registration statistics to update. + Parameters: + subtensor: Subtensor instance. + netuid: The netuid to use for retrieving the difficulty. + old_block_number: The old block number to check against. + hotkey_bytes: The bytes of the hotkey's pubkey. + curr_diff: The current difficulty as a multiprocessing array. + curr_block: Where the current block is stored as a multiprocessing array. + curr_block_num: Where the current block number is stored as a multiprocessing value. + update_curr_block_: A function that updates the current block. + check_block: A mp lock that is used to check for a new block. + solvers: A list of solvers to update the current block for. + curr_stats: The current registration statistics to update. Returns: - (int) The current block number. + The current block number. """ block_number = subtensor.get_current_block() if block_number != old_block_number: @@ -905,20 +904,20 @@ def _solve_for_difficulty_fast_cuda( """ Solves the registration fast using CUDA. - Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor node to grab blocks. - wallet (bittensor_wallet.Wallet): The wallet to register. - netuid (int): The netuid of the subnet to register to. - output_in_place (bool) If true, prints the output in place, otherwise prints to new lines. - update_interval (int): The number of nonces to try before checking for more blocks. - tpb (int): The number of threads per block. CUDA param that should match the GPU capability - dev_id (Union[list[int], int]): The CUDA device IDs to execute the registration on, either a single device or a - list of devices. - n_samples (int): The number of samples of the hash_rate to keep for the EWMA. - alpha_ (float): The alpha for the EWMA for the hash_rate calculation. - log_verbose (bool): If true, prints more verbose logging of the registration metrics. - - Note: The hash rate is calculated as an exponentially weighted moving average in order to make the measure more robust. + Parameters: + subtensor: Subtensor instance. + wallet: Bittensor Wallet instance. + netuid: The netuid of the subnet to register to. + output_in_place: If true, prints the output in place, otherwise prints to new lines. + update_interval: The number of nonces to try before checking for more blocks. + tpb: The number of threads per block. CUDA param that should match the GPU capability + dev_id: The CUDA device IDs to execute the registration on, either a single device or a list of devices. + n_samples: The number of samples of the hash_rate to keep for the EWMA. + alpha_: The alpha for the EWMA for the hash_rate calculation. + log_verbose: If true, prints more verbose logging of the registration metrics. + + Note: + The hash rate is calculated as an exponentially weighted moving average in order to make the measure more robust. """ if isinstance(dev_id, int): dev_id = [dev_id] @@ -1131,25 +1130,23 @@ def create_pow( """ Creates a proof of work for the given subtensor and wallet. - Args: - subtensor (bittensor.core.subtensor.Subtensor): The subtensor to create a proof of work for. - wallet (bittensor_wallet.Wallet): The wallet to create a proof of work for. - netuid (int): The netuid for the subnet to create a proof of work for. - 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. Default is ``True``. - cuda (bool): If true, uses CUDA to solve the proof of work. Default is ``False``. - dev_id (Union[List[int], int]): The CUDA device id(s) to use. If cuda is true and dev_id is a list, then - multiple CUDA devices will be used to solve the proof of work. Default is ``0``. - tpb (int): The number of threads per block to use when solving the proof of work. Should be a multiple of 32. - Default is ``256``. - num_processes (Optional[int]): The number of processes to use when solving the proof of work. If None, then the - number of processes is equal to the number of CPU cores. Default is None. - update_interval (Optional[int]): The number of nonces to run before checking for a new block. Default is ``None``. - log_verbose (bool): If true, prints the progress of the proof of work more verbosely. Default is ``False``. + Parameters: + subtensor: The Subtensor instance. + wallet: The Bittensor Wallet instance. + netuid: The netuid for the subnet to create a proof of work for. + 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, uses CUDA to solve the proof of work. + dev_id: The CUDA device id(s) to use. If cuda is true and dev_id is a list, then multiple CUDA devices will be + used to solve the proof of work. + tpb: The number of threads per block to use when solving the proof of work. Should be a multiple of 32. + num_processes: The number of processes to use when solving the proof of work. If None, then the number of + processes is equal to the number of CPU cores. + update_interval: The number of nonces to run before checking for a new block. + log_verbose: If true, prints the progress of the proof of work more verbosely. Returns: - Optional[Dict[str, Any]]: The proof of work solution or None if the wallet is already registered or there is a - different error. + The proof of work solution or None if the wallet is already registered or there is a different error. Raises: ValueError: If the subnet does not exist. diff --git a/bittensor/utils/registration/register_cuda.py b/bittensor/utils/registration/register_cuda.py index b46dab9f0c..5d41056258 100644 --- a/bittensor/utils/registration/register_cuda.py +++ b/bittensor/utils/registration/register_cuda.py @@ -15,8 +15,8 @@ def _hex_bytes_to_u8_list(hex_bytes: bytes) -> list[int]: Convert a sequence of bytes in hexadecimal format to a list of unsigned 8-bit integers. - Args: - hex_bytes (bytes): A sequence of bytes in hexadecimal format. + Parameters: + hex_bytes: A sequence of bytes in hexadecimal format. Returns: A list of unsigned 8-bit integers. @@ -54,18 +54,17 @@ def solve_cuda( """ Solves the PoW problem using CUDA. - Args: - nonce_start (numpy.int64): Starting nonce. - update_interval (numpy.int64): Number of nonces to solve before updating block information. - tpb (int): Threads per block. - block_and_hotkey_hash_bytes (bytes): Keccak(Bytes of the block hash + bytes of the hotkey) 64 bytes. - difficulty (int): Difficulty of the PoW problem. - limit (int): Upper limit of the nonce. - dev_id (int): The CUDA device ID. Defaults to ``0``. + Parameters: + nonce_start: Starting nonce. + update_interval: Number of nonces to solve before updating block information. + tpb: Threads per block. + block_and_hotkey_hash_bytes: Keccak(Bytes of the block hash + bytes of the hotkey) 64 bytes. + difficulty: Difficulty of the PoW problem. + limit: Upper limit of the nonce. + dev_id: The CUDA device ID. Defaults to ``0``. Returns: - (Union[tuple[Any, bytes], tuple[int, bytes], tuple[Any, None]]): Tuple of the nonce and the seal corresponding - to the solution. Returns -1 for nonce if no solution is found. + Tuple of the nonce and the seal corresponding to the solution. Returns -1 for nonce if no solution is found. """ try: diff --git a/bittensor/utils/subnets.py b/bittensor/utils/subnets.py index d92a4f58f3..a0714c1d2a 100644 --- a/bittensor/utils/subnets.py +++ b/bittensor/utils/subnets.py @@ -40,10 +40,10 @@ async def query_api( """ Queries the API nodes of a subnet using the given synapse and bespoke query function. - Args: - axons (Union[bt.axon, list[bt.axon]]): The list of axon(s) to query. - deserialize (Optional[bool]): Whether to deserialize the responses. Defaults to False. - timeout (Optional[int]): The timeout in seconds for the query. Defaults to 12. + Parameters: + axons: The list of axon(s) to query. + deserialize: Whether to deserialize the responses. + timeout: The timeout in seconds for the query. **kwargs: Keyword arguments for the prepare_synapse_fn. Returns: diff --git a/bittensor/utils/substrate_utils/storage.py b/bittensor/utils/substrate_utils/storage.py index 315f7e44c8..1253b072f9 100644 --- a/bittensor/utils/substrate_utils/storage.py +++ b/bittensor/utils/substrate_utils/storage.py @@ -56,7 +56,7 @@ def create_from_data( """ Create a StorageKey instance providing raw storage key bytes - Args: + Parameters: data: bytes representation of the storage key runtime_config: RuntimeConfigurationObject metadata: GenericMetadataVersioned @@ -105,7 +105,7 @@ def create_from_storage_function( """ Create a StorageKey instance providing storage function details - Args: + Parameters: pallet: name of pallet storage_function: name of storage function params: Optional list of parameters in case of a Mapped storage function diff --git a/bittensor/utils/version.py b/bittensor/utils/version.py index c8f899d760..9025ce663b 100644 --- a/bittensor/utils/version.py +++ b/bittensor/utils/version.py @@ -54,11 +54,11 @@ def get_and_save_latest_version(timeout: int = 15) -> str: """ Retrieves and saves the latest version of Bittensor. - Args: - timeout (int): The timeout for the request to PyPI in seconds. Default is ``15``. + Parameters: + timeout: The timeout for the request to PyPI in seconds. Returns: - str: The latest version of Bittensor. + The latest version of Bittensor. """ version_file = _get_version_file_path() @@ -80,8 +80,8 @@ def check_version(timeout: int = 15): Check if the current version of Bittensor is up-to-date with the latest version on PyPi. Raises a VersionCheckError if the version check fails. - Args: - timeout (int): The timeout for the request to PyPI in seconds. Default is ``15``. + Parameters: + timeout: The timeout for the request to PyPI in seconds. Default is ``15``. """ try: @@ -101,8 +101,8 @@ def check_version(timeout: int = 15): def version_checking(timeout: int = 15): """Deprecated, kept for backwards compatibility. Use check_version() instead. - Args: - timeout (int): The timeout for calling :func:``check_version`` function. Default is ``15``. + Parameters: + timeout: The timeout for calling :func:``check_version`` function. """ from warnings import warn diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index b56604c720..d313bfda36 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -27,12 +27,13 @@ def normalize_max_weight( x: Union[NDArray[np.float32], "torch.FloatTensor"], limit: float = 0.1 ) -> Union[NDArray[np.float32], "torch.FloatTensor"]: """Normalizes the tensor x so that sum(x) = 1 and the max value is not greater than the limit. - Args: - x (:obj:`np.float32`): Tensor to be max_value normalized. + + Parameters: + x: Tensor to be max_value normalized. limit: float: Max value after normalization. Returns: - y (:obj:`np.float32`): Normalized x tensor. + y: Normalized x tensor. """ epsilon = 1e-7 # For numerical stability after normalization @@ -77,13 +78,13 @@ def convert_weight_uids_and_vals_to_tensor( Converts weights and uids from chain representation into a np.array (inverse operation from convert_weights_and_uids_for_emit). - Args: - n (int): number of neurons on network. - uids (list[int]): Tensor of uids as destinations for passed weights. - weights (list[int]): Tensor of weights. + Parameters: + n: number of neurons on network. + uids: Tensor of uids as destinations for passed weights. + weights: Tensor of weights. Returns: - row_weights (np.float32 or torch.FloatTensor): Converted row weights. + Converted row weights. """ row_weights = ( torch.zeros([n], dtype=torch.float32) @@ -106,14 +107,14 @@ def convert_root_weight_uids_and_vals_to_tensor( """Converts root weights and uids from chain representation into a np.array or torch FloatTensor (inverse operation from convert_weights_and_uids_for_emit) - Args: - n (int): number of neurons on network. - uids (list[int]): Tensor of uids as destinations for passed weights. - weights (list[int]): Tensor of weights. - subnets (list[int]): list of subnets on the network. + Parameters: + n: number of neurons on network. + uids: Tensor of uids as destinations for passed weights. + weights: Tensor of weights. + subnets: list of subnets on the network. Returns: - row_weights (np.float32): Converted row weights. + Converted row weights. """ row_weights = ( torch.zeros([n], dtype=torch.float32) @@ -143,13 +144,13 @@ def convert_bond_uids_and_vals_to_tensor( ) -> Union[NDArray[np.int64], "torch.LongTensor"]: """Converts bond and uids from chain representation into a np.array. - Args: - n (int): number of neurons on network. - uids (list[int]): Tensor of uids as destinations for passed bonds. - bonds (list[int]): Tensor of bonds. + Parameters: + n: number of neurons on network. + uids: Tensor of uids as destinations for passed bonds. + bonds: Tensor of bonds. Returns: - row_bonds (np.float32): Converted row bonds. + Converted row bonds. """ row_bonds = ( torch.zeros([n], dtype=torch.int64) @@ -167,13 +168,13 @@ def convert_weights_and_uids_for_emit( ) -> tuple[list[int], list[int]]: """Converts weights into integer u32 representation that sum to MAX_INT_WEIGHT. - Args: - uids (np.int64):Tensor of uids as destinations for passed weights. - weights (np.float32):Tensor of weights. + Parameters: + uids:Tensor of uids as destinations for passed weights. + weights:Tensor of weights. Returns: - weight_uids (list[int]): Uids as a list. - weight_vals (list[int]): Weights as a list. + weight_uids: Uids as a list. + weight_vals: Weights as a list. """ # Checks. weights = weights.tolist() @@ -222,23 +223,22 @@ def process_weights_for_netuid( tuple[NDArray[np.int64], NDArray[np.float32]], ]: """ - Processes weight tensors for a given subnet id using the provided weight and UID arrays, applying constraints - and normalization based on the subtensor and metagraph data. This function can handle both NumPy arrays and PyTorch + Processes weight tensors for a given subnet id using the provided weight and UID arrays, applying constraints and + normalization based on the subtensor and metagraph data. This function can handle both NumPy arrays and PyTorch tensors. - Args: - uids (Union[NDArray[np.int64], "torch.Tensor"]): Array of unique identifiers of the neurons. - weights (Union[NDArray[np.float32], "torch.Tensor"]): Array of weights associated with the user IDs. - netuid (int): The network uid to process weights for. - subtensor (Subtensor): Subtensor instance to access blockchain data. - metagraph (Optional[Metagraph]): Metagraph instance for additional network data. If None, it is fetched from - the subtensor using the netuid. - exclude_quantile (int): Quantile threshold for excluding lower weights. Defaults to ``0``. + Parameters: + uids: Array of unique identifiers of the neurons. + weights: Array of weights associated with the user IDs. + netuid: The network uid to process weights for. + subtensor: Subtensor instance to access blockchain data. + metagraph: Metagraph instance for additional network data. If None, it is fetched from the subtensor using the + netuid. + exclude_quantile: Quantile threshold for excluding lower weights. Returns: - Union[tuple["torch.Tensor", "torch.FloatTensor"], tuple[NDArray[np.int64], NDArray[np.float32]]]: tuple - containing the array of user IDs and the corresponding normalized weights. The data type of the return - matches the type of the input weights (NumPy or PyTorch). + Tuple containing the array of user IDs and the corresponding normalized weights. The data type of the return + matches the type of the input weights (NumPy or PyTorch). """ logging.debug("process_weights_for_netuid()") @@ -272,22 +272,20 @@ def process_weights( tuple[NDArray[np.int64], NDArray[np.float32]], ]: """ - Processes weight tensors for a given weights and UID arrays and hyperparams, applying constraints - and normalization based on the subtensor and metagraph data. This function can handle both NumPy arrays and PyTorch - tensors. + Processes weight tensors for a given weights and UID arrays and hyperparams, applying constraints and normalization + based on the subtensor and metagraph data. This function can handle both NumPy arrays and PyTorch tensors. - Args: - uids (Union[NDArray[np.int64], "torch.Tensor"]): Array of unique identifiers of the neurons. - weights (Union[NDArray[np.float32], "torch.Tensor"]): Array of weights associated with the user IDs. - num_neurons (int): The number of neurons in the network. - min_allowed_weights (Optional[int]): Subnet hyperparam Minimum number of allowed weights. - max_weight_limit (Optional[float]): Subnet hyperparam Maximum weight limit. - exclude_quantile (int): Quantile threshold for excluding lower weights. Defaults to ``0``. + Parameters: + uids: Array of unique identifiers of the neurons. + weights: Array of weights associated with the user IDs. + num_neurons: The number of neurons in the network. + min_allowed_weights: Subnet hyperparam Minimum number of allowed weights. + max_weight_limit: Subnet hyperparam Maximum weight limit. + exclude_quantile: Quantile threshold for excluding lower weights. Defaults to ``0``. Returns: - Union[tuple["torch.Tensor", "torch.FloatTensor"], tuple[NDArray[np.int64], NDArray[np.float32]]]: tuple - containing the array of user IDs and the corresponding normalized weights. The data type of the return - matches the type of the input weights (NumPy or PyTorch). + Tuple containing the array of user IDs and the corresponding normalized weights. The data type of the return + matches the type of the input weights (NumPy or PyTorch). """ logging.debug("process_weights()") logging.debug(f"weights: {weights}") @@ -445,9 +443,9 @@ def convert_uids_and_weights( ) -> tuple[np.ndarray, np.ndarray]: """Converts netuids and weights to numpy arrays if they are not already. - Arguments: - uids (Union[NDArray[np.int64], list]): The uint64 uids of destination neurons. - weights (Union[NDArray[np.float32], list]): The weights to set. These must be floated. + Parameters: + uids: The uint64 uids of destination neurons. + weights: The weights to set. These must be floated. Returns: tuple[ndarray, ndarray]: Bytes converted netuids and weights. @@ -465,10 +463,9 @@ def convert_and_normalize_weights_and_uids( ) -> tuple[list[int], list[int]]: """Converts weights and uids to numpy arrays if they are not already. - Arguments: - 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. + Parameters: + uids: The ``uint64`` uids of destination neurons. + weights: The weights to set. These must be ``float`` s and correspond to the passed ``uid`` s. Returns: weight_uids, weight_vals: Bytes converted weights and uids From 09b07857184ff27a70c4314eb50d93005005d968 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 13:07:22 -0700 Subject: [PATCH 232/416] docstrings: removed `default to/is` --- MIGRATION.md | 27 ++++++++++--------- bittensor/core/async_subtensor.py | 17 ++++++------ bittensor/core/axon.py | 4 +-- bittensor/core/dendrite.py | 6 ++--- .../core/extrinsics/asyncex/liquidity.py | 4 +-- bittensor/core/extrinsics/asyncex/serving.py | 3 +-- bittensor/core/extrinsics/asyncex/staking.py | 6 ++--- .../core/extrinsics/asyncex/unstaking.py | 4 +-- bittensor/core/extrinsics/liquidity.py | 4 +-- bittensor/core/extrinsics/serving.py | 9 +++---- bittensor/core/extrinsics/staking.py | 6 ++--- bittensor/core/extrinsics/unstaking.py | 4 +-- bittensor/core/subtensor.py | 25 +++++++++-------- bittensor/core/timelock.py | 6 ++--- bittensor/utils/balance.py | 2 +- bittensor/utils/registration/register_cuda.py | 2 +- bittensor/utils/version.py | 2 +- bittensor/utils/weight_utils.py | 2 +- tests/e2e_tests/utils/chain_interactions.py | 4 +-- 19 files changed, 67 insertions(+), 70 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index b5876a5768..dd27b8f1e7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -49,7 +49,7 @@ 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) +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. @@ -57,11 +57,13 @@ rename this variable in documentation. `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. +7. ✅ Replace `Arguments` with `Parameters`. Matches Python rules. Improve docstrings for writing MСP server. -8. Remove all type annotations for parameters in docstrings. +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. + - [x] CRv3 extrinsics + - [ ] CRv3 logic related subtensor's calls 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. @@ -69,7 +71,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. +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 @@ -83,13 +85,13 @@ rename this variable in documentation. 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. + - 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. +4. ✅ Add an async test versions. This will help us greatly improve the asynchronous implementation of Subtensors and Extrinsics. ## Implementation @@ -98,13 +100,13 @@ 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. -- [ ] 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). -- [ ] Based on the final `migration.md`, develop migration documentation for the community. +- [ ] Based on the final `MIGRATION.md`, develop migration documentation for the community. - [ ] Once complete, merge SDKv10 into staging and release version 10. @@ -219,4 +221,3 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `query_module` has updated parameters order. - method `query_map_subtensor` has updated parameters order. - method `query_map` has updated parameters order. -- \ No newline at end of file diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index df3086308d..312f80ed6e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -573,7 +573,7 @@ async def query_map( Parameters: module: The name of the module from which to query the map storage (e.g., "SubtensorModule", "System"). name: The specific storage function within the module to query (e.g., "Bonds", "Weights"). - params: Parameters to be passed to the query. Defaults to ``None``. + params: Parameters to be passed to the query. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. @@ -1647,7 +1647,7 @@ async def get_revealed_commitment( Parameters: netuid: The unique identifier of the subnetwork. uid: The neuron uid to retrieve the commitment from. - block: The block number to retrieve the commitment from. Default is ``None``. + block: The block number to retrieve the commitment from. Returns: A tuple of reveal block and commitment message. @@ -2840,7 +2840,7 @@ async def get_timelocked_weight_commits( Parameters: netuid: Subnet identifier. - block (Optional[int]): The blockchain block number for the query. Default is ``None``. + block (Optional[int]): The blockchain block number for the query. block_hash: The hash of the block to retrieve the stake 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. mechid: Subnet mechanism identifier. @@ -4413,10 +4413,9 @@ async def add_stake( 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``. + only execute if the price change doesn't exceed the rate tolerance. 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. period: The number of blocks during which the transaction will remain valid after it's submitted. If @@ -5736,8 +5735,8 @@ async def transfer( 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`. + transfer_all: Flag to transfer all tokens. + keep_alive: Flag to keep the connection alive. 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. @@ -5888,7 +5887,7 @@ async def unstake_all( 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. + price decrease. If not passed (None), then unstaking goes without price limit. 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. @@ -5975,7 +5974,7 @@ async def unstake_multiple( 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. + unstake_all: If true, unstakes all tokens. 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. diff --git a/bittensor/core/axon.py b/bittensor/core/axon.py index ad46604ee7..92bb7783e5 100644 --- a/bittensor/core/axon.py +++ b/bittensor/core/axon.py @@ -628,7 +628,7 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: Optional[str] = None) Parameters: parser: Argument parser to which the arguments will be added. - prefix: Prefix to add to the argument names. Defaults to None. + prefix: Prefix to add to the argument names. Note: Environment variables are used to define default values for the arguments. @@ -1020,7 +1020,7 @@ def log_and_handle_error( Parameters: synapse: The synapse object to be updated with error information. exception: The exception that was raised and needs to be logged and handled. - status_code: The HTTP status code to be set on the synapse object. Defaults to None. + status_code: The HTTP status code to be set on the synapse object. start_time: The timestamp marking the start of the processing, used to calculate process time. Returns: diff --git a/bittensor/core/dendrite.py b/bittensor/core/dendrite.py index d785e3bfb8..57c34ee7b1 100644 --- a/bittensor/core/dendrite.py +++ b/bittensor/core/dendrite.py @@ -361,8 +361,8 @@ def query( Parameters: axons: The list of target Axon information. - synapse: The Synapse object. Defaults to :func:`Synapse()`. - timeout: The request timeout duration in seconds. Defaults to ``12.0`` seconds. + synapse: The Synapse object. + timeout: The request timeout duration in seconds. Returns: If a single target axon is provided, returns the response from that axon. If multiple target axons are @@ -694,7 +694,7 @@ def preprocess_synapse_for_request( Parameters: target_axon_info: The target axon information. synapse: The synapse object to be preprocessed. - timeout: The request timeout duration in seconds. Defaults to ``12.0`` seconds. + timeout: The request timeout duration in seconds. Returns: The preprocessed synapse. diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index c2826b83e2..115d4ec99c 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -101,7 +101,7 @@ async def modify_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. 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`. + 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. @@ -162,7 +162,7 @@ async def remove_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. 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`. + 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. diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 9ab8b078b9..2ae86504da 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -144,7 +144,6 @@ async def serve_axon_extrinsic( netuid (int): The ``netuid`` being served on. axon (bittensor.core.axon.Axon): Axon to serve. certificate (bittensor.utils.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. @@ -220,7 +219,7 @@ async def publish_metadata_extrinsic( algorithm used for the data. 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'``.) - reset_bonds: 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. 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. diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index a065f8c7c7..b55b15b571 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -40,9 +40,9 @@ async def add_stake_extrinsic( 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: 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``. + 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 increase 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. diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 6bf725b8e0..44cf8f1653 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -202,7 +202,7 @@ async def unstake_all_extrinsic( 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`. + price decrease. If not passed (None), then unstaking goes without price limit. 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. @@ -272,7 +272,7 @@ async def unstake_multiple_extrinsic( 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. - unstake_all: If true, unstakes all tokens. Default is ``False``. + unstake_all: If true, unstakes all tokens. 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. diff --git a/bittensor/core/extrinsics/liquidity.py b/bittensor/core/extrinsics/liquidity.py index 56c1f6d13e..97efc27bcb 100644 --- a/bittensor/core/extrinsics/liquidity.py +++ b/bittensor/core/extrinsics/liquidity.py @@ -101,7 +101,7 @@ def modify_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. 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`. + 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. @@ -162,7 +162,7 @@ def remove_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. 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`. + 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. diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 3f08e5b63e..554ba890ba 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -141,10 +141,9 @@ def serve_axon_extrinsic( Parameters: subtensor: Subtensor instance object. - netuid (int): The ``netuid`` being served on. - axon (bittensor.core.axon.Axon): Axon to serve. - certificate (bittensor.utils.Certificate): Certificate to use for TLS. If ``None``, no TLS will be used. - Defaults to ``None``. + netuid: The ``netuid`` being served on. + axon: Axon to serve. + 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. @@ -218,7 +217,7 @@ def publish_metadata_extrinsic( algorithm used for the data. 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'``.) - reset_bonds: 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. 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. diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 99b692250d..ed87bc91eb 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -39,9 +39,9 @@ def add_stake_extrinsic( 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: 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``. + 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 increase 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. diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 2a72ae3d8d..a926fdd9d8 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -199,7 +199,7 @@ def unstake_all_extrinsic( 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`. + price decrease. If not passed (None), then unstaking goes without price limit. 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. @@ -268,7 +268,7 @@ def unstake_multiple_extrinsic( 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. - unstake_all: If true, unstakes all tokens. Default is ``False``. + unstake_all: If true, unstakes all tokens. 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. diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ea3c72dca0..4a15446601 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1025,7 +1025,7 @@ def get_revealed_commitment_by_hotkey( Parameters: netuid: The unique identifier of the subnetwork. hotkey_ss58_address: The ss58 address of the committee member. - block: The block number to retrieve the commitment from. Default is ``None``. + block: The block number to retrieve the commitment from. Returns: A tuple of reveal block and commitment message. @@ -1054,7 +1054,7 @@ def get_revealed_commitment( Parameters: netuid: The unique identifier of the subnetwork. uid: The neuron uid to retrieve the commitment from. - block: The block number to retrieve the commitment from. Default is ``None``. + block: The block number to retrieve the commitment from. Returns: A tuple of reveal block and commitment message. @@ -1083,7 +1083,7 @@ def get_all_revealed_commitments( Parameters: netuid: The unique identifier of the subnetwork. - block: The block number to retrieve the commitment from. Default is ``None``. + block: The block number to retrieve the commitment from. Returns: result: A dictionary of all revealed commitments in view {ss58_address: (reveal block, commitment message)}. @@ -1118,7 +1118,7 @@ def get_current_weight_commit_info( Parameters: netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. Default is ``None``. + block: The blockchain block number for the query. Returns: A list of commit details, where each item contains: @@ -1151,7 +1151,7 @@ def get_current_weight_commit_info_v2( Parameters: netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. Default is ``None``. + block: The blockchain block number for the query. Returns: A list of commit details, where each item contains: @@ -2008,7 +2008,7 @@ def get_subnet_prices( """Gets the current Alpha price in TAO for a specified subnet. Parameters: - block: The blockchain block number for the query. Default to `None`. + block: The blockchain block number for the query. Returns: dict: @@ -2047,7 +2047,7 @@ def get_timelocked_weight_commits( Parameters: netuid: Subnet identifier. - block: The blockchain block number for the query. Default is ``None``. + block: The blockchain block number for the query. mechid: Subnet mechanism identifier. Returns: @@ -3282,10 +3282,9 @@ def add_stake( 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``. + only execute if the price change doesn't exceed the rate tolerance. 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. period: The number of blocks during which the transaction will remain valid after it's submitted. If @@ -4591,8 +4590,8 @@ def transfer( 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`. + transfer_all: Flag to transfer all tokens. + keep_alive: Flag to keep the connection alive. 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. @@ -4744,7 +4743,7 @@ def unstake_all( 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. + price decrease. If not passed (None), then unstaking goes without price limit. 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. @@ -4830,7 +4829,7 @@ def unstake_multiple( 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. + unstake_all: If true, unstakes all tokens. 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. diff --git a/bittensor/core/timelock.py b/bittensor/core/timelock.py index 35c0fc1768..3e26006977 100644 --- a/bittensor/core/timelock.py +++ b/bittensor/core/timelock.py @@ -74,7 +74,7 @@ def encrypt( Parameters: data: Any bytes data to be encrypted. n_blocks: Number of blocks to encrypt. - block_time: Time in seconds for each block. Default is `12.0` seconds. + block_time: Time in seconds for each block. Returns: tuple: A tuple containing the encrypted data and reveal TimeLock reveal round. @@ -111,7 +111,7 @@ def decrypt( Parameters: encrypted_data: Encrypted data to be decrypted. no_errors: If True, no errors will be raised during decryption. - return_str: convert decrypted data to string if `True`. Default is `False`. + return_str: convert decrypted data to string if `True`. Returns: decrypted_data: Decrypted data, when reveled round is reached. @@ -147,7 +147,7 @@ def wait_reveal_and_decrypt( encrypted_data: Encrypted data to be decrypted. reveal_round: Reveal round to wait for. If None, will be parsed from encrypted data. no_errors: If True, no errors will be raised during decryption. - return_str: convert decrypted data to string if `True`. Default is `False`. + return_str: convert decrypted data to string if `True`. Raises: struct.error: If failed to parse reveal round from encrypted data. diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 5e3d7d9c56..5a84960152 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -279,7 +279,7 @@ def from_float(amount: float, netuid: int = 0) -> "Balance": Parameters: amount: The amount in tao. - netuid: The subnet uid for set currency unit. Defaults to `0`. + netuid: The subnet uid for set currency unit. Returns: A Balance object representing the given amount. diff --git a/bittensor/utils/registration/register_cuda.py b/bittensor/utils/registration/register_cuda.py index 5d41056258..34c8e00115 100644 --- a/bittensor/utils/registration/register_cuda.py +++ b/bittensor/utils/registration/register_cuda.py @@ -61,7 +61,7 @@ def solve_cuda( block_and_hotkey_hash_bytes: Keccak(Bytes of the block hash + bytes of the hotkey) 64 bytes. difficulty: Difficulty of the PoW problem. limit: Upper limit of the nonce. - dev_id: The CUDA device ID. Defaults to ``0``. + dev_id: The CUDA device ID. Returns: Tuple of the nonce and the seal corresponding to the solution. Returns -1 for nonce if no solution is found. diff --git a/bittensor/utils/version.py b/bittensor/utils/version.py index 9025ce663b..3f920e8333 100644 --- a/bittensor/utils/version.py +++ b/bittensor/utils/version.py @@ -81,7 +81,7 @@ def check_version(timeout: int = 15): Raises a VersionCheckError if the version check fails. Parameters: - timeout: The timeout for the request to PyPI in seconds. Default is ``15``. + timeout: The timeout for the request to PyPI in seconds. """ try: diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index d313bfda36..ab928d4be9 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -281,7 +281,7 @@ def process_weights( num_neurons: The number of neurons in the network. min_allowed_weights: Subnet hyperparam Minimum number of allowed weights. max_weight_limit: Subnet hyperparam Maximum weight limit. - exclude_quantile: Quantile threshold for excluding lower weights. Defaults to ``0``. + exclude_quantile: Quantile threshold for excluding lower weights. Returns: Tuple containing the array of user IDs and the corresponding normalized weights. The data type of the return diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index acb876d152..b988d68557 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -321,7 +321,7 @@ def sudo_set_admin_utils( 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". + call_module: The AdminUtils module to call. Returns: tuple: (success status, error details). @@ -364,7 +364,7 @@ async def async_sudo_set_admin_utils( 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". + call_module: The AdminUtils module to call. Returns: tuple: (success status, error details). From 5defaf4cd00e4e6f41115ca5ebfea636da95cb19 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 13:19:16 -0700 Subject: [PATCH 233/416] rollback with typing.Self --- bittensor/core/chain_data/info_base.py | 12 ++++++++---- bittensor/core/config.py | 7 +++++-- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/bittensor/core/chain_data/info_base.py b/bittensor/core/chain_data/info_base.py index 722f9b32ee..e2718619af 100644 --- a/bittensor/core/chain_data/info_base.py +++ b/bittensor/core/chain_data/info_base.py @@ -1,15 +1,19 @@ from dataclasses import dataclass -from typing import Any, Self +from typing import Any, TypeVar from bittensor.core.errors import SubstrateRequestException +# NOTE: once Python 3.10+ is required, we can use `typing.Self` instead of this for better ide integration and type hinting. +# This current generic does not play so nice with the inherited type hinting. +T = TypeVar("T", bound="InfoBase") + @dataclass class InfoBase: """Base dataclass for info objects.""" @classmethod - def from_dict(cls, decoded: dict) -> Self: + def from_dict(cls, decoded: dict) -> T: try: return cls._from_dict(decoded) except KeyError as e: @@ -18,9 +22,9 @@ def from_dict(cls, decoded: dict) -> Self: ) @classmethod - def list_from_dicts(cls, any_list: list[Any]) -> list[Self]: + def list_from_dicts(cls, any_list: list[Any]) -> list[T]: return [cls.from_dict(any_) for any_ in any_list] @classmethod - def _from_dict(cls, decoded: dict) -> Self: + def _from_dict(cls, decoded: dict) -> T: return cls(**decoded) diff --git a/bittensor/core/config.py b/bittensor/core/config.py index 3d9c105ace..2fb3d8495b 100644 --- a/bittensor/core/config.py +++ b/bittensor/core/config.py @@ -20,7 +20,7 @@ import os import sys from copy import deepcopy -from typing import Any, Optional, Self +from typing import Any, TypeVar, Type, Optional import yaml from munch import DefaultMunch @@ -236,10 +236,13 @@ def _add_default_arguments(self, parser: argparse.ArgumentParser) -> None: pass +T = TypeVar("T", bound="DefaultConfig") + + class DefaultConfig(Config): """A Config with a set of default values.""" @classmethod - def default(cls: Self) -> Self: + def default(cls: Type[T]) -> T: """Get default config.""" raise NotImplementedError("Function default is not implemented.") From f1111e7279eba75ac896d431b84a74c44ccd2e57 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 13:47:22 -0700 Subject: [PATCH 234/416] remove `bittensor.utils.substrate_utils` (not used) --- bittensor/utils/substrate_utils/__init__.py | 0 bittensor/utils/substrate_utils/hasher.py | 62 ----- bittensor/utils/substrate_utils/storage.py | 277 -------------------- 3 files changed, 339 deletions(-) delete mode 100644 bittensor/utils/substrate_utils/__init__.py delete mode 100644 bittensor/utils/substrate_utils/hasher.py delete mode 100644 bittensor/utils/substrate_utils/storage.py diff --git a/bittensor/utils/substrate_utils/__init__.py b/bittensor/utils/substrate_utils/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/utils/substrate_utils/hasher.py b/bittensor/utils/substrate_utils/hasher.py deleted file mode 100644 index 0075bb69dd..0000000000 --- a/bittensor/utils/substrate_utils/hasher.py +++ /dev/null @@ -1,62 +0,0 @@ -"""Helper functions used to calculate keys for Substrate storage items""" - -from hashlib import blake2b - -import xxhash - - -def blake2_256(data): - """ - Helper function to calculate a 32 bytes Blake2b hash for provided data, used as key for Substrate storage items - """ - return blake2b(data, digest_size=32).digest() - - -def blake2_128(data): - """ - Helper function to calculate a 16 bytes Blake2b hash for provided data, used as key for Substrate storage items - """ - return blake2b(data, digest_size=16).digest() - - -def blake2_128_concat(data): - """ - Helper function to calculate a 16 bytes Blake2b hash for provided data, concatenated with data, used as key - for Substrate storage items - """ - return blake2b(data, digest_size=16).digest() + data - - -def xxh128(data): - """ - Helper function to calculate a 2 concatenated xxh64 hash for provided data, used as key for several Substrate - """ - storage_key1 = bytearray(xxhash.xxh64(data, seed=0).digest()) - storage_key1.reverse() - - storage_key2 = bytearray(xxhash.xxh64(data, seed=1).digest()) - storage_key2.reverse() - - return storage_key1 + storage_key2 - - -def two_x64_concat(data): - """ - Helper function to calculate a xxh64 hash with concatenated data for provided data, - used as key for several Substrate - """ - storage_key = bytearray(xxhash.xxh64(data, seed=0).digest()) - storage_key.reverse() - - return storage_key + data - - -def xxh64(data): - storage_key = bytearray(xxhash.xxh64(data, seed=0).digest()) - storage_key.reverse() - - return storage_key - - -def identity(data): - return data diff --git a/bittensor/utils/substrate_utils/storage.py b/bittensor/utils/substrate_utils/storage.py deleted file mode 100644 index 1253b072f9..0000000000 --- a/bittensor/utils/substrate_utils/storage.py +++ /dev/null @@ -1,277 +0,0 @@ -import binascii -from typing import Any, Optional - -from scalecodec import ScaleBytes, GenericMetadataVersioned, ss58_decode -from scalecodec.base import ScaleDecoder, RuntimeConfigurationObject, ScaleType - -from bittensor.core.errors import StorageFunctionNotFound -from bittensor.utils.substrate_utils.hasher import ( - blake2_256, - two_x64_concat, - xxh128, - blake2_128, - blake2_128_concat, - identity, -) - - -class StorageKey: - """ - A StorageKey instance is a representation of a single state entry. - - Substrate uses a simple key-value data store implemented as a database-backed, modified Merkle tree. - All of Substrate's higher-level storage abstractions are built on top of this simple key-value store. - """ - - def __init__( - self, - pallet: Optional[str], - storage_function: Optional[str], - params: Optional[list], - data: Optional[bytes], - value_scale_type: Optional[str], - metadata: GenericMetadataVersioned, - runtime_config: RuntimeConfigurationObject, - ): - self.pallet = pallet - self.storage_function = storage_function - self.params = params - self.params_encoded = [] - self.data = data - self.metadata = metadata - self.runtime_config = runtime_config - self.value_scale_type = value_scale_type - self.metadata_storage_function = None - - @classmethod - def create_from_data( - cls, - data: bytes, - runtime_config: RuntimeConfigurationObject, - metadata: GenericMetadataVersioned, - value_scale_type: str = None, - pallet: str = None, - storage_function: str = None, - ) -> "StorageKey": - """ - Create a StorageKey instance providing raw storage key bytes - - Parameters: - data: bytes representation of the storage key - runtime_config: RuntimeConfigurationObject - metadata: GenericMetadataVersioned - value_scale_type: type string of to decode result data - pallet: name of pallet - storage_function: name of storage function - - Returns: - StorageKey - """ - if not value_scale_type and pallet and storage_function: - metadata_pallet = metadata.get_metadata_pallet(pallet) - - if not metadata_pallet: - raise StorageFunctionNotFound(f'Pallet "{pallet}" not found') - - storage_item = metadata_pallet.get_storage_function(storage_function) - - if not storage_item: - raise StorageFunctionNotFound( - f'Storage function "{pallet}.{storage_function}" not found' - ) - - # Process specific type of storage function - value_scale_type = storage_item.get_value_type_string() - - return cls( - pallet=None, - storage_function=None, - params=None, - data=data, - metadata=metadata, - value_scale_type=value_scale_type, - runtime_config=runtime_config, - ) - - @classmethod - def create_from_storage_function( - cls, - pallet: str, - storage_function: str, - params: list, - runtime_config: RuntimeConfigurationObject, - metadata: GenericMetadataVersioned, - ) -> "StorageKey": - """ - Create a StorageKey instance providing storage function details - - Parameters: - pallet: name of pallet - storage_function: name of storage function - params: Optional list of parameters in case of a Mapped storage function - runtime_config: RuntimeConfigurationObject - metadata: GenericMetadataVersioned - - Returns: - StorageKey - """ - storage_key_obj = cls( - pallet=pallet, - storage_function=storage_function, - params=params, - data=None, - runtime_config=runtime_config, - metadata=metadata, - value_scale_type=None, - ) - - storage_key_obj.generate() - - return storage_key_obj - - def convert_storage_parameter(self, scale_type: str, value: Any): - if type(value) is bytes: - value = f"0x{value.hex()}" - - if scale_type == "AccountId": - if value[0:2] != "0x": - return "0x{}".format( - ss58_decode(value, self.runtime_config.ss58_format) - ) - - return value - - def to_hex(self) -> Optional[str]: - """ - Returns a Hex-string representation of current StorageKey data - - Returns: - Hex string - """ - if self.data: - return f"0x{self.data.hex()}" - - def generate(self) -> bytes: - """ - Generate a storage key for current specified pallet/function/params - """ - - # Search storage call in metadata - metadata_pallet = self.metadata.get_metadata_pallet(self.pallet) - - if not metadata_pallet: - raise StorageFunctionNotFound(f'Pallet "{self.pallet}" not found') - - self.metadata_storage_function = metadata_pallet.get_storage_function( - self.storage_function - ) - - if not self.metadata_storage_function: - raise StorageFunctionNotFound( - f'Storage function "{self.pallet}.{self.storage_function}" not found' - ) - - # Process specific type of storage function - self.value_scale_type = self.metadata_storage_function.get_value_type_string() - param_types = self.metadata_storage_function.get_params_type_string() - - hashers = self.metadata_storage_function.get_param_hashers() - - storage_hash = xxh128( - metadata_pallet.value["storage"]["prefix"].encode() - ) + xxh128(self.storage_function.encode()) - - # Encode parameters - self.params_encoded = [] - if self.params: - for idx, param in enumerate(self.params): - if type(param) is ScaleBytes: - # Already encoded - self.params_encoded.append(param) - else: - param = self.convert_storage_parameter(param_types[idx], param) - param_obj = self.runtime_config.create_scale_object( - type_string=param_types[idx] - ) - self.params_encoded.append(param_obj.encode(param)) - - for idx, param in enumerate(self.params_encoded): - # Get hasher associated with param - try: - param_hasher = hashers[idx] - except IndexError: - raise ValueError(f"No hasher found for param #{idx + 1}") - - params_key = bytes() - - # Convert param to bytes - if type(param) is str: - params_key += binascii.unhexlify(param) - elif type(param) is ScaleBytes: - params_key += param.data - elif isinstance(param, ScaleDecoder): - params_key += param.data.data - - if not param_hasher: - param_hasher = "Twox128" - - if param_hasher == "Blake2_256": - storage_hash += blake2_256(params_key) - - elif param_hasher == "Blake2_128": - storage_hash += blake2_128(params_key) - - elif param_hasher == "Blake2_128Concat": - storage_hash += blake2_128_concat(params_key) - - elif param_hasher == "Twox128": - storage_hash += xxh128(params_key) - - elif param_hasher == "Twox64Concat": - storage_hash += two_x64_concat(params_key) - - elif param_hasher == "Identity": - storage_hash += identity(params_key) - - else: - raise ValueError('Unknown storage hasher "{}"'.format(param_hasher)) - - self.data = storage_hash - - return self.data - - def decode_scale_value(self, data: Optional[ScaleBytes] = None) -> ScaleType: - result_found = False - - if data is not None: - change_scale_type = self.value_scale_type - result_found = True - elif self.metadata_storage_function.value["modifier"] == "Default": - # Fallback to default value of storage function if no result - change_scale_type = self.value_scale_type - data = ScaleBytes( - self.metadata_storage_function.value_object["default"].value_object - ) - else: - # No result is interpreted as an Option<...> result - change_scale_type = f"Option<{self.value_scale_type}>" - data = ScaleBytes( - self.metadata_storage_function.value_object["default"].value_object - ) - - # Decode SCALE result data - updated_obj = self.runtime_config.create_scale_object( - type_string=change_scale_type, data=data, metadata=self.metadata - ) - updated_obj.decode() - updated_obj.meta_info = {"result_found": result_found} - - return updated_obj - - def __repr__(self): - if self.pallet and self.storage_function: - return f"" - elif self.data: - return f"" - else: - return repr(self) From 038e9bdb1462f2e2c4d8ff858bfefbe9d445722c Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 13:57:33 -0700 Subject: [PATCH 235/416] remove unused parameters --- bittensor/core/async_subtensor.py | 104 ++---------------------------- bittensor/core/subtensor.py | 88 ++----------------------- 2 files changed, 8 insertions(+), 184 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 312f80ed6e..3850b90ffa 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1710,85 +1710,6 @@ async def get_all_revealed_commitments( result[hotkey_ss58_address] = commitment_message return result - # TODO: remove in SDKv10 - async def get_current_weight_commit_info( - self, - netuid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> list[tuple[str, str, int]]: - """ - Retrieves CRV3 weight commit information for a specific subnet. - - Parameters: - netuid: The unique identifier of the subnet. - block: The block number to query. Do not specify if using block_hash or reuse_block. - block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - - Returns: - A list of commit details, where each item contains: - - ss58_address: The address of the committer. - - commit_message: The commit message. - - reveal_round: The round when the commitment was revealed. - - The list may be empty if there are no commits found. - """ - deprecated_message( - message="The method `get_current_weight_commit_info` is deprecated and will be removed in version 10.0.0. " - "Use `get_current_weight_commit_info_v2` instead." - ) - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.substrate.query_map( - module="SubtensorModule", - storage_function="CRV3WeightCommits", - params=[netuid], - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) - - commits = result.records[0][1] if result.records else [] - return [WeightCommitInfo.from_vec_u8(commit) for commit in commits] - - # TODO: remove in SDKv10 - async def get_current_weight_commit_info_v2( - self, - netuid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> list[tuple[str, int, str, int]]: - """ - Retrieves CRV3 weight commit information for a specific subnet. - - Parameters: - netuid: The unique identifier of the subnet. - block: The block number to query. Do not specify if using block_hash or reuse_block. - block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - - Returns: - A list of commit details, where each item contains: - - ss58_address: The address of the committer. - - commit_block: The block number when the commitment was made. - - commit_message: The commit message. - - reveal_round: The round when the commitment was revealed. - - The list may be empty if there are no commits found. - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.substrate.query_map( - module="SubtensorModule", - storage_function="CRV3WeightCommitsV2", - params=[netuid], - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) - - commits = result.records[0][1] if result.records else [] - return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] - async def get_delegate_by_hotkey( self, hotkey_ss58: str, @@ -2635,13 +2556,11 @@ async def get_stake( return Balance.from_rao(int(stake)).set_unit(netuid=netuid) - # TODO: remove unused parameters in SDK.v10 + # TODO: update related with fee calculation async def get_stake_add_fee( self, amount: Balance, netuid: int, - coldkey_ss58: str, - hotkey_ss58: str, block: Optional[int] = None, ) -> Balance: """ @@ -2650,8 +2569,6 @@ async def get_stake_add_fee( Parameters: amount: Amount of stake to add in TAO netuid: Netuid of subnet - coldkey_ss58: SS58 address of source coldkey - hotkey_ss58: SS58 address of destination hotkey block: Block number at which to perform the calculation Returns: @@ -2868,13 +2785,11 @@ async def get_timelocked_weight_commits( commits = result.records[0][1] if result.records else [] return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] - # TODO: remove unused parameters in SDK.v10 + # TODO: update related with fee calculation async def get_unstake_fee( self, amount: Balance, netuid: int, - coldkey_ss58: str, - hotkey_ss58: str, block: Optional[int] = None, ) -> Balance: """ @@ -2883,8 +2798,6 @@ async def get_unstake_fee( Parameters: amount: Amount of stake to unstake in TAO netuid: Netuid of subnet - coldkey_ss58: SS58 address of source coldkey - hotkey_ss58: SS58 address of destination hotkey block: Block number at which to perform the calculation Returns: @@ -2894,16 +2807,11 @@ async def get_unstake_fee( amount=amount, netuid=netuid, block=block ) - # TODO: remove unused parameters in SDK.v10 + # TODO: update related with fee calculation async def get_stake_movement_fee( self, amount: Balance, origin_netuid: int, - origin_hotkey_ss58: str, - origin_coldkey_ss58: str, - destination_netuid: int, - destination_hotkey_ss58: str, - destination_coldkey_ss58: str, block: Optional[int] = None, ) -> Balance: """ @@ -2912,11 +2820,6 @@ async def get_stake_movement_fee( Parameters: amount: Amount of stake to move in TAO origin_netuid: Netuid of source subnet - origin_hotkey_ss58: SS58 address of source hotkey - origin_coldkey_ss58: SS58 address of source coldkey - destination_netuid: Netuid of destination subnet - destination_hotkey_ss58: SS58 address of destination hotkey - destination_coldkey_ss58: SS58 address of destination coldkey block: Block number at which to perform the calculation Returns: @@ -3246,6 +3149,7 @@ async def get_total_subnets( ) return getattr(result, "value", None) + # TODO: update related with fee calculation async def get_transfer_fee( self, wallet: "Wallet", dest: str, value: Balance, keep_alive: bool = True ) -> Balance: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 4a15446601..227e0618f5 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1109,69 +1109,6 @@ def get_all_revealed_commitments( result[hotkey_ss58_address] = commitment_message return result - # TODO: remove in SDKv10 - def get_current_weight_commit_info( - self, netuid: int, block: Optional[int] = None - ) -> list[tuple[str, str, int]]: - """ - Retrieves CRV3 weight commit information for a specific subnet. - - Parameters: - netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - - Returns: - A list of commit details, where each item contains: - - ss58_address: The address of the committer. - - commit_message: The commit message. - - reveal_round: The round when the commitment was revealed. - - The list may be empty if there are no commits found. - """ - deprecated_message( - message="The method `get_current_weight_commit_info` is deprecated and will be removed in version 10.0.0. " - "Use `get_current_weight_commit_info_v2` instead." - ) - result = self.substrate.query_map( - module="SubtensorModule", - storage_function="CRV3WeightCommits", - params=[netuid], - block_hash=self.determine_block_hash(block), - ) - - commits = result.records[0][1] if result.records else [] - return [WeightCommitInfo.from_vec_u8(commit) for commit in commits] - - # TODO: remove in SDKv10 - def get_current_weight_commit_info_v2( - self, netuid: int, block: Optional[int] = None - ) -> list[tuple[str, int, str, int]]: - """ - Retrieves CRV3 weight commit information for a specific subnet. - - Parameters: - netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - - Returns: - A list of commit details, where each item contains: - - ss58_address: The address of the committer. - - commit_block: The block number when the commitment was made. - - commit_message: The commit message. - - reveal_round: The round when the commitment was revealed. - - The list may be empty if there are no commits found. - """ - result = self.substrate.query_map( - module="SubtensorModule", - storage_function="CRV3WeightCommitsV2", - params=[netuid], - block_hash=self.determine_block_hash(block), - ) - - commits = result.records[0][1] if result.records else [] - return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] - def get_delegate_by_hotkey( self, hotkey_ss58: str, block: Optional[int] = None ) -> Optional["DelegateInfo"]: @@ -1875,13 +1812,11 @@ def get_stake( return Balance.from_rao(int(stake)).set_unit(netuid=netuid) - # TODO: remove unused parameters in SDK.v10 + # TODO: update related with fee calculation def get_stake_add_fee( self, amount: Balance, netuid: int, - coldkey_ss58: str, - hotkey_ss58: str, block: Optional[int] = None, ) -> Balance: """ @@ -1890,8 +1825,6 @@ def get_stake_add_fee( Parameters: amount: Amount of stake to add in TAO netuid: Netuid of subnet - coldkey_ss58: SS58 address of coldkey - hotkey_ss58: SS58 address of hotkey block: Block number at which to perform the calculation Returns: @@ -2070,13 +2003,11 @@ def get_timelocked_weight_commits( commits = result.records[0][1] if result.records else [] return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] - # TODO: remove unused parameters in SDK.v10 + # TODO: update related with fee calculation def get_unstake_fee( self, amount: Balance, netuid: int, - coldkey_ss58: str, - hotkey_ss58: str, block: Optional[int] = None, ) -> Balance: """ @@ -2085,8 +2016,6 @@ def get_unstake_fee( Parameters: amount: Amount of stake to unstake in TAO netuid: Netuid of subnet - coldkey_ss58: SS58 address of coldkey - hotkey_ss58: SS58 address of hotkey block: Block number at which to perform the calculation Returns: @@ -2094,16 +2023,11 @@ def get_unstake_fee( """ return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) - # TODO: remove unused parameters in SDK.v10 + # TODO: update related with fee calculation def get_stake_movement_fee( self, amount: Balance, origin_netuid: int, - origin_hotkey_ss58: str, - origin_coldkey_ss58: str, - destination_netuid: int, - destination_hotkey_ss58: str, - destination_coldkey_ss58: str, block: Optional[int] = None, ) -> Balance: """ @@ -2112,11 +2036,6 @@ def get_stake_movement_fee( Parameters: amount: Amount of stake to move in TAO origin_netuid: Netuid of origin subnet - origin_hotkey_ss58: SS58 address of origin hotkey - origin_coldkey_ss58: SS58 address of origin coldkey - destination_netuid: Netuid of destination subnet - destination_hotkey_ss58: SS58 address of destination hotkey - destination_coldkey_ss58: SS58 address of destination coldkey block: Block number at which to perform the calculation Returns: @@ -2368,6 +2287,7 @@ def get_total_subnets(self, block: Optional[int] = None) -> Optional[int]: ) return getattr(result, "value", None) + # TODO: update related with fee calculation def get_transfer_fee( self, wallet: "Wallet", From e9b16c74b854ebe8fe4e544aafdc47cbf9ac9b56 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 13:59:45 -0700 Subject: [PATCH 236/416] F401 'bittensor.utils.deprecated_message' imported but unused --- bittensor/core/async_subtensor.py | 1 - bittensor/core/subtensor.py | 1 - 2 files changed, 2 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 3850b90ffa..9c18d5f763 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -108,7 +108,6 @@ get_transfer_fn_params, get_mechid_storage_index, ) -from bittensor.utils import deprecated_message from bittensor.utils.balance import ( Balance, fixed_to_float, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 227e0618f5..2257b00696 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -108,7 +108,6 @@ is_valid_ss58_address, u16_normalized_float, u64_normalized_float, - deprecated_message, get_transfer_fn_params, get_mechid_storage_index, ) From e4caf7208bb0ca5b37376ae3c08c57f5568c8d4e Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 14:02:02 -0700 Subject: [PATCH 237/416] fix tests --- bittensor/core/subtensor_api/commitments.py | 4 --- bittensor/core/subtensor_api/subnets.py | 1 - bittensor/core/subtensor_api/utils.py | 6 ---- tests/unit_tests/test_async_subtensor.py | 9 ----- tests/unit_tests/test_subtensor.py | 9 ----- tests/unit_tests/test_subtensor_extended.py | 38 --------------------- 6 files changed, 67 deletions(-) diff --git a/bittensor/core/subtensor_api/commitments.py b/bittensor/core/subtensor_api/commitments.py index ff130a3e54..9c3c989c47 100644 --- a/bittensor/core/subtensor_api/commitments.py +++ b/bittensor/core/subtensor_api/commitments.py @@ -11,10 +11,6 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_all_commitments = subtensor.get_all_commitments self.get_all_revealed_commitments = subtensor.get_all_revealed_commitments self.get_commitment = subtensor.get_commitment - self.get_current_weight_commit_info = subtensor.get_current_weight_commit_info - self.get_current_weight_commit_info_v2 = ( - subtensor.get_current_weight_commit_info_v2 - ) self.get_last_commitment_bonds_reset_block = ( subtensor.get_last_commitment_bonds_reset_block ) diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py index a8b3b74ca8..c0e9e6d751 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -19,7 +19,6 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_parents = subtensor.get_parents self.get_children = subtensor.get_children self.get_children_pending = subtensor.get_children_pending - self.get_current_weight_commit_info = subtensor.get_current_weight_commit_info self.get_hyperparameter = subtensor.get_hyperparameter self.get_liquidity_list = subtensor.get_liquidity_list self.get_neuron_for_pubkey_and_subnet = ( diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index b0a2f29f16..ec5785e88d 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -46,12 +46,6 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_last_commitment_bonds_reset_block = ( subtensor._subtensor.get_last_commitment_bonds_reset_block ) - subtensor.get_current_weight_commit_info = ( - subtensor._subtensor.get_current_weight_commit_info - ) - subtensor.get_current_weight_commit_info_v2 = ( - subtensor._subtensor.get_current_weight_commit_info_v2 - ) subtensor.get_delegate_by_hotkey = subtensor._subtensor.get_delegate_by_hotkey subtensor.get_delegate_identities = subtensor._subtensor.get_delegate_identities subtensor.get_delegate_take = subtensor._subtensor.get_delegate_take diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 31020ec84c..92ffc815da 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3790,8 +3790,6 @@ async def test_get_stake_add_fee(subtensor, mocker): result = await subtensor.get_stake_add_fee( amount=amount, netuid=netuid, - coldkey_ss58=mocker.Mock(), - hotkey_ss58=mocker.Mock(), ) # Asserts @@ -3815,8 +3813,6 @@ async def test_get_unstake_fee(subtensor, mocker): result = await subtensor.get_unstake_fee( amount=amount, netuid=netuid, - coldkey_ss58=mocker.Mock(), - hotkey_ss58=mocker.Mock(), ) # Asserts @@ -3840,11 +3836,6 @@ async def test_get_stake_movement_fee(subtensor, mocker): result = await subtensor.get_stake_movement_fee( amount=amount, origin_netuid=netuid, - origin_hotkey_ss58=mocker.Mock(), - origin_coldkey_ss58=mocker.Mock(), - destination_netuid=mocker.Mock(), - destination_hotkey_ss58=mocker.Mock(), - destination_coldkey_ss58=mocker.Mock(), ) # Asserts diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 34ff218220..0665972c77 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4004,8 +4004,6 @@ def test_get_stake_add_fee(subtensor, mocker): result = subtensor.get_stake_add_fee( amount=amount, netuid=netuid, - coldkey_ss58=mocker.Mock(), - hotkey_ss58=mocker.Mock(), ) # Asserts @@ -4028,8 +4026,6 @@ def test_get_unstake_fee(subtensor, mocker): result = subtensor.get_unstake_fee( amount=amount, netuid=netuid, - coldkey_ss58=mocker.Mock(), - hotkey_ss58=mocker.Mock(), ) # Asserts @@ -4052,11 +4048,6 @@ def test_get_stake_movement_fee(subtensor, mocker): result = subtensor.get_stake_movement_fee( amount=amount, origin_netuid=netuid, - origin_hotkey_ss58=mocker.Mock(), - origin_coldkey_ss58=mocker.Mock(), - destination_netuid=mocker.Mock(), - destination_hotkey_ss58=mocker.Mock(), - destination_coldkey_ss58=mocker.Mock(), ) # Asserts diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index f6544f6299..7e90488d63 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -424,44 +424,6 @@ def test_get_children_pending(mock_substrate, subtensor): ) -def test_get_current_weight_commit_info_v2( - mock_substrate, subtensor, fake_wallet, mocker -): - mock_substrate.query_map.return_value.records = [ - ( - mocker.ANY, - [ - ( - bytearray(32), - 100, - b"data", - 123, - ), - ], - ), - ] - - result = subtensor.get_current_weight_commit_info_v2( - netuid=1, - ) - - assert result == [ - ( - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - 100, - "0x64617461", - 123, - ), - ] - - mock_substrate.query_map.assert_called_once_with( - module="SubtensorModule", - storage_function="CRV3WeightCommitsV2", - params=[1], - block_hash=None, - ) - - def test_get_delegate_by_hotkey(mock_substrate, subtensor, mock_delegate_info): mock_substrate.runtime_call.return_value.value = mock_delegate_info From 8b0be7d1fda68f2d3ff51d818d7b2490254be101 Mon Sep 17 00:00:00 2001 From: Roman Date: Tue, 23 Sep 2025 14:51:37 -0700 Subject: [PATCH 238/416] fix e2e tests (fee related --- tests/e2e_tests/test_stake_fee.py | 68 ------------------------------- 1 file changed, 68 deletions(-) diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index 8d26ecbed2..11518cb037 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -31,8 +31,6 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): 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, ( @@ -43,8 +41,6 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): 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." @@ -58,41 +54,21 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): # 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, }, ] @@ -101,11 +77,6 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): 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"], ( @@ -124,11 +95,6 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): 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, ( @@ -168,8 +134,6 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): stake_fee_0 = await async_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, ( @@ -180,8 +144,6 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): unstake_fee_root = await async_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." @@ -195,41 +157,21 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): # 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, }, ] @@ -238,11 +180,6 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): stake_fee = await async_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"], ( @@ -261,11 +198,6 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): stake_fee = await async_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, ( From f1d4f7268b3e9d20f0d80bfea31a814e1ebe7952 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 19:54:16 -0700 Subject: [PATCH 239/416] add logging --- bittensor/utils/registration/async_pow.py | 4 +++- bittensor/utils/registration/pow.py | 12 +++++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bittensor/utils/registration/async_pow.py b/bittensor/utils/registration/async_pow.py index a6d1c67c99..aa45cf502d 100644 --- a/bittensor/utils/registration/async_pow.py +++ b/bittensor/utils/registration/async_pow.py @@ -7,7 +7,7 @@ from typing import Callable, Union, Optional, TYPE_CHECKING from bittensor.core.errors import SubstrateRequestException - +from bittensor.utils.btlogging import logging from bittensor.utils.registration.pow import ( get_cpu_count, update_curr_block, @@ -512,6 +512,7 @@ async def create_pow_async( raise ValueError(f"Subnet {netuid} does not exist") solution: Optional[POWSolution] if cuda: + logging.debug("Solve difficulty with CUDA.") solution = await _solve_for_difficulty_fast_cuda( subtensor=subtensor, wallet=wallet, @@ -523,6 +524,7 @@ async def create_pow_async( log_verbose=log_verbose, ) else: + logging.debug("Solve difficulty.") solution = await _solve_for_difficulty_fast( subtensor=subtensor, wallet=wallet, diff --git a/bittensor/utils/registration/pow.py b/bittensor/utils/registration/pow.py index dfbd9b8fc3..1fa4c23ee8 100644 --- a/bittensor/utils/registration/pow.py +++ b/bittensor/utils/registration/pow.py @@ -409,7 +409,7 @@ def _solve_for_nonce_block_cuda( ) if solution != -1: - # Check if solution is valid (i.e. not -1) + # Check if solution is valid (i.e., not -1) return POWSolution(solution, block_number, difficulty, seal) return None @@ -1156,9 +1156,10 @@ def create_pow( raise ValueError(f"Subnet {netuid} does not exist.") if cuda: + logging.debug("Solve difficulty with CUDA.") solution: Optional[POWSolution] = _solve_for_difficulty_fast_cuda( - subtensor, - wallet, + subtensor=subtensor, + wallet=wallet, netuid=netuid, output_in_place=output_in_place, dev_id=dev_id, @@ -1167,9 +1168,10 @@ def create_pow( log_verbose=log_verbose, ) else: + logging.debug("Solve difficulty.") solution: Optional[POWSolution] = _solve_for_difficulty_fast( - subtensor, - wallet, + subtensor=subtensor, + wallet=wallet, netuid=netuid, output_in_place=output_in_place, num_processes=num_processes, From 23981a34f3a90fdf0794c7acec37bc954e69cadb Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 19:54:46 -0700 Subject: [PATCH 240/416] add `get_caller_name` --- bittensor/utils/__init__.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 662193792d..c75a0b7936 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -510,5 +510,14 @@ def get_transfer_fn_params( def get_function_name() -> str: - """Returns the name of the calling function.""" + """Return the current function's name.""" return inspect.currentframe().f_back.f_code.co_name + + +def get_caller_name(depth: int = 2) -> str: + """Return the name of the caller function.""" + frame = inspect.currentframe() + for _ in range(depth): + if frame is not None: + frame = frame.f_back + return frame.f_code.co_name if frame else "unknown" From a3c1354235ee8a33fd4597e355bcf3e05d67e818 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:15:04 -0700 Subject: [PATCH 241/416] improve extrinsics --- bittensor/core/extrinsics/asyncex/children.py | 83 +-- .../core/extrinsics/asyncex/liquidity.py | 197 +++--- .../core/extrinsics/asyncex/move_stake.py | 473 +++++++------ .../core/extrinsics/asyncex/registration.py | 616 ++++++++--------- bittensor/core/extrinsics/asyncex/root.py | 144 ++-- bittensor/core/extrinsics/asyncex/serving.py | 254 +++---- bittensor/core/extrinsics/asyncex/staking.py | 565 +++++++-------- .../core/extrinsics/asyncex/start_call.py | 50 +- bittensor/core/extrinsics/asyncex/sudo.py | 20 +- bittensor/core/extrinsics/asyncex/take.py | 114 +-- bittensor/core/extrinsics/asyncex/transfer.py | 192 +++--- .../core/extrinsics/asyncex/unstaking.py | 650 +++++++++--------- bittensor/core/extrinsics/asyncex/utils.py | 25 +- bittensor/core/extrinsics/asyncex/weights.py | 113 +-- bittensor/core/extrinsics/children.py | 105 +-- bittensor/core/extrinsics/liquidity.py | 197 +++--- bittensor/core/extrinsics/move_stake.py | 461 +++++++------ bittensor/core/extrinsics/registration.py | 600 ++++++++-------- bittensor/core/extrinsics/root.py | 149 ++-- bittensor/core/extrinsics/serving.py | 285 ++++---- bittensor/core/extrinsics/staking.py | 546 ++++++++------- bittensor/core/extrinsics/start_call.py | 44 +- bittensor/core/extrinsics/sudo.py | 19 +- bittensor/core/extrinsics/take.py | 113 +-- bittensor/core/extrinsics/transfer.py | 186 +++-- bittensor/core/extrinsics/unstaking.py | 641 ++++++++--------- bittensor/core/extrinsics/utils.py | 25 +- bittensor/core/extrinsics/weights.py | 113 +-- 28 files changed, 3411 insertions(+), 3569 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index 5ed14b7929..f758d3e4cc 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING, Optional from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import float_to_u64, unlock_key, get_function_name -from bittensor.utils.btlogging import logging +from bittensor.core.extrinsics.asyncex.utils import sudo_call_extrinsic +from bittensor.utils import float_to_u64 if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -12,7 +12,7 @@ async def set_children_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], period: Optional[int] = None, @@ -26,7 +26,7 @@ async def set_children_extrinsic( Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: bittensor wallet instance. - hotkey: The ``SS58`` address of the neuron's hotkey. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -52,16 +52,13 @@ async def set_children_extrinsic( bittensor_wallet.errors.KeyFileError: Failed to decode keyfile data. bittensor_wallet.errors.PasswordError: Decryption failed or wrong password for decryption provided. """ - unlock = unlock_key(wallet, raise_error=raise_error) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - async with subtensor.substrate as substrate: - call = await substrate.compose_call( + call = await subtensor.substrate.compose_call( call_module="SubtensorModule", call_function="set_children", call_params={ @@ -72,7 +69,7 @@ async def set_children_extrinsic( ) for proportion, child_hotkey in children ], - "hotkey": hotkey, + "hotkey": hotkey_ss58, "netuid": netuid, }, ) @@ -84,17 +81,12 @@ async def set_children_extrinsic( raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - calling_function=get_function_name(), ) - - if not wait_for_finalization and not wait_for_inclusion: - return response - - if response.success: - return response - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) + async def root_set_pending_childkey_cooldown_extrinsic( subtensor: "AsyncSubtensor", @@ -122,39 +114,14 @@ async def root_set_pending_childkey_cooldown_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - async with subtensor.substrate as substrate: - call = await substrate.compose_call( - call_module="SubtensorModule", - call_function="set_pending_childkey_cooldown", - call_params={"cooldown": cooldown}, - ) - - sudo_call = await substrate.compose_call( - call_module="Sudo", - call_function="sudo", - call_params={"call": call}, - ) - - response = 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, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response - - if response.success: - return response - - return response + return await sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, + call_module="SubtensorModule", + call_function="set_pending_childkey_cooldown", + call_params={"cooldown": cooldown}, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index 115d4ec99c..f655da7a21 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -1,9 +1,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import price_to_tick if TYPE_CHECKING: @@ -48,37 +46,37 @@ async def add_liquidity_extrinsic( 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) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + tick_low = price_to_tick(price_low.tao) + tick_high = price_to_tick(price_high.tao) + + call = await subtensor.substrate.compose_call( + call_module="Swap", + call_function="add_liquidity", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "tick_low": tick_low, + "tick_high": tick_high, + "liquidity": liquidity.rao, + }, ) - tick_low = price_to_tick(price_low.tao) - tick_high = price_to_tick(price_high.tao) - - call = await subtensor.substrate.compose_call( - call_module="Swap", - call_function="add_liquidity", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "tick_low": tick_low, - "tick_high": tick_high, - "liquidity": liquidity.rao, - }, - ) - - return 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, - raise_error=raise_error, - calling_function=get_function_name(), - ) + 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, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def modify_liquidity_extrinsic( @@ -115,33 +113,33 @@ async def modify_liquidity_extrinsic( Note: Modifying 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) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="Swap", + call_function="modify_position", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "position_id": position_id, + "liquidity_delta": liquidity_delta.rao, + }, ) - call = await subtensor.substrate.compose_call( - call_module="Swap", - call_function="modify_position", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - "liquidity_delta": liquidity_delta.rao, - }, - ) - - return 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, - raise_error=raise_error, - calling_function=get_function_name(), - ) + 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, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def remove_liquidity_extrinsic( @@ -176,32 +174,32 @@ async def remove_liquidity_extrinsic( 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) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="Swap", + call_function="remove_liquidity", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "position_id": position_id, + }, ) - call = await subtensor.substrate.compose_call( - call_module="Swap", - call_function="remove_liquidity", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - }, - ) - - return 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, - raise_error=raise_error, - calling_function=get_function_name(), - ) + 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, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def toggle_user_liquidity_extrinsic( @@ -231,24 +229,25 @@ async def toggle_user_liquidity_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="Swap", + call_function="toggle_user_liquidity", + call_params={"netuid": netuid, "enable": enable}, ) - call = await subtensor.substrate.compose_call( - call_module="Swap", - call_function="toggle_user_liquidity", - call_params={"netuid": netuid, "enable": enable}, - ) - - 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, - calling_function=get_function_name(), - ) + 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, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 525f780b0a..192b94e7e0 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -1,8 +1,7 @@ import asyncio -from typing import TYPE_CHECKING, Optional +from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import get_function_name from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -73,60 +72,16 @@ async def transfer_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - amount.set_unit(netuid=origin_netuid) - - # Check sufficient stake - stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=destination_coldkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - ) - if stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message) - - logging.info( - f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " - f"[blue]{destination_coldkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " - f"[yellow]{destination_netuid}[/yellow]" - ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": destination_coldkey_ss58, - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, - ) - - response = 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, - calling_function=get_function_name(), - ) + amount.set_unit(netuid=origin_netuid) - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + # Check sufficient stake + stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, @@ -135,17 +90,69 @@ async def transfer_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + if stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + logging.debug( + f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " + f"[blue]{destination_coldkey_ss58}[/blue]" ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + logging.debug( + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " + f"[yellow]{destination_netuid}[/yellow]" ) + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="transfer_stake", + call_params={ + "destination_coldkey": destination_coldkey_ss58, + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, + ) + + response = 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 response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + # Get updated stakes + origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + ) + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + return response + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def swap_stake_extrinsic( @@ -187,86 +194,18 @@ async def swap_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount.set_unit(netuid=origin_netuid) - - # Check sufficient stake - stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - ) - - if stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call_params = { - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - } - - if safe_swapping: - origin_pool, destination_pool = await asyncio.gather( - subtensor.subnet(netuid=origin_netuid), - subtensor.subnet(netuid=destination_netuid), - ) - swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao - swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - - logging.info( - f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]\n" - f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " - f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" - ) - call_params.update( - { - "limit_price": swap_rate_ratio_with_tolerance, - "allow_partial": allow_partial_stake, - } - ) - call_function = "swap_stake_limit" - else: - logging.info( - f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]" - ) - call_function = "swap_stake" - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - response = 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, - calling_function=get_function_name(), - ) - - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + amount.set_unit(netuid=origin_netuid) - # Get updated stakes - origin_stake, dest_stake = await _get_stake_in_origin_and_dest( - subtensor, + # Check sufficient stake + stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( + subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, @@ -274,23 +213,105 @@ async def swap_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + + if stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + call_params = { + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + } + + if safe_swapping: + origin_pool, destination_pool = await asyncio.gather( + subtensor.subnet(netuid=origin_netuid), + subtensor.subnet(netuid=destination_netuid), + ) + swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao + swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) + + logging.debug( + f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]\n" + f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " + f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" + ) + call_params.update( + { + "limit_price": swap_rate_ratio_with_tolerance, + "allow_partial": allow_partial_stake, + } + ) + call_function = "swap_stake_limit" + else: + logging.debug( + f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]" + ) + call_function = "swap_stake" + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + + response = 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, ) - return response + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + ) + + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } + return response - if safe_swapping and "Custom error: 8" in response.message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + if safe_swapping and "Custom error: 8" in response.message: + response.message = "Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." + + logging.error(f"[red]{response.message}[/red]") + return response - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def move_stake_extrinsic( @@ -329,84 +350,98 @@ async def move_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not amount and not move_all_stake: - message = ( - "Please specify an `amount` or `move_all_stake` argument to move stake." - ) - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Check sufficient stake - stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( - subtensor=subtensor, - 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: - message = f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - amount.set_unit(netuid=origin_netuid) - - logging.info( - 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]" - ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": origin_hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey_ss58, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, - ) - - response = 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, - calling_function=get_function_name(), - ) - - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if not amount and not move_all_stake: + return ExtrinsicResponse( + False, + "Please specify an `amount` or `move_all_stake` argument to move stake.", + ).with_log() + + # Check sufficient stake + stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( subtensor=subtensor, 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, destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + if move_all_stake: + amount = stake_in_origin + + elif stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + amount.set_unit(netuid=origin_netuid) + + logging.debug( + 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]" ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="move_stake", + call_params={ + "origin_hotkey": origin_hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_hotkey": destination_hotkey_ss58, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, ) + response = 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 response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = await _get_stake_in_origin_and_dest( + subtensor=subtensor, + 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, + destination_netuid=destination_netuid, + ) + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index f3a29b1d42..7b455d087b 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -1,19 +1,14 @@ """ 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. -- burned_register_extrinsic: Registers the wallet to chain by recycling TAO. """ import asyncio from typing import Optional, Union, TYPE_CHECKING -from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee +from bittensor.core.errors import RegistrationError from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging -from bittensor.utils.registration import log_no_torch_error, create_pow_async, torch +from bittensor.utils.registration import create_pow_async, log_no_torch_error, torch if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -45,107 +40,98 @@ async def burned_register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - block_hash = await subtensor.substrate.get_chain_head() - if not await subtensor.subnet_exists(netuid, block_hash=block_hash): - logging.error( - f":cross_mark: [red]Failed error:[/red] subnet [blue]{netuid}[/blue] does not exist." - ) - return ExtrinsicResponse( - False, - f"Subnet #{netuid} does not exist", - extrinsic_function=get_function_name(), + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + block_hash = await subtensor.substrate.get_chain_head() + if not await subtensor.subnet_exists(netuid, block_hash=block_hash): + return ExtrinsicResponse( + False, f"Subnet {netuid} does not exist." + ).with_log() + + neuron, old_balance, recycle_amount = await asyncio.gather( + subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid, block_hash=block_hash + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + subtensor.recycle(netuid=netuid, block_hash=block_hash), ) - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + if not neuron.is_null: + message = "Already registered." + logging.debug(f"[green]{message}[/green]") + logging.debug(f"\t\tuid: [blue]{neuron.uid}[/blue]") + logging.debug(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") + logging.debug(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") + logging.debug(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") + return ExtrinsicResponse( + message=message, data={"neuron": neuron, "old_balance": old_balance} + ) + + logging.debug(f"Recycling {recycle_amount} to register on subnet:{netuid}") + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="burned_register", + call_params={ + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + }, + ) + response = 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, + ) + extrinsic_fee = response.extrinsic_fee + logging.debug( + f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{extrinsic_fee}[/blue]." ) + if not response.success: + logging.error(f"[red]{response.message}[/red]") + await asyncio.sleep(0.5) + return response - logging.info( - f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue][magenta] ...[/magenta]" - ) - - # We could do this as_completed because we don't need old_balance and recycle - # if neuron is null, but the complexity isn't worth it considering the small performance - # gains we'd hypothetically receive in this situation - neuron, old_balance, recycle_amount = await asyncio.gather( - subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid, block_hash=block_hash - ), - subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash), - subtensor.recycle(netuid=netuid, block_hash=block_hash), - ) - - if not neuron.is_null: - message = "Already registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - logging.info(f"\t\tuid: [blue]{neuron.uid}[/blue]") - logging.info(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") - logging.info(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") - logging.info(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") - return ExtrinsicResponse( - message=message, extrinsic_function=get_function_name() + # Successful registration, final check for neuron and pubkey + new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) + + logging.debug( + 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 ) - logging.debug(":satellite: [magenta]Recycling TAO for Registration...[/magenta]") - - # 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]." - ) - response = 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, - calling_function=get_function_name(), - ) - - if not response.success: - logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") - await asyncio.sleep(0.5) - return response + response.data = { + "neuron": neuron, + "balance_before": old_balance, + "balance_after": new_balance, + "recycle_amount": recycle_amount, + } - # 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 - 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: - message = "Registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - return response + if is_registered: + logging.debug("[green]Registered.[/green]") + return response + + # neuron not found + message = f"Neuron with hotkey {wallet.hotkey.ss58_address} not found in subnet {netuid} after registration." + return ExtrinsicResponse( + success=False, + message=message, + extrinsic=response.extrinsic, + error=RegistrationError(message), + ).with_log() - # neuron not found, try again - message = "Unknown error. Neuron not found." - logging.error(f":cross_mark: [red]{message}[/red]") - response.success = False - response.message = message - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def register_subnet_extrinsic( @@ -172,44 +158,50 @@ async def register_subnet_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) - burn_cost = await subtensor.get_subnet_burn_cost() - - if burn_cost > balance: - message = f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO." - logging.error(message) - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - - response = 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, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) + burn_cost = await subtensor.get_subnet_burn_cost() + + if burn_cost > balance: + return ExtrinsicResponse( + False, + f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO.", + ).with_log() + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register_network", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + }, + ) - if response.success: - logging.success( - ":white_heavy_check_mark: [green]Successfully registered subnet[/green]" + response = 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, ) + + if not wait_for_finalization and not wait_for_inclusion: + return response + + if response.success: + logging.debug("[green]Successfully registered subnet.[/green]") + return response + + logging.error(f"Failed to register subnet: {response.message}") return response - logging.error(f"Failed to register subnet: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def register_extrinsic( @@ -256,166 +248,149 @@ async def register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - block_hash = await subtensor.substrate.get_chain_head() - logging.debug("[magenta]Checking subnet status... [/magenta]") - if not await subtensor.subnet_exists(netuid, block_hash=block_hash): - message = f"Subnet #{netuid} does not exist." - logging.error(f":cross_mark: [red]Failed error:[/red] {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info( - f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue] [magenta]...[/magenta]" - ) - neuron = await subtensor.get_neuron_for_pubkey_and_subnet( - hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid, block_hash=block_hash - ) - - if not neuron.is_null: - message = "Already registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - logging.info(f"\t\tuid: [blue]{neuron.uid}[/blue]") - logging.info(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") - logging.info(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") - logging.info(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") - return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Registration hotkey: {wallet.hotkey.ss58_address}, Public coldkey: " - f"{wallet.coldkey.ss58_address} in the network: {subtensor.network}." - ) - - if not torch: - log_no_torch_error() - return ExtrinsicResponse( - False, "Torch is not installed.", extrinsic_function=get_function_name() - ) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + block_hash = await subtensor.substrate.get_chain_head() + if not await subtensor.subnet_exists(netuid, block_hash=block_hash): + return ExtrinsicResponse( + False, f"Subnet {netuid} does not exist." + ).with_log() - # Attempt rolling registration. - attempts = 1 + neuron = await subtensor.get_neuron_for_pubkey_and_subnet( + hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid, block_hash=block_hash + ) - while True: - logging.info( - f":satellite: [magenta]Registering...[/magenta] [blue]({attempts}/{max_allowed_attempts})[/blue]" + if not neuron.is_null: + message = "Already registered." + logging.debug(f"[green]{message}[/green]") + logging.debug(f"\t\tuid: [blue]{neuron.uid}[/blue]") + logging.debug(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") + logging.debug(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") + logging.debug(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") + return ExtrinsicResponse(message=message, data={"neuron": neuron}) + + logging.debug( + f"Registration hotkey: [blue]{wallet.hotkey.ss58_address}[/blue], Public coldkey: " + f"[blue]{wallet.coldkey.ss58_address}[/blue] in the network: [blue]{subtensor.network}[/blue]." ) - # Solve latest POW. - if cuda: - if not torch.cuda.is_available(): - return ExtrinsicResponse( - False, "CUDA not available.", extrinsic_function=get_function_name() - ) - pow_result = await create_pow_async( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - output_in_place=output_in_place, - cuda=cuda, - dev_id=dev_id, - tpb=tpb, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) - else: - pow_result = await create_pow_async( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - output_in_place=output_in_place, - cuda=cuda, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) + if not torch: + log_no_torch_error() + return ExtrinsicResponse(False, "Torch is not installed.").with_log() - # pow failed - if not pow_result: - # might be registered already on this subnet - is_registered = await subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - message = f"Already registered on netuid: {netuid}" - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - return ExtrinsicResponse( - True, message, extrinsic_function=get_function_name() - ) + # Attempt rolling registration. + attempts = 1 - # pow successful, proceed to submit pow to chain for registration - else: - 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): - 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, - }, + while True: + # Solve latest POW. + if cuda: + if not torch.cuda.is_available(): + return ExtrinsicResponse(False, "CUDA not available.").with_log() + + logging.debug(f"Creating a POW with CUDA.") + pow_result = await create_pow_async( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + output_in_place=output_in_place, + cuda=cuda, + dev_id=dev_id, + tpb=tpb, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, ) - response = await subtensor.sign_and_send_extrinsic( - call=call, + else: + logging.debug(f"Creating a POW.") + pow_result = await create_pow_async( + subtensor=subtensor, wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), + netuid=netuid, + output_in_place=output_in_place, + cuda=cuda, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, ) - if not response.success: - # Look error here - # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs + # pow failed + if not pow_result: + # might be registered already on this subnet + is_registered = await subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + message = f"Already registered in subnet {netuid}." + logging.debug(f"[green]{message}[/green]") + return ExtrinsicResponse(message=message) - if "HotKeyAlreadyRegisteredInSubNet" in response.message: - logging.info( - f":white_heavy_check_mark: [green]Already Registered on subnet:[/green] " - f"[blue]{netuid}[/blue]." - ) - return response - await asyncio.sleep(0.5) - - # Successful registration, final check for neuron and pubkey - if response.success: - logging.info(":satellite: Checking Registration status...") - is_registered = await subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + # pow successful, proceed to submit pow to chain for registration + else: + # check if a pow result is still valid + while not await pow_result.is_stale_async(subtensor=subtensor): + 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, + }, ) - if is_registered: - logging.success( - ":white_heavy_check_mark: [green]Registered.[/green]" - ) - return response - - # neuron not found, try again - logging.error( - ":cross_mark: [red]Unknown error. Neuron not found.[/red]" + response = 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, ) - continue + + if not response.success: + # Look error here + # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs + if "HotKeyAlreadyRegisteredInSubNet" in response.message: + logging.debug( + f"[green]Already registered on subnet:[/green] [blue]{netuid}[/blue]." + ) + return response + await asyncio.sleep(0.5) + + if response.success: + is_registered = await subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + logging.debug("[green]Registered.[/green]") + return response + + # neuron not found, try again + logging.warning("[red]Unknown error. Neuron not found.[/red]") + continue + else: + # Exited loop because pow is no longer valid. + logging.warning("[red]POW is stale.[/red]") + # Try again. + + if attempts < max_allowed_attempts: + # Failed registration, retry pow + attempts += 1 + logging.warning( + f"Failed registration, retrying pow ... [blue]({attempts}/{max_allowed_attempts})[/blue]" + ) else: - # Exited loop because pow is no longer valid. - logging.error("[red]POW is stale.[/red]") - # Try again. - - if attempts < max_allowed_attempts: - # Failed registration, retry pow - attempts += 1 - logging.error( - f":satellite: [magenta]Failed registration, retrying pow ...[/magenta] " - f"[blue]({attempts}/{max_allowed_attempts})[/blue]" - ) - else: - # Failed to register after max attempts. - message = "No more attempts." - logging.error(f"[red]{message}[/red]") - return ExtrinsicResponse( - False, message, extrinsic_function=get_function_name() - ) + # Failed to register after max attempts. + return ExtrinsicResponse(False, "No more attempts.").with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def set_subnet_identity_extrinsic( @@ -460,50 +435,51 @@ async def set_subnet_identity_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_subnet_identity", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + "netuid": netuid, + "subnet_name": subnet_name, + "github_repo": github_repo, + "subnet_contact": subnet_contact, + "subnet_url": subnet_url, + "logo_url": logo_url, + "discord": discord, + "description": description, + "additional": additional, + }, + ) - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + response = 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, ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_subnet_identity", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "netuid": netuid, - "subnet_name": subnet_name, - "github_repo": github_repo, - "subnet_contact": subnet_contact, - "subnet_url": subnet_url, - "logo_url": logo_url, - "discord": discord, - "description": description, - "additional": additional, - }, - ) - - response = 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, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response + if not wait_for_finalization and not wait_for_inclusion: + return response - if response.success: - logging.success( - f":white_heavy_check_mark: [green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" + if response.success: + logging.debug( + f"[green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" + ) + return response + + logging.error( + f"[red]Failed to set identity for subnet {netuid}: {response.message}[/red]" ) return response - message = f"Failed to set identity for subnet #{netuid}" - logging.error(f":cross_mark: {message}: {response.message}") - response.message = message - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/root.py b/bittensor/core/extrinsics/asyncex/root.py index 88a7f13c4d..cae2149052 100644 --- a/bittensor/core/extrinsics/asyncex/root.py +++ b/bittensor/core/extrinsics/asyncex/root.py @@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import u16_normalized_float, unlock_key, get_function_name +from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -59,85 +59,83 @@ async def root_register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + netuid = 0 + logging.debug( + f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]." ) - netuid = 0 - logging.info( - f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]" - ) - - logging.info("Fetching recycle amount & balance.") - block_hash = await subtensor.get_block_hash() - recycle_call, balance = await asyncio.gather( - subtensor.get_hyperparameter( - param_name="Burn", - netuid=netuid, - block_hash=block_hash, - ), - subtensor.get_balance( - wallet.coldkeypub.ss58_address, - block_hash=block_hash, - ), - ) + block_hash = await subtensor.get_block_hash() + recycle_call, balance = await asyncio.gather( + subtensor.get_hyperparameter( + param_name="Burn", + netuid=netuid, + block_hash=block_hash, + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, + block_hash=block_hash, + ), + ) - current_recycle = Balance.from_rao(int(recycle_call)) + current_recycle = Balance.from_rao(int(recycle_call)) - if balance < current_recycle: - message = f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO" - logging.error(f"[red]{message}[/red].") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) + if balance < current_recycle: + return ExtrinsicResponse( + False, + f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO.", + ).with_log() - logging.debug( - f"Checking if hotkey ([blue]{wallet.hotkey_str}[/blue]) is registered on root." - ) - is_registered = await subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - message = "Already registered on root network." - logging.error(f":white_heavy_check_mark: [green]{message}[/green]") - return ExtrinsicResponse( - message=message, extrinsic_function=get_function_name() + logging.debug( + f"Checking if hotkey ([blue]{wallet.hotkey.ss58_address}[/blue]) is registered on root." + ) + is_registered = await subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) + if is_registered: + return ExtrinsicResponse(message="Already registered on root network.") - logging.info(":satellite: [magenta]Registering to root network...[/magenta]") - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="root_register", - call_params={"hotkey": wallet.hotkey.ss58_address}, - ) - response = 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, - ) + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="root_register", + call_params={"hotkey": wallet.hotkey.ss58_address}, + ) + response = 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 response.success: - logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") - await asyncio.sleep(0.5) - return response + if not response.success: + logging.error(f"[red]{response.message}[/red]") + await asyncio.sleep(0.5) + return response - # Successful registration, final check for neuron and pubkey - uid = await subtensor.substrate.query( - module="SubtensorModule", - storage_function="Uids", - params=[netuid, wallet.hotkey.ss58_address], - ) - if uid is not None: - response.data = {"uid": uid} - logging.info( - f":white_heavy_check_mark: [green]Registered with UID: {uid}[/green]." + # Successful registration, final check for neuron and pubkey + uid = await subtensor.get_uid_for_hotkey_on_subnet( + hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid ) - return response - - # neuron not found, try again - message = "Unknown error. Neuron not found." - logging.error(f":cross_mark: [red]{message}[/red]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) + if uid is not None: + response.data = { + "hotkey_ss58": wallet.hotkey.ss58_address, + "netuid": netuid, + "uid": uid, + } + logging.debug( + f"Hotkey {wallet.hotkey.ss58_address} registered in subnet {netuid} with UID: {uid}." + ) + return response + + # neuron not found, try again + return ExtrinsicResponse(False, "Unknown error. Neuron not found.").with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 2ae86504da..88c05e63ea 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -3,12 +3,9 @@ from bittensor.core.errors import MetadataError from bittensor.core.settings import version_as_int -from bittensor.core.types import AxonServeCallParams -from bittensor.core.types import ExtrinsicResponse +from bittensor.core.types import AxonServeCallParams, ExtrinsicResponse from bittensor.utils import ( - get_function_name, networking as net, - unlock_key, Certificate, ) from bittensor.utils.btlogging import logging @@ -57,73 +54,77 @@ async def serve_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - # Decrypt hotkey - if not (unlock := unlock_key(wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + signing_keypair = "hotkey" + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair + ) + ).success: + return unlocked + + params = AxonServeCallParams( + **{ + "version": version_as_int, + "ip": net.ip_to_int(ip), + "port": port, + "ip_type": net.ip_version(ip), + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + "protocol": protocol, + "placeholder1": placeholder1, + "placeholder2": placeholder2, + "certificate": certificate, + } ) + logging.debug("Checking axon ...") + neuron = await subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid + ) + neuron_up_to_date = not neuron.is_null and params == neuron + if neuron_up_to_date: + message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" + logging.debug(f"[blue]{message}[/blue]") + return ExtrinsicResponse(message=message) - params = AxonServeCallParams( - **{ - "version": version_as_int, - "ip": net.ip_to_int(ip), - "port": port, - "ip_type": net.ip_version(ip), - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - "protocol": protocol, - "placeholder1": placeholder1, - "placeholder2": placeholder2, - "certificate": certificate, - } - ) - logging.debug("Checking axon ...") - neuron = await subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid - ) - neuron_up_to_date = not neuron.is_null and params == neuron - if neuron_up_to_date: - message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" - logging.debug(f"[blue]{message}[/blue]") - return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " - f"[green]{subtensor.network}:{netuid}[/green]" - ) - - 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(), - ) - response = 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, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - if response.success: logging.debug( - f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " + f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " f"[green]{subtensor.network}:{netuid}[/green]" ) + + 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(), + ) + response = await subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + sign_with=signing_keypair, + period=period, + raise_error=raise_error, + ) + + if response.success: + logging.debug( + f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " + f"[green]{subtensor.network}:{netuid}[/green]" + ) + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f"Failed: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def serve_axon_extrinsic( @@ -154,45 +155,57 @@ async def serve_axon_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(axon.wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + axon.wallet, raise_error, "hotkey" + ) + ).success: + return unlocked + + external_port = axon.external_port + + # ---- Get external ip ---- + if axon.external_ip is None: + try: + external_ip = await asyncio.get_running_loop().run_in_executor( + None, net.get_external_ip + ) + logging.debug( + f"[green]Found external ip:[/green] [blue]{external_ip}[/blue]" + ) + except Exception as error: + message = f"Unable to attain your external ip. Check your internet connection. Error: {error}" + if raise_error: + raise ConnectionError(message) from error + + return ExtrinsicResponse(False, message).with_log() + else: + external_ip = axon.external_ip + + # ---- Subscribe to chain ---- + response = await serve_extrinsic( + subtensor=subtensor, + wallet=axon.wallet, + ip=external_ip, + port=external_port, + protocol=4, + netuid=netuid, + certificate=certificate, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) + response.data = { + "external_ip": external_ip, + "external_port": external_port, + "axon": axon, + } + return response - external_port = axon.external_port - - # ---- Get external ip ---- - if axon.external_ip is None: - try: - external_ip = await asyncio.get_running_loop().run_in_executor( - None, net.get_external_ip - ) - logging.success( - f":white_heavy_check_mark: [green]Found external ip:[/green] [blue]{external_ip}[/blue]" - ) - except Exception as e: - raise ConnectionError( - f"Unable to attain your external ip. Check your internet connection. error: {e}" - ) from e - else: - external_ip = axon.external_ip - - # ---- Subscribe to chain ---- - response = await serve_extrinsic( - subtensor=subtensor, - wallet=axon.wallet, - ip=external_ip, - port=external_port, - protocol=4, - netuid=netuid, - certificate=certificate, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def publish_metadata_extrinsic( @@ -234,18 +247,20 @@ async def publish_metadata_extrinsic( MetadataError: If there is an error in submitting the extrinsic, or if the response from the blockchain indicates failure. """ - if not (unlock := unlock_key(wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + try: + signing_keypair = "hotkey" + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair + ) + ).success: + return unlocked - fields = [{f"{data_type}": data}] - if reset_bonds: - fields.append({"ResetBondsFlag": b""}) + fields = [{f"{data_type}": data}] + if reset_bonds: + fields.append({"ResetBondsFlag": b""}) - async with subtensor.substrate as substrate: - call = await substrate.compose_call( + call = await subtensor.substrate.compose_call( call_module="Commitments", call_function="set_commitment", call_params={ @@ -257,23 +272,26 @@ async def publish_metadata_extrinsic( response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, - sign_with="hotkey", + sign_with=signing_keypair, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, raise_error=raise_error, - calling_function=get_function_name(), ) if response.success: return response + raise MetadataError(response.message) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) + async def get_metadata( subtensor: "AsyncSubtensor", netuid: int, - hotkey: str, + hotkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, @@ -286,7 +304,7 @@ async def get_metadata( commit_data = await subtensor.substrate.query( module="Commitments", storage_function="CommitmentOf", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=block_hash, reuse_block_hash=reuse_block, ) @@ -296,7 +314,7 @@ async def get_metadata( async def get_last_bonds_reset( subtensor: "AsyncSubtensor", netuid: int, - hotkey: str, + hotkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, @@ -307,7 +325,7 @@ async def get_last_bonds_reset( 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. + hotkey_ss58: 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. @@ -319,7 +337,7 @@ async def get_last_bonds_reset( block = await subtensor.substrate.query( module="Commitments", storage_function="LastBondsReset", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=block_hash, ) return block diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index b55b15b571..865b17f806 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -2,11 +2,9 @@ from typing import Optional, Sequence, TYPE_CHECKING from async_substrate_interface.errors import SubstrateRequestException -from bittensor.core.types import ExtrinsicResponse from bittensor.core.extrinsics.utils import get_old_stakes -from bittensor.utils import unlock_key, format_error_message, get_function_name -from bittensor.core.types import UIDs -from bittensor.utils import unlock_key, format_error_message +from bittensor.utils import format_error_message +from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -55,153 +53,146 @@ async def add_stake_extrinsic( Raises: SubstrateRequestException: Raised if the extrinsic fails to be included in the block within the timeout. + + Notes: + The `data` field in the returned `ExtrinsicResponse` contains extra information about the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + old_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) + block_hash = await subtensor.substrate.get_chain_head() - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - old_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) - block_hash = await subtensor.substrate.get_chain_head() - - # Get current stake and existential deposit - old_stake, existential_deposit = await asyncio.gather( - subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block_hash=block_hash, - ), - subtensor.get_existential_deposit(block_hash=block_hash), - ) - - # Leave existential balance to keep key alive. - if amount > old_balance - existential_deposit: - # If we are staking all, we need to leave at least the existential deposit. - amount = old_balance - existential_deposit - else: - amount = amount - - # Check enough to stake. - if amount > old_balance: - message = "Not enough stake" - logging.error(f":cross_mark: [red]{message}:[/red]") - logging.error(f"\t\tbalance:{old_balance}") - logging.error(f"\t\tamount: {amount}") - logging.error(f"\t\twallet: {wallet.name}") - return ExtrinsicResponse( - False, f"{message}.", extrinsic_function=get_function_name() + # Get current stake and existential deposit + old_stake, existential_deposit = await asyncio.gather( + subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block_hash=block_hash, + ), + subtensor.get_existential_deposit(block_hash=block_hash), ) - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_staked": amount.rao, - } + # Leave existential balance to keep key alive. + if amount > old_balance - existential_deposit: + # If we are staking all, we need to leave at least the existential deposit. + amount = old_balance - existential_deposit + else: + amount = amount - if safe_staking: - pool = await subtensor.subnet(netuid=netuid) - base_price = pool.price.tao + # Check enough to stake. + if amount > old_balance: + message = "Not enough stake" + logging.debug(f":cross_mark: [red]{message}:[/red]") + logging.debug(f"\t\tbalance:{old_balance}") + logging.debug(f"\t\tamount: {amount}") + logging.debug(f"\t\twallet: {wallet.name}") + return ExtrinsicResponse(False, f"{message}.").with_log() + + 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 + + price_with_tolerance = ( + base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) + ) - price_with_tolerance = ( - base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) - ) + logging.debug( + f"Safe Staking to: [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]." + ) - 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]" - ) + 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.debug( + f"Staking to: [blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]." + ) + call_function = "add_stake" - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - 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]" + response = 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", + use_nonce=True, + period=period, + raise_error=raise_error, ) - call_function = "add_stake" - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - response = 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, - calling_function=get_function_name(), - ) - if response.success: # If we successfully staked. - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return response + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + logging.debug("[green]Finalized.[/green]") + + 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.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + logging.debug( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + "stake_before": old_stake, + "stake_after": new_stake, + } + return response - 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 response.message: + response.message = "Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - 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]" - ) + logging.error(f"[red]{response.message}[/red]") return response - if safe_staking and "Custom error: 8" in response.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: {response.message}.[/red]") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def add_stake_multiple_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + hotkey_ss58s: list[str], amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, @@ -227,172 +218,196 @@ async def add_stake_multiple_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. + + Note: + The `data` field in the returned `ExtrinsicResponse` contains the results of each individual internal + `add_stake_extrinsic` call. Each entry maps a tuple key `(idx, hotkey_ss58, netuid)` to either: + - the corresponding `ExtrinsicResponse` object if the staking attempt was executed, or + - `None` if the staking was skipped due to failing validation (e.g., wrong balance, zero amount, etc.). + In the key, `idx` is the index the stake attempt. + + This allows the caller to inspect which specific operations were attempted and which were not. """ - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if not all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ): + raise TypeError( + "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." + ) - 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 ExtrinsicResponse(True, "Success") - if len(hotkey_ss58s) == 0: - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() - ) + if not len(netuids) == len(hotkey_ss58s) == len(amounts): + raise ValueError( + "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.") - 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(a, Balance) for a in amounts): + raise TypeError("Each `amount` must be an instance of Balance.") + + new_amounts: Sequence[Optional[Balance]] = [ + amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) + ] - if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): - raise TypeError("hotkey_ss58s must be a list of str") + if sum(amount.tao for amount in new_amounts) == 0: + # Staking 0 tao + return ExtrinsicResponse(True, "Success") - new_amounts: Sequence[Optional[Balance]] = [ - amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) - ] + block_hash = await subtensor.substrate.get_chain_head() - if sum(amount.tao for amount in new_amounts) == 0: - # Staking 0 tao - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() + all_stakes = await subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash + ) + old_stakes: list[Balance] = get_old_stakes( + wallet=wallet, + hotkey_ss58s=hotkey_ss58s, + netuids=netuids, + all_stakes=all_stakes, ) - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - block_hash = await subtensor.substrate.get_chain_head() - - all_stakes = await subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash - ) - old_stakes: list[Balance] = get_old_stakes( - wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes - ) - - # Remove existential balance to keep key alive. - # Keys must maintain a balance of at least 1000 rao to stay alive. - total_staking_rao = sum( - [amount.rao if amount is not None else 0 for amount in new_amounts] - ) - 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. - if old_balance.rao > 1000: - old_balance -= Balance.from_rao(1000) - - elif total_staking_rao < 1000: - # Staking less than 1000 rao to the wallets. - pass - else: - # Staking more than 1000 rao to the wallets. - # Reduce the amount to stake to each wallet to keep the balance above 1000 rao. - percent_reduction = 1 - (1000 / total_staking_rao) - new_amounts = [ - Balance.from_tao(amount.tao * percent_reduction) for amount in new_amounts - ] + # Remove existential balance to keep key alive. Keys must maintain a balance of at least 1000 rao to stay alive. + total_staking_rao = sum( + [amount.rao if amount is not None else 0 for amount in new_amounts] + ) + old_balance = initial_balance = await subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block_hash=block_hash + ) - successful_stakes = 0 - response = ExtrinsicResponse(False, "", extrinsic_function=get_function_name()) - for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( - zip(hotkey_ss58s, new_amounts, old_stakes, netuids) - ): - # Check enough to stake - if amount > old_balance: - logging.error( - f":cross_mark: [red]Not enough balance[/red]: [green]{old_balance}[/green] to stake: " - f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white]" - ) - continue + if total_staking_rao == 0: + # Staking all to the first wallet. + if old_balance.rao > 1000: + old_balance -= Balance.from_rao(1000) + + elif total_staking_rao < 1000: + # Staking less than 1000 rao to the wallets. + pass + else: + # Staking more than 1000 rao to the wallets. + # Reduce the amount to stake to each wallet to keep the balance above 1000 rao. + percent_reduction = 1 - (1000 / total_staking_rao) + new_amounts = [ + Balance.from_tao(amount.tao * percent_reduction) + for amount in new_amounts + ] + + successful_stakes = 0 + data = {} + for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( + zip(hotkey_ss58s, new_amounts, old_stakes, netuids) + ): + data.update({(idx, hotkey_ss58, netuid): None}) + + # Check enough to stake + if amount > old_balance: + logging.warning( + f"Not enough balance: [green]{old_balance}[/green] to stake " + f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white] " + f"with hotkey: [blue]{hotkey_ss58}[/blue] on netuid [blue]{netuid}[/blue]." + ) + continue - try: - logging.info( - f"Staking [blue]{amount}[/blue] to hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " - f"[blue]{netuid}[/blue]" - ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={ - "hotkey": hotkey_ss58, - "amount_staked": amount.rao, - "netuid": netuid, - }, - ) - response = 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, - calling_function=get_function_name(), - ) + try: + logging.debug( + f"Staking [blue]{amount}[/blue] to hotkey [blue]{hotkey_ss58}[/blue] on netuid " + f"[blue]{netuid}[/blue]." + ) + response = await add_stake_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) - # If we successfully staked. - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - old_balance -= amount + data.update({(idx, hotkey_ss58, netuid): response}) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + old_balance -= amount + successful_stakes += 1 + continue + + logging.debug("[green]Finalized[/green]") + + new_block_hash = await subtensor.substrate.get_chain_head() + new_stake, new_balance = await asyncio.gather( + subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block_hash=new_block_hash, + ), + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=new_block_hash + ), + ) + logging.debug( + f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: " + f"[green]{new_stake}[/green]" + ) + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + old_balance = new_balance successful_stakes += 1 continue - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - new_block_hash = await subtensor.substrate.get_chain_head() - new_stake, new_balance = await asyncio.gather( - subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block_hash=new_block_hash, - ), - subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=new_block_hash - ), - ) - logging.info( - f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: " - f"[green]{new_stake}[/green]" + logging.warning( + f"Staking amount {amount} to hotkey_ss58 {hotkey_ss58} in subnet {netuid} was not successful." ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - old_balance = new_balance - successful_stakes += 1 - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - continue - except SubstrateRequestException as error: - logging.error( - f":cross_mark: [red]Add Stake Multiple error: {format_error_message(error)}[/red]" + except SubstrateRequestException as error: + logging.error( + f"[red]Add Stake Multiple error: {format_error_message(error)}[/red]" + ) + if raise_error: + raise + + if len(netuids) > successful_stakes > 0: + success = False + message = "Some stake were successful." + elif successful_stakes == len(netuids): + success = True + message = "Success" + else: + success = False + message = "No one stake were successful." + + if ( + new_balance := await subtensor.get_balance(wallet.coldkeypub.ss58_address) + ) != initial_balance: + logging.debug( + f"Balance: [blue]{initial_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + data.update( + {"balance_before": initial_balance, "balance_after": new_balance} ) - if successful_stakes != 0: - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) - logging.info( - f"Balance: [blue]{initial_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - return response + response = ExtrinsicResponse(success, message, data=data) + if response.success: + return response + return response.with_log() - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def set_auto_stake_extrinsic( diff --git a/bittensor/core/extrinsics/asyncex/start_call.py b/bittensor/core/extrinsics/asyncex/start_call.py index d2a8747694..f40f8e77e1 100644 --- a/bittensor/core/extrinsics/asyncex/start_call.py +++ b/bittensor/core/extrinsics/asyncex/start_call.py @@ -1,8 +1,6 @@ from typing import TYPE_CHECKING, Optional from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -36,27 +34,27 @@ async def start_call_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - async with subtensor.substrate as substrate: - start_call = await substrate.compose_call( - call_module="SubtensorModule", - call_function="start_call", - call_params={"netuid": netuid}, - ) - - response = await subtensor.sign_and_send_extrinsic( - call=start_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + async with subtensor.substrate as substrate: + start_call = await substrate.compose_call( + call_module="SubtensorModule", + call_function="start_call", + call_params={"netuid": netuid}, + ) + + return await subtensor.sign_and_send_extrinsic( + call=start_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/sudo.py b/bittensor/core/extrinsics/asyncex/sudo.py index b61cb10bfc..ec0bee9999 100644 --- a/bittensor/core/extrinsics/asyncex/sudo.py +++ b/bittensor/core/extrinsics/asyncex/sudo.py @@ -1,7 +1,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.extrinsics.asyncex.utils import sudo_call_extrinsic -from bittensor.core.types import Weights as MaybeSplit +from bittensor.core.types import Weights as MaybeSplit, ExtrinsicResponse from bittensor.utils.weight_utils import convert_maybe_split_to_u16 if TYPE_CHECKING: @@ -17,7 +17,7 @@ async def sudo_set_admin_freeze_window_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """ Sets the admin freeze window length (in blocks) at the end of a tempo. @@ -33,9 +33,7 @@ async def sudo_set_admin_freeze_window_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ call_function = "sudo_set_admin_freeze_window" call_params = {"window": window} @@ -60,7 +58,7 @@ async def sudo_set_mechanism_count_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the number of subnet mechanisms. @@ -77,9 +75,7 @@ async def sudo_set_mechanism_count_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ call_function = "sudo_set_mechanism_count" call_params = {"netuid": netuid, "mechanism_count": mech_count} @@ -104,7 +100,7 @@ async def sudo_set_mechanism_emission_split_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the emission split between mechanisms in a provided subnet. @@ -121,9 +117,7 @@ async def sudo_set_mechanism_emission_split_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. Note: The `maybe_split` list defines the relative emission share for each subnet mechanism. diff --git a/bittensor/core/extrinsics/asyncex/take.py b/bittensor/core/extrinsics/asyncex/take.py index a60272461a..96416e18c2 100644 --- a/bittensor/core/extrinsics/asyncex/take.py +++ b/bittensor/core/extrinsics/asyncex/take.py @@ -1,20 +1,19 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Literal from bittensor_wallet.bittensor_wallet import Wallet from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor.core.async_subtensor import AsyncSubtensor -async def increase_take_extrinsic( +async def set_take_extrinsic( subtensor: "AsyncSubtensor", wallet: Wallet, hotkey_ss58: str, take: int, + action: Literal["increase_take", "decrease_take"], period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -27,6 +26,7 @@ async def increase_take_extrinsic( 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. + action: The call function to use to set the take. Can be either "increase_take" or "decrease_take". 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. @@ -35,90 +35,30 @@ async def increase_take_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ - - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=action, + call_params={ + "hotkey": hotkey_ss58, + "take": take, + }, ) - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="increase_take", - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, - ) - - 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, - calling_function=get_function_name(), - ) - - -async def decrease_take_extrinsic( - subtensor: "AsyncSubtensor", - 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, -) -> ExtrinsicResponse: - """ - 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]: - - 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) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + 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, ) - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="decrease_take", - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, - ) - - return 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, - calling_function=get_function_name(), - ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index ce7639b9d8..f8dc6f8b49 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -1,13 +1,12 @@ import asyncio from typing import TYPE_CHECKING, Optional + +from bittensor.core.settings import NETWORK_EXPLORER_MAP, DEFAULT_NETWORK from bittensor.core.types import ExtrinsicResponse -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_function_name, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -48,104 +47,103 @@ async def transfer_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ - # Unlock wallet coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if amount is None and not transfer_all: + return ExtrinsicResponse( + False, "If not transferring all, `amount` must be specified." + ).with_log() + + # Validate destination address. + if not is_valid_bittensor_address_or_public_key(destination): + return ExtrinsicResponse( + False, f"Invalid destination SS58 address: {destination}" + ).with_log() + + # check existential deposit and fee + logging.debug("Fetching existential and fee.") + block_hash = await subtensor.substrate.get_chain_head() + old_balance, existential_deposit = await asyncio.gather( + subtensor.get_balance( + wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + subtensor.get_existential_deposit(block_hash=block_hash), ) - if amount is None and not transfer_all: - message = "If not transferring all, `amount` must be specified." - logging.error(message) - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Validate destination address. - if not is_valid_bittensor_address_or_public_key(destination): - message = f"Invalid destination SS58 address: {destination}" - logging.error(f":cross_mark: [red]{message}[/red].") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Check balance. - logging.info( - f":satellite: [magenta]Checking balance and fees on chain [/magenta] [blue]{subtensor.network}[/blue]" - ) - # check existential deposit and fee - logging.debug("Fetching existential and fee") - block_hash = await subtensor.substrate.get_chain_head() - account_balance, existential_deposit = await asyncio.gather( - subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash), - subtensor.get_existential_deposit(block_hash=block_hash), - ) - - fee = await subtensor.get_transfer_fee( - wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive - ) - - if not keep_alive: - # Check if the transfer should keep_alive the account - existential_deposit = Balance(0) - - # Check if we have enough balance. - if transfer_all is True: - if (account_balance - fee) < existential_deposit: - message = "Not enough balance to transfer." - logging.error(message) + fee = await subtensor.get_transfer_fee( + wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive + ) + + if not keep_alive: + # Check if the transfer should keep_alive the account + existential_deposit = Balance(0) + + # Check if we have enough balance. + if transfer_all: + if (old_balance - fee) < existential_deposit: + return ExtrinsicResponse( + False, "Not enough balance to transfer all stake." + ).with_log() + + elif old_balance < (amount + fee + existential_deposit): return ExtrinsicResponse( - False, message, extrinsic_function=get_function_name() - ) - elif account_balance < (amount + fee + existential_deposit): - message = "Not enough balance." - logging.error(":cross_mark: [red]Not enough balance[/red]") - logging.error(f"\t\tBalance:\t[blue]{account_balance}[/blue]") - logging.error(f"\t\tAmount:\t[blue]{amount}[/blue]") - logging.error(f"\t\tFor fee:\t[blue]{fee}[/blue]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info(":satellite: [magenta]Transferring... old_stake: - message = f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {wallet.hotkey_str}" - logging.error(f":cross_mark: [red]{message}[/red]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_unstaked": amount.rao, - } - if safe_unstaking: - 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 Unstaking from:[/magenta] " - 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], " - f"with partial unstake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "remove_stake_limit" - else: - logging_info = ( - f":satellite: [magenta]Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - call_function = "remove_stake" - - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - fee = await get_extrinsic_fee( - subtensor=subtensor, call=call, keypair=wallet.coldkeypub, netuid=netuid - ) - logging.info(f"{logging_info} for fee [blue]{fee}[/blue][magenta]...[/magenta]") - response = 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, - calling_function=get_function_name(), - ) - - if response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - new_block_hash = await subtensor.substrate.get_chain_head() - new_balance, new_stake = await asyncio.gather( + block_hash = await subtensor.substrate.get_chain_head() + old_balance, old_stake = await asyncio.gather( subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=new_block_hash + address=wallet.coldkeypub.ss58_address, block_hash=block_hash ), subtensor.get_stake( coldkey_ss58=wallet.coldkeypub.ss58_address, hotkey_ss58=hotkey_ss58, netuid=netuid, - block_hash=new_block_hash, + block_hash=block_hash, ), ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + + amount.set_unit(netuid) + + # Check enough to unstake. + if amount > old_stake: + return ExtrinsicResponse( + False, + f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {hotkey_ss58}", + ).with_log() + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_unstaked": amount.rao, + } + if safe_unstaking: + 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_message = ( + f"Safe Unstaking from: " + 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], " + f"with partial unstake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue]" + ) + + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } + ) + call_function = "remove_stake_limit" + else: + logging_message = ( + f"Unstaking from: " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]" + ) + call_function = "remove_stake" + + logging.debug(logging_message) + call = await subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - logging.info( - f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + + response = 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", + use_nonce=True, + period=period, + raise_error=raise_error, ) + + if response.success: # If we successfully unstaked. + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + 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.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + logging.debug( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + "stake_before": old_stake, + "stake_after": new_stake, + } + return response + + if safe_unstaking and "Custom error: 8" in response.message: + response.message = "Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." + + logging.error(f"[red]{response.message}[/red]") return response - if safe_unstaking and "Custom error: 8" in response.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: {response.message}.[/red]") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def unstake_all_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, @@ -199,7 +196,7 @@ async def unstake_all_extrinsic( Parameters: subtensor: Subtensor instance. wallet: The wallet of the stake owner. - hotkey: The SS58 address of the hotkey to unstake from. + hotkey_ss58: 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. @@ -213,42 +210,43 @@ async def unstake_all_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - call_params = { - "hotkey": hotkey, - "netuid": netuid, - "limit_price": None, - } - - if rate_tolerance: - current_price = (await subtensor.subnet(netuid=netuid)).price - limit_price = current_price * (1 - rate_tolerance) - call_params.update({"limit_price": limit_price}) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "limit_price": None, + } + + if rate_tolerance: + current_price = (await subtensor.subnet(netuid=netuid)).price + limit_price = current_price * (1 - rate_tolerance) + call_params.update({"limit_price": limit_price}) + + async with subtensor.substrate as substrate: + call = await substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake_full_limit", + call_params=call_params, + ) - async with subtensor.substrate as substrate: - call = await substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake_full_limit", - call_params=call_params, - ) + return 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", + use_nonce=True, + period=period, + raise_error=raise_error, + ) - return 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, - calling_function=get_function_name(), - ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def unstake_multiple_extrinsic( @@ -257,6 +255,7 @@ async def unstake_multiple_extrinsic( netuids: UIDs, hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, + rate_tolerance: Optional[float] = 0.05, unstake_all: bool = False, period: Optional[int] = None, raise_error: bool = False, @@ -272,6 +271,7 @@ async def unstake_multiple_extrinsic( 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. + rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). unstake_all: If true, unstakes all tokens. 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 @@ -282,183 +282,203 @@ async def unstake_multiple_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. - """ - # Unlock coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - # or amounts or unstake_all (no both) - if amounts and unstake_all: - raise ValueError("Cannot specify both `amounts` and `unstake_all`.") - - if amounts is not None and not all( - isinstance(amount, Balance) for amount in amounts - ): - raise TypeError("amounts must be a [list of bittensor.Balance] or None") - - if amounts is None: - amounts = [None] * len(hotkey_ss58s) - else: - # Convert to Balance - amounts = [amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids)] - if sum(amount.tao for amount in amounts) == 0: - # Staking 0 tao - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() + Note: + The `data` field in the returned `ExtrinsicResponse` contains the results of each individual internal + `unstake_extrinsic` or `unstake_all_extrinsic` call. Each entry maps a tuple key `(idx, hotkey_ss58, netuid)` to + either: + - the corresponding `ExtrinsicResponse` object if the unstaking attempt was executed, or + - `None` if the unstaking was skipped due to failing validation (e.g., wrong balance, zero amount, etc.). + In the key, `idx` is the index the unstake attempt. This allows the caller to inspect which specific operations + were attempted and which were not. + """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + # or amounts or unstake_all (no both) + if amounts and unstake_all: + raise ValueError("Cannot specify both `amounts` and `unstake_all`.") + + if amounts is not None and not all( + isinstance(amount, Balance) for amount in amounts + ): + raise TypeError("`amounts` must be a list of Balance or None.") + + if amounts is None: + amounts = [None] * len(hotkey_ss58s) + else: + # Convert to Balance + amounts = [ + amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) + ] + if sum(amount.tao for amount in amounts) == 0: + # Staking 0 tao + return ExtrinsicResponse(True, "Success") + + if not all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ): + raise TypeError( + "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." ) - 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 ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() - ) + if len(hotkey_ss58s) == 0: + return ExtrinsicResponse(True, "Success") - 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]" - ) - - block_hash = await subtensor.substrate.get_chain_head() - - all_stakes, old_balance = await asyncio.gather( - subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash - ), - subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash), - ) - - old_stakes: list[Balance] = get_old_stakes( - wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes - ) - - successful_unstakes = 0 - response = ExtrinsicResponse( - False, "Failed", extrinsic_function=get_function_name() - ) - for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( - zip(hotkey_ss58s, amounts, old_stakes, netuids) - ): - # 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]" + if not len(netuids) == len(hotkey_ss58s) == len(amounts): + raise ValueError( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." ) - else: - unstaking_balance = amount - # Check enough to unstake. - stake_on_uid = old_stake - if unstaking_balance > stake_on_uid: - logging.error( - f":cross_mark: [red]Not enough stake[/red]: [green]{stake_on_uid}[/green] to unstake: " - f"[blue]{unstaking_balance}[/blue] from hotkey: [blue]{wallet.hotkey_str}[/blue]." - ) - continue + if not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): + raise TypeError("`hotkey_ss58s` must be a list of str.") - try: - call = await subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": hotkey_ss58, - "amount_unstaked": unstaking_balance.rao, - "netuid": netuid, - }, - ) - fee = await get_extrinsic_fee( - subtensor=subtensor, call=call, keypair=wallet.coldkeypub, netuid=netuid - ) - logging.info( - f"Unstaking [blue]{unstaking_balance}[/blue] from hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " - f"[blue]{netuid}[/blue] for fee [blue]{fee}[/blue]" + 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`." ) - response = 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, - calling_function=get_function_name(), + 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 response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. + block_hash = await subtensor.substrate.get_chain_head() + all_stakes, old_balance = await asyncio.gather( + subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block_hash=block_hash + ), + ) + old_stakes = get_old_stakes( + wallet=wallet, + hotkey_ss58s=hotkey_ss58s, + netuids=netuids, + all_stakes=all_stakes, + ) + + successful_unstakes = 0 + data = {} + for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( + zip(hotkey_ss58s, amounts, old_stakes, netuids) + ): + data.update({(idx, hotkey_ss58, netuid): None}) + + # 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) + + # Check enough to unstake. + if unstaking_balance > old_stake: + logging.warning( + f"[red]Not enough stake[/red]: [green]{old_stake}[/green] to unstake: " + f"[blue]{unstaking_balance}[/blue] from hotkey: [blue]{hotkey_ss58}[/blue]." + ) + continue - if not wait_for_finalization and not wait_for_inclusion: + try: + logging.debug( + f"Unstaking [blue]{amount}[/blue] from hotkey [blue]{hotkey_ss58}[/blue] on netuid " + f"[blue]{netuid}[/blue]." + ) + if unstake_all: + response = await unstake_all_extrinsic( + subtensor=subtensor, + wallet=wallet, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + rate_tolerance=rate_tolerance, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + else: + response = await unstake_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=unstaking_balance, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + data.update({(idx, hotkey_ss58, netuid): response}) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + successful_unstakes += 1 + continue + + logging.debug("[green]Finalized[/green]") + + new_stake = await subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + ) + logging.debug( + f"Stake ({hotkey_ss58}) in subnet {netuid}: " + f"[blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]." + ) successful_unstakes += 1 continue - logging.info(":white_heavy_check_mark: [green]Finalized[/green]") - - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]..." + logging.warning( + f"Unstaking from hotkey_ss58 {hotkey_ss58} in subnet {netuid} was not successful." ) - block_hash = await subtensor.substrate.get_chain_head() - new_stake = await subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block_hash=block_hash, - ) - logging.info( - f"Stake ({hotkey_ss58}): [blue]{stake_on_uid}[/blue] :arrow_right: [green]{new_stake}[/green]" + + except SubstrateRequestException as error: + logging.error( + f"[red]Add Stake Multiple error: {format_error_message(error)}[/red]" ) - successful_unstakes += 1 - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - continue + if raise_error: + raise + + if len(netuids) > successful_unstakes > 0: + success = False + message = "Some unstake were successful." + elif successful_unstakes == len(netuids): + success = True + message = "Success" + else: + success = False + message = "No one unstake were successful." - except SubstrateRequestException as error: - logging.error( - f":cross_mark: [red]Multiple unstake filed with error: {format_error_message(error)}[/red]" + if ( + new_balance := await subtensor.get_balance( + address=wallet.coldkeypub.ss58_address ) - if raise_error: - raise error - return response + ) != old_balance: + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + data.update({"balance_before": old_balance, "balance_after": new_balance}) - if successful_unstakes != 0: - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/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]" - ) - return response + response = ExtrinsicResponse(success, message, data=data) + if response.success: + return response + return response.with_log() - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/utils.py b/bittensor/core/extrinsics/asyncex/utils.py index 407c80c024..14665807ad 100644 --- a/bittensor/core/extrinsics/asyncex/utils.py +++ b/bittensor/core/extrinsics/asyncex/utils.py @@ -1,8 +1,7 @@ from typing import TYPE_CHECKING, Optional -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from scalecodec import GenericCall @@ -50,7 +49,7 @@ async def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Execute a sudo call extrinsic. Parameters: @@ -70,15 +69,16 @@ async def sudo_call_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return False, unlock.message + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type=sign_with + ) + ).success: + return unlocked + sudo_call = await subtensor.substrate.compose_call( call_module="Sudo", call_function="sudo", @@ -103,7 +103,4 @@ async def sudo_call_extrinsic( ) except Exception as error: - if raise_error: - raise error - - return False, str(error) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index a66c8e6213..e0bda0effa 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -6,12 +6,7 @@ from bittensor.core.settings import version_as_int from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights -from bittensor.utils import ( - format_error_message, - get_function_name, - get_mechid_storage_index, - unlock_key, -) +from bittensor.utils import get_mechid_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -61,14 +56,15 @@ async def commit_timelocked_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) current_block = await subtensor.block @@ -111,8 +107,8 @@ async def commit_timelocked_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -128,16 +124,7 @@ async def commit_timelocked_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def commit_weights_extrinsic( @@ -176,13 +163,13 @@ async def commit_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Generate the hash of the weights @@ -211,8 +198,8 @@ async def commit_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -224,16 +211,7 @@ async def commit_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def reveal_weights_extrinsic( @@ -273,14 +251,15 @@ async def reveal_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) call = await subtensor.substrate.compose_call( @@ -302,8 +281,8 @@ async def reveal_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -315,16 +294,7 @@ async def reveal_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) async def set_weights_extrinsic( @@ -362,15 +332,15 @@ async def set_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked - # Convert, reformat and normalize. + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) call = await subtensor.substrate.compose_call( @@ -391,26 +361,17 @@ async def set_weights_extrinsic( wait_for_finalization=wait_for_finalization, period=period, use_nonce=True, - nonce_key="hotkey", - sign_with="hotkey", + nonce_key=signing_keypair, + sign_with=signing_keypair, raise_error=raise_error, ) if response.success: - logging.debug("Successfully set weights and Finalized.") + logging.debug(response.message) return response logging.error(response.message) return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index f18a91ee52..1d81ea0a8a 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING, Optional from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import float_to_u64, unlock_key, get_function_name -from bittensor.utils.btlogging import logging +from bittensor.utils import float_to_u64 +from bittensor.core.extrinsics.utils import sudo_call_extrinsic if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -12,7 +12,7 @@ def set_children_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], period: Optional[int] = None, @@ -26,7 +26,7 @@ def set_children_extrinsic( Parameters: subtensor: The Subtensor client instance used for blockchain interaction. wallet: bittensor wallet instance. - hotkey: The ``SS58`` address of the neuron's hotkey. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -52,46 +52,40 @@ def set_children_extrinsic( bittensor_wallet.errors.KeyFileError: Failed to decode keyfile data. bittensor_wallet.errors.PasswordError: Decryption failed or wrong password for decryption provided. """ - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_children", + call_params={ + "children": [ + ( + float_to_u64(proportion), + child_hotkey, + ) + for proportion, child_hotkey in children + ], + "hotkey": hotkey_ss58, + "netuid": netuid, + }, ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_children", - call_params={ - "children": [ - ( - float_to_u64(proportion), - child_hotkey, - ) - for proportion, child_hotkey in children - ], - "hotkey": hotkey, - "netuid": netuid, - }, - ) - - response = 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, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response - - if response.success: + response = 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, + ) return response - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def root_set_pending_childkey_cooldown_extrinsic( @@ -120,39 +114,14 @@ def root_set_pending_childkey_cooldown_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - unlock = unlock_key(wallet) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - call = subtensor.substrate.compose_call( + return sudo_call_extrinsic( + subtensor=subtensor, + wallet=wallet, call_module="SubtensorModule", call_function="set_pending_childkey_cooldown", call_params={"cooldown": cooldown}, - ) - - sudo_call = subtensor.substrate.compose_call( - call_module="Sudo", - call_function="sudo", - call_params={"call": call}, - ) - - response = 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, - calling_function=get_function_name(), ) - - if not wait_for_finalization and not wait_for_inclusion: - return response - - if response.success: - return response - - return response diff --git a/bittensor/core/extrinsics/liquidity.py b/bittensor/core/extrinsics/liquidity.py index 97efc27bcb..64ebc8c674 100644 --- a/bittensor/core/extrinsics/liquidity.py +++ b/bittensor/core/extrinsics/liquidity.py @@ -1,9 +1,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import price_to_tick if TYPE_CHECKING: @@ -48,37 +46,37 @@ def add_liquidity_extrinsic( 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) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + tick_low = price_to_tick(price_low.tao) + tick_high = price_to_tick(price_high.tao) + + call = subtensor.substrate.compose_call( + call_module="Swap", + call_function="add_liquidity", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "tick_low": tick_low, + "tick_high": tick_high, + "liquidity": liquidity.rao, + }, ) - tick_low = price_to_tick(price_low.tao) - tick_high = price_to_tick(price_high.tao) - - call = subtensor.substrate.compose_call( - call_module="Swap", - call_function="add_liquidity", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "tick_low": tick_low, - "tick_high": tick_high, - "liquidity": liquidity.rao, - }, - ) - - return 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, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return 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, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def modify_liquidity_extrinsic( @@ -115,33 +113,33 @@ def modify_liquidity_extrinsic( Note: Modifying 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) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="Swap", + call_function="modify_position", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "position_id": position_id, + "liquidity_delta": liquidity_delta.rao, + }, ) - call = subtensor.substrate.compose_call( - call_module="Swap", - call_function="modify_position", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - "liquidity_delta": liquidity_delta.rao, - }, - ) - - return 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, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return 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, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def remove_liquidity_extrinsic( @@ -176,32 +174,32 @@ def remove_liquidity_extrinsic( 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) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="Swap", + call_function="remove_liquidity", + call_params={ + "hotkey": hotkey or wallet.hotkey.ss58_address, + "netuid": netuid, + "position_id": position_id, + }, ) - call = subtensor.substrate.compose_call( - call_module="Swap", - call_function="remove_liquidity", - call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - }, - ) - - return 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, - raise_error=raise_error, - calling_function=get_function_name(), - ) + return 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, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def toggle_user_liquidity_extrinsic( @@ -231,24 +229,25 @@ def toggle_user_liquidity_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="Swap", + call_function="toggle_user_liquidity", + call_params={"netuid": netuid, "enable": enable}, ) - call = subtensor.substrate.compose_call( - call_module="Swap", - call_function="toggle_user_liquidity", - call_params={"netuid": netuid, "enable": enable}, - ) - - return 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, - calling_function=get_function_name(), - ) + return 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, + ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index fc5fe29bf3..610c4c218a 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -3,7 +3,6 @@ from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from bittensor.utils import get_function_name if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -70,60 +69,16 @@ def transfer_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - amount.set_unit(netuid=origin_netuid) - - # Check sufficient stake - stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( - subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=destination_coldkey_ss58, - ) - if stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info( - f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey [" - f"blue]{destination_coldkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " - f"[yellow]{destination_netuid}[/yellow]" - ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": destination_coldkey_ss58, - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, - ) - - response = 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, - calling_function=get_function_name(), - ) + amount.set_unit(netuid=origin_netuid) - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = _get_stake_in_origin_and_dest( + # Check sufficient stake + stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, @@ -132,17 +87,69 @@ def transfer_stake_extrinsic( origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=destination_coldkey_ss58, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + if stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + logging.debug( + f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " + f"[blue]{destination_coldkey_ss58}[/blue]" + ) + logging.debug( + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " + f"[yellow]{destination_netuid}[/yellow]" ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="transfer_stake", + call_params={ + "destination_coldkey": destination_coldkey_ss58, + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, ) + response = 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 response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + # Get updated stakes + origin_stake, dest_stake = _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, + ) + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def swap_stake_extrinsic( @@ -184,84 +191,16 @@ def swap_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - amount.set_unit(netuid=origin_netuid) - - # Check sufficient stake - stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( - subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=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 stake_in_origin < amount: - message = f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call_params = { - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - } - - 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 - swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - - logging.info( - f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]\n" - f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " - f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" - ) - call_params.update( - { - "limit_price": swap_rate_ratio_with_tolerance, - "allow_partial": allow_partial_stake, - } - ) - call_function = "swap_stake_limit" - else: - logging.info( - f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]" - ) - call_function = "swap_stake" - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) + amount.set_unit(netuid=origin_netuid) - response = 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, - calling_function=get_function_name(), - ) - - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = _get_stake_in_origin_and_dest( + # Check sufficient stake + stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, @@ -270,23 +209,103 @@ def swap_stake_extrinsic( origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + + if stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + call_params = { + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + } + + 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 + swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) + + logging.debug( + f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]\n" + f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " + f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" + ) + call_params.update( + { + "limit_price": swap_rate_ratio_with_tolerance, + "allow_partial": allow_partial_stake, + } + ) + call_function = "swap_stake_limit" + else: + logging.debug( + f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]" + ) + call_function = "swap_stake" + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + + response = 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, ) - return response + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = _get_stake_in_origin_and_dest( + subtensor=subtensor, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + ) + + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } + return response - if safe_swapping and "Custom error: 8" in response.message: - logging.error( - ":cross_mark: [red]Failed[/red]: Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." - ) - else: - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") + if safe_swapping and "Custom error: 8" in response.message: + response.message = "Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." - return response + logging.error(f"[red]{response.message}[/red]") + return response + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def move_stake_extrinsic( @@ -325,67 +344,20 @@ def move_stake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not amount and not move_all_stake: - message = ( - "Please specify an `amount` or `move_all_stake` argument to move stake." - ) - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Check sufficient stake - stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( - subtensor=subtensor, - 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: - message = f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}." - logging.error(f":cross_mark: [red]Failed[/red]: {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - amount.set_unit(netuid=origin_netuid) - - logging.info( - 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_ss58, - "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey_ss58, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, - ) - - response = 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, - calling_function=get_function_name(), - ) - - if response.success: - if not wait_for_finalization and not wait_for_inclusion: - return response - - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - # Get updated stakes - origin_stake, dest_stake = _get_stake_in_origin_and_dest( + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if not amount and not move_all_stake: + return ExtrinsicResponse( + False, + "Please specify an `amount` or `move_all_stake` argument to move stake.", + ).with_log() + + # Check sufficient stake + stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( subtensor=subtensor, origin_hotkey_ss58=origin_hotkey_ss58, destination_hotkey_ss58=destination_hotkey_ss58, @@ -394,14 +366,75 @@ def move_stake_extrinsic( origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) - logging.info( - f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + if move_all_stake: + amount = stake_in_origin + + elif stake_in_origin < amount: + return ExtrinsicResponse( + False, + f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + ).with_log() + + amount.set_unit(netuid=origin_netuid) + + logging.debug( + 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_ss58, + "origin_netuid": origin_netuid, + "destination_hotkey": destination_hotkey_ss58, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + }, ) - logging.info( - f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + + response = 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 response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + # Get updated stakes + origin_stake, dest_stake = _get_stake_in_origin_and_dest( + subtensor=subtensor, + 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, + ) + logging.debug( + f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" + ) + logging.debug( + f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" + ) + + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index f710ef96e0..351af9ecc1 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -1,17 +1,12 @@ """ 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. -- burned_register_extrinsic: Registers the wallet to chain by recycling TAO. """ import time from typing import Optional, Union, TYPE_CHECKING -from bittensor.core.extrinsics.utils import get_extrinsic_fee +from bittensor.core.errors import RegistrationError from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name from bittensor.utils.btlogging import logging from bittensor.utils.registration import create_pow, log_no_torch_error, torch @@ -45,99 +40,95 @@ def burned_register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - block = subtensor.get_current_block() - if not subtensor.subnet_exists(netuid, block=block): - logging.error( - f":cross_mark: [red]Failed error:[/red] subnet [blue]{netuid}[/blue] does not exist." - ) - return ExtrinsicResponse( - False, - f"Subnet #{netuid} does not exist", - extrinsic_function=get_function_name(), + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + block = subtensor.get_current_block() + if not subtensor.subnet_exists(netuid, block=block): + return ExtrinsicResponse( + False, f"Subnet {netuid} does not exist." + ).with_log() + + neuron = subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid, block=block ) - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) + + if not neuron.is_null: + message = "Already registered." + logging.debug(f"[green]{message}[/green]") + logging.debug(f"\t\tuid: [blue]{neuron.uid}[/blue]") + logging.debug(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") + logging.debug(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") + logging.debug(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") + return ExtrinsicResponse( + message=message, data={"neuron": neuron, "old_balance": old_balance} + ) + + recycle_amount = subtensor.recycle(netuid=netuid, block=block) + logging.debug(f"Recycling {recycle_amount} to register on subnet:{netuid}") + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="burned_register", + call_params={ + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + }, ) + response = 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, + ) + extrinsic_fee = response.extrinsic_fee + logging.debug( + f"The registration fee for SN #[blue]{netuid}[/blue] is [blue]{extrinsic_fee}[/blue]." + ) + if not response.success: + logging.error(f"[red]{response.message}[/red]") + time.sleep(0.5) + return response - logging.info( - f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue][magenta] ...[/magenta]" - ) - neuron = subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid, block=block - ) - - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - - if not neuron.is_null: - message = "Already registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - logging.info(f"\t\tuid: [blue]{neuron.uid}[/blue]") - logging.info(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") - logging.info(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") - logging.info(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") - return ExtrinsicResponse( - message=message, extrinsic_function=get_function_name() + # Successful registration, final check for neuron and pubkey + new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + + logging.debug( + 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 ) - recycle_amount = subtensor.recycle(netuid=netuid, block=block) - logging.debug(":satellite: [magenta]Recycling TAO for Registration...[/magenta]") - logging.info(f"Recycling {recycle_amount} to register on subnet:{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]." - ) - response = 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, - calling_function=get_function_name(), - ) - - if not response.success: - logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") - time.sleep(0.5) - return response + response.data = { + "neuron": neuron, + "balance_before": old_balance, + "balance_after": new_balance, + "recycle_amount": recycle_amount, + } - # 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 - 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: - message = "Registered" - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - return response + if is_registered: + logging.debug("[green]Registered.[/green]") + return response - # neuron not found, try again - message = "Unknown error. Neuron not found." - logging.error(f":cross_mark: [red]{message}[/red]") - response.success = False - response.message = message - return response + # neuron not found + message = f"Neuron with hotkey {wallet.hotkey.ss58_address} not found in subnet {netuid} after registration." + return ExtrinsicResponse( + success=False, + message=message, + extrinsic=response.extrinsic, + error=RegistrationError(message), + ).with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def register_subnet_extrinsic( @@ -164,44 +155,50 @@ def register_subnet_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - burn_cost = subtensor.get_subnet_burn_cost() - - if burn_cost > balance: - message = f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO." - logging.error(message) - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - - response = 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, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - if response.success: - logging.success( - ":white_heavy_check_mark: [green]Successfully registered subnet[/green]" + balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + burn_cost = subtensor.get_subnet_burn_cost() + + if burn_cost > balance: + return ExtrinsicResponse( + False, + f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO.", + ).with_log() + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="register_network", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + }, + ) + + response = 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, ) + + if not wait_for_finalization and not wait_for_inclusion: + return response + + if response.success: + logging.debug("[green]Successfully registered subnet.[/green]") + return response + + logging.error(f"Failed to register subnet: {response.message}") return response - logging.error(f"Failed to register subnet: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def register_extrinsic( @@ -245,168 +242,150 @@ def register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + block = subtensor.get_current_block() + if not subtensor.subnet_exists(netuid, block=block): + return ExtrinsicResponse( + False, f"Subnet {netuid} does not exist." + ).with_log() - logging.debug("[magenta]Checking subnet status... [/magenta]") - block = subtensor.get_current_block() - if not subtensor.subnet_exists(netuid, block=block): - message = f"Subnet #{netuid} does not exist." - logging.error(f":cross_mark: [red]Failed error:[/red] {message}") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info( - f":satellite: [magenta]Checking Account on subnet[/magenta] [blue]{netuid}[/blue] [magenta]...[/magenta]" - ) - neuron = subtensor.get_neuron_for_pubkey_and_subnet( - hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid, block=block - ) - - if not neuron.is_null: - message = "Already registered." - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - logging.info(f"\t\tuid: [blue]{neuron.uid}[/blue]") - logging.info(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") - logging.info(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") - logging.info(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") - return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Registration hotkey: {wallet.hotkey.ss58_address}, Public coldkey: " - f"{wallet.coldkey.ss58_address} in the network: {subtensor.network}." - ) - - if not torch: - log_no_torch_error() - return ExtrinsicResponse( - False, "No torch installed.", extrinsic_function=get_function_name() + neuron = subtensor.get_neuron_for_pubkey_and_subnet( + hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid, block=block ) - # Attempt rolling registration. - attempts = 1 - - while True: - logging.info( - f":satellite: [magenta]Registering...[/magenta] [blue]({attempts}/{max_allowed_attempts})[/blue]" + if not neuron.is_null: + message = "Already registered." + logging.debug(f"[green]{message}[/green]") + logging.debug(f"\t\tuid: [blue]{neuron.uid}[/blue]") + logging.debug(f"\t\tnetuid: [blue]{neuron.netuid}[/blue]") + logging.debug(f"\t\thotkey: [blue]{neuron.hotkey}[/blue]") + logging.debug(f"\t\tcoldkey: [blue]{neuron.coldkey}[/blue]") + return ExtrinsicResponse(message=message, data={"neuron": neuron}) + + logging.debug( + f"Registration hotkey: [blue]{wallet.hotkey.ss58_address}[/blue], Public coldkey: " + f"[blue]{wallet.coldkey.ss58_address}[/blue] in the network: [blue]{subtensor.network}[/blue]." ) - # Solve latest POW. - if cuda: - if not torch.cuda.is_available(): - return ExtrinsicResponse( - False, "CUDA not available.", extrinsic_function=get_function_name() - ) - pow_result = create_pow( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - output_in_place=output_in_place, - cuda=cuda, - dev_id=dev_id, - tpb=tpb, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) - else: - pow_result = create_pow( - subtensor=subtensor, - wallet=wallet, - netuid=netuid, - output_in_place=output_in_place, - cuda=cuda, - num_processes=num_processes, - update_interval=update_interval, - log_verbose=log_verbose, - ) + if not torch: + log_no_torch_error() + return ExtrinsicResponse(False, "Torch is not installed.").with_log() - # pow failed - if not pow_result: - # might be registered already on this subnet - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - message = f"Already registered on netuid: {netuid}" - logging.info(f":white_heavy_check_mark: [green]{message}[/green]") - return ExtrinsicResponse( - True, message, extrinsic_function=get_function_name() - ) + # Attempt rolling registration. + attempts = 1 + + while True: + # Solve latest POW. + if cuda: + if not torch.cuda.is_available(): + return ExtrinsicResponse(False, "CUDA not available.").with_log() - # pow successful, proceed to submit pow to chain for registration - else: - logging.info(":satellite: [magenta]Submitting POW...[/magenta]") - # check if a pow result is still valid - while not pow_result.is_stale(subtensor=subtensor): - # 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(f"Creating a POW with CUDA.") + pow_result = create_pow( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + output_in_place=output_in_place, + cuda=cuda, + dev_id=dev_id, + tpb=tpb, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, ) - response = subtensor.sign_and_send_extrinsic( - call=call, + else: + logging.debug(f"Creating a POW.") + pow_result = create_pow( + subtensor=subtensor, wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), + netuid=netuid, + output_in_place=output_in_place, + cuda=cuda, + num_processes=num_processes, + update_interval=update_interval, + log_verbose=log_verbose, ) - if not response.success: - # Look error here - # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs + # pow failed + if not pow_result: + # might be registered already on this subnet + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + message = f"Already registered in subnet {netuid}." + logging.debug(f"[green]{message}[/green]") + return ExtrinsicResponse(message=message) - if "HotKeyAlreadyRegisteredInSubNet" in response.message: - logging.info( - f":white_heavy_check_mark: [green]Already Registered on subnet:[/green] " - f"[blue]{netuid}[/blue]." - ) - return response - time.sleep(0.5) - - # Successful registration, final check for neuron and pubkey - if response.success: - logging.info(":satellite: Checking Registration status...") - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + # pow successful, proceed to submit pow to chain for registration + else: + # check if a pow result is still valid + while not pow_result.is_stale(subtensor=subtensor): + # 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, + }, ) - if is_registered: - logging.success( - ":white_heavy_check_mark: [green]Registered.[/green]" + response = 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, + ) + + if not response.success: + # Look error here + # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs + if "HotKeyAlreadyRegisteredInSubNet" in response.message: + logging.debug( + f"[green]Already registered on subnet:[/green] [blue]{netuid}[/blue]." + ) + return response + time.sleep(0.5) + + if response.success: + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) - return response + if is_registered: + logging.debug("[green]Registered.[/green]") + return response # neuron not found, try again - logging.error( - ":cross_mark: [red]Unknown error. Neuron not found.[/red]" - ) - continue + logging.warning("[red]Unknown error. Neuron not found.[/red]") + continue + else: + # Exited loop because pow is no longer valid. + logging.warning("[red]POW is stale.[/red]") + # Try again. + + if attempts < max_allowed_attempts: + # Failed registration, retry pow + attempts += 1 + logging.warning( + f"Failed registration, retrying pow ... [blue]({attempts}/{max_allowed_attempts})[/blue]" + ) else: - # Exited loop because pow is no longer valid. - logging.error("[red]POW is stale.[/red]") - # Try again. - - if attempts < max_allowed_attempts: - # Failed registration, retry pow - attempts += 1 - logging.error( - f":satellite: [magenta]Failed registration, retrying pow ...[/magenta] " - f"[blue]({attempts}/{max_allowed_attempts})[/blue]" - ) - else: - # Failed to register after max attempts. - message = "No more attempts." - logging.error(f"[red]{message}[/red]") - return ExtrinsicResponse( - False, message, extrinsic_function=get_function_name() - ) + # Failed to register after max attempts. + return ExtrinsicResponse(False, "No more attempts.").with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def set_subnet_identity_extrinsic( @@ -451,50 +430,51 @@ def set_subnet_identity_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="set_subnet_identity", + call_params={ + "hotkey": wallet.hotkey.ss58_address, + "netuid": netuid, + "subnet_name": subnet_name, + "github_repo": github_repo, + "subnet_contact": subnet_contact, + "subnet_url": subnet_url, + "logo_url": logo_url, + "discord": discord, + "description": description, + "additional": additional, + }, + ) - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + response = 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, ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="set_subnet_identity", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "netuid": netuid, - "subnet_name": subnet_name, - "github_repo": github_repo, - "subnet_contact": subnet_contact, - "subnet_url": subnet_url, - "logo_url": logo_url, - "discord": discord, - "description": description, - "additional": additional, - }, - ) - - response = 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, - calling_function=get_function_name(), - ) - - if not wait_for_finalization and not wait_for_inclusion: - return response + if not wait_for_finalization and not wait_for_inclusion: + return response - if response.success: - logging.success( - f":white_heavy_check_mark: [green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" + if response.success: + logging.debug( + f"[green]Identities for subnet[/green] [blue]{netuid}[/blue] [green]are set.[/green]" + ) + return response + + logging.error( + f"[red]Failed to set identity for subnet {netuid}: {response.message}[/red]" ) return response - message = f"Failed to set identity for subnet #{netuid}" - logging.error(f":cross_mark: {message}: {response.message}") - response.message = message - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/root.py b/bittensor/core/extrinsics/root.py index 3458444b70..91f3753c16 100644 --- a/bittensor/core/extrinsics/root.py +++ b/bittensor/core/extrinsics/root.py @@ -2,7 +2,7 @@ from typing import Optional, TYPE_CHECKING from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import u16_normalized_float, unlock_key, get_function_name +from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -57,84 +57,81 @@ def root_register_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + netuid = 0 + logging.debug( + f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]." ) - netuid = 0 - logging.info( - f"Registering on netuid [blue]{netuid}[/blue] on network: [blue]{subtensor.network}[/blue]" - ) - - logging.info("Fetching recycle amount & balance.") - block = subtensor.get_current_block() - recycle_call = subtensor.get_hyperparameter( - param_name="Burn", - netuid=netuid, - block=block, - ) - balance = subtensor.get_balance( - address=wallet.coldkeypub.ss58_address, - block=block, - ) - - current_recycle = Balance.from_rao(int(recycle_call)) - - if balance < current_recycle: - message = f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO" - logging.error(f"[red]{message}[/red].") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Checking if hotkey ([blue]{wallet.hotkey_str}[/blue]) is registered on root." - ) - is_registered = subtensor.is_hotkey_registered( - netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address - ) - if is_registered: - message = "Already registered on root network." - logging.error(f":white_heavy_check_mark: [green]{message}[/green]") - return ExtrinsicResponse( - message=message, extrinsic_function=get_function_name() + block = subtensor.get_current_block() + recycle_call = subtensor.get_hyperparameter( + param_name="Burn", + netuid=netuid, + block=block, ) + balance = subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, + block=block, + ) + + current_recycle = Balance.from_rao(int(recycle_call)) + + if balance < current_recycle: + return ExtrinsicResponse( + False, + f"Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO.", + ).with_log() - logging.info(":satellite: [magenta]Registering to root network...[/magenta]") - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="root_register", - call_params={"hotkey": wallet.hotkey.ss58_address}, - ) - response = 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 response.success: - logging.error(f":cross_mark: [red]Failed error:[/red] {response.message}") - time.sleep(0.5) - return response - - # Successful registration, final check for neuron and pubkey - uid = subtensor.substrate.query( - module="SubtensorModule", - storage_function="Uids", - params=[netuid, wallet.hotkey.ss58_address], - ) - if uid is not None: - response.data = {"uid": uid} - logging.info( - f":white_heavy_check_mark: [green]Registered with UID: {uid}[/green]." + logging.debug( + f"Checking if hotkey ([blue]{wallet.hotkey.ss58_address}[/blue]) is registered on root." ) - return response + is_registered = subtensor.is_hotkey_registered( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ) + if is_registered: + return ExtrinsicResponse(message="Already registered on root network.") - # neuron not found - # neuron not found, try again - message = "Unknown error. Neuron not found." - logging.error(f":cross_mark: [red]{message}[/red]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="root_register", + call_params={"hotkey": wallet.hotkey.ss58_address}, + ) + response = 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, + ) + + if not response.success: + logging.error(f"[red]{response.message}[/red]") + time.sleep(0.5) + return response + + # Successful registration, final check for neuron and pubkey + uid = subtensor.get_uid_for_hotkey_on_subnet( + hotkey_ss58=wallet.hotkey.ss58_address, netuid=netuid + ) + if uid is not None: + response.data = { + "hotkey_ss58": wallet.hotkey.ss58_address, + "netuid": netuid, + "uid": uid, + } + logging.debug( + f"Hotkey {wallet.hotkey.ss58_address} registered in subnet {netuid} with UID: {uid}." + ) + return response + + # neuron not found, try again + return ExtrinsicResponse(False, "Unknown error. Neuron not found.").with_log() + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 554ba890ba..c1efae01f1 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -2,12 +2,9 @@ from bittensor.core.errors import MetadataError from bittensor.core.settings import version_as_int -from bittensor.core.types import AxonServeCallParams -from bittensor.core.types import ExtrinsicResponse +from bittensor.core.types import AxonServeCallParams, ExtrinsicResponse from bittensor.utils import ( - get_function_name, networking as net, - unlock_key, Certificate, ) from bittensor.utils.btlogging import logging @@ -56,74 +53,78 @@ def serve_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - # Decrypt hotkey - if not (unlock := unlock_key(wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + signing_keypair = "hotkey" + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair + ) + ).success: + return unlocked + + params = AxonServeCallParams( + **{ + "version": version_as_int, + "ip": net.ip_to_int(ip), + "port": port, + "ip_type": net.ip_version(ip), + "netuid": netuid, + "hotkey": wallet.hotkey.ss58_address, + "coldkey": wallet.coldkeypub.ss58_address, + "protocol": protocol, + "placeholder1": placeholder1, + "placeholder2": placeholder2, + "certificate": certificate, + } ) + logging.debug("Checking axon ...") + neuron = subtensor.get_neuron_for_pubkey_and_subnet( + wallet.hotkey.ss58_address, netuid=netuid + ) + neuron_up_to_date = not neuron.is_null and params == neuron + if neuron_up_to_date: + message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" + logging.debug(f"[blue]{message}[/blue]") + return ExtrinsicResponse(message=message) - params = AxonServeCallParams( - **{ - "version": version_as_int, - "ip": net.ip_to_int(ip), - "port": port, - "ip_type": net.ip_version(ip), - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - "protocol": protocol, - "placeholder1": placeholder1, - "placeholder2": placeholder2, - "certificate": certificate, - } - ) - logging.debug("Checking axon ...") - neuron = subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid - ) - neuron_up_to_date = not neuron.is_null and params == neuron - if neuron_up_to_date: - message = f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})" - logging.debug(f"[blue]{message}[/blue]") - return ExtrinsicResponse(True, message, extrinsic_function=get_function_name()) - - logging.debug( - f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " - f"[green]{subtensor.network}:{netuid}[/green]" - ) - - if params.certificate is None: - call_function = "serve_axon" - else: - call_function = "serve_axon_tls" + logging.debug( + f"Serving axon with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] -> " + f"[green]{subtensor.network}:{netuid}[/green]" + ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=params.dict(), - ) + if params.certificate is None: + call_function = "serve_axon" + else: + call_function = "serve_axon_tls" - response = 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, - calling_function=get_function_name(), - ) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=params.dict(), + ) - if response.success: - logging.debug( - f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " - f"[green]{subtensor.network}:{netuid}[/green]" + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + sign_with=signing_keypair, + period=period, + raise_error=raise_error, ) + + if response.success: + logging.debug( + f"Axon served with: [blue]AxonInfo({wallet.hotkey.ss58_address}, {ip}:{port})[/blue] on " + f"[green]{subtensor.network}:{netuid}[/green]" + ) + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f"Failed: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def serve_axon_extrinsic( @@ -154,43 +155,55 @@ def serve_axon_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(axon.wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + axon.wallet, raise_error, "hotkey" + ) + ).success: + return unlocked + + external_port = axon.external_port + + # ---- Get external ip ---- + if axon.external_ip is None: + try: + external_ip = net.get_external_ip() + logging.debug( + f"[green]Found external ip:[/green] [blue]{external_ip}[/blue]" + ) + except Exception as error: + message = f"Unable to attain your external ip. Check your internet connection. Error: {error}" + if raise_error: + raise ConnectionError(message) from error + + return ExtrinsicResponse(False, message).with_log() + else: + external_ip = axon.external_ip + + # ---- Subscribe to chain ---- + response = serve_extrinsic( + subtensor=subtensor, + wallet=axon.wallet, + ip=external_ip, + port=external_port, + protocol=4, + netuid=netuid, + certificate=certificate, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) + response.data = { + "external_ip": external_ip, + "external_port": external_port, + "axon": axon, + } + return response - external_port = axon.external_port - - # ---- Get external ip ---- - if axon.external_ip is None: - try: - external_ip = net.get_external_ip() - logging.success( - f":white_heavy_check_mark: [green]Found external ip:[/green] [blue]{external_ip}[/blue]" - ) - except Exception as e: - raise ConnectionError( - f"Unable to attain your external ip. Check your internet connection. error: {e}" - ) from e - else: - external_ip = axon.external_ip - - # ---- Subscribe to chain ---- - response = serve_extrinsic( - subtensor=subtensor, - wallet=axon.wallet, - ip=external_ip, - port=external_port, - protocol=4, - netuid=netuid, - certificate=certificate, - period=period, - raise_error=raise_error, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - ) - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def publish_metadata_extrinsic( @@ -231,56 +244,62 @@ def publish_metadata_extrinsic( MetadataError: If there is an error in submitting the extrinsic, or if the response from the blockchain indicates failure. """ - if not (unlock := unlock_key(wallet, "hotkey")).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + signing_keypair = "hotkey" + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair + ) + ).success: + return unlocked + + fields = [{f"{data_type}": data}] + if reset_bonds: + fields.append({"ResetBondsFlag": b""}) + + call = subtensor.substrate.compose_call( + call_module="Commitments", + call_function="set_commitment", + call_params={ + "netuid": netuid, + "info": {"fields": [fields]}, + }, ) - fields = [{f"{data_type}": data}] - if reset_bonds: - fields.append({"ResetBondsFlag": b""}) - - call = subtensor.substrate.compose_call( - call_module="Commitments", - call_function="set_commitment", - call_params={ - "netuid": netuid, - "info": {"fields": [fields]}, - }, - ) + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + sign_with="hotkey", + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - sign_with="hotkey", - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + if response.success: + return response - if response.success: - return response - raise MetadataError(response.message) + raise MetadataError(response.message) + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def get_metadata( - subtensor: "Subtensor", netuid: int, hotkey: str, block: Optional[int] = None + subtensor: "Subtensor", netuid: int, hotkey_ss58: str, block: Optional[int] = None ) -> Union[str, dict]: """Fetches metadata from the blockchain for a given hotkey and netuid.""" commit_data = subtensor.substrate.query( module="Commitments", storage_function="CommitmentOf", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=subtensor.determine_block_hash(block), ) return commit_data def get_last_bonds_reset( - subtensor: "Subtensor", netuid: int, hotkey: str, block: Optional[int] = None + subtensor: "Subtensor", netuid: int, hotkey_ss58: str, block: Optional[int] = None ) -> bytes: """ Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid. @@ -288,7 +307,7 @@ def get_last_bonds_reset( 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. + hotkey_ss58: 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: @@ -297,6 +316,6 @@ def get_last_bonds_reset( return subtensor.substrate.query( module="Commitments", storage_function="LastBondsReset", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=subtensor.determine_block_hash(block), ) diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index ed87bc91eb..ba53409f17 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -1,11 +1,10 @@ from typing import Optional, TYPE_CHECKING, Sequence from async_substrate_interface.errors import SubstrateRequestException -from bittensor.core.types import ExtrinsicResponse + from bittensor.core.extrinsics.utils import get_old_stakes -from bittensor.utils import unlock_key, format_error_message, get_function_name -from bittensor.core.types import UIDs -from bittensor.utils import unlock_key, format_error_message +from bittensor.core.types import ExtrinsicResponse, UIDs +from bittensor.utils import format_error_message from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -54,148 +53,141 @@ def add_stake_extrinsic( Raises: SubstrateRequestException: Raised if the extrinsic fails to be included in the block within the timeout. + + Notes: + The `data` field in the returned `ExtrinsicResponse` contains extra information about the extrinsic execution. """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + block = subtensor.get_current_block() - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - block = subtensor.get_current_block() - - # Get current stake and existential deposit - old_stake = subtensor.get_stake( - hotkey_ss58=hotkey_ss58, - coldkey_ss58=wallet.coldkeypub.ss58_address, - netuid=netuid, - block=block, - ) - existential_deposit = subtensor.get_existential_deposit(block=block) - - # Leave existential balance to keep key alive. - if amount > old_balance - existential_deposit: - # If we are staking all, we need to leave at least the existential deposit. - amount = old_balance - existential_deposit - - # Check enough to stake. - if amount > old_balance: - message = "Not enough stake" - logging.error(f":cross_mark: [red]{message}:[/red]") - logging.error(f"\t\tbalance:{old_balance}") - logging.error(f"\t\tamount: {amount}") - logging.error(f"\t\twallet: {wallet.name}") - return ExtrinsicResponse( - False, f"{message}.", extrinsic_function=get_function_name() + # Get current stake and existential deposit + old_stake = subtensor.get_stake( + hotkey_ss58=hotkey_ss58, + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuid=netuid, + block=block, ) + existential_deposit = subtensor.get_existential_deposit(block=block) + + # Leave existential balance to keep key alive. + if amount > old_balance - existential_deposit: + # If we are staking all, we need to leave at least the existential deposit. + amount = old_balance - existential_deposit + + # Check enough to stake. + if amount > old_balance: + message = "Not enough stake" + logging.debug(f":cross_mark: [red]{message}:[/red]") + logging.debug(f"\t\tbalance:{old_balance}") + logging.debug(f"\t\tamount: {amount}") + logging.debug(f"\t\twallet: {wallet.name}") + return ExtrinsicResponse(False, f"{message}.").with_log() + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_staked": amount.rao, + } + + if safe_staking: + pool = subtensor.subnet(netuid=netuid) + base_price = pool.price.tao + + price_with_tolerance = ( + base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) + ) - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_staked": amount.rao, - } + logging.debug( + f"Safe Staking to: [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]." + ) - if safe_staking: - pool = subtensor.subnet(netuid=netuid) - base_price = pool.price.tao + 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.debug( + f"Staking to: [blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]." + ) + 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]" + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + use_nonce=True, + nonce_key="coldkeypub", + period=period, + raise_error=raise_error, ) + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + return response + logging.debug("[green]Finalized.[/green]") + + 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, + ) - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + logging.debug( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + "stake_before": old_stake, + "stake_after": new_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]" - ) - call_function = "add_stake" - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - - response = 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, - calling_function=get_function_name(), - ) - # If we successfully staked. - if response.success: - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: return response - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") + if safe_staking and "Custom error: 8" in response.message: + response.message = "Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." - 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]" - ) + logging.error(f"[red]{response.message}[/red]") return response - if safe_staking and "Custom error: 8" in response.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: {response.message}.[/red]") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def add_stake_multiple_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + hotkey_ss58s: list[str], amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, @@ -221,168 +213,192 @@ def add_stake_multiple_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. + + Note: + The `data` field in the returned `ExtrinsicResponse` contains the results of each individual internal + `add_stake_extrinsic` call. Each entry maps a tuple key `(idx, hotkey_ss58, netuid)` to either: + - the corresponding `ExtrinsicResponse` object if the staking attempt was executed, or + - `None` if the staking was skipped due to failing validation (e.g., wrong balance, zero amount, etc.). + In the key, `idx` is the index the stake attempt. This allows the caller to inspect which specific operations + were attempted and which were not. """ - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if not all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ): + raise TypeError( + "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." + ) - 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 ExtrinsicResponse(True, "Success") - if len(hotkey_ss58s) == 0: - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() - ) + if not len(netuids) == len(hotkey_ss58s) == len(amounts): + raise ValueError( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." + ) - 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 not all(isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s): - raise TypeError("hotkey_ss58s must be a list of str") + if not all(isinstance(a, Balance) for a in amounts): + raise TypeError("Each `amount` must be an instance of Balance.") - new_amounts: Sequence[Optional[Balance]] = [ - amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) - ] + new_amounts: Sequence[Optional[Balance]] = [ + 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 ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() + if sum(amount.tao for amount in new_amounts) == 0: + # Staking 0 tao + return ExtrinsicResponse(True, "Success") + + block = subtensor.get_current_block() + all_stakes = subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, + ) + old_stakes: list[Balance] = get_old_stakes( + wallet=wallet, + hotkey_ss58s=hotkey_ss58s, + netuids=netuids, + all_stakes=all_stakes, ) - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - block = subtensor.get_current_block() - all_stakes = subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - old_stakes: list[Balance] = get_old_stakes( - wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes - ) - - # Remove existential balance to keep key alive. - # Keys must maintain a balance of at least 1000 rao to stay alive. - 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( - address=wallet.coldkeypub.ss58_address, block=block - ) - if total_staking_rao == 0: - # Staking all to the first wallet. - if old_balance.rao > 1000: - old_balance -= Balance.from_rao(1000) - - elif total_staking_rao < 1000: - # Staking less than 1000 rao to the wallets. - pass - else: - # Staking more than 1000 rao to the wallets. - # Reduce the amount to stake to each wallet to keep the balance above 1000 rao. - percent_reduction = 1 - (1000 / total_staking_rao) - new_amounts = [ - Balance.from_tao(amount.tao * percent_reduction) for amount in new_amounts - ] + # Remove existential balance to keep key alive. Keys must maintain a balance of at least 1000 rao to stay alive. + 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( + address=wallet.coldkeypub.ss58_address, block=block + ) - successful_stakes = 0 - response = ExtrinsicResponse(False, "", extrinsic_function=get_function_name()) - for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( - zip(hotkey_ss58s, new_amounts, old_stakes, netuids) - ): - # Check enough to stake - if amount > old_balance: - logging.error( - f":cross_mark: [red]Not enough balance[/red]: [green]{old_balance}[/green] to stake: " - f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white]" - ) - continue + if total_staking_rao == 0: + # Staking all to the first wallet. + if old_balance.rao > 1000: + old_balance -= Balance.from_rao(1000) + + elif total_staking_rao < 1000: + # Staking less than 1000 rao to the wallets. + pass + else: + # Staking more than 1000 rao to the wallets. + # Reduce the amount to stake to each wallet to keep the balance above 1000 rao. + percent_reduction = 1 - (1000 / total_staking_rao) + new_amounts = [ + Balance.from_tao(amount.tao * percent_reduction) + for amount in new_amounts + ] + + successful_stakes = 0 + data = {} + for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( + zip(hotkey_ss58s, new_amounts, old_stakes, netuids) + ): + data.update({(idx, hotkey_ss58, netuid): None}) + + # Check enough to stake + if amount > old_balance: + logging.warning( + f"Not enough balance: [green]{old_balance}[/green] to stake " + f"[blue]{amount}[/blue] from wallet: [white]{wallet.name}[/white] " + f"with hotkey: [blue]{hotkey_ss58}[/blue] on netuid [blue]{netuid}[/blue]." + ) + continue - try: - logging.info( - 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": amount.rao, - "netuid": netuid, - }, - ) - response = subtensor.sign_and_send_extrinsic( - call=call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - use_nonce=True, - nonce_key="coldkeypub", - sign_with="coldkey", - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) + try: + logging.debug( + f"Staking [blue]{amount}[/blue] to hotkey [blue]{hotkey_ss58}[/blue] on netuid " + f"[blue]{netuid}[/blue]." + ) + response = add_stake_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) - # If we successfully staked. - if response.success: - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - old_balance -= amount + data.update({(idx, hotkey_ss58, netuid): response}) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + old_balance -= amount + successful_stakes += 1 + continue + + logging.debug("[green]Finalized[/green]") + + new_block = subtensor.get_current_block() + new_stake = subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + block=new_block, + ) + new_balance = subtensor.get_balance( + wallet.coldkeypub.ss58_address, block=new_block + ) + logging.debug( + f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: " + f"[green]{new_stake}[/green]" + ) + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + old_balance = new_balance successful_stakes += 1 continue - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - new_block = subtensor.get_current_block() - new_stake = subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block=new_block, - ) - new_balance = subtensor.get_balance( - wallet.coldkeypub.ss58_address, block=new_block + logging.warning( + f"Staking amount {amount} to hotkey_ss58 {hotkey_ss58} in subnet {netuid} was not successful." ) - logging.info( - f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" - ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - old_balance = new_balance - successful_stakes += 1 - else: - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - continue - except SubstrateRequestException as error: - logging.error( - f":cross_mark: [red]Add Stake Multiple error: {format_error_message(error)}[/red]" + except SubstrateRequestException as error: + logging.error( + f"[red]Add Stake Multiple error: {format_error_message(error)}[/red]" + ) + if raise_error: + raise + + if len(netuids) > successful_stakes > 0: + success = False + message = "Some stake were successful." + elif successful_stakes == len(netuids): + success = True + message = "Success" + else: + success = False + message = "No one stake were successful." + + if ( + new_balance := subtensor.get_balance(wallet.coldkeypub.ss58_address) + ) != old_balance: + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + data.update( + {"balance_before": initial_balance, "balance_after": new_balance} ) - if successful_stakes != 0: - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - logging.info( - f"Balance: [blue]{initial_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - return response + response = ExtrinsicResponse(success, message, data=data) + if response.success: + return response + return response.with_log() - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def set_auto_stake_extrinsic( diff --git a/bittensor/core/extrinsics/start_call.py b/bittensor/core/extrinsics/start_call.py index 9c3026d07b..af4de4620c 100644 --- a/bittensor/core/extrinsics/start_call.py +++ b/bittensor/core/extrinsics/start_call.py @@ -1,8 +1,6 @@ from typing import TYPE_CHECKING, Optional from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -36,26 +34,26 @@ def start_call_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + start_call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="start_call", + call_params={"netuid": netuid}, ) - start_call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="start_call", - call_params={"netuid": netuid}, - ) - - response = subtensor.sign_and_send_extrinsic( - call=start_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - calling_function=get_function_name(), - ) - - return response + return subtensor.sign_and_send_extrinsic( + call=start_call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + period=period, + raise_error=raise_error, + ) + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/sudo.py b/bittensor/core/extrinsics/sudo.py index 9606446858..0878245818 100644 --- a/bittensor/core/extrinsics/sudo.py +++ b/bittensor/core/extrinsics/sudo.py @@ -7,6 +7,7 @@ if TYPE_CHECKING: from bittensor_wallet import Wallet from bittensor.core.subtensor import Subtensor + from bittensor.core.types import ExtrinsicResponse def sudo_set_admin_freeze_window_extrinsic( @@ -17,7 +18,7 @@ def sudo_set_admin_freeze_window_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the admin freeze window length (in blocks) at the end of a tempo. @@ -33,9 +34,7 @@ def sudo_set_admin_freeze_window_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ call_function = "sudo_set_admin_freeze_window" call_params = {"window": window} @@ -60,7 +59,7 @@ def sudo_set_mechanism_count_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the number of subnet mechanisms. @@ -77,9 +76,7 @@ def sudo_set_mechanism_count_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ call_function = "sudo_set_mechanism_count" call_params = {"netuid": netuid, "mechanism_count": mech_count} @@ -104,7 +101,7 @@ def sudo_set_mechanism_emission_split_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> "ExtrinsicResponse": """ Sets the emission split between mechanisms in a provided subnet. @@ -121,9 +118,7 @@ def sudo_set_mechanism_emission_split_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. Note: The `maybe_split` list defines the relative emission share for each subnet mechanism. diff --git a/bittensor/core/extrinsics/take.py b/bittensor/core/extrinsics/take.py index 40411f3740..325a50ee9c 100644 --- a/bittensor/core/extrinsics/take.py +++ b/bittensor/core/extrinsics/take.py @@ -1,20 +1,19 @@ -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Optional, Literal from bittensor_wallet.bittensor_wallet import Wallet from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import unlock_key, get_function_name -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from bittensor.core.subtensor import Subtensor -def increase_take_extrinsic( +def set_take_extrinsic( subtensor: "Subtensor", wallet: Wallet, hotkey_ss58: str, take: int, + action: Literal["increase_take", "decrease_take"], period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -27,6 +26,7 @@ def increase_take_extrinsic( 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. + action: The call function to use to set the take. Can be either "increase_take" or "decrease_take". 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. @@ -35,89 +35,30 @@ def increase_take_extrinsic( 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. + ExtrinsicResponse: The result object of the extrinsic execution. """ - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="increase_take", - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, - ) - - return 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, - calling_function=get_function_name(), - ) - - -def decrease_take_extrinsic( - subtensor: "Subtensor", - 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, -) -> ExtrinsicResponse: - """ - Sets the delegate `take` percentage for a neuron identified by its hotkey. + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - 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]: - - 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) - if not unlock.success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=action, + call_params={ + "hotkey": hotkey_ss58, + "take": take, + }, + ) + return 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, ) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="decrease_take", - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, - ) - - return 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, - calling_function=get_function_name(), - ) + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index 3769ec9f65..888f9b1ade 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -1,12 +1,11 @@ from typing import TYPE_CHECKING, Optional + +from bittensor.core.settings import NETWORK_EXPLORER_MAP, DEFAULT_NETWORK from bittensor.core.types import ExtrinsicResponse -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_function_name, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -47,102 +46,99 @@ def transfer_extrinsic( Returns: bool: True if the subnet registration was successful, False otherwise. """ - # Unlock wallet coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + if amount is None and not transfer_all: + return ExtrinsicResponse( + False, "If not transferring all, `amount` must be specified." + ).with_log() + + # Validate destination address. + if not is_valid_bittensor_address_or_public_key(destination): + return ExtrinsicResponse( + False, f"Invalid destination SS58 address: {destination}" + ).with_log() + + # check existential deposit and fee + logging.debug("Fetching existential and fee.") + block = subtensor.get_current_block() + old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) + if not keep_alive: + # Check if the transfer should keep_alive the account + existential_deposit = Balance(0) + else: + existential_deposit = subtensor.get_existential_deposit(block=block) + + fee = subtensor.get_transfer_fee( + wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive ) - if amount is None and not transfer_all: - message = "If not transferring all, `amount` must be specified." - logging.error(message) - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Validate destination address. - if not is_valid_bittensor_address_or_public_key(destination): - message = f"Invalid destination SS58 address: {destination}" - logging.error(f":cross_mark: [red]{message}[/red].") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - # Check balance. - logging.info( - f":satellite: [magenta]Checking balance and fees on chain [/magenta] [blue]{subtensor.network}[/blue]" - ) - # check existential deposit and fee - logging.debug("Fetching existential and fee") - block = subtensor.get_current_block() - account_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - if not keep_alive: - # Check if the transfer should keep_alive the account - existential_deposit = Balance(0) - else: - existential_deposit = subtensor.get_existential_deposit(block=block) - - fee = subtensor.get_transfer_fee( - wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive - ) - - # Check if we have enough balance. - if transfer_all is True: - if (account_balance - fee) < existential_deposit: - message = "Not enough balance to transfer." - logging.error(message) + # Check if we have enough balance. + if transfer_all: + if (old_balance - fee) < existential_deposit: + return ExtrinsicResponse( + False, "Not enough balance to transfer all stake." + ).with_log() + + elif old_balance < (amount + fee + existential_deposit): return ExtrinsicResponse( - False, message, extrinsic_function=get_function_name() - ) - elif account_balance < (amount + fee + existential_deposit): - message = "Not enough balance." - logging.error(":cross_mark: [red]Not enough balance[/red]") - logging.error(f"\t\tBalance:\t[blue]{account_balance}[/blue]") - logging.error(f"\t\tAmount:\t[blue]{amount}[/blue]") - logging.error(f"\t\tFor fee:\t[blue]{fee}[/blue]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - logging.info(":satellite: [magenta]Transferring...[/magenta]") - - 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, - ) - - response = 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, - calling_function=get_function_name(), - ) - - if response.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]") - - if subtensor.network == "finney": - logging.debug("Fetching explorer URLs") - explorer_urls = get_explorer_url_for_network( - subtensor.network, block_hash, NETWORK_EXPLORER_MAP - ) - if explorer_urls: - logging.info( - f"[green]Opentensor Explorer Link: {explorer_urls.get('opentensor')}[/green]" - ) - logging.info( - f"[green]Taostats Explorer Link: {explorer_urls.get('taostats')}[/green]" - ) + False, + f"Not enough balance for transfer {amount} to {destination}. " + f"Account balance is {old_balance}. Transfers fee is {fee}.", + ).with_log() - logging.info(":satellite: [magenta]Checking Balance...[magenta]") - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - logging.info( - f"Balance: [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + 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, + ) + + response = 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, + ) + response.transaction_fee = fee + + if response.success: + block_hash = subtensor.get_block_hash() + + if subtensor.network == DEFAULT_NETWORK: + logging.debug("Fetching explorer URLs") + explorer_urls = get_explorer_url_for_network( + subtensor.network, block_hash, NETWORK_EXPLORER_MAP + ) + if explorer_urls: + logging.debug( + f"[green]Opentensor Explorer Link: {explorer_urls.get('opentensor')}[/green]" + ) + logging.debug( + f"[green]Taostats Explorer Link: {explorer_urls.get('taostats')}[/green]" + ) + + new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + } + return response + + logging.error(f"[red]{response.message}[/red]") return response - logging.error(f":cross_mark: [red]Failed[/red]: {response.message}") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index a926fdd9d8..6e2d2bdb27 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -1,12 +1,10 @@ from typing import Optional, TYPE_CHECKING -from bittensor.core.types import ExtrinsicResponse + from async_substrate_interface.errors import SubstrateRequestException -from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.core.extrinsics.utils import get_old_stakes -from bittensor.utils import unlock_key, format_error_message, get_function_name -from bittensor.core.types import UIDs -from bittensor.utils import unlock_key, format_error_message +from bittensor.core.types import ExtrinsicResponse, UIDs +from bittensor.utils import format_error_message from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -51,139 +49,137 @@ def unstake_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - # Decrypt keys, - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - - logging.info( - f":satellite: [magenta]Syncing with chain:[/magenta] [blue]{subtensor.network}[/blue] [magenta]...[/magenta]" - ) - block = subtensor.get_current_block() - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - old_stake = subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block=block, - ) - - # unstaking_balance = amount - amount.set_unit(netuid) - - # Check enough to unstake. - if amount > old_stake: - message = f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {wallet.hotkey_str}" - logging.error(f":cross_mark: [red]{message}[/red]") - return ExtrinsicResponse(False, message, extrinsic_function=get_function_name()) - - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_unstaked": amount.rao, - } - - if safe_unstaking: - 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 Unstaking from:[/magenta] " - 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], " - f"with partial unstake: [green]{allow_partial_stake}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "remove_stake_limit" - else: - logging_info = ( - f":satellite: [magenta]Unstaking from:[/magenta] " - f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " - f"on [blue]{subtensor.network}[/blue]" - ) - call_function = "remove_stake" - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, - ) - fee = get_extrinsic_fee( - subtensor=subtensor, netuid=netuid, call=call, keypair=wallet.coldkeypub - ) - logging.info(f"{logging_info} for fee [blue]{fee}[/blue][magenta]...[/magenta]") - - response = 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, - calling_function=get_function_name(), - ) - - if response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. - if not wait_for_finalization and not wait_for_inclusion: - return response + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked - logging.success(":white_heavy_check_mark: [green]Finalized[/green]") - - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) block = subtensor.get_current_block() - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - new_stake = subtensor.get_stake( + old_balance = subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block=block + ) + old_stake = subtensor.get_stake( coldkey_ss58=wallet.coldkeypub.ss58_address, hotkey_ss58=hotkey_ss58, netuid=netuid, block=block, ) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + + # unstaking_balance = amount + amount.set_unit(netuid) + + # Check enough to unstake. + if amount > old_stake: + return ExtrinsicResponse( + False, + f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {hotkey_ss58}", + ).with_log() + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "amount_unstaked": amount.rao, + } + + if safe_unstaking: + 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_message = ( + f":satellite: [magenta]Safe Unstaking from:[/magenta] " + 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], " + f"with partial unstake: [green]{allow_partial_stake}[/green] " + f"on [blue]{subtensor.network}[/blue]" + ) + + limit_price = Balance.from_tao(price_with_tolerance).rao + call_params.update( + { + "limit_price": limit_price, + "allow_partial": allow_partial_stake, + } + ) + call_function = "remove_stake_limit" + else: + logging_message = ( + f":satellite: [magenta]Unstaking from:[/magenta] " + f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " + f"on [blue]{subtensor.network}[/blue]" + ) + call_function = "remove_stake" + + logging.debug(logging_message) + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function=call_function, + call_params=call_params, ) - logging.info( - f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + + response = subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, ) + + if response.success: # If we successfully unstaked. + # We only wait here if we expect finalization. + if not wait_for_finalization and not wait_for_inclusion: + return response + + logging.debug("[green]Finalized[/green]") + + 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.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + ) + logging.debug( + f"Stake: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + ) + response.data = { + "balance_before": old_balance, + "balance_after": new_balance, + "stake_before": old_stake, + "stake_after": new_stake, + } + return response + + if safe_unstaking and "Custom error: 8" in response.message: + response.message = "Price exceeded tolerance limit. Either increase price tolerance or enable partial staking." + + logging.error(f"[red]{response.message}[/red]") return response - if safe_unstaking and "Custom error: 8" in response.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: {response.message}.[/red]") - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def unstake_all_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, @@ -196,7 +192,7 @@ def unstake_all_extrinsic( Parameters: subtensor: Subtensor instance. wallet: The wallet of the stake owner. - hotkey: The SS58 address of the hotkey to unstake from. + hotkey_ss58: 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. @@ -210,41 +206,42 @@ def unstake_all_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "limit_price": None, + } + + if rate_tolerance: + current_price = subtensor.subnet(netuid=netuid).price + limit_price = current_price * (1 - rate_tolerance) + call_params.update({"limit_price": limit_price}) + + call = subtensor.substrate.compose_call( + call_module="SubtensorModule", + call_function="remove_stake_full_limit", + call_params=call_params, ) - call_params = { - "hotkey": hotkey, - "netuid": netuid, - "limit_price": None, - } - - if rate_tolerance: - current_price = subtensor.subnet(netuid=netuid).price - limit_price = current_price * (1 - rate_tolerance) - call_params.update({"limit_price": limit_price}) - - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake_full_limit", - call_params=call_params, - ) - - return 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, - calling_function=get_function_name(), - ) + return subtensor.sign_and_send_extrinsic( + call=call, + wallet=wallet, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + nonce_key="coldkeypub", + use_nonce=True, + period=period, + raise_error=raise_error, + ) + + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def unstake_multiple_extrinsic( @@ -253,6 +250,7 @@ def unstake_multiple_extrinsic( netuids: UIDs, hotkey_ss58s: list[str], amounts: Optional[list[Balance]] = None, + rate_tolerance: Optional[float] = 0.05, unstake_all: bool = False, period: Optional[int] = None, raise_error: bool = False, @@ -268,6 +266,7 @@ def unstake_multiple_extrinsic( 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. + rate_tolerance: Maximum allowed price decrease percentage (0.005 = 0.5%). unstake_all: If true, unstakes all tokens. 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 @@ -278,174 +277,200 @@ def unstake_multiple_extrinsic( Returns: ExtrinsicResponse: The result object of the extrinsic execution. - """ - # Unlock coldkey. - if not (unlock := unlock_key(wallet)).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() - ) - # or amounts or unstake_all (no both) - if amounts and unstake_all: - raise ValueError("Cannot specify both `amounts` and `unstake_all`.") - - if amounts is not None and not all( - isinstance(amount, Balance) for amount in amounts - ): - raise TypeError("amounts must be a [list of bittensor.Balance] or None") - - if amounts is None: - amounts = [None] * len(hotkey_ss58s) - else: - # Convert to Balance - amounts = [amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids)] - if sum(amount.tao for amount in amounts) == 0: - # Staking 0 tao - return ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() + Note: + The `data` field in the returned `ExtrinsicResponse` contains the results of each individual internal + `unstake_extrinsic` or `unstake_all_extrinsic` call. Each entry maps a tuple key `(idx, hotkey_ss58, netuid)` to + either: + - the corresponding `ExtrinsicResponse` object if the unstaking attempt was executed, or + - `None` if the unstaking was skipped due to failing validation (e.g., wrong balance, zero amount, etc.). + In the key, `idx` is the index the unstake attempt. This allows the caller to inspect which specific operations + were attempted and which were not. + """ + try: + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked + + # or amounts or unstake_all (no both) + if amounts and unstake_all: + raise ValueError("Cannot specify both `amounts` and `unstake_all`.") + + if amounts is not None and not all( + isinstance(amount, Balance) for amount in amounts + ): + raise TypeError("`amounts` must be a list of Balance or None.") + + if amounts is None: + amounts = [None] * len(hotkey_ss58s) + else: + # Convert to Balance + amounts = [ + amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) + ] + if sum(amount.tao for amount in amounts) == 0: + # Staking 0 tao + return ExtrinsicResponse(True, "Success") + + if not all( + [ + isinstance(netuids, list), + isinstance(hotkey_ss58s, list), + isinstance(amounts, list), + ] + ): + raise TypeError( + "The `netuids`, `hotkey_ss58s` and `amounts` must be lists." ) - 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 ExtrinsicResponse( - True, "Success", extrinsic_function=get_function_name() - ) + if len(hotkey_ss58s) == 0: + return ExtrinsicResponse(True, "Success") - 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]" - ) - block = subtensor.get_current_block() - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) - all_stakes = subtensor.get_stake_for_coldkey( - coldkey_ss58=wallet.coldkeypub.ss58_address - ) - old_stakes = get_old_stakes( - wallet=wallet, hotkey_ss58s=hotkey_ss58s, netuids=netuids, all_stakes=all_stakes - ) - - successful_unstakes = 0 - response = ExtrinsicResponse( - False, "Failed", extrinsic_function=get_function_name() - ) - for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( - zip(hotkey_ss58s, amounts, old_stakes, netuids) - ): - # 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]" + if not len(netuids) == len(hotkey_ss58s) == len(amounts): + raise ValueError( + "The number of items in `netuids`, `hotkey_ss58s` and `amounts` must be the same." ) - else: - unstaking_balance = amount - unstaking_balance.set_unit(netuid) - # Check enough to unstake. - if unstaking_balance > old_stake: - logging.error( - f":cross_mark: [red]Not enough stake[/red]: [green]{old_stake}[/green] to unstake: " - f"[blue]{unstaking_balance}[/blue] from hotkey: [blue]{wallet.hotkey_str}[/blue]." - ) - continue - - try: - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": hotkey_ss58, - "amount_unstaked": unstaking_balance.rao, - "netuid": netuid, - }, - ) - fee = get_extrinsic_fee( - subtensor=subtensor, netuid=netuid, call=call, keypair=wallet.coldkeypub - ) - logging.info( - f"Unstaking [blue]{unstaking_balance}[/blue] from hotkey: [magenta]{hotkey_ss58}[/magenta] on netuid: " - f"[blue]{netuid}[/blue] for fee [blue]{fee}[/blue]" + 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`." ) - response = 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, - calling_function=get_function_name(), + 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 response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. + block = subtensor.get_current_block() + old_balance = subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block=block + ) + all_stakes = subtensor.get_stake_for_coldkey( + coldkey_ss58=wallet.coldkeypub.ss58_address, + block=block, + ) + old_stakes = get_old_stakes( + wallet=wallet, + hotkey_ss58s=hotkey_ss58s, + netuids=netuids, + all_stakes=all_stakes, + ) + + successful_unstakes = 0 + data = {} + for idx, (hotkey_ss58, amount, old_stake, netuid) in enumerate( + zip(hotkey_ss58s, amounts, old_stakes, netuids) + ): + data.update({(idx, hotkey_ss58, netuid): None}) + + # 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) + + # Check enough to unstake. + if unstaking_balance > old_stake: + logging.warning( + f"[red]Not enough stake[/red]: [green]{old_stake}[/green] to unstake: " + f"[blue]{unstaking_balance}[/blue] from hotkey: [blue]{hotkey_ss58}[/blue]." + ) + continue - if not wait_for_finalization and not wait_for_inclusion: + try: + logging.debug( + f"Unstaking [blue]{amount}[/blue] from hotkey [blue]{hotkey_ss58}[/blue] on netuid " + f"[blue]{netuid}[/blue]." + ) + if unstake_all: + response = unstake_all_extrinsic( + subtensor=subtensor, + wallet=wallet, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + rate_tolerance=rate_tolerance, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + else: + response = unstake_extrinsic( + subtensor=subtensor, + wallet=wallet, + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=unstaking_balance, + period=period, + raise_error=raise_error, + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, + ) + + data.update({(idx, hotkey_ss58, netuid): response}) + + if response.success: + if not wait_for_finalization and not wait_for_inclusion: + successful_unstakes += 1 + continue + + logging.debug("[green]Finalized[/green]") + + new_stake = subtensor.get_stake( + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=hotkey_ss58, + netuid=netuid, + ) + logging.debug( + f"Stake ({hotkey_ss58}) in subnet {netuid}: " + f"[blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]." + ) successful_unstakes += 1 continue - logging.info(":white_heavy_check_mark: [green]Finalized[/green]") - - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] [blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]..." - ) - new_stake = subtensor.get_stake( - coldkey_ss58=wallet.coldkeypub.ss58_address, - hotkey_ss58=hotkey_ss58, - netuid=netuid, - block=block, - ) - logging.info( - f"Stake ({hotkey_ss58}) on netuid {netuid}: [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]" + logging.warning( + f"Unstaking from hotkey_ss58 {hotkey_ss58} in subnet {netuid} was not successful." ) - successful_unstakes += 1 - else: - logging.error(f":cross_mark: [red]Failed: {response.message}.[/red]") - continue - except SubstrateRequestException as error: - logging.error( - f":cross_mark: [red]Multiple unstake filed with error: {format_error_message(error)}[/red]" + except SubstrateRequestException as error: + logging.error( + f"[red]Add Stake Multiple error: {format_error_message(error)}[/red]" + ) + if raise_error: + raise + + if len(netuids) > successful_unstakes > 0: + success = False + message = "Some unstake were successful." + elif successful_unstakes == len(netuids): + success = True + message = "Success" + else: + success = False + message = "No one unstake were successful." + + if ( + new_balance := subtensor.get_balance(address=wallet.coldkeypub.ss58_address) + ) != old_balance: + logging.debug( + f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) - if raise_error: - raise error - return response + data.update({"balance_before": old_balance, "balance_after": new_balance}) - if successful_unstakes != 0: - logging.info( - f":satellite: [magenta]Checking Balance on:[/magenta] ([blue]{subtensor.network}[/blue] " - f"[magenta]...[/magenta]" - ) - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - logging.info( - f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" - ) - return response + response = ExtrinsicResponse(success, message, data=data) + if response.success: + return response + return response.with_log() - return response + except Exception as error: + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 493dbf6fdf..7849144f8e 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -2,9 +2,8 @@ from typing import TYPE_CHECKING, Optional -from bittensor.utils import unlock_key +from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging if TYPE_CHECKING: from scalecodec import GenericCall @@ -85,7 +84,7 @@ def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Execute a sudo call extrinsic. Parameters: @@ -105,15 +104,16 @@ def sudo_call_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return False, unlock.message + if not ( + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type=sign_with + ) + ).success: + return unlocked + sudo_call = subtensor.substrate.compose_call( call_module="Sudo", call_function="sudo", @@ -138,7 +138,4 @@ def sudo_call_extrinsic( ) except Exception as error: - if raise_error: - raise error - - return False, str(error) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index 94b69bea9b..934190c72d 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -6,12 +6,7 @@ from bittensor.core.settings import version_as_int from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights -from bittensor.utils import ( - format_error_message, - get_function_name, - get_mechid_storage_index, - unlock_key, -) +from bittensor.utils import get_mechid_storage_index from bittensor.utils.btlogging import logging from bittensor.utils.weight_utils import ( convert_and_normalize_weights_and_uids, @@ -62,14 +57,15 @@ def commit_timelocked_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) current_block = subtensor.get_current_block() @@ -112,8 +108,8 @@ def commit_timelocked_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -129,16 +125,7 @@ def commit_timelocked_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def commit_weights_extrinsic( @@ -177,13 +164,13 @@ def commit_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked storage_index = get_mechid_storage_index(netuid=netuid, mechid=mechid) # Generate the hash of the weights @@ -212,8 +199,8 @@ def commit_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -225,16 +212,7 @@ def commit_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def reveal_weights_extrinsic( @@ -274,14 +252,15 @@ def reveal_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) call = subtensor.substrate.compose_call( @@ -303,8 +282,8 @@ def reveal_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -316,16 +295,7 @@ def reveal_weights_extrinsic( return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) def set_weights_extrinsic( @@ -363,15 +333,15 @@ def set_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( - unlock := unlock_key(wallet, unlock_type="hotkey", raise_error=raise_error) - ).success: - logging.error(unlock.message) - return ExtrinsicResponse( - False, unlock.message, extrinsic_function=get_function_name() + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, signing_keypair ) + ).success: + return unlocked - # Convert, reformat and normalize. + # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) call = subtensor.substrate.compose_call( @@ -392,26 +362,17 @@ def set_weights_extrinsic( wait_for_finalization=wait_for_finalization, period=period, use_nonce=True, - nonce_key="hotkey", - sign_with="hotkey", + nonce_key=signing_keypair, + sign_with=signing_keypair, raise_error=raise_error, ) if response.success: - logging.debug("Successfully set weights and Finalized.") + logging.debug(response.message) return response logging.error(response.message) return response except Exception as error: - if raise_error: - raise error - - logging.error(str(error)) - return ExtrinsicResponse( - success=False, - message=format_error_message(error), - error=error, - extrinsic_function=get_function_name(), - ) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) From 16ef067bed0023a62f15bbf81dd22c1aaf1ac70f Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:15:27 -0700 Subject: [PATCH 242/416] improve ExtrinsicResponse class --- bittensor/core/types.py | 170 ++++++++++++++++++++++++++++++++++------ 1 file changed, 148 insertions(+), 22 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 4dae382736..94dc91bd20 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -1,9 +1,7 @@ import argparse from abc import ABC from dataclasses import dataclass -from typing import Any, TypedDict, Optional, Union - -from scalecodec.types import GenericExtrinsic +from typing import Any, Literal, Optional, TypedDict, Union, TYPE_CHECKING import numpy as np from numpy.typing import NDArray @@ -12,12 +10,21 @@ from bittensor.core.chain_data import NeuronInfo, NeuronInfoLite from bittensor.core.config import Config from bittensor.utils import ( + Certificate, determine_chain_endpoint_and_network, + get_caller_name, + format_error_message, networking, - Certificate, + unlock_key, ) from bittensor.utils.btlogging import logging +if TYPE_CHECKING: + from bittensor_wallet import Wallet + from bittensor.utils.balance import Balance + from scalecodec.types import GenericExtrinsic + + # Type annotations for UIDs and weights. UIDs = Union[NDArray[np.int64], list[Union[int]]] Weights = Union[NDArray[np.float32], list[Union[int, float]]] @@ -292,10 +299,21 @@ class ExtrinsicResponse: Attributes: success: Indicates if the extrinsic execution was successful. message: A status or informational message returned from the execution (e.g., "Successfully registered subnet"). - error: Captures the underlying exception if the extrinsic failed, otherwise `None`. - data: Arbitrary data returned from the extrinsic, such as decoded events, or extra context. - extrinsic_function: The name of the SDK extrinsic function that was executed (e.g. "register_subnet_extrinsic"). + extrinsic_function: The SDK extrinsic or external function name that was executed (e.g., "add_stake_extrinsic"). extrinsic: The raw extrinsic object used in the call, if available. + extrinsic_fee: The fee charged by the extrinsic, if available. + transaction_fee: The fee charged by the transaction (e.g., fee for add_stake or transfer_stake), if available. + error: Captures the underlying exception if the extrinsic failed, otherwise `None`. + data: Arbitrary data returned from the extrinsic, such as decoded events, balance or another extra context. + + Instance methods: + as_dict: Returns a dictionary representation of this object. + with_log: Returns itself but with logging message. + + Class methods: + from_exception: Checks if error is raised or return ExtrinsicResponse accordingly. + unlock_wallet: Checks if keypair is unlocked and can be used for signing the extrinsic. + Example: import bittensor as bt @@ -309,9 +327,12 @@ class ExtrinsicResponse: ExtrinsicResponse: success: True message: Successfully registered subnet - error: None extrinsic_function: register_subnet_extrinsic extrinsic: {'account_id': '0xd43593c715fdd31c... + extrinsic_fee: τ1.0 + transaction_fee: τ1.0 + error: None + data: None success, message = response print(success, message) @@ -325,10 +346,12 @@ class ExtrinsicResponse: """ success: bool = True - message: str = None - error: Optional[Exception] = None + message: Optional[str] = None extrinsic_function: Optional[str] = None - extrinsic: Optional[GenericExtrinsic] = None + extrinsic: Optional["GenericExtrinsic"] = None + extrinsic_fee: Optional["Balance"] = None + transaction_fee: Optional["Balance"] = None + error: Optional[Exception] = None data: Optional[Any] = None def __iter__(self): @@ -336,29 +359,49 @@ def __iter__(self): yield self.message def __str__(self): - return str( - f"{self.__class__.__name__}:" - f"\n\tsuccess: {self.success}" - f"\n\tmessage: {self.message}" - f"\n\terror: {self.error}" - f"\n\textrinsic_function: {self.extrinsic_function}" - f"\n\textrinsic: {self.extrinsic}" - f"\n\tdata: {self.data}" + return ( + f"{self.__class__.__name__}:\n" + f"\tsuccess: {self.success}\n" + f"\tmessage: {self.message}\n" + f"\textrinsic_function: {self.extrinsic_function}\n" + f"\textrinsic: {self.extrinsic}\n" + f"\textrinsic_fee: {self.extrinsic_fee}\n" + f"\ttransaction_fee: {self.transaction_fee}\n" + f"\tdata: {self.data}\n" + f"\terror: {self.error}" ) def __repr__(self): return repr((self.success, self.message)) + def as_dict(self) -> dict: + """Represents this object as a dictionary.""" + return { + "success": self.success, + "message": self.message, + "extrinsic_function": self.extrinsic_function, + "extrinsic": self.extrinsic, + "extrinsic_fee": str(self.extrinsic_fee) if self.extrinsic_fee else None, + "transaction_fee": str(self.transaction_fee) + if self.transaction_fee + else None, + "error": str(self.error) if self.error else None, + "data": self.data, + } + def __eq__(self, other: Any) -> bool: - if isinstance(other, tuple): - return (self.success, self.message) == other + if isinstance(other, (tuple, list)): + return (self.success, self.message) == tuple(other) if isinstance(other, ExtrinsicResponse): return ( self.success == other.success and self.message == other.message - and self.error == other.error and self.extrinsic_function == other.extrinsic_function and self.extrinsic == other.extrinsic + and self.extrinsic_fee == other.extrinsic_fee + and self.transaction_fee == other.transaction_fee + and self.error == other.error + and self.data == other.data ) return super().__eq__(other) @@ -374,3 +417,86 @@ def __getitem__(self, index: int) -> Any: def __len__(self): return 2 + + def __post_init__(self): + if self.extrinsic_function is None: + self.extrinsic_function = get_caller_name(depth=3) + + @classmethod + def unlock_wallet( + cls, + wallet: "Wallet", + raise_error: bool = False, + unlock_type: str = "coldkey", + nonce_key: Optional[str] = None, + ) -> "ExtrinsicResponse": + """Check if keypair is unlocked and return ExtrinsicResponse accordingly. + + Parameters: + wallet: Bittensor Wallet instance. + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + unlock_type: The key type, 'coldkey' or 'hotkey'. + nonce_key: Key used for generating nonce in extrinsic function. + + Returns: + Extrinsic Response is used to check if the key is unlocked. + """ + unlock = unlock_key(wallet, unlock_type=unlock_type, raise_error=raise_error) + if not unlock.success: + logging.error(unlock.message) + + # If extrinsic uses `unlock_type` and `nonce_key` and `nonce_key` is not public, we need to check the + # availability of both keys. + if nonce_key and nonce_key != unlock_type and "pub" not in nonce_key: + nonce_key_unlock = unlock_key( + wallet, unlock_type=nonce_key, raise_error=raise_error + ) + if not nonce_key_unlock.success: + logging.error(nonce_key_unlock.message) + + return cls( + success=all([unlock.success, nonce_key_unlock.success]), + message=unlock.message, + extrinsic_function=get_caller_name(), + ) + + return cls( + success=unlock.success, + message=unlock.message, + extrinsic_function=get_caller_name(), + ) + + @classmethod + def from_exception(cls, raise_error: bool, error: Exception) -> "ExtrinsicResponse": + """Check if error is raised and return ExtrinsicResponse accordingly. + Parameters: + raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. + error: Exception raised during extrinsic execution. + + Returns: + Extrinsic Response with False checks whether to raise an error or simply return the instance. + """ + if raise_error: + raise error + return cls( + success=False, + message=format_error_message(error), + error=error, + extrinsic_function=get_caller_name(), + ).with_log() + + def with_log( + self, level: Literal["trace", "debug", "info", "warning", "error", "success"] = "error" + ) -> "ExtrinsicResponse": + """Logs provided message with provided level. + + Parameters: + level: Logging level represented as "trace", "debug", "info", "warning", "error", "success" uses to logging + message. + + Returns: + ExtrinsicResponse instance. + """ + if self.message: + getattr(logging, level)(self.message) + return self From 091c3cac700fab39f071570c1384090a4dc2296f Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:16:37 -0700 Subject: [PATCH 243/416] update/fix tests --- Makefile | 1 - bittensor/utils/mock/subtensor_mock.py | 66 +- tests/e2e_tests/conftest.py | 50 +- tests/e2e_tests/test_axon.py | 10 - tests/e2e_tests/test_commit_reveal.py | 45 +- tests/e2e_tests/test_commit_weights.py | 14 - tests/e2e_tests/test_commitment.py | 26 +- tests/e2e_tests/test_delegate.py | 40 - tests/e2e_tests/test_dendrite.py | 200 +- tests/e2e_tests/test_hotkeys.py | 576 ++-- tests/e2e_tests/test_incentive.py | 14 +- tests/e2e_tests/test_liquid_alpha.py | 4 - tests/e2e_tests/test_liquidity.py | 8 +- tests/e2e_tests/test_metagraph.py | 16 - tests/e2e_tests/test_neuron_certificate.py | 4 - tests/e2e_tests/test_reveal_commitments.py | 20 +- tests/e2e_tests/test_root_set_weights.py | 8 - .../test_set_subnet_identity_extrinsic.py | 15 - tests/e2e_tests/test_set_weights.py | 4 - tests/e2e_tests/test_stake_fee.py | 6 - tests/e2e_tests/test_staking.py | 94 +- tests/e2e_tests/test_subnets.py | 8 - tests/e2e_tests/test_subtensor_functions.py | 44 +- tests/e2e_tests/test_transfer.py | 18 - tests/e2e_tests/utils/chain_interactions.py | 13 +- tests/e2e_tests/utils/e2e_test_utils.py | 2 +- tests/pytest.ini | 3 +- tests/unit_tests/conftest.py | 2 +- .../extrinsics/asyncex/test_children.py | 29 +- .../extrinsics/asyncex/test_liquidity.py | 7 - .../extrinsics/asyncex/test_mechanisms.py | 0 .../extrinsics/asyncex/test_registration.py | 7 +- .../extrinsics/asyncex/test_root.py | 50 +- .../extrinsics/asyncex/test_start_call.py | 1 - .../extrinsics/asyncex/test_transfer.py | 42 +- .../extrinsics/asyncex/test_unstaking.py | 59 +- tests/unit_tests/extrinsics/test_children.py | 9 +- tests/unit_tests/extrinsics/test_liquidity.py | 7 - .../extrinsics/test_registration.py | 33 +- tests/unit_tests/extrinsics/test_root.py | 17 +- tests/unit_tests/extrinsics/test_serving.py | 2 +- tests/unit_tests/extrinsics/test_staking.py | 86 +- .../unit_tests/extrinsics/test_start_call.py | 1 - tests/unit_tests/extrinsics/test_transfer.py | 49 +- tests/unit_tests/extrinsics/test_unstaking.py | 76 +- tests/unit_tests/test_async_subtensor.py | 200 +- tests/unit_tests/test_subtensor.py | 14 +- tests/unit_tests/test_subtensor_extended.py | 2862 ++++++++--------- 48 files changed, 2282 insertions(+), 2580 deletions(-) delete mode 100644 tests/unit_tests/extrinsics/asyncex/test_mechanisms.py diff --git a/Makefile b/Makefile index c4a49e8d1d..6d4d4db485 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,6 @@ ruff: @python -m ruff format bittensor check: ruff - @mypy --ignore-missing-imports bittensor/ --python-version=3.9 @mypy --ignore-missing-imports bittensor/ --python-version=3.10 @mypy --ignore-missing-imports bittensor/ --python-version=3.11 @mypy --ignore-missing-imports bittensor/ --python-version=3.12 diff --git a/bittensor/utils/mock/subtensor_mock.py b/bittensor/utils/mock/subtensor_mock.py index becbd90595..69ed181d7e 100644 --- a/bittensor/utils/mock/subtensor_mock.py +++ b/bittensor/utils/mock/subtensor_mock.py @@ -359,7 +359,7 @@ def set_difficulty(self, netuid: int, difficulty: int) -> None: subtensor_state["Difficulty"][netuid][self.block_number] = difficulty - def _register_neuron(self, netuid: int, hotkey: str, coldkey: str) -> int: + def _register_neuron(self, netuid: int, hotkey_ss58: str, coldkey: str) -> int: subtensor_state = self.chain_state["SubtensorModule"] if netuid not in subtensor_state["NetworksAdded"]: raise Exception("Subnet does not exist") @@ -370,7 +370,7 @@ def _register_neuron(self, netuid: int, hotkey: str, coldkey: str) -> int: if subnetwork_n > 0 and any( self._get_most_recent_storage(subtensor_state["Keys"][netuid][uid]) - == hotkey + == hotkey_ss58 for uid in range(subnetwork_n) ): # already_registered @@ -390,18 +390,18 @@ def _register_neuron(self, netuid: int, hotkey: str, coldkey: str) -> int: subnetwork_n + 1 ) - subtensor_state["Stake"][hotkey] = {} - subtensor_state["Stake"][hotkey][coldkey] = {} - subtensor_state["Stake"][hotkey][coldkey][self.block_number] = 0 + subtensor_state["Stake"][hotkey_ss58] = {} + subtensor_state["Stake"][hotkey_ss58][coldkey] = {} + subtensor_state["Stake"][hotkey_ss58][coldkey][self.block_number] = 0 - subtensor_state["Uids"][netuid][hotkey] = {} - subtensor_state["Uids"][netuid][hotkey][self.block_number] = uid + subtensor_state["Uids"][netuid][hotkey_ss58] = {} + subtensor_state["Uids"][netuid][hotkey_ss58][self.block_number] = uid subtensor_state["Keys"][netuid][uid] = {} - subtensor_state["Keys"][netuid][uid][self.block_number] = hotkey + subtensor_state["Keys"][netuid][uid][self.block_number] = hotkey_ss58 - subtensor_state["Owner"][hotkey] = {} - subtensor_state["Owner"][hotkey][self.block_number] = coldkey + subtensor_state["Owner"][hotkey_ss58] = {} + subtensor_state["Owner"][hotkey_ss58][self.block_number] = coldkey subtensor_state["Active"][netuid][uid] = {} subtensor_state["Active"][netuid][uid][self.block_number] = True @@ -444,16 +444,18 @@ def _register_neuron(self, netuid: int, hotkey: str, coldkey: str) -> int: subtensor_state["Bonds"][netuid][uid] = {} subtensor_state["Bonds"][netuid][uid][self.block_number] = [] - subtensor_state["Axons"][netuid][hotkey] = {} - subtensor_state["Axons"][netuid][hotkey][self.block_number] = {} + subtensor_state["Axons"][netuid][hotkey_ss58] = {} + subtensor_state["Axons"][netuid][hotkey_ss58][self.block_number] = {} - subtensor_state["Prometheus"][netuid][hotkey] = {} - subtensor_state["Prometheus"][netuid][hotkey][self.block_number] = {} + subtensor_state["Prometheus"][netuid][hotkey_ss58] = {} + subtensor_state["Prometheus"][netuid][hotkey_ss58][self.block_number] = {} - if hotkey not in subtensor_state["IsNetworkMember"]: - subtensor_state["IsNetworkMember"][hotkey] = {} - subtensor_state["IsNetworkMember"][hotkey][netuid] = {} - subtensor_state["IsNetworkMember"][hotkey][netuid][self.block_number] = True + if hotkey_ss58 not in subtensor_state["IsNetworkMember"]: + subtensor_state["IsNetworkMember"][hotkey_ss58] = {} + subtensor_state["IsNetworkMember"][hotkey_ss58][netuid] = {} + subtensor_state["IsNetworkMember"][hotkey_ss58][netuid][ + self.block_number + ] = True return uid @@ -470,8 +472,8 @@ def _convert_to_balance(balance: Union["Balance", float, int]) -> "Balance": def force_register_neuron( self, netuid: int, - hotkey: str, - coldkey: str, + hotkey_ss58: str, + coldkey_ss58: str, stake: Union["Balance", float, int] = Balance(0), balance: Union["Balance", float, int] = Balance(0), ) -> int: @@ -485,16 +487,20 @@ def force_register_neuron( if netuid not in subtensor_state["NetworksAdded"]: raise Exception("Subnet does not exist") - uid = self._register_neuron(netuid=netuid, hotkey=hotkey, coldkey=coldkey) + uid = self._register_neuron( + netuid=netuid, hotkey_ss58=hotkey_ss58, coldkey=coldkey_ss58 + ) subtensor_state["TotalStake"][self.block_number] = ( self._get_most_recent_storage(subtensor_state["TotalStake"]) + stake.rao ) - subtensor_state["Stake"][hotkey][coldkey][self.block_number] = stake.rao + subtensor_state["Stake"][hotkey_ss58][coldkey_ss58][self.block_number] = ( + stake.rao + ) if balance.rao > 0: - self.force_set_balance(coldkey, balance) - self.force_set_balance(coldkey, balance) + self.force_set_balance(coldkey_ss58, balance) + self.force_set_balance(coldkey_ss58, balance) return uid @@ -786,18 +792,18 @@ def _get_most_recent_storage( return None def _get_axon_info( - self, netuid: int, hotkey: str, block: Optional[int] = None + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None ) -> AxonInfoDict: # Axons [netuid][hotkey][block_number] subtensor_state = self.chain_state["SubtensorModule"] if netuid not in subtensor_state["Axons"]: return AxonInfoDict.default() - if hotkey not in subtensor_state["Axons"][netuid]: + if hotkey_ss58 not in subtensor_state["Axons"][netuid]: return AxonInfoDict.default() result = self._get_most_recent_storage( - subtensor_state["Axons"][netuid][hotkey], block + subtensor_state["Axons"][netuid][hotkey_ss58], block ) if not result: return AxonInfoDict.default() @@ -805,17 +811,17 @@ def _get_axon_info( return result def _get_prometheus_info( - self, netuid: int, hotkey: str, block: Optional[int] = None + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None ) -> PrometheusInfoDict: subtensor_state = self.chain_state["SubtensorModule"] if netuid not in subtensor_state["Prometheus"]: return PrometheusInfoDict.default() - if hotkey not in subtensor_state["Prometheus"][netuid]: + if hotkey_ss58 not in subtensor_state["Prometheus"][netuid]: return PrometheusInfoDict.default() result = self._get_most_recent_storage( - subtensor_state["Prometheus"][netuid][hotkey], block + subtensor_state["Prometheus"][netuid][hotkey_ss58], block ) if not result: return PrometheusInfoDict.default() diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 1c2f8a42e3..579d7dcd6c 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -1,3 +1,4 @@ +import asyncio import os import re import shlex @@ -7,6 +8,7 @@ import sys import threading import time +from typing import Optional import pytest import pytest_asyncio @@ -26,8 +28,11 @@ CONTAINER_NAME_PREFIX = "test_local_chain_" -def wait_for_node_start(process, timestamp=None): - """Waits for node to start in the docker.""" +def wait_for_node_start(process, timestamp=None, timeout: Optional[int] = 120): + """Waits for node to start in the docker. + + The `timeout` is set to 2 mins bc sometimes in GH the chain takes time after finalizing the first block. + """ while True: line = process.stdout.readline() if not line: @@ -35,8 +40,7 @@ def wait_for_node_start(process, timestamp=None): timestamp = timestamp or int(time.time()) print(line.strip()) - # 10 min as timeout - if int(time.time()) - timestamp > 20 * 30: + if int(time.time()) - timestamp > timeout: print("Subtensor not started in time") raise TimeoutError @@ -111,12 +115,12 @@ def legacy_runner(params): text=True, ) as process: try: - wait_for_node_start(process) + wait_for_node_start(process, timeout=300) except TimeoutError: raise else: - with SubstrateInterface(url="ws://127.0.0.1:9944") as substrate: - yield substrate + yield + finally: # Terminate the process group (includes all child processes) os.killpg(os.getpgid(process.pid), signal.SIGTERM) @@ -132,6 +136,14 @@ def legacy_runner(params): def docker_runner(params): """Starts a Docker container before tests and gracefully terminates it after.""" + def kill_local_nodes(): + """Closes subtensor local running nodes.""" + try: + subprocess.run(["pkill", "-9", "-f", "node-subtensor"], check=False) + print("Killed all local 'node-subtensor' processes.") + except Exception as e: + print(f"Warning: failed to kill local node-subtensor: {e}") + def is_docker_running(): """Check if Docker is running and optionally skip pulling the image.""" try: @@ -155,7 +167,7 @@ def is_docker_running(): def try_start_docker(): """Run docker based on OS.""" try: - subprocess.run(["open", "-a", "Docker"], check=True) # macOS + subprocess.run(["open", "-g", "-a", "Docker"], check=True) # macOS except (FileNotFoundError, subprocess.CalledProcessError): try: subprocess.run(["systemctl", "start", "docker"], check=True) # Linux @@ -222,6 +234,8 @@ def stop_existing_test_containers(): print("Entire run command: ", cmds) + kill_local_nodes() + try_start_docker() stop_existing_test_containers() @@ -248,8 +262,7 @@ def stop_existing_test_containers(): if not result.stdout.strip(): raise RuntimeError("Docker container failed to start.") - with SubstrateInterface(url="ws://127.0.0.1:9944") as substrate: - yield substrate + yield finally: try: @@ -270,15 +283,6 @@ def subtensor(local_chain): return SubtensorApi(network="ws://localhost:9944", legacy_methods=False) -# @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( @@ -315,3 +319,11 @@ def dave_wallet(): def eve_wallet(): keypair, wallet = setup_wallet("//Eve") return wallet + + +@pytest.fixture(autouse=True) +def log_test_start_and_end(request): + test_name = request.node.nodeid + logging.console.info(f"🏁[green]Testing[/green] [yellow]{test_name}[/yellow]") + yield + logging.console.success(f"✅ [green]Passed[/green] [yellow]{test_name}[/yellow]") diff --git a/tests/e2e_tests/test_axon.py b/tests/e2e_tests/test_axon.py index 61dff37431..2723a61261 100644 --- a/tests/e2e_tests/test_axon.py +++ b/tests/e2e_tests/test_axon.py @@ -19,9 +19,6 @@ async def test_axon(subtensor, templates, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - - logging.console.info("Testing test_axon") - netuid = 2 # Register a subnet, netuid 2 @@ -81,8 +78,6 @@ async def test_axon(subtensor, templates, alice_wallet): "Coldkey mismatch after mining" ) - logging.console.success("✅ Passed test_axon") - @pytest.mark.asyncio async def test_axon_async(async_subtensor, templates, alice_wallet): @@ -97,9 +92,6 @@ async def test_axon_async(async_subtensor, templates, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - - logging.console.info("Testing test_axon") - netuid = 2 # Register a subnet, netuid 2 @@ -162,5 +154,3 @@ async def test_axon_async(async_subtensor, templates, alice_wallet): assert updated_axon.coldkey == alice_wallet.coldkey.ss58_address, ( "Coldkey mismatch after mining" ) - - logging.console.success("✅ Passed test_axon_async") diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 3f460391c8..ee8d10386d 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -28,11 +28,10 @@ TESTED_SUB_SUBNETS = 2 -# @pytest.mark.parametrize("local_chain", [True], indirect=True) @pytest.mark.asyncio -async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_wallet): +async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): """ - Tests the commit/reveal weights mechanism (CR3) + Tests the commit/reveal weights mechanism (CRv4) Steps: 1. Register a subnet through Alice @@ -45,8 +44,6 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") - # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0) @@ -77,7 +74,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( - substrate=local_chain, + substrate=subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_commit_reveal_weights_enabled", value=True, @@ -97,7 +94,7 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # Change the weights rate limit on the subnet status, error = sudo_set_admin_utils( - substrate=local_chain, + substrate=subtensor.substrate, wallet=alice_wallet, call_function="sudo_set_weights_set_rate_limit", call_params={"netuid": alice_subnet_netuid, "weights_set_rate_limit": "0"}, @@ -119,8 +116,8 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle # Change the tempo of the subnet 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": TEMPO_TO_SET}, )[0] @@ -273,15 +270,11 @@ async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_walle f"latest_drand_round ({latest_drand_round}) is less than expected_reveal_round ({expected_reveal_round})" ) - logging.console.success("✅ Passed `test_commit_and_reveal_weights_cr4`") - @pytest.mark.asyncio -async def test_commit_and_reveal_weights_cr4_async( - async_subtensor, alice_wallet, local_chain -): +async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet): """ - Tests the commit/reveal weights mechanism (CR3) + Tests the commit/reveal weights mechanism (CRv4) Steps: 1. Register a subnet through Alice @@ -294,9 +287,6 @@ async def test_commit_and_reveal_weights_cr4_async( Raises: AssertionError: If any of the checks or verifications fail """ - - logging.console.info("Testing `test_commit_and_reveal_weights_cr4`") - # turn off admin freeze window limit for testing assert await async_sudo_set_admin_freeze_window_extrinsic( async_subtensor, alice_wallet, 0 @@ -348,8 +338,8 @@ async def test_commit_and_reveal_weights_cr4_async( assert cr_version == 4, f"Commit reveal version is not 3, got {cr_version}" # Change the weights rate limit on the subnet - status, error = sudo_set_admin_utils( - substrate=local_chain, + 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": "0"}, @@ -372,14 +362,13 @@ async def test_commit_and_reveal_weights_cr4_async( # Change the tempo of the subnet assert ( - sudo_set_admin_utils( - local_chain, - alice_wallet, + 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 - ) + ) + )[0] is True tempo = ( await async_subtensor.subnets.get_subnet_hyperparameters( @@ -427,8 +416,6 @@ async def test_commit_and_reveal_weights_cr4_async( f"[magenta]Testing subnet mechanism: {alice_subnet_netuid}.{mechid}[/magenta]" ) - # commit_block is the block when weights were committed on the chain (transaction block) - expected_commit_block = await async_subtensor.block + 1 # Commit weights response = await async_subtensor.extrinsics.set_weights( wallet=alice_wallet, @@ -531,5 +518,3 @@ async def test_commit_and_reveal_weights_cr4_async( assert latest_drand_round - expected_reveal_round >= -3, ( f"latest_drand_round ({latest_drand_round}) is less than expected_reveal_round ({expected_reveal_round})" ) - - logging.console.success("✅ Passed `test_commit_and_reveal_weights_cr4`") diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 53c8c389df..692fe29cd6 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -42,8 +42,6 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_commit_and_reveal_weights") - # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( "Failed to set admin freeze window to 0" @@ -143,7 +141,6 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): ) assert response.success, response.message - logging.console.info(f"block: {subtensor.block} response: {response}") storage_index = get_mechid_storage_index(netuid, mechid) weight_commits = subtensor.queries.query_module( @@ -192,8 +189,6 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): f"Incorrect revealed weights. Expected: {weights[0]}, Actual: {revealed_weights[0][1]}" ) - 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): @@ -209,8 +204,6 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_commit_and_reveal_weights_async") - # turn off admin freeze window limit for testing assert ( await async_sudo_set_admin_utils( @@ -352,7 +345,6 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal 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 @@ -385,8 +377,6 @@ async def test_commit_weights_uses_next_nonce(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_commit_and_reveal_weights") - # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( "Failed to set admin freeze window to 0" @@ -522,7 +512,6 @@ 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 @@ -540,8 +529,6 @@ async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_walle Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_commit_and_reveal_weights") - assert ( await async_sudo_set_admin_utils( substrate=async_subtensor.substrate, @@ -722,4 +709,3 @@ 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.") diff --git a/tests/e2e_tests/test_commitment.py b/tests/e2e_tests/test_commitment.py index 825f3a3625..a886ae5bb3 100644 --- a/tests/e2e_tests/test_commitment.py +++ b/tests/e2e_tests/test_commitment.py @@ -1,18 +1,16 @@ import pytest from async_substrate_interface.errors import SubstrateRequestException -from bittensor import logging -from tests.e2e_tests.utils.chain_interactions import ( - async_sudo_set_admin_utils, - sudo_set_admin_utils, +from bittensor.utils.btlogging import logging +from bittensor.core.extrinsics.utils import sudo_call_extrinsic +from bittensor.core.extrinsics.asyncex.utils import ( + sudo_call_extrinsic as async_sudo_call_extrinsic, ) from tests.e2e_tests.utils.e2e_test_utils import ( wait_to_start_call, async_wait_to_start_call, ) -logging.set_trace() - def test_commitment(subtensor, alice_wallet, dave_wallet): dave_subnet_netuid = 2 @@ -25,9 +23,10 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"): subtensor.commitments.set_commitment( - alice_wallet, + wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!", + raise_error=True, ) assert subtensor.subnets.burned_register( @@ -53,8 +52,8 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): data="Hello World!", ) - status, error = sudo_set_admin_utils( - substrate=subtensor.substrate, + status, error = sudo_call_extrinsic( + subtensor=subtensor, wallet=alice_wallet, call_module="Commitments", call_function="set_max_space", @@ -74,6 +73,7 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!1", + raise_error=True, ) assert "Hello World!" == subtensor.commitments.get_commitment( @@ -104,9 +104,10 @@ 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.commitments.set_commitment( - alice_wallet, + wallet=alice_wallet, netuid=dave_subnet_netuid, data="Hello World!", + raise_error=True, ) assert ( @@ -134,8 +135,8 @@ async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): data="Hello World!", ) - status, error = await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, + status, error = await async_sudo_call_extrinsic( + subtensor=async_subtensor, wallet=alice_wallet, call_module="Commitments", call_function="set_max_space", @@ -155,6 +156,7 @@ async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): alice_wallet, netuid=dave_subnet_netuid, data="Hello World!1", + raise_error=True, ) assert "Hello World!" == await sub.commitments.get_commitment( diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 6851fc6110..176522dea8 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -37,8 +37,6 @@ def test_identity(subtensor, alice_wallet, bob_wallet): - Check Delegate's default identity - Update Delegate's identity """ - logging.console.info("Testing [green]test_identity[/green].") - identity = subtensor.neurons.query_identity(alice_wallet.coldkeypub.ss58_address) assert identity is None @@ -89,7 +87,6 @@ def test_identity(subtensor, alice_wallet, bob_wallet): name="Alice", url="https://www.example.com", ) - logging.console.success("Test [green]test_identity[/green] passed.") @pytest.mark.asyncio @@ -99,8 +96,6 @@ 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[/green].") - identity = await async_subtensor.neurons.query_identity( alice_wallet.coldkeypub.ss58_address ) @@ -159,7 +154,6 @@ async def test_identity_async(async_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(subtensor, alice_wallet, bob_wallet): @@ -169,8 +163,6 @@ def test_change_take(subtensor, alice_wallet, bob_wallet): - Increase and decreased Delegate's take - Try corner cases (increase/decrease beyond allowed min/max) """ - - logging.console.info("Testing [green]test_change_take[/green].") with pytest.raises(HotKeyAccountNotExists): subtensor.delegates.set_delegate_take( wallet=alice_wallet, @@ -244,8 +236,6 @@ def test_change_take(subtensor, alice_wallet, bob_wallet): 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): @@ -255,8 +245,6 @@ async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): - 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, @@ -344,8 +332,6 @@ async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): ) assert take == 0.14999618524452582 - logging.console.success("Test [green]test_change_take_async[/green] passed.") - def test_delegates(subtensor, alice_wallet, bob_wallet): """ @@ -355,8 +341,6 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): - Check if Hotkey is a Delegate - Nominator Staking """ - logging.console.info("Testing [green]test_delegates[/green].") - assert subtensor.delegates.get_delegates() == [] assert subtensor.delegates.get_delegated(alice_wallet.coldkey.ss58_address) == [] assert ( @@ -484,7 +468,6 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_subnet_netuid), ), ] - logging.console.success("Test [green]test_delegates[/green] passed.") @pytest.mark.asyncio @@ -496,8 +479,6 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): - 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) @@ -657,7 +638,6 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): 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): @@ -668,8 +648,6 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ - Update NominatorMinRequiredStake - Check Nominator is removed """ - logging.console.info("Testing [green]test_delegates_async[/green].") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 @@ -728,10 +706,6 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ ) 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( @@ -744,10 +718,6 @@ async def test_nominator_min_required_stake_async( - Update NominatorMinRequiredStake - Check Nominator is removed """ - logging.console.info( - "Testing [green]test_nominator_min_required_stake_async[/green]." - ) - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 @@ -816,10 +786,6 @@ async def test_nominator_min_required_stake_async( ) 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): """ @@ -829,8 +795,6 @@ def test_get_vote_data(subtensor, alice_wallet): - Votes - Checks Proposal is updated """ - logging.console.info("Testing [green]test_get_vote_data[/green].") - assert subtensor.extrinsics.root_register(alice_wallet).success, ( "Can not register Alice in root SN." ) @@ -924,7 +888,6 @@ 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 @@ -936,8 +899,6 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): - 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)).success, ( "Can not register Alice in root SN." ) @@ -1032,4 +993,3 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): nays=[], threshold=3, ) - logging.console.success("Test [green]test_get_vote_data_async[/green] passed.") diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index 7912182941..748c1fc6f8 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -5,19 +5,23 @@ 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_sudo_set_admin_utils, async_wait_epoch, - sudo_set_admin_utils, + # sudo_set_admin_utils, wait_epoch, ) +from bittensor.core.extrinsics.utils import sudo_call_extrinsic +from bittensor.core.extrinsics import sudo +from bittensor.core.extrinsics.asyncex import sudo as async_sudo +from bittensor.core.extrinsics.asyncex.utils import ( + sudo_call_extrinsic as async_sudo_call_extrinsic, +) from tests.e2e_tests.utils.e2e_test_utils import ( async_wait_to_start_call, wait_to_start_call, ) -logging.on() -logging.set_debug() - +FAST_RUNTIME_TEMPO = 100 NON_FAST_RUNTIME_TEMPO = 10 @@ -35,18 +39,38 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing `test_dendrite`.") + SET_TEMPO = ( + FAST_RUNTIME_TEMPO + if subtensor.chain.is_fast_blocks() + else NON_FAST_RUNTIME_TEMPO + ) + + assert sudo.sudo_set_admin_freeze_window_extrinsic( + subtensor, alice_wallet, 0 + ).success alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # Register a subnet, netuid 2 - assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created." + assert subtensor.subnets.register_subnet(alice_wallet).success, ( + "Subnet wasn't created." + ) # Verify subnet created successfully assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( "Subnet wasn't created successfully." ) + assert sudo_call_extrinsic( + subtensor=subtensor, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={ + "netuid": alice_subnet_netuid, + "tempo": SET_TEMPO, + }, + ).success, "Unable to set tempo." + assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid), ( "Subnet wasn't started." ) @@ -62,44 +86,32 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), ).success - # 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( - substrate=subtensor.substrate, + assert sudo_call_extrinsic( + subtensor=subtensor, wallet=alice_wallet, call_function="sudo_set_max_allowed_validators", call_params={ "netuid": alice_subnet_netuid, "max_allowed_validators": 1, }, - ) + ).success, "Unable to set max_allowed_validators." # update weights_set_rate_limit for fast-blocks - status, error = sudo_set_admin_utils( - substrate=subtensor.substrate, + assert sudo_call_extrinsic( + subtensor=subtensor, 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 + ).success, "Unable to set weights_set_rate_limit." # Register Bob to the network assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid).success, ( - "Unable to register Bob as a neuron" + "Unable to register Bob as a neuron." ) metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) @@ -107,11 +119,15 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): # Assert neurons are Alice and Bob assert len(metagraph.neurons) == 2 - alice_neuron = metagraph.neurons[0] + alice_neuron = next( + n for n in metagraph.neurons if n.hotkey == alice_wallet.hotkey.ss58_address + ) assert alice_neuron.hotkey == alice_wallet.hotkey.ss58_address assert alice_neuron.coldkey == alice_wallet.coldkey.ss58_address - bob_neuron = metagraph.neurons[1] + bob_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) assert bob_neuron.hotkey == bob_wallet.hotkey.ss58_address assert bob_neuron.coldkey == bob_wallet.coldkey.ss58_address @@ -129,15 +145,20 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=tao, - ).success + ).success, "Unable to stake to Bob." # Waiting to give the chain a chance to update its state subtensor.wait_for_block() # Refresh metagraph metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) - bob_neuron = metagraph.neurons[1] + bob_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) + logging.console.info( + f"block: {subtensor.block}, bob_neuron.stake.rao: {bob_neuron.stake.rao}, alpha.rao: {alpha.rao}, division: {bob_neuron.stake.rao / alpha.rao}" + ) # Assert alpha is close to stake equivalent assert 0.95 < bob_neuron.stake.rao / alpha.rao < 1.05 @@ -150,13 +171,17 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): async with templates.validator(bob_wallet, alice_subnet_netuid): await asyncio.sleep(5) # wait for 5 seconds for the Validator to process - await wait_epoch(subtensor, netuid=alice_subnet_netuid) + subtensor.wait_for_block( + subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 1 + ) # Refresh metagraph metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) # Refresh validator neuron - updated_neuron = metagraph.neurons[1] + updated_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) assert len(metagraph.neurons) == 2 assert updated_neuron.active is True @@ -165,8 +190,6 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): 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): @@ -182,20 +205,42 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing `test_dendrite_async`.") + SET_TEMPO = ( + FAST_RUNTIME_TEMPO + if await async_subtensor.chain.is_fast_blocks() + else NON_FAST_RUNTIME_TEMPO + ) + + assert ( + await async_sudo.sudo_set_admin_freeze_window_extrinsic( + async_subtensor, alice_wallet, 0 + ) + ).success 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), ( - "Subnet wasn't created" + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success, ( + "Subnet wasn't created." ) # Verify subnet created successfully assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" + "Subnet wasn't created successfully." ) + assert ( + await async_sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={ + "netuid": alice_subnet_netuid, + "tempo": SET_TEMPO, + }, + ) + ).success, "Unable to set tempo." + assert await async_wait_to_start_call( async_subtensor, alice_wallet, alice_subnet_netuid ), "Subnet wasn't started." @@ -216,56 +261,52 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall wait_for_finalization=False, ) ).success - # set tempo to 10 block for non-fast-runtime - assert await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, + + # update max_allowed_validators so only one neuron can get validator_permit + assert ( + await async_sudo_call_extrinsic( + subtensor=async_subtensor, wallet=alice_wallet, - call_function="sudo_set_tempo", + call_function="sudo_set_max_allowed_validators", call_params={ "netuid": alice_subnet_netuid, - "tempo": NON_FAST_RUNTIME_TEMPO, + "max_allowed_validators": 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, - }, - ) + ).success, "Unable to set max_allowed_validators." # 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 + assert ( + await async_sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_weights_set_rate_limit", + call_params={ + "netuid": alice_subnet_netuid, + "weights_set_rate_limit": 10, + }, + ) + ).success, "Unable to set weights_set_rate_limit." # Register Bob to the network assert ( await async_subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid) - ).success, "Unable to register Bob as a neuron" + ).success, "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] + alice_neuron = next( + n for n in metagraph.neurons if n.hotkey == alice_wallet.hotkey.ss58_address + ) assert alice_neuron.hotkey == alice_wallet.hotkey.ss58_address assert alice_neuron.coldkey == alice_wallet.coldkey.ss58_address - bob_neuron = metagraph.neurons[1] + bob_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) assert bob_neuron.hotkey == bob_wallet.hotkey.ss58_address assert bob_neuron.coldkey == bob_wallet.coldkey.ss58_address @@ -287,14 +328,20 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall wait_for_inclusion=False, wait_for_finalization=False, ) - ).success + ).success, "Unable to stake to Bob." # Waiting to give the chain a chance to update its state await async_subtensor.wait_for_block() # Refresh metagraph metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) - bob_neuron = metagraph.neurons[1] + bob_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) + + logging.console.info( + f"block: {await async_subtensor.block}, bob_neuron.stake.rao: {bob_neuron.stake.rao}, alpha.rao: {alpha.rao}, division: {bob_neuron.stake.rao / alpha.rao}" + ) # Assert alpha is close to stake equivalent assert 0.95 < bob_neuron.stake.rao / alpha.rao < 1.05 @@ -308,13 +355,20 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall 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) + await async_subtensor.wait_for_block( + await async_subtensor.subnets.get_next_epoch_start_block( + alice_subnet_netuid + ) + + 1 + ) # Refresh metagraph metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) # Refresh validator neuron - updated_neuron = metagraph.neurons[1] + updated_neuron = next( + n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address + ) assert len(metagraph.neurons) == 2 assert updated_neuron.active is True @@ -322,5 +376,3 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall 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_async`") diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 6416b5c404..f5fe16e857 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -1,3 +1,5 @@ +import asyncio + import pytest from bittensor.core.errors import ( @@ -11,14 +13,9 @@ TxRateLimitExceeded, NonAssociatedColdKey, ) -from bittensor.core.extrinsics.sudo import ( - sudo_set_admin_freeze_window_extrinsic, -) +from bittensor.core.extrinsics import sudo +from bittensor.core.extrinsics.asyncex import sudo as async_sudo from bittensor.utils.btlogging import logging -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, @@ -26,6 +23,8 @@ SET_CHILDREN_RATE_LIMIT = 30 ROOT_COOLDOWN = 50 # blocks +FAST_RUNTIME_TEMPO = 100 +NON_FAST_RUNTIME_TEMPO = 10 def test_hotkeys(subtensor, alice_wallet, dave_wallet): @@ -34,8 +33,6 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): - Check if Hotkey exists - Check if Hotkey is registered """ - logging.console.info("Testing [green]test_hotkeys[/green].") - dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(dave_wallet) assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( @@ -80,7 +77,6 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): ) is True ) - logging.console.success("✅ Test [green]test_hotkeys[/green] passed") @pytest.mark.asyncio @@ -90,8 +86,6 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): - 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) assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( @@ -140,7 +134,6 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): ) is True ) - logging.console.success("✅ Test [green]test_hotkeys[/green] passed") def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -154,14 +147,18 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): - Trigger rate limit - Clear children list """ - - logging.console.info("Testing [green]test_children[/green].") - # turn off admin freeze window limit for testing - assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0) + assert sudo.sudo_set_admin_freeze_window_extrinsic( + subtensor, alice_wallet, 0 + ).success + + SET_TEMPO = ( + FAST_RUNTIME_TEMPO + if subtensor.chain.is_fast_blocks() + else NON_FAST_RUNTIME_TEMPO + ) dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - set_tempo = 10 # affect to non-fast-blocks mode # Set cooldown success, message = subtensor.extrinsics.root_set_pending_childkey_cooldown( @@ -178,26 +175,20 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): 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.chain.is_fast_blocks(): - assert ( - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={"netuid": dave_subnet_netuid, "tempo": set_tempo}, - )[0] - is True - ) + assert sudo.sudo_call_extrinsic( + subtensor=subtensor, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, + ).success + assert subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO - assert ( - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tx_rate_limit", - call_params={"tx_rate_limit": 0}, - )[0] - is True - ) + assert sudo.sudo_call_extrinsic( + subtensor=subtensor, + wallet=alice_wallet, + call_function="sudo_set_tx_rate_limit", + call_params={"tx_rate_limit": 0}, + ).success with pytest.raises(RegistrationNotPermittedOnRootSubnet): subtensor.extrinsics.set_children( @@ -355,7 +346,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): 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) + subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2 + 1) success, children, error = subtensor.wallets.get_children( hotkey=alice_wallet.hotkey.ss58_address, @@ -439,8 +430,8 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) - sudo_set_admin_utils( - substrate=subtensor.substrate, + sudo.sudo_call_extrinsic( + subtensor=subtensor, wallet=alice_wallet, call_function="sudo_set_stake_threshold", call_params={ @@ -462,11 +453,11 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): raise_error=True, ) - 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 def test_children_async( + async_subtensor, alice_wallet, bob_wallet, dave_wallet, subtensor +): """ Async tests: - Get default children (empty list) @@ -477,137 +468,175 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa - Trigger rate limit - Clear children list """ + async with async_subtensor as async_subtensor: + # turn off admin freeze window limit for testing + window_response, is_fast_blocks = await asyncio.gather( + async_sudo.sudo_set_admin_freeze_window_extrinsic( + async_subtensor, alice_wallet, 0 + ), + async_subtensor.chain.is_fast_blocks(), + ) - logging.console.info("Testing [green]test_children_async[/green].") + dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - set_tempo = 10 # affect to non-fast-blocks mode + assert window_response.success, window_response.message - # Set cooldown - ( - success, - message, - ) = await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( - wallet=alice_wallet, cooldown=ROOT_COOLDOWN - ) - assert success is True, message - assert message == "Success" + SET_TEMPO = FAST_RUNTIME_TEMPO if is_fast_blocks else NON_FAST_RUNTIME_TEMPO - 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." - ) + cooldown_response = ( + await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( + wallet=alice_wallet, cooldown=ROOT_COOLDOWN + ) + ) + assert cooldown_response.success, cooldown_response.message - assert ( - await async_wait_to_start_call(async_subtensor, dave_wallet, dave_subnet_netuid) - is True - ) + assert await async_subtensor.subnets.register_subnet(dave_wallet) - # 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, + subnet_exists, start_call, tempo_call = await asyncio.gather( + async_subtensor.subnets.subnet_exists(dave_subnet_netuid), + async_wait_to_start_call(async_subtensor, dave_wallet, dave_subnet_netuid), + async_sudo.sudo_call_extrinsic( + subtensor=async_subtensor, wallet=alice_wallet, call_function="sudo_set_tempo", - call_params={"netuid": dave_subnet_netuid, "tempo": set_tempo}, + call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, + ), + ) + assert subnet_exists, "Subnet does not exist." + assert start_call, "Subnet did not start." + + tempo_call = await async_sudo.sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_tx_rate_limit", + call_params={"tx_rate_limit": 0}, + ) + + assert tempo_call.success, tempo_call.message + assert await async_subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO + + 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, ) - )[0] is True - assert ( - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, + with pytest.raises(NonAssociatedColdKey): + await async_subtensor.extrinsics.set_children( wallet=alice_wallet, - call_function="sudo_set_tx_rate_limit", - call_params={"tx_rate_limit": 0}, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=1, + children=[], + raise_error=True, ) - )[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(SubnetNotExists): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey=alice_wallet.hotkey.ss58_address, + netuid=3, + children=[], + raise_error=True, + ) - with pytest.raises(NonAssociatedColdKey): - await async_subtensor.extrinsics.set_children( + # we can't do more than one registration within one block (avoid gather) + # https://docs.learnbittensor.org/errors/subtensor#toomanyregistrationsthisblock + alice_register_call = await async_subtensor.subnets.burned_register( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=1, - children=[], - raise_error=True, + netuid=dave_subnet_netuid, ) + await async_subtensor.wait_for_block() - with pytest.raises(SubnetNotExists): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=3, - children=[], - raise_error=True, + bob_register_call, (success, children, error) = await asyncio.gather( + async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, + ), + async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ), ) - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - ) - ).success - logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") + assert alice_register_call.success, alice_register_call.message + 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, - ) - ).success - logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") + assert bob_register_call.success, bob_register_call.message + 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 == [] - 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(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(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(ProportionOverflow): - await async_subtensor.extrinsics.set_children( + 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, @@ -616,102 +645,76 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa 1.0, bob_wallet.hotkey.ss58_address, ), - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), ], raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, ) + assert success, message + assert message == "Success" - with pytest.raises(DuplicateChild): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, + 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, - 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 success is True, message - assert message == "Success" - 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 == "" - 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, + ) - # 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]" + ) - logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" - ) + assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] - 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 + 1) - # 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, + ) - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + # we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait + while not children: + 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, + ) + logging.console.info(f"block: {await async_subtensor.block}") - assert error == "" - assert success is True - assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + 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 - ) + parent_ = await async_subtensor.wallets.get_parents( + bob_wallet.hotkey.ss58_address, dave_subnet_netuid + ) - assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] + 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]" - ) + # 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() @@ -730,67 +733,80 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa raise_error=True, ) - await async_subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) + await async_subtensor.wait_for_block( + set_children_block + SET_CHILDREN_RATE_LIMIT + 1 + ) - 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 success is True, message - assert message == "Success" - logging.console.info(f"[orange]success: {success}, message: {message}[/orange]") + 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 success is True, message + assert message == "Success" - set_children_block = await async_subtensor.chain.get_current_block() + 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, - ) + 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]" - ) + assert pending == [] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) - await async_subtensor.wait_for_block(cooldown + 1) + await async_subtensor.wait_for_block(cooldown + 1) - 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)] + success, children, error = await async_subtensor.wallets.get_children( + hotkey=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) - await async_subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) + # we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait + while not children: + 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, + ) + logging.console.info(f"block: {await async_subtensor.block}") - 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, - }, - ) + assert error == "" + assert success is True + assert children == [(1.0, bob_wallet.hotkey.ss58_address)] - 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, + await async_subtensor.wait_for_block( + set_children_block + SET_CHILDREN_RATE_LIMIT ) - logging.console.success(f"✅ Test [green]test_children_async[/green] passed") + assert ( + await async_sudo.sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_stake_threshold", + call_params={ + "min_stake": 1_000_000_000_000, + }, + ) + ).success + + 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, + ) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index 4e6e14c468..ed1c180a54 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -27,8 +27,6 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - - logging.console.info("Testing [blue]test_incentive[/blue]") alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 # turn off admin freeze window limit for testing assert ( @@ -75,7 +73,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): # Wait for the first epoch to pass subtensor.wait_for_block( - subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 1 + subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 5 ) # Get current miner/validator stats @@ -188,14 +186,11 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): ), ] - print("✅ Passed test_incentive") break 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): @@ -210,8 +205,6 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal 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 # turn off admin freeze window limit for testing @@ -264,7 +257,7 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal next_epoch_start_block = await async_subtensor.subnets.get_next_epoch_start_block( netuid=alice_subnet_netuid ) - await async_subtensor.wait_for_block(next_epoch_start_block + 1) + await async_subtensor.wait_for_block(next_epoch_start_block + 5) # Get current miner/validator stats alice_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[ @@ -380,10 +373,7 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal ), ] - 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.") diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index 0dd6e0a4fb..8d5fd2b6b9 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -38,8 +38,6 @@ def test_liquid_alpha(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing test_liquid_alpha_enabled") - # turn off admin freeze window limit for testing assert ( sudo_set_admin_utils( @@ -244,8 +242,6 @@ 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_async[/blue]") - # turn off admin freeze window limit for testing assert ( await async_sudo_set_admin_utils( diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index d180dc3759..62e0dfe55d 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -25,8 +25,6 @@ async def test_liquidity(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.subnets.get_total_subnets() # 2 # Make sure `get_liquidity_list` return None if SN doesn't exist @@ -258,8 +256,8 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): # Bob remove all stake from alice assert subtensor.extrinsics.unstake_all( wallet=bob_wallet, - hotkey=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, rate_tolerance=0.9, # keep high rate tolerance to avoid flaky behavior wait_for_inclusion=True, wait_for_finalization=True, @@ -314,8 +312,6 @@ async def test_liquidity_async(async_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_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 @@ -558,8 +554,8 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): assert ( await async_subtensor.extrinsics.unstake_all( wallet=bob_wallet, - hotkey=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, rate_tolerance=0.9, # keep high rate tolerance to avoid flaky behavior wait_for_inclusion=True, wait_for_finalization=True, diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 6b9385fd51..aed0270286 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -52,8 +52,6 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - 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") @@ -210,8 +208,6 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w 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 @@ -377,8 +373,6 @@ 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.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet) @@ -633,8 +627,6 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): - 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) @@ -896,8 +888,6 @@ 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.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet) @@ -1132,8 +1122,6 @@ async def test_metagraph_info_with_indexes_async( - 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) @@ -1370,8 +1358,6 @@ def test_blocks(subtensor): - Get block hash - Wait for block """ - logging.console.info("Testing [blue]test_blocks[/blue]") - get_current_block = subtensor.chain.get_current_block() block = subtensor.block @@ -1395,8 +1381,6 @@ async def test_blocks_async(subtensor): - 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 diff --git a/tests/e2e_tests/test_neuron_certificate.py b/tests/e2e_tests/test_neuron_certificate.py index 880a8b8662..d4c80a286b 100644 --- a/tests/e2e_tests/test_neuron_certificate.py +++ b/tests/e2e_tests/test_neuron_certificate.py @@ -52,8 +52,6 @@ async def test_neuron_certificate(subtensor, alice_wallet): 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]") - @pytest.mark.asyncio async def test_neuron_certificate_async(async_subtensor, alice_wallet): @@ -107,5 +105,3 @@ async def test_neuron_certificate_async(async_subtensor, alice_wallet): ) 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]") diff --git a/tests/e2e_tests/test_reveal_commitments.py b/tests/e2e_tests/test_reveal_commitments.py index 7d7807cd2c..865e7777d5 100644 --- a/tests/e2e_tests/test_reveal_commitments.py +++ b/tests/e2e_tests/test_reveal_commitments.py @@ -27,16 +27,12 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): 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 subtensor.chain.is_fast_blocks() else (12.0, 5) ) alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - logging.console.info("Testing Drand encrypted commitments.") - # Register subnet as Alice assert subtensor.subnets.register_subnet(alice_wallet), ( "Unable to register the subnet" @@ -83,14 +79,14 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): # 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}") + logging.console.info(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}") + logging.console.info(f"Current last reveled drand round {last_drand_round}") time.sleep(3) actual_all = subtensor.commitments.get_all_revealed_commitments(alice_subnet_netuid) @@ -120,8 +116,6 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): 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(async_subtensor, alice_wallet, bob_wallet): @@ -142,16 +136,12 @@ async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wa 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), ( "Unable to register the subnet" @@ -199,14 +189,14 @@ async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wa # 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}") + logging.console.info(f"Waiting for reveal round {target_reveal_round}") 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) last_drand_round = await async_subtensor.chain.last_drand_round() - print(f"Current last reveled drand round {last_drand_round}") + logging.console.info(f"Current last reveled drand round {last_drand_round}") time.sleep(3) actual_all = await async_subtensor.commitments.get_all_revealed_commitments( @@ -241,5 +231,3 @@ async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wa assert message_alice == actual_alice_message assert message_bob == actual_bob_message - - logging.console.success("✅ Passed [blue]test_set_reveal_commitment[/blue]") diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index dc906a86ef..5b402f0d5f 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -59,8 +59,6 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall Raises: AssertionError: If any of the checks or verifications fail. """ - - 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 @@ -152,8 +150,6 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall 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( @@ -179,8 +175,6 @@ async def test_root_reg_hyperparams_async( 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 @@ -278,5 +272,3 @@ async def test_root_reg_hyperparams_async( ) 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") diff --git a/tests/e2e_tests/test_set_subnet_identity_extrinsic.py b/tests/e2e_tests/test_set_subnet_identity_extrinsic.py index 228cfdd744..1a35280aad 100644 --- a/tests/e2e_tests/test_set_subnet_identity_extrinsic.py +++ b/tests/e2e_tests/test_set_subnet_identity_extrinsic.py @@ -47,10 +47,6 @@ def test_set_subnet_identity_extrinsic_happy_pass(subtensor, alice_wallet): # Check SubnetIdentity of the subnet 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_happy_pass_async( @@ -102,9 +98,6 @@ async def test_set_subnet_identity_extrinsic_happy_pass_async( 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): @@ -152,10 +145,6 @@ def test_set_subnet_identity_extrinsic_failed(subtensor, alice_wallet, bob_walle 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( @@ -207,7 +196,3 @@ async def test_set_subnet_identity_extrinsic_failed_async( subnet_identity=subnet_identity, ) ).success is False, "Set subnet identity failed" - - logging.console.success( - "✅ Passed [blue]test_set_subnet_identity_extrinsic_failed[/blue]" - ) diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index f29b129198..582ff52a3c 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -40,7 +40,6 @@ def test_set_weights_uses_next_nonce(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]") # turn off admin freeze window limit for testing assert sudo_set_admin_freeze_window_extrinsic( subtensor=subtensor, @@ -56,7 +55,6 @@ def test_set_weights_uses_next_nonce(subtensor, alice_wallet): (0.25, 50) if subtensor.chain.is_fast_blocks() else (12.0, 20) ) - print("Testing test_set_weights_uses_next_nonce") subnet_tempo = 50 # Lower the network registration rate limit and cost @@ -229,8 +227,6 @@ async def test_set_weights_uses_next_nonce_async(async_subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.console.info("Testing [blue]test_set_weights_uses_next_nonce_async[/blue]") - # turn off admin freeze window limit for testing assert ( await async_sudo_set_admin_utils( diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index 11518cb037..4a7882e332 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -14,8 +14,6 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): - 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 @@ -100,7 +98,6 @@ def test_stake_fee_api(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[/blue]") @pytest.mark.asyncio @@ -115,8 +112,6 @@ async def test_stake_fee_api_async(async_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 stake_amount = Balance.from_tao(100) # 100 TAO @@ -203,4 +198,3 @@ async def test_stake_fee_api_async(async_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]") diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 1f4c20282b..62d7f879b8 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -25,8 +25,6 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): - Unstaking using `unstake` - Checks StakeInfo """ - 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 @@ -188,7 +186,6 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): # all balances have been unstaked assert stake == Balance(0).set_unit(alice_subnet_netuid) - logging.console.success("✅ Test [green]test_single_operation[/green] passed") @pytest.mark.asyncio @@ -199,8 +196,6 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) - 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 @@ -357,7 +352,7 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) success, message = await async_subtensor.staking.unstake_all( wallet=alice_wallet, netuid=alice_subnet_netuid, - hotkey=bob_wallet.hotkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, period=16, ) assert success is True, message @@ -372,8 +367,6 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) # 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): """ @@ -383,8 +376,6 @@ 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, 3, @@ -486,14 +477,15 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): fee_tao = dynamic_info.alpha_to_tao(fee_alpha) expected_fee_paid += fee_tao - success = subtensor.staking.unstake_multiple( + response = subtensor.staking.unstake_multiple( wallet=alice_wallet, netuids=netuids, hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], amounts=[Balance.from_tao(100) for _ in netuids], + raise_error=True, ) - - assert success.success is True + logging.console.info(f">>> res {response}") + assert response.success, response.message for netuid, old_stake in zip(netuids, stakes): stake = subtensor.staking.get_stake( @@ -514,7 +506,6 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): ) == Balance.from_tao(999_999.7994) assert balances[alice_wallet.coldkey.ss58_address] > alice_balance - logging.console.success("✅ Test [green]test_batch_operations[/green] passed") @pytest.mark.asyncio @@ -526,8 +517,6 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) - Checks StakeInfo - Checks Accounts Balance """ - logging.console.info("Testing [blue]test_batch_operations_async[/blue]") - netuids = [ 2, 3, @@ -575,19 +564,18 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) alice_balance = balances[alice_wallet.coldkey.ss58_address] - assert ( - 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], - ) - ).success + response = await async_subtensor.staking.add_stake_multiple( + wallet=alice_wallet, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + netuids=netuids, + amounts=[Balance.from_tao(10_000) for _ in netuids], + ) + assert response.success stakes = [ 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, ) for netuid in netuids @@ -633,14 +621,13 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) fee_tao = dynamic_info.alpha_to_tao(fee_alpha) expected_fee_paid += fee_tao - success = await async_subtensor.staking.unstake_multiple( + response = await async_subtensor.staking.unstake_multiple( wallet=alice_wallet, netuids=netuids, hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], amounts=[Balance.from_tao(100) for _ in netuids], ) - - assert success.success is True + assert response.success, response.message for netuid, old_stake in zip(netuids, stakes): stake = await async_subtensor.staking.get_stake( @@ -661,7 +648,6 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) ) == 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): @@ -673,8 +659,6 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) 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]") - # turn off admin freeze window limit for testing assert ( sudo_set_admin_utils( @@ -856,7 +840,6 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) allow_partial_stake=False, ) assert response.success is True, "Unstake should succeed" - logging.console.success("✅ Test [green]test_safe_staking_scenarios[/green] passed") @pytest.mark.asyncio @@ -871,8 +854,6 @@ async def test_safe_staking_scenarios_async( 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_async[/blue]") - # turn off admin freeze window limit for testing assert ( await async_sudo_set_admin_utils( @@ -1061,9 +1042,6 @@ async def test_safe_staking_scenarios_async( allow_partial_stake=False, ) assert response.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): @@ -1074,8 +1052,6 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): 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).success @@ -1171,9 +1147,6 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): 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 @@ -1187,8 +1160,6 @@ async def test_safe_swap_stake_scenarios_async( 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)).success @@ -1290,9 +1261,6 @@ async def test_safe_swap_stake_scenarios_async( 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_async[/green] passed" - ) def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -1302,8 +1270,6 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): - 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).success assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -1450,8 +1416,6 @@ 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.") - @pytest.mark.asyncio async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -1461,8 +1425,6 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ - 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 = await async_subtensor.subnets.get_total_subnets() # 2 assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( @@ -1626,8 +1588,6 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ assert dave_stake.rao == CloseInValue(0, 0.00001) - logging.console.success("✅ Test [green]test_move_stake_async[/green] passed.") - def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): """ @@ -1635,8 +1595,6 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): - Adding stake - Transferring stake from one coldkey-subnet pair to another """ - logging.console.info("Testing [blue]test_transfer_stake[/blue]") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet).success @@ -1751,7 +1709,6 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ), ] assert bob_stakes == expected_bob_stake - logging.console.success("✅ Test [green]test_transfer_stake[/green] passed") @pytest.mark.asyncio @@ -1763,8 +1720,6 @@ async def test_transfer_stake_async( - 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)).success @@ -1889,7 +1844,6 @@ async def test_transfer_stake_async( ), ] 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 ` @@ -1907,8 +1861,6 @@ 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.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet).success @@ -1986,8 +1938,8 @@ def test_unstaking_with_limit( ): subtensor.staking.unstake_all( wallet=bob_wallet, - hotkey=bob_stakes[0].hotkey_ss58, netuid=bob_stakes[0].netuid, + hotkey_ss58=bob_stakes[0].hotkey_ss58, rate_tolerance=rate_tolerance, ) else: @@ -1995,8 +1947,8 @@ def test_unstaking_with_limit( for si in bob_stakes: assert subtensor.staking.unstake_all( wallet=bob_wallet, - hotkey=si.hotkey_ss58, netuid=si.netuid, + hotkey_ss58=si.hotkey_ss58, rate_tolerance=rate_tolerance, ).success @@ -2020,8 +1972,6 @@ 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)).success @@ -2116,7 +2066,7 @@ async def test_unstaking_with_limit_async( await async_subtensor.staking.unstake_all( wallet=bob_wallet, netuid=bob_stakes[0].netuid, - hotkey=bob_stakes[0].hotkey_ss58, + hotkey_ss58=bob_stakes[0].hotkey_ss58, rate_tolerance=rate_tolerance, ) else: @@ -2125,8 +2075,8 @@ async def test_unstaking_with_limit_async( assert ( await async_subtensor.staking.unstake_all( wallet=bob_wallet, + hotkey_ss58=si.hotkey_ss58, netuid=si.netuid, - hotkey=si.hotkey_ss58, rate_tolerance=rate_tolerance, ) ).success @@ -2137,10 +2087,6 @@ async def test_unstaking_with_limit_async( ) assert len(bob_stakes) == 0 - logging.console.success( - "✅ Test [green]test_unstaking_with_limit_async[/green] passed" - ) - def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet): """Tests auto staking logic.""" diff --git a/tests/e2e_tests/test_subnets.py b/tests/e2e_tests/test_subnets.py index 89028d2d06..7079073d82 100644 --- a/tests/e2e_tests/test_subnets.py +++ b/tests/e2e_tests/test_subnets.py @@ -9,8 +9,6 @@ def test_subnets(subtensor, alice_wallet): - Filtering subnets - Checks default TxRateLimit """ - logging.console.info("Testing [blue]test_subnets[/blue]") - subnets = subtensor.subnets.all_subnets() assert len(subnets) == 2 @@ -30,8 +28,6 @@ def test_subnets(subtensor, alice_wallet): 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): @@ -41,8 +37,6 @@ async def test_subnets_async(async_subtensor, alice_wallet): - 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 @@ -61,5 +55,3 @@ async def test_subnets_async(async_subtensor, alice_wallet): 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") diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index aebe05871b..654f69c035 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -52,7 +52,6 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall Raises: AssertionError: If any of the checks or verifications fail """ - 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) @@ -73,24 +72,12 @@ 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), ( - "Unable to register the subnet." - ) + response = subtensor.subnets.register_subnet(alice_wallet) + assert response.success, "Unable to register the subnet." # Subnet burn cost is increased immediately after a subnet is registered post_subnet_creation_cost = subtensor.subnets.get_subnet_burn_cost() - # TODO: in SDKv10 replace this logic with using `ExtrinsicResponse.extrinsic_fee` - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": alice_wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - register_fee = get_extrinsic_fee(subtensor, call, alice_wallet.hotkey) - # 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 @@ -101,7 +88,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall alice_wallet.coldkeypub.ss58_address ) assert ( - alice_balance_post_sn + pre_subnet_creation_cost + register_fee + alice_balance_post_sn + pre_subnet_creation_cost + response.extrinsic_fee == initial_alice_balance ), "Balance is the same even after registering a subnet." @@ -234,8 +221,6 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall 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( @@ -253,7 +238,6 @@ async def test_subtensor_extrinsics_async( 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) @@ -276,22 +260,8 @@ 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), ( - "Unable to register the subnet" - ) - - # TODO: in SDKv10 replace this logic with using `ExtrinsicResponse.extrinsic_fee` - call = await async_subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": alice_wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - register_fee = await get_extrinsic_fee_async( - async_subtensor, call, alice_wallet.hotkey - ) + response = await async_subtensor.subnets.register_subnet(alice_wallet) + assert response.success, "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() @@ -306,7 +276,7 @@ async def test_subtensor_extrinsics_async( alice_wallet.coldkeypub.ss58_address ) assert ( - alice_balance_post_sn + pre_subnet_creation_cost + register_fee + alice_balance_post_sn + pre_subnet_creation_cost + response.extrinsic_fee == initial_alice_balance ), "Balance is the same even after registering a subnet." @@ -442,5 +412,3 @@ async def test_subtensor_extrinsics_async( assert actual_owner == expected_owner, ( f"Expected owner {expected_owner}, but found {actual_owner}" ) - - logging.console.success("✅ Passed [blue]test_subtensor_extrinsics[/blue]") diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py index cfa3eede93..4f65a5828d 100644 --- a/tests/e2e_tests/test_transfer.py +++ b/tests/e2e_tests/test_transfer.py @@ -9,8 +9,6 @@ if typing.TYPE_CHECKING: from bittensor.core.subtensor_api import SubtensorApi -logging.set_trace() - def test_transfer(subtensor, alice_wallet): """ @@ -22,8 +20,6 @@ def test_transfer(subtensor, alice_wallet): 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" @@ -53,8 +49,6 @@ def test_transfer(subtensor, alice_wallet): 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): @@ -67,8 +61,6 @@ async def test_transfer_async(async_subtensor, alice_wallet): 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" @@ -104,12 +96,8 @@ async def test_transfer_async(async_subtensor, alice_wallet): f"Expected {balance_before - transfer_value - transfer_fee}, got {balance_after}" ) - logging.console.success("✅ Passed [blue]test_transfer[/blue]") - 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") @@ -153,14 +141,10 @@ def test_transfer_all(subtensor, alice_wallet): ) assert balance_after == Balance(0) - logging.console.success("✅ Test [green]test_transfer_all[/green] passed.") - @pytest.mark.asyncio 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) @@ -208,5 +192,3 @@ async def test_transfer_all_async(async_subtensor, alice_wallet): dummy_account_2.coldkeypub.ss58_address ) assert balance_after == Balance(0) - - logging.console.success("✅ Test [green]test_transfer_async[/green] passed.") diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index b988d68557..43af7a1532 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -6,8 +6,9 @@ import asyncio import functools import time +from symtable import Class from typing import Union, Optional, TYPE_CHECKING - +from dataclasses import dataclass from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -216,10 +217,7 @@ async def wait_interval( current_block = subtensor.chain.get_current_block() if last_reported is None or current_block - last_reported >= reporting_interval: last_reported = current_block - print( - f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" - ) - logging.info( + logging.console.info( f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" ) @@ -254,10 +252,7 @@ async def async_wait_interval( 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( - f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" - ) - logging.info( + logging.console.info( f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" ) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index 296f75fb0a..3ad0b896c9 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -209,7 +209,7 @@ async def _reader(self): if b"Starting validator loop." in line: logging.console.info("Validator started.") self.started.set() - elif b"Successfully set weights and Finalized." in line: + elif b"Success" in line: logging.console.info("Validator is setting weights.") self.set_weights.set() diff --git a/tests/pytest.ini b/tests/pytest.ini index 42fa0889f5..7da2f158de 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 +addopts = -s \ No newline at end of file diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 9fb5d19e41..bed6f28bb3 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -28,7 +28,7 @@ def mock_substrate(mocker): @pytest.fixture def subtensor(mock_substrate): - return bittensor.core.subtensor.Subtensor() + return bittensor.core.subtensor.Subtensor(_mock=True) @pytest.fixture diff --git a/tests/unit_tests/extrinsics/asyncex/test_children.py b/tests/unit_tests/extrinsics/asyncex/test_children.py index b89b4dc0ec..8ce1fe9563 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_children.py +++ b/tests/unit_tests/extrinsics/asyncex/test_children.py @@ -8,7 +8,7 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): """Test that set_children_extrinsic correctly constructs and submits the extrinsic.""" # Preps - hotkey = "fake hotkey" + hotkey_ss58 = "fake hotkey" netuid = 123 fake_children = [ ( @@ -17,8 +17,7 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): ), ] - substrate = subtensor.substrate.__aenter__.return_value - substrate.compose_call = mocker.AsyncMock() + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -29,13 +28,16 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): success, message = await children.set_children_extrinsic( subtensor=subtensor, wallet=fake_wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, children=fake_children, ) # Asserts - substrate.compose_call.assert_awaited_once_with( + assert success is True + assert "Success" in message + + mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", call_function="set_children", call_params={ @@ -51,18 +53,14 @@ 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, + call=mocked_compose_call.return_value, wallet=fake_wallet, period=None, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, - calling_function="set_children_extrinsic", ) - assert success is True - assert "Success" in message - @pytest.mark.asyncio async def test_root_set_pending_childkey_cooldown_extrinsic( @@ -72,8 +70,7 @@ async def test_root_set_pending_childkey_cooldown_extrinsic( # Preps cooldown = 100 - substrate = subtensor.substrate.__aenter__.return_value - substrate.compose_call = mocker.AsyncMock() + mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -88,15 +85,17 @@ async def test_root_set_pending_childkey_cooldown_extrinsic( ) # Asserts - substrate.compose_call.call_count == 2 + assert mocked_compose_call.call_count == 2 mocked_sign_and_send_extrinsic.assert_awaited_once_with( - call=substrate.compose_call.return_value, + call=mocked_compose_call.return_value, wallet=fake_wallet, period=None, + nonce_key="hotkey", + sign_with="coldkey", + use_nonce=False, raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, - calling_function="root_set_pending_childkey_cooldown_extrinsic", ) assert success is True assert "Success" in message diff --git a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py index 430c8b522f..92cc03ff18 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py +++ b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py @@ -44,10 +44,8 @@ async def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="add_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -90,10 +88,8 @@ async def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="modify_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -133,10 +129,8 @@ async def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="remove_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -177,6 +171,5 @@ async def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="toggle_user_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py b/tests/unit_tests/extrinsics/asyncex/test_mechanisms.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tests/unit_tests/extrinsics/asyncex/test_registration.py b/tests/unit_tests/extrinsics/asyncex/test_registration.py index 21f3c72338..047b653111 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_registration.py +++ b/tests/unit_tests/extrinsics/asyncex/test_registration.py @@ -61,7 +61,6 @@ async def test_register_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="register_extrinsic", ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" @@ -129,7 +128,6 @@ async def test_register_extrinsic_success_with_cuda(subtensor, fake_wallet, mock wait_for_finalization=True, period=None, raise_error=False, - calling_function="register_extrinsic", ) mocked_is_hotkey_registered.assert_called_once_with( netuid=1, hotkey_ss58="hotkey_ss58" @@ -204,7 +202,7 @@ async def test_register_extrinsic_subnet_not_exists(subtensor, fake_wallet, mock ) assert result == ExtrinsicResponse( False, - f"Subnet #{netuid} does not exist.", + f"Subnet {netuid} does not exist.", extrinsic_function="register_extrinsic", ) @@ -301,7 +299,6 @@ async def is_stale_side_effect(*_, **__): wait_for_finalization=True, period=None, raise_error=False, - calling_function="register_extrinsic", ) assert result[0] is False assert result[1] == "No more attempts." @@ -366,7 +363,6 @@ async def test_set_subnet_identity_extrinsic_is_success(subtensor, fake_wallet, wait_for_finalization=True, period=None, raise_error=False, - calling_function="set_subnet_identity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -435,7 +431,6 @@ async def test_set_subnet_identity_extrinsic_is_failed(subtensor, fake_wallet, m wait_for_finalization=True, period=None, raise_error=False, - calling_function="set_subnet_identity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/asyncex/test_root.py b/tests/unit_tests/extrinsics/asyncex/test_root.py index 329d051c15..cc0b7112aa 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_root.py +++ b/tests/unit_tests/extrinsics/asyncex/test_root.py @@ -43,9 +43,9 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): fake_uid = 123 mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, @@ -83,7 +83,7 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -93,6 +93,8 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): module="SubtensorModule", storage_function="Uids", params=[0, "fake_hotkey_address"], + block_hash=None, + reuse_block_hash=False, ) assert result.success is True assert result.message == "Success" @@ -104,6 +106,11 @@ async def test_root_register_extrinsic_insufficient_balance( fake_wallet, mocker, ): + mocked_unlock_key = mocker.patch.object( + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), + ) mocker.patch.object( subtensor, "get_hyperparameter", @@ -122,6 +129,7 @@ async def test_root_register_extrinsic_insufficient_balance( wait_for_finalization=True, ) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert result.success is False subtensor.get_balance.assert_called_once_with( @@ -146,9 +154,9 @@ async def test_root_register_extrinsic_unlock_failed(subtensor, fake_wallet, moc return_value=Balance(1), ) mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=False, message="Unlock failed"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=False, message="Unlocked"), ) # Call @@ -160,7 +168,7 @@ async def test_root_register_extrinsic_unlock_failed(subtensor, fake_wallet, moc ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert result.success is False @@ -183,9 +191,9 @@ async def test_root_register_extrinsic_already_registered( return_value=Balance(1), ) mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, @@ -202,7 +210,7 @@ async def test_root_register_extrinsic_already_registered( ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -228,9 +236,9 @@ async def test_root_register_extrinsic_transaction_failed( return_value=Balance(1), ) mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, @@ -253,7 +261,7 @@ async def test_root_register_extrinsic_transaction_failed( ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -279,9 +287,9 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc return_value=Balance(1), ) mocked_unlock_key = mocker.patch.object( - async_root, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_root.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_is_hotkey_registered = mocker.patch.object( subtensor, @@ -309,7 +317,7 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -319,5 +327,7 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc module="SubtensorModule", storage_function="Uids", params=[0, "fake_hotkey_address"], + block_hash=None, + reuse_block_hash=False, ) assert result.success is False diff --git a/tests/unit_tests/extrinsics/asyncex/test_start_call.py b/tests/unit_tests/extrinsics/asyncex/test_start_call.py index 4f9bdddbd9..23d9bf755c 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_start_call.py +++ b/tests/unit_tests/extrinsics/asyncex/test_start_call.py @@ -37,7 +37,6 @@ async def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wait_for_finalization=False, period=None, raise_error=False, - calling_function="start_call_extrinsic", ) assert success is True diff --git a/tests/unit_tests/extrinsics/asyncex/test_transfer.py b/tests/unit_tests/extrinsics/asyncex/test_transfer.py index df0a82a56d..4fe074e8b7 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_transfer.py +++ b/tests/unit_tests/extrinsics/asyncex/test_transfer.py @@ -19,9 +19,9 @@ async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): return_value=True, ) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -56,7 +56,7 @@ 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_unlock_key.assert_called_once_with(fake_wallet, False) assert mocked_get_chain_head.call_count == 2 mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, @@ -71,7 +71,6 @@ async def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="transfer_extrinsic", ) assert result.success is True @@ -92,9 +91,9 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( return_value=True, ) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -129,7 +128,7 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_chain_head.assert_called_once() mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, @@ -145,7 +144,6 @@ async def test_transfer_extrinsic_call_successful_with_failed_response( wait_for_finalization=True, period=None, raise_error=False, - calling_function="transfer_extrinsic", ) assert result.success is False @@ -164,9 +162,9 @@ async def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, m return_value=True, ) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -197,7 +195,7 @@ async def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, m # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_chain_head.assert_called_once() mocked_get_balance.assert_called_once() mocked_get_existential_deposit.assert_called_once_with( @@ -246,9 +244,9 @@ async def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocke fake_amount = Balance(15) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=False, message=""), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=False, message="Unlocked"), ) # Call @@ -264,7 +262,7 @@ async def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocke ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert result.success is False @@ -284,9 +282,9 @@ async def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( return_value=True, ) mocked_unlock_key = mocker.patch.object( - async_transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + async_transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -321,7 +319,7 @@ async def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_chain_head.assert_called_once() mocked_get_existential_deposit.assert_called_once_with( diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 2124f2377a..8b0032a79e 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -56,12 +56,10 @@ async def test_unstake_extrinsic(fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, raise_error=False, - calling_function="unstake_extrinsic", ) @@ -82,7 +80,7 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): result = await unstaking.unstake_all_extrinsic( subtensor=fake_subtensor, wallet=fake_wallet, - hotkey=hotkey, + hotkey_ss58=hotkey, netuid=fake_netuid, ) @@ -104,17 +102,15 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, raise_error=False, - calling_function="unstake_all_extrinsic", ) @pytest.mark.asyncio -async def test_unstake_multiple_extrinsic(fake_wallet, mocker): +async def test_unstake_multiple_extrinsic_some_unstake_is_happy(fake_wallet, mocker): """Verify that sync `unstake_multiple_extrinsic` method calls proper async method.""" # Preps fake_substrate = mocker.AsyncMock( @@ -122,13 +118,12 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): ) fake_subtensor = mocker.AsyncMock( **{ - "get_hotkey_owner.return_value": "hotkey_owner", - "get_stake_for_coldkey_and_hotkey.return_value": [Balance(10.0)], - "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), - "tx_rate_limit.return_value": 0, "substrate": fake_substrate, } ) + mocked_unstake_extrinsic = mocker.patch.object( + unstaking, "unstake_extrinsic", return_value=ExtrinsicResponse(True, "") + ) mocker.patch.object( unstaking, "get_old_stakes", return_value=[Balance(1.1), Balance(0.3)] ) @@ -140,7 +135,7 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): wait_for_finalization = True # Call - result = await unstaking.unstake_multiple_extrinsic( + response = await unstaking.unstake_multiple_extrinsic( subtensor=fake_subtensor, wallet=fake_wallet, hotkey_ss58s=hotkey_ss58s, @@ -151,37 +146,19 @@ async def test_unstake_multiple_extrinsic(fake_wallet, mocker): ) # Asserts - assert result.success is True - assert fake_subtensor.substrate.compose_call.call_count == 1 - assert fake_subtensor.sign_and_send_extrinsic.call_count == 1 - - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": "hotkey1", - "amount_unstaked": 1100000000, - "netuid": 1, - }, - ) - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": "hotkey1", - "amount_unstaked": 1100000000, - "netuid": 1, - }, - ) - fake_subtensor.sign_and_send_extrinsic.assert_awaited_with( - call=fake_subtensor.substrate.compose_call.return_value, + print(">>> result", response) + print(">>> result.success", response.success) + assert response.success is False + assert response.message == "Some unstake were successful." + assert len(response.data) == 2 + mocked_unstake_extrinsic.assert_awaited_once_with( + subtensor=fake_subtensor, wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - sign_with="coldkey", - nonce_key="coldkeypub", - use_nonce=True, + amount=Balance.from_tao(1.1), + hotkey_ss58=hotkey_ss58s[0], + netuid=fake_netuids[0], period=None, raise_error=False, - calling_function="unstake_multiple_extrinsic", + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) diff --git a/tests/unit_tests/extrinsics/test_children.py b/tests/unit_tests/extrinsics/test_children.py index 1b0710abb5..ca5b53b0d8 100644 --- a/tests/unit_tests/extrinsics/test_children.py +++ b/tests/unit_tests/extrinsics/test_children.py @@ -5,7 +5,7 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): """Test that set_children_extrinsic correctly constructs and submits the extrinsic.""" # Preps - hotkey = "fake hotkey" + hotkey_ss58 = "fake hotkey" netuid = 123 fake_children = [ ( @@ -25,7 +25,7 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): success, message = children.set_children_extrinsic( subtensor=subtensor, wallet=fake_wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, children=fake_children, ) @@ -53,7 +53,6 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): raise_error=False, wait_for_inclusion=True, wait_for_finalization=True, - calling_function="set_children_extrinsic", ) assert success is True @@ -84,11 +83,13 @@ 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, + nonce_key="hotkey", + sign_with="coldkey", + use_nonce=False, period=None, raise_error=False, wait_for_inclusion=True, wait_for_finalization=False, - calling_function="root_set_pending_childkey_cooldown_extrinsic", ) assert success is True assert "Success" in message diff --git a/tests/unit_tests/extrinsics/test_liquidity.py b/tests/unit_tests/extrinsics/test_liquidity.py index eda7580d34..26ab1686d1 100644 --- a/tests/unit_tests/extrinsics/test_liquidity.py +++ b/tests/unit_tests/extrinsics/test_liquidity.py @@ -42,10 +42,8 @@ def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="add_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -87,10 +85,8 @@ def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="modify_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -129,10 +125,8 @@ def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - use_nonce=True, period=None, raise_error=False, - calling_function="remove_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -172,6 +166,5 @@ def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="toggle_user_liquidity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index 286491bd55..d14685e1fa 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -45,34 +45,33 @@ def mock_new_wallet(mocker): @pytest.mark.parametrize( - "subnet_exists, neuron_is_null, cuda_available, expected_result, test_id, expected_message", + "subnet_exists, neuron_is_null, cuda_available, expected_result, expected_message", [ ( False, True, True, False, - "subnet-does-not-exist", - "Subnet #123 does not exist.", + "Subnet 123 does not exist.", ), - (True, False, True, True, "neuron-already-registered", "Already registered."), - (True, True, False, False, "cuda-unavailable", "CUDA not available."), + (True, False, True, True, "Already registered."), + (True, True, False, False, "CUDA not available."), ], + ids=["subnet-does-not-exist", "neuron-already-registered", "cuda-unavailable"], ) def test_register_extrinsic_without_pow( mock_subtensor, mock_wallet, + mocker, subnet_exists, neuron_is_null, cuda_available, expected_result, - test_id, - mocker, expected_message, ): # Arrange mocker.patch.object(mock_subtensor, "subnet_exists", return_value=subnet_exists) - mocker.patch.object( + fake_neuron = mocker.patch.object( mock_subtensor, "get_neuron_for_pubkey_and_subnet", return_value=mocker.MagicMock(is_null=neuron_is_null), @@ -101,9 +100,18 @@ def test_register_extrinsic_without_pow( ) # Assert - assert result == ExtrinsicResponse( - expected_result, expected_message, extrinsic_function="register_extrinsic" - ), f"Test failed for test_id: {test_id}" + data = ( + {"neuron": fake_neuron.return_value} + if fake_neuron.call_count > 0 and cuda_available + else None + ) + expected_result = ExtrinsicResponse( + expected_result, + expected_message, + extrinsic_function="register_extrinsic", + data=data, + ) + assert result == expected_result @pytest.mark.parametrize( @@ -216,7 +224,6 @@ 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.get_extrinsic_fee") mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic", @@ -291,7 +298,6 @@ def test_set_subnet_identity_extrinsic_is_success(mock_subtensor, mock_wallet, m wait_for_finalization=True, period=None, raise_error=False, - calling_function="set_subnet_identity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value @@ -357,7 +363,6 @@ def test_set_subnet_identity_extrinsic_is_failed(mock_subtensor, mock_wallet, mo wait_for_finalization=True, period=None, raise_error=False, - calling_function="set_subnet_identity_extrinsic", ) assert result == mocked_sign_and_send_extrinsic.return_value diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index 193be0f3e1..7d887964bf 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -21,12 +21,13 @@ def mock_wallet(mocker): @pytest.mark.parametrize( - "wait_for_inclusion, wait_for_finalization, hotkey_registered, registration_success, expected_result", + "wait_for_inclusion, wait_for_finalization, hotkey_registered, get_uid_for_hotkey_on_subnet, registration_success, expected_result", [ ( False, True, [True, None], + 0, True, True, ), # Already registered after attempt @@ -34,14 +35,16 @@ def mock_wallet(mocker): False, True, [False, 1], + 0, True, True, ), # Registration succeeds with user confirmation - (False, True, [False, None], False, False), # Registration fails + (False, True, [False, None], 0, False, False), # Registration fails ( False, True, [False, None], + None, True, False, ), # Registration succeeds but neuron not found @@ -59,14 +62,13 @@ def test_root_register_extrinsic( wait_for_inclusion, wait_for_finalization, hotkey_registered, + get_uid_for_hotkey_on_subnet, registration_success, expected_result, mocker, ): - # Arrange - mock_subtensor.is_hotkey_registered.return_value = hotkey_registered[0] - # Preps + mock_subtensor.is_hotkey_registered.return_value = hotkey_registered[0] mocked_sign_and_send_extrinsic = mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic", @@ -82,6 +84,11 @@ def test_root_register_extrinsic( "get_balance", return_value=Balance(1), ) + mocked_get_uid_for_hotkey_on_subnet = mocker.patch.object( + mock_subtensor, + "get_uid_for_hotkey_on_subnet", + return_value=get_uid_for_hotkey_on_subnet, + ) # Act result = root.root_register_extrinsic( diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 0af11ae913..619ca2e565 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -299,6 +299,7 @@ def test_serve_axon_extrinsic( axon=mock_axon, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + raise_error=True, ) else: result = serving.serve_axon_extrinsic( @@ -375,5 +376,4 @@ def test_publish_metadata( wait_for_finalization=wait_for_finalization, period=None, raise_error=False, - calling_function="publish_metadata_extrinsic", ) diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index f8e7dd6892..0ec04007e5 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -52,50 +52,30 @@ def test_add_stake_extrinsic(mocker): wait_for_inclusion=True, wait_for_finalization=True, nonce_key="coldkeypub", - sign_with="coldkey", use_nonce=True, period=None, raise_error=False, - calling_function="add_stake_extrinsic", ) -def test_add_stake_multiple_extrinsic(mocker): +def test_add_stake_multiple_extrinsic(subtensor, mocker, fake_wallet): """Verify that sync `add_stake_multiple_extrinsic` method calls proper async method.""" # Preps - fake_subtensor = mocker.Mock( - **{ - "get_balance.return_value": Balance(10.0), - "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), - "substrate.query_multi.return_value": [ - ( - mocker.Mock( - **{ - "params": ["hotkey1"], - }, - ), - 0, - ), - ( - mocker.Mock( - **{ - "params": ["hotkey2"], - }, - ), - 0, - ), - ], - "substrate.query.return_value": 0, - } + mocked_get_stake_for_coldkey = mocker.patch.object( + subtensor, "get_stake_for_coldkey", return_value=[Balance(1.1), Balance(0.3)] + ) + mocked_get_balance = mocker.patch.object( + subtensor, "get_balance", return_value=Balance.from_tao(10) ) mocker.patch.object( staking, "get_old_stakes", return_value=[Balance(1.1), Balance(0.3)] ) - fake_wallet_ = mocker.Mock( - **{ - "coldkeypub.ss58_address": "hotkey_owner", - } + mocked_add_stake_extrinsic = mocker.patch.object( + staking, + "add_stake_extrinsic", + return_value=ExtrinsicResponse(True, "Success"), ) + hotkey_ss58s = ["hotkey1", "hotkey2"] netuids = [1, 2] amounts = [Balance.from_tao(1.1), Balance.from_tao(2.2)] @@ -104,50 +84,22 @@ def test_add_stake_multiple_extrinsic(mocker): # Call result = staking.add_stake_multiple_extrinsic( - subtensor=fake_subtensor, - wallet=fake_wallet_, - hotkey_ss58s=hotkey_ss58s, + subtensor=subtensor, + wallet=fake_wallet, netuids=netuids, + hotkey_ss58s=hotkey_ss58s, amounts=amounts, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, + raise_error=True, ) # Asserts - assert result.success is True - assert fake_subtensor.substrate.compose_call.call_count == 2 - assert fake_subtensor.sign_and_send_extrinsic.call_count == 2 - - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={ - "hotkey": "hotkey2", - "amount_staked": 2199999333, - "netuid": 2, - }, - ) - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="add_stake", - call_params={ - "hotkey": "hotkey2", - "amount_staked": 2199999333, - "netuid": 2, - }, - ) - fake_subtensor.sign_and_send_extrinsic.assert_called_with( - call=fake_subtensor.substrate.compose_call.return_value, - wallet=fake_wallet_, - nonce_key="coldkeypub", - sign_with="coldkey", - use_nonce=True, - period=None, - raise_error=False, - wait_for_inclusion=True, - wait_for_finalization=True, - calling_function="add_stake_multiple_extrinsic", + mocked_get_stake_for_coldkey.assert_called_once_with( + coldkey_ss58=fake_wallet.coldkeypub.ss58_address, ) + assert result.success is True + assert mocked_add_stake_extrinsic.call_count == 2 @pytest.mark.parametrize( diff --git a/tests/unit_tests/extrinsics/test_start_call.py b/tests/unit_tests/extrinsics/test_start_call.py index dd00bf2028..7f85675620 100644 --- a/tests/unit_tests/extrinsics/test_start_call.py +++ b/tests/unit_tests/extrinsics/test_start_call.py @@ -35,7 +35,6 @@ def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wait_for_finalization=False, period=None, raise_error=False, - calling_function="start_call_extrinsic", ) assert success is True diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index b56c3dbc90..e8bce586ed 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -17,9 +17,9 @@ def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): return_value=True, ) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -54,7 +54,7 @@ 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_unlock_key.assert_called_once_with(fake_wallet, False) assert mocked_get_chain_head.call_count == 1 mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, @@ -69,7 +69,6 @@ def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): wait_for_finalization=True, period=None, raise_error=False, - calling_function="transfer_extrinsic", ) assert result.success is True @@ -89,9 +88,9 @@ def test_transfer_extrinsic_call_successful_with_failed_response( return_value=True, ) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -126,7 +125,7 @@ def test_transfer_extrinsic_call_successful_with_failed_response( # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_balance.assert_called_with( fake_wallet.coldkeypub.ss58_address, block=subtensor.substrate.get_block_number.return_value, @@ -141,7 +140,6 @@ def test_transfer_extrinsic_call_successful_with_failed_response( wait_for_finalization=True, period=None, raise_error=False, - calling_function="transfer_extrinsic", ) assert result.success is False @@ -159,9 +157,9 @@ def test_transfer_extrinsic_insufficient_balance(subtensor, fake_wallet, mocker) return_value=True, ) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -192,7 +190,7 @@ def test_transfer_extrinsic_insufficient_balance(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_unlock_key.assert_called_once_with(fake_wallet, False) mocked_get_balance.assert_called_once() mocked_get_existential_deposit.assert_called_once_with( block=subtensor.substrate.get_block_number.return_value @@ -207,6 +205,12 @@ def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mocker): fake_destination = "invalid_address" fake_amount = Balance(15) + mocked_unlock_key = mocker.patch.object( + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), + ) + mocked_is_valid_address = mocker.patch.object( transfer, "is_valid_bittensor_address_or_public_key", @@ -226,6 +230,7 @@ def test_transfer_extrinsic_invalid_destination(subtensor, fake_wallet, mocker): ) # Asserts + mocked_unlock_key.assert_called_once_with(fake_wallet, False) mocked_is_valid_address.assert_called_once_with(fake_destination) assert result.success is False @@ -238,9 +243,9 @@ def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocker): fake_amount = Balance(15) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=False, message=""), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=False, message="Unlocked"), ) # Call @@ -256,7 +261,7 @@ def test_transfer_extrinsic_unlock_key_false(subtensor, fake_wallet, mocker): ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert result.success is False @@ -275,9 +280,9 @@ def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( return_value=True, ) mocked_unlock_key = mocker.patch.object( - transfer, - "unlock_key", - return_value=mocker.Mock(success=True, message="Unlocked"), + transfer.ExtrinsicResponse, + "unlock_wallet", + return_value=ExtrinsicResponse(success=True, message="Unlocked"), ) mocked_get_chain_head = mocker.patch.object( subtensor.substrate, "get_chain_head", return_value="some_block_hash" @@ -312,7 +317,7 @@ def test_transfer_extrinsic_keep_alive_false_and_transfer_all_true( # Asserts mocked_is_valid_address.assert_called_once_with(fake_destination) - mocked_unlock_key.assert_called_once_with(fake_wallet) + mocked_unlock_key.assert_called_once_with(fake_wallet, False) assert mocked_compose_call.call_count == 0 assert mocked_sign_and_send_extrinsic.call_count == 0 assert result.success is False diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 5ed2da82fd..6c8320ff8d 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -52,12 +52,10 @@ def test_unstake_extrinsic(fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, raise_error=False, - calling_function="unstake_extrinsic", ) @@ -77,7 +75,7 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): result = unstaking.unstake_all_extrinsic( subtensor=fake_subtensor, wallet=fake_wallet, - hotkey=hotkey, + hotkey_ss58=hotkey, netuid=fake_netuid, ) @@ -99,32 +97,29 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, - sign_with="coldkey", nonce_key="coldkeypub", use_nonce=True, period=None, raise_error=False, - calling_function="unstake_all_extrinsic", ) -def test_unstake_multiple_extrinsic(fake_wallet, mocker): - """Verify that sync `unstake_multiple_extrinsic` method calls proper async method.""" +def test_unstake_multiple_extrinsic(subtensor, fake_wallet, mocker): + """Tests when out of 2 unstakes 1 is completed and 1 is not.""" # Preps - fake_substrate = mocker.Mock( - **{"get_payment_info.return_value": {"partial_fee": 10}} + mocked_balance = mocker.patch.object( + subtensor, "get_balance", return_value=Balance.from_tao(1.0) ) - fake_subtensor = mocker.Mock( - **{ - "get_hotkey_owner.return_value": "hotkey_owner", - "get_stake_for_coldkey_and_hotkey.return_value": [Balance(10.0)], - "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), - "tx_rate_limit.return_value": 0, - "substrate": fake_substrate, - } + mocked_get_stake_for_coldkey_and_hotkey = mocker.patch.object( + subtensor, "get_stake_for_coldkey_and_hotkey", return_value=[Balance(10.0)] + ) + mocked_unstake_extrinsic = mocker.patch.object( + unstaking, "unstake_extrinsic", return_value=ExtrinsicResponse(True, "") ) mocker.patch.object( - unstaking, "get_old_stakes", return_value=[Balance(1.1), Balance(0.3)] + unstaking, + "get_old_stakes", + return_value=[Balance.from_tao(10), Balance.from_tao(0.3)], ) fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58s = ["hotkey1", "hotkey2"] @@ -135,7 +130,7 @@ def test_unstake_multiple_extrinsic(fake_wallet, mocker): # Call result = unstaking.unstake_multiple_extrinsic( - subtensor=fake_subtensor, + subtensor=subtensor, wallet=fake_wallet, hotkey_ss58s=hotkey_ss58s, netuids=fake_netuids, @@ -145,37 +140,22 @@ def test_unstake_multiple_extrinsic(fake_wallet, mocker): ) # Asserts - assert result.success is True - assert fake_subtensor.substrate.compose_call.call_count == 1 - assert fake_subtensor.sign_and_send_extrinsic.call_count == 1 - - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": "hotkey1", - "amount_unstaked": 1100000000, - "netuid": 1, - }, + mocked_balance.assert_called_with( + address=fake_wallet.coldkeypub.ss58_address, ) - fake_subtensor.substrate.compose_call.assert_any_call( - call_module="SubtensorModule", - call_function="remove_stake", - call_params={ - "hotkey": "hotkey1", - "amount_unstaked": 1100000000, - "netuid": 1, - }, - ) - fake_subtensor.sign_and_send_extrinsic.assert_called_with( - call=fake_subtensor.substrate.compose_call.return_value, + + assert result.success is False + assert result.message == "Some unstake were successful." + assert len(result.data) == 2 + + mocked_unstake_extrinsic.assert_called_once_with( + subtensor=subtensor, wallet=fake_wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - sign_with="coldkey", - nonce_key="coldkeypub", - use_nonce=True, + netuid=1, + hotkey_ss58="hotkey1", + amount=Balance.from_tao(1.1), period=None, raise_error=False, - calling_function="unstake_multiple_extrinsic", + wait_for_inclusion=wait_for_inclusion, + wait_for_finalization=wait_for_finalization, ) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 92ffc815da..52fe385f47 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -1,6 +1,6 @@ import datetime import unittest.mock as mock -import numpy as np + import pytest from async_substrate_interface.types import ScaleObj from bittensor_wallet import Wallet @@ -16,7 +16,7 @@ SelectiveMetagraphIndex, ) from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import U64_MAX +from bittensor.utils import U64_MAX, get_function_name from bittensor.utils.balance import Balance from tests.helpers.helpers import assert_submit_signed_extrinsic @@ -213,40 +213,17 @@ async def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, m mock_substrate.submit_extrinsic.return_value = mocker.AsyncMock( is_success=mocker.AsyncMock(return_value=True)(), ) - mocker.patch.object( - subtensor, - "get_balance", - return_value=Balance(1), - ) - mocker.patch.object( - subtensor, - "is_hotkey_registered", - return_value=False, + mocked_root_register_extrinsic = mocker.patch.object( + async_subtensor, + "root_register_extrinsic", ) - success, _ = await subtensor.burned_register( - fake_wallet, + response = await subtensor.burned_register( + wallet=fake_wallet, netuid=0, ) - assert success is True - - subtensor.is_hotkey_registered.assert_called_once_with( - netuid=0, - hotkey_ss58=fake_wallet.hotkey.ss58_address, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="root_register", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) + assert response == mocked_root_register_extrinsic.return_value @pytest.mark.asyncio @@ -595,8 +572,8 @@ async def test_get_stake_for_coldkey_and_hotkey(subtensor, mocker): mocked_query_runtime_api.assert_has_calls( [ mock.call( - "StakeInfoRuntimeApi", - "get_stake_info_for_hotkey_coldkey_netuid", + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_hotkey_coldkey_netuid", params=["hotkey", "coldkey", netuid], block_hash=block_hash, ) @@ -1705,6 +1682,8 @@ async def test_sign_and_send_extrinsic_success_finalization( fake_extrinsic = mocker.Mock() fake_response = mocker.Mock() + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1727,6 +1706,9 @@ async def fake_is_success(): ) # Asserts + mocked_get_extrinsic_fee.assert_awaited_once_with( + subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + ) mocked_create_signed_extrinsic.assert_called_once_with( call=fake_call, keypair=fake_wallet.coldkey ) @@ -1736,6 +1718,11 @@ async def fake_is_success(): wait_for_finalization=True, ) assert result == (True, "Success") + assert result.extrinsic_function == get_function_name() + assert result.extrinsic == fake_extrinsic + assert result.extrinsic_fee == mocked_get_extrinsic_fee.return_value + assert result.error is None + assert result.data is None @pytest.mark.asyncio @@ -1747,6 +1734,9 @@ async def test_sign_and_send_extrinsic_error_finalization( fake_call = mocker.Mock() fake_extrinsic = mocker.Mock() fake_response = mocker.Mock() + fake_error = {"some error": "message"} + + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1762,7 +1752,7 @@ async def fake_is_success(): fake_response.is_success = fake_is_success() async def fake_error_message(): - return {"some error": "message"} + return fake_error fake_response.error_message = fake_error_message() @@ -1778,6 +1768,9 @@ async def fake_error_message(): ) # Asserts + mocked_get_extrinsic_fee.assert_awaited_once_with( + subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + ) mocked_create_signed_extrinsic.assert_called_once_with( call=fake_call, keypair=fake_wallet.coldkey ) @@ -1787,6 +1780,11 @@ async def fake_error_message(): wait_for_finalization=True, ) assert result == (False, mocked_format_error_message.return_value) + assert result.extrinsic_function == get_function_name() + assert result.extrinsic == fake_extrinsic + assert result.extrinsic_fee == mocked_get_extrinsic_fee.return_value + assert result.error is fake_error + assert result.data is None @pytest.mark.asyncio @@ -1798,6 +1796,8 @@ async def test_sign_and_send_extrinsic_success_without_inclusion_finalization( fake_call = mocker.Mock() fake_extrinsic = mocker.Mock() + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1813,6 +1813,9 @@ async def test_sign_and_send_extrinsic_success_without_inclusion_finalization( ) # Asserts + mocked_get_extrinsic_fee.assert_awaited_once_with( + subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + ) mocked_create_signed_extrinsic.assert_awaited_once() mocked_create_signed_extrinsic.assert_called_once_with( call=fake_call, keypair=fake_wallet.coldkey @@ -1824,6 +1827,11 @@ async def test_sign_and_send_extrinsic_success_without_inclusion_finalization( wait_for_finalization=False, ) assert result == (True, "Not waiting for finalization or inclusion.") + assert result.extrinsic_function == get_function_name() + assert result.extrinsic == fake_extrinsic + assert result.extrinsic_fee == mocked_get_extrinsic_fee.return_value + assert result.error is None + assert result.data is None @pytest.mark.asyncio @@ -1836,6 +1844,8 @@ async def test_sign_and_send_extrinsic_substrate_request_exception( fake_extrinsic = mocker.Mock() fake_exception = async_subtensor.SubstrateRequestException("Test Exception") + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1857,13 +1867,25 @@ async def test_sign_and_send_extrinsic_substrate_request_exception( ) # Asserts + mocked_get_extrinsic_fee.assert_awaited_once_with( + subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + ) assert result == (False, str(fake_exception)) + assert result.extrinsic_function == get_function_name() + assert result.extrinsic == fake_extrinsic + assert result.extrinsic_fee == mocked_get_extrinsic_fee.return_value + assert result.error == fake_exception + assert result.data is None @pytest.mark.asyncio async def test_sign_and_send_extrinsic_raises_error( mock_substrate, subtensor, fake_wallet, mocker ): + """Tests sign_and_send_extrinsic when an error is raised.""" + # Preps + mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mock_substrate.submit_extrinsic.return_value = mocker.AsyncMock( error_message=mocker.AsyncMock( return_value={ @@ -1873,6 +1895,7 @@ async def test_sign_and_send_extrinsic_raises_error( is_success=mocker.AsyncMock(return_value=False)(), ) + # Call and asserts with pytest.raises( async_subtensor.SubstrateRequestException, match="{'name': 'Exception'}", @@ -1882,6 +1905,7 @@ async def test_sign_and_send_extrinsic_raises_error( wallet=fake_wallet, raise_error=True, ) + mocked_get_extrinsic_fee.assert_awaited_once() @pytest.mark.asyncio @@ -1911,7 +1935,7 @@ async def test_get_children_success(subtensor, mocker): ] # Call - result = await subtensor.get_children(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_children(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -1939,7 +1963,7 @@ async def test_get_children_no_children(subtensor, mocker): subtensor.substrate.query = mocked_query # Call - result = await subtensor.get_children(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_children(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -1969,7 +1993,7 @@ async def test_get_children_substrate_request_exception(subtensor, mocker): ) # Call - result = await subtensor.get_children(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_children(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -2010,7 +2034,7 @@ async def test_get_parents_success(subtensor, mocker): ] # Call - result = await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -2038,7 +2062,7 @@ async def test_get_parents_no_parents(subtensor, mocker): subtensor.substrate.query = mocked_query # Call - result = await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + result = await subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -2064,7 +2088,7 @@ async def test_get_parents_substrate_request_exception(subtensor, mocker): # Call with pytest.raises(async_subtensor.SubstrateRequestException): - await subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + await subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) @pytest.mark.asyncio @@ -2662,8 +2686,8 @@ async def test_set_children(subtensor, fake_wallet, mocker): # Call result = await subtensor.set_children( - fake_wallet, - fake_wallet.hotkey.ss58_address, + wallet=fake_wallet, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, children=fake_children, ) @@ -2672,7 +2696,7 @@ async def test_set_children(subtensor, fake_wallet, mocker): mocked_set_children_extrinsic.assert_awaited_once_with( subtensor=subtensor, wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, children=fake_children, wait_for_finalization=True, @@ -2698,67 +2722,53 @@ async def test_set_delegate_take_equal(subtensor, fake_wallet, mocker): subtensor.substrate.submit_extrinsic.assert_not_called() +@pytest.mark.parametrize( + "take, delegate_take, extrinsic_call", + [ + (0.1, 0.1, None), + (0.2, 0.1, "increase"), + (0.1, 0.2, "decrease"), + ], + ids=[ + "already set", + "increase_take_extrinsic", + "decrease_take_extrinsic", + ], +) @pytest.mark.asyncio async def test_set_delegate_take_increase( - mock_substrate, subtensor, fake_wallet, mocker + subtensor, fake_wallet, mocker, take, delegate_take, extrinsic_call ): - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - is_success=mocker.AsyncMock(return_value=True)(), + mocked_get_delegate_take = mocker.patch.object( + subtensor, "get_delegate_take", return_value=delegate_take ) - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - - assert ( - await subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.2, - ) - ).success - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="increase_take", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "take": 13107, - }, - wait_for_inclusion=True, - wait_for_finalization=True, + mocked_set_take_extrinsic = mocker.patch.object( + async_subtensor, "set_take_extrinsic" ) - - -@pytest.mark.asyncio -async def test_set_delegate_take_decrease( - mock_substrate, subtensor, fake_wallet, mocker -): - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - is_success=mocker.AsyncMock(return_value=True)(), + already_set_result = ExtrinsicResponse( + True, + f"The take for {fake_wallet.hotkey.ss58_address} is already set to 0.1.", + extrinsic_function="set_delegate_take", ) - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - assert ( - await subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.1, - ) - ).success + expected_result = already_set_result + if extrinsic_call == "increase": + expected_result = mocked_set_take_extrinsic.return_value + elif extrinsic_call == "decrease": + expected_result = mocked_set_take_extrinsic.return_value - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="decrease_take", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "take": 6553, - }, - wait_for_inclusion=True, - wait_for_finalization=True, + # Call + result = await subtensor.set_delegate_take( + wallet=fake_wallet, + hotkey_ss58=fake_wallet.hotkey.ss58_address, + take=take, ) + # Assert + mocked_get_delegate_take.assert_awaited_once_with(fake_wallet.hotkey.ss58_address) + assert result == expected_result + @pytest.mark.asyncio async def test_get_all_subnets_info_success(mocker, subtensor): @@ -3268,14 +3278,14 @@ async def test_unstake_all(subtensor, fake_wallet, mocker): # Call result = await subtensor.unstake_all( wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, ) # Asserts fake_unstake_all_extrinsic.assert_awaited_once_with( subtensor=subtensor, wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, rate_tolerance=0.005, wait_for_inclusion=True, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 0665972c77..0d2f5830be 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -2100,8 +2100,8 @@ def test_get_stake_for_coldkey_and_hotkey(subtensor, mocker): mocked_query_runtime_api.assert_has_calls( [ mock.call( - "StakeInfoRuntimeApi", - "get_stake_info_for_hotkey_coldkey_netuid", + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_hotkey_coldkey_netuid", params=["hotkey", "coldkey", netuid], block=None, ) @@ -3497,7 +3497,7 @@ def test_get_parents_success(subtensor, mocker): ] # Call - result = subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + result = subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -3523,7 +3523,7 @@ def test_get_parents_no_parents(subtensor, mocker): subtensor.substrate.query = mocked_query # Call - result = subtensor.get_parents(hotkey=fake_hotkey, netuid=fake_netuid) + result = subtensor.get_parents(hotkey_ss58=fake_hotkey, netuid=fake_netuid) # Asserts mocked_query.assert_called_once_with( @@ -3561,7 +3561,7 @@ def test_set_children(subtensor, fake_wallet, mocker): mocked_set_children_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, children=fake_children, wait_for_finalization=True, @@ -3582,14 +3582,14 @@ def test_unstake_all(subtensor, fake_wallet, mocker): # Call result = subtensor.unstake_all( wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, ) # Asserts fake_unstake_all_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey_ss58=fake_wallet.hotkey.ss58_address, netuid=1, rate_tolerance=0.005, wait_for_inclusion=True, diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 7e90488d63..29133717de 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1,1446 +1,1416 @@ -import unittest.mock - -import async_substrate_interface.errors -import pytest - -from bittensor.core.chain_data.axon_info import AxonInfo -from bittensor.core.chain_data.chain_identity import ChainIdentity -from bittensor.core.chain_data.delegate_info import DelegatedInfo, DelegateInfo -from bittensor.core.chain_data.dynamic_info import DynamicInfo -from bittensor.core.chain_data.neuron_info import NeuronInfo -from bittensor.core.chain_data.neuron_info_lite import NeuronInfoLite -from bittensor.core.chain_data.prometheus_info import PrometheusInfo -from bittensor.core.chain_data.stake_info import StakeInfo -from bittensor.utils import U16_MAX, U64_MAX -from bittensor.utils.balance import Balance -from tests.helpers.helpers import assert_submit_signed_extrinsic - - -@pytest.fixture -def mock_delegate_info(): - return { - "delegate_ss58": tuple(bytearray(32)), - "total_stake": {}, - "nominators": [], - "owner_ss58": tuple(bytearray(32)), - "take": U16_MAX, - "validator_permits": [], - "registrations": [], - "return_per_1000": 2, - "total_daily_return": 3, - } - - -@pytest.fixture -def mock_dynamic_info(): - return { - "netuid": 0, - "owner_hotkey": tuple(bytearray(32)), - "owner_coldkey": tuple(bytearray(32)), - "subnet_name": (114, 111, 111, 116), - "token_symbol": (206, 164), - "tempo": 100, - "last_step": 4919910, - "blocks_since_last_step": 84234, - "emission": 0, - "alpha_in": 14723086336554, - "alpha_out": 6035890271491007, - "tao_in": 6035892206947246, - "alpha_out_emission": 0, - "alpha_in_emission": 0, - "tao_in_emission": 0, - "pending_alpha_emission": 0, - "pending_root_emission": 0, - "subnet_volume": 2240411565906691, - "network_registered_at": 0, - "subnet_identity": None, - "moving_price": {"bits": 0}, - } - - -@pytest.fixture -def mock_neuron_info(): - return { - "active": 0, - "axon_info": { - "ip_type": 4, - "ip": 2130706433, - "placeholder1": 0, - "placeholder2": 0, - "port": 8080, - "protocol": 0, - "version": 1, - }, - "bonds": [], - "coldkey": tuple(bytearray(32)), - "consensus": 0.0, - "dividends": 0.0, - "emission": 0.0, - "hotkey": tuple(bytearray(32)), - "incentive": 0.0, - "is_null": False, - "last_update": 0, - "netuid": 1, - "prometheus_info": { - "block": 0, - "ip_type": 0, - "ip": 0, - "port": 0, - "version": 1, - }, - "pruning_score": 0.0, - "rank": 0.0, - "stake_dict": {}, - "stake": [], - "total_stake": 1e12, - "trust": 0.0, - "uid": 1, - "validator_permit": True, - "validator_trust": 0.0, - "weights": [], - } - - -def test_all_subnets(mock_substrate, subtensor, mock_dynamic_info): - mock_substrate.runtime_call.return_value.decode.return_value = [ - mock_dynamic_info, - ] - - result = subtensor.all_subnets() - - assert result == [ - DynamicInfo( - netuid=0, - owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - subnet_name="root", - symbol="Τ", - tempo=100, - last_step=4919910, - blocks_since_last_step=84234, - emission=Balance(0), - alpha_in=Balance(14723086336554), - alpha_out=Balance(6035890271491007), - tao_in=Balance(6035892206947246), - price=Balance.from_tao(1), - k=88866962081017766138079430284, - is_dynamic=False, - 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), - network_registered_at=0, - subnet_volume=Balance(2240411565906691), - subnet_identity=None, - moving_price=0.0, - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "SubnetInfoRuntimeApi", - "get_all_dynamic_info", - block_hash=None, - ) - - -def test_bonds(mock_substrate, subtensor, mocker): - mock_substrate.query_map.return_value = [ - (0, mocker.Mock(value=[(1, 100), (2, 200)])), - (1, mocker.Mock(value=[(0, 150), (2, 250)])), - (2, mocker.Mock(value=None)), - ] - - result = subtensor.bonds(netuid=1) - - assert result == [ - (0, [(1, 100), (2, 200)]), - (1, [(0, 150), (2, 250)]), - ] - - mock_substrate.query_map.assert_called_once_with( - module="SubtensorModule", - storage_function="Bonds", - params=[1], - block_hash=None, - ) - - -def test_burned_register(mock_substrate, subtensor, fake_wallet, mocker): - mock_substrate.get_payment_info.return_value = {"partial_fee": 10} - mocker.patch.object( - subtensor, - "get_neuron_for_pubkey_and_subnet", - return_value=NeuronInfo.get_null_neuron(), - ) - mocker.patch.object(subtensor, "get_balance") - - success, _ = subtensor.burned_register( - fake_wallet, - netuid=1, - ) - - assert success is True - - subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( - fake_wallet.hotkey.ss58_address, - netuid=1, - block=mock_substrate.get_block_number.return_value, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="burned_register", - call_params={ - "netuid": 1, - "hotkey": fake_wallet.hotkey.ss58_address, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - -def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object( - subtensor, - "get_balance", - return_value=Balance(1), - ) - mocker.patch.object( - subtensor, - "is_hotkey_registered", - return_value=False, - ) - - success, _ = subtensor.burned_register( - fake_wallet, - netuid=0, - ) - - assert success is True - - subtensor.is_hotkey_registered.assert_called_once_with( - netuid=0, - hotkey_ss58=fake_wallet.hotkey.ss58_address, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="root_register", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - -def test_get_all_commitments(mock_substrate, subtensor): - mock_substrate.query_map.return_value = [ - ( - (tuple(bytearray(32)),), - { - "info": { - "fields": [ - ( - { - "Raw4": (tuple(b"Test"),), - }, - ), - ], - }, - }, - ), - ] - - result = subtensor.get_all_commitments(netuid=1) - - assert result == { - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": "Test", - } - - mock_substrate.query_map.assert_called_once_with( - module="Commitments", - storage_function="CommitmentOf", - params=[1], - block_hash=None, - ) - - -def test_get_balance(mock_substrate, subtensor): - mock_substrate.query.return_value = { - "data": { - "free": 123, - }, - } - - result = subtensor.get_balance( - "hotkey_ss58", - ) - - assert result == Balance(123) - - mock_substrate.query.assert_called_once_with( - module="System", - storage_function="Account", - params=["hotkey_ss58"], - block_hash=None, - ) - - -def test_get_balances(mock_substrate, subtensor, mocker): - create_storage_keys = [ - mocker.Mock(), - mocker.Mock(), - ] - - mock_substrate.create_storage_key.side_effect = create_storage_keys - mock_substrate.query_multi.return_value = [ - ( - mocker.Mock( - params=["hotkey1_ss58"], - ), - { - "data": { - "free": 1, - }, - }, - ), - ( - mocker.Mock( - params=["hotkey2_ss58"], - ), - { - "data": { - "free": 2, - }, - }, - ), - ] - - result = subtensor.get_balances( - "hotkey1_ss58", - "hotkey2_ss58", - ) - - assert result == { - "hotkey1_ss58": Balance(1), - "hotkey2_ss58": Balance(2), - } - - mock_substrate.query_multi.assert_called_once_with( - create_storage_keys, - block_hash=mock_substrate.get_chain_head.return_value, - ) - mock_substrate.create_storage_key.assert_has_calls( - [ - mocker.call( - "System", - "Account", - ["hotkey1_ss58"], - block_hash=mock_substrate.get_chain_head.return_value, - ), - mocker.call( - "System", - "Account", - ["hotkey2_ss58"], - block_hash=mock_substrate.get_chain_head.return_value, - ), - ] - ) - - -def test_get_block_hash_none(mock_substrate, subtensor): - result = subtensor.get_block_hash(block=None) - - assert result == mock_substrate.get_chain_head.return_value - - mock_substrate.get_chain_head.assert_called_once() - - -def test_get_children(mock_substrate, subtensor, fake_wallet): - mock_substrate.query.return_value.value = [ - ( - U64_MAX, - (tuple(bytearray(32)),), - ), - ] - - success, children, error = subtensor.get_children( - "hotkey_ss58", - netuid=1, - ) - - assert success is True - assert children == [ - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - ] - assert error == "" - - mock_substrate.query.assert_called_once_with( - module="SubtensorModule", - storage_function="ChildKeys", - params=["hotkey_ss58", 1], - block_hash=None, - ) - - -def test_get_children_pending(mock_substrate, subtensor): - mock_substrate.query.return_value.value = [ - [ - ( - U64_MAX, - (tuple(bytearray(32)),), - ), - ], - 123, - ] - - children, cooldown = subtensor.get_children_pending( - "hotkey_ss58", - netuid=1, - ) - - assert children == [ - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - ] - assert cooldown == 123 - - mock_substrate.query.assert_called_once_with( - module="SubtensorModule", - storage_function="PendingChildKeys", - params=[1, "hotkey_ss58"], - block_hash=None, - ) - - -def test_get_delegate_by_hotkey(mock_substrate, subtensor, mock_delegate_info): - mock_substrate.runtime_call.return_value.value = mock_delegate_info - - result = subtensor.get_delegate_by_hotkey( - "hotkey_ss58", - ) - - assert result == DelegateInfo( - hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - owner_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - take=1.0, - validator_permits=[], - registrations=[], - return_per_1000=Balance(2), - total_daily_return=Balance(3), - total_stake={}, - nominators={}, - ) - - mock_substrate.runtime_call.assert_called_once_with( - "DelegateInfoRuntimeApi", - "get_delegate", - ["hotkey_ss58"], - None, - ) - - -def test_get_delegate_identities(mock_substrate, subtensor, mocker): - mock_substrate.query_map.return_value = [ - ( - (tuple(bytearray(32)),), - mocker.Mock( - value={ - "additional": "Additional", - "description": "Description", - "discord": "", - "github_repo": "https://github.com/opentensor/bittensor", - "image": "", - "name": "Chain Delegate", - "url": "https://www.example.com", - }, - ), - ), - ] - - result = subtensor.get_delegate_identities() - - assert result == { - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": ChainIdentity( - additional="Additional", - description="Description", - discord="", - github="https://github.com/opentensor/bittensor", - image="", - name="Chain Delegate", - url="https://www.example.com", - ), - } - - -def test_get_delegated(mock_substrate, subtensor, mock_delegate_info): - mock_substrate.runtime_call.return_value.value = [ - ( - mock_delegate_info, - ( - 0, - 999, - ), - ), - ] - - result = subtensor.get_delegated( - "coldkey_ss58", - ) - - assert result == [ - DelegatedInfo( - hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - owner_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - take=1.0, - validator_permits=[], - registrations=[], - return_per_1000=Balance(2), - total_daily_return=Balance(3), - netuid=0, - stake=Balance(999), - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "DelegateInfoRuntimeApi", - "get_delegated", - ["coldkey_ss58"], - None, - ) - - -def test_get_neuron_certificate(mock_substrate, subtensor): - mock_substrate.query.return_value = { - "public_key": (tuple(b"CERTDATA"),), - "algorithm": 63, - } - - result = subtensor.get_neuron_certificate( - "hotkey_ss58", - netuid=1, - ) - - assert result == "?CERTDATA" - - mock_substrate.query.assert_called_once_with( - module="SubtensorModule", - storage_function="NeuronCertificates", - params=[1, "hotkey_ss58"], - block_hash=None, - ) - - -def test_get_stake_for_coldkey(mock_substrate, subtensor): - mock_substrate.runtime_call.return_value.value = [ - { - "coldkey": tuple(bytearray(32)), - "drain": 0, - "emission": 3, - "hotkey": tuple(bytearray(32)), - "is_registered": True, - "locked": 2, - "netuid": 1, - "stake": 999, - }, - # filter out (stake=0): - { - "coldkey": tuple(bytearray(32)), - "drain": 1000, - "emission": 1000, - "hotkey": tuple(bytearray(32)), - "is_registered": True, - "locked": 1000, - "netuid": 2, - "stake": 0, - }, - ] - - result = subtensor.get_stake_for_coldkey( - "coldkey_ss58", - ) - - assert result == [ - StakeInfo( - coldkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - drain=0, - emission=Balance(3), - hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - is_registered=True, - locked=Balance(2), - netuid=1, - stake=Balance(999), - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "StakeInfoRuntimeApi", - "get_stake_info_for_coldkey", - ["coldkey_ss58"], - None, - ) - - -def test_filter_netuids_by_registered_hotkeys( - mock_substrate, subtensor, fake_wallet, mocker -): - mock_substrate.query_map.return_value = mocker.MagicMock( - **{ - "__iter__.return_value": iter( - [ - ( - 2, - mocker.Mock( - value=1, - ), - ), - ( - 3, - mocker.Mock( - value=1, - ), - ), - ] - ), - }, - ) - - result = subtensor.filter_netuids_by_registered_hotkeys( - all_netuids=[0, 1, 2], - filter_for_netuids=[2], - all_hotkeys=[fake_wallet], - block=10, - ) - - assert result == [2] - - mock_substrate.get_block_hash.assert_called_once_with(10) - mock_substrate.query_map.assert_called_once_with( - module="SubtensorModule", - storage_function="IsNetworkMember", - params=[fake_wallet.hotkey.ss58_address], - block_hash=mock_substrate.get_block_hash.return_value, - ) - - -def test_last_drand_round(mock_substrate, subtensor): - mock_substrate.query.return_value.value = 123 - - result = subtensor.last_drand_round() - - assert result == 123 - - mock_substrate.query.assert_called_once_with( - module="Drand", - storage_function="LastStoredRound", - ) - - -@pytest.mark.parametrize( - "wait,resp_message", - ( - [True, "Success"], - [False, "Not waiting for finalization or inclusion."], - ), -) -def test_move_stake(mock_substrate, subtensor, fake_wallet, wait, resp_message): - success, message = subtensor.move_stake( - wallet=fake_wallet, - origin_hotkey_ss58="origin_hotkey", - origin_netuid=1, - destination_hotkey_ss58="destination_hotkey", - destination_netuid=2, - amount=Balance(1), - wait_for_finalization=wait, - wait_for_inclusion=wait, - ) - - assert success is True - assert message == resp_message - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": "origin_hotkey", - "origin_netuid": 1, - "destination_hotkey": "destination_hotkey", - "destination_netuid": 2, - "alpha_amount": 1, - }, - wait_for_finalization=wait, - wait_for_inclusion=wait, - ) - - -def test_move_stake_insufficient_stake(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_stake", return_value=Balance(0)) - - success, message = subtensor.move_stake( - fake_wallet, - origin_hotkey_ss58="origin_hotkey", - origin_netuid=1, - destination_hotkey_ss58="destination_hotkey", - destination_netuid=2, - amount=Balance(1), - ) - - assert success is False - assert "Insufficient stake in origin hotkey" in message - - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_move_stake_error(mock_substrate, subtensor, fake_wallet, mocker): - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - error_message="ERROR", - is_success=False, - ) - - success, message = subtensor.move_stake( - fake_wallet, - origin_hotkey_ss58="origin_hotkey", - origin_netuid=1, - destination_hotkey_ss58="destination_hotkey", - destination_netuid=2, - amount=Balance(1), - ) - - assert success is False - assert ( - message - == "Subtensor returned `UnknownError(UnknownType)` error. This means: `Unknown Description`." - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": "origin_hotkey", - "origin_netuid": 1, - "destination_hotkey": "destination_hotkey", - "destination_netuid": 2, - "alpha_amount": 1, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - -def test_move_stake_exception(mock_substrate, subtensor, fake_wallet): - mock_substrate.submit_extrinsic.side_effect = RuntimeError - - with pytest.raises(RuntimeError) as exc: - subtensor.move_stake( - fake_wallet, - origin_hotkey_ss58="origin_hotkey", - origin_netuid=1, - destination_hotkey_ss58="destination_hotkey", - destination_netuid=2, - amount=Balance(1), - raise_error=True, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": "origin_hotkey", - "origin_netuid": 1, - "destination_hotkey": "destination_hotkey", - "destination_netuid": 2, - "alpha_amount": 1, - }, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - -def test_neurons(mock_substrate, subtensor, mock_neuron_info): - mock_substrate.runtime_call.return_value.value = [ - mock_neuron_info, - ] - - neurons = subtensor.neurons(netuid=1) - - assert neurons == [ - NeuronInfo( - axon_info=AxonInfo( - version=1, - ip="127.0.0.1", - port=8080, - ip_type=4, - hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - active=0, - bonds=[], - coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - consensus=0.0, - dividends=0.0, - emission=0.0, - hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - incentive=0.0, - is_null=False, - last_update=0, - netuid=1, - prometheus_info=PrometheusInfo( - block=0, - version=1, - ip="0.0.0.0", - port=0, - ip_type=0, - ), - pruning_score=0.0, - rank=0.0, - stake_dict={}, - stake=Balance(0), - total_stake=Balance(0), - trust=0.0, - uid=1, - validator_permit=True, - validator_trust=0.0, - weights=[], - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "NeuronInfoRuntimeApi", - "get_neurons", - [1], - None, - ) - - -def test_neurons_lite(mock_substrate, subtensor, mock_neuron_info): - mock_substrate.runtime_call.return_value.value = [ - mock_neuron_info, - ] - - result = subtensor.neurons_lite(netuid=1) - - assert result == [ - NeuronInfoLite( - axon_info=AxonInfo( - version=1, - ip="127.0.0.1", - port=8080, - ip_type=4, - hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - active=0, - coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - consensus=0.0, - dividends=0.0, - emission=0.0, - hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - incentive=0.0, - is_null=False, - last_update=0, - netuid=1, - prometheus_info=PrometheusInfo( - block=0, - version=1, - ip="0.0.0.0", - port=0, - ip_type=0, - ), - pruning_score=0.0, - rank=0.0, - stake_dict={}, - stake=Balance(0), - total_stake=Balance(0), - trust=0.0, - uid=1, - validator_permit=True, - validator_trust=0.0, - ), - ] - - mock_substrate.runtime_call.assert_called_once_with( - "NeuronInfoRuntimeApi", - "get_neurons_lite", - [1], - None, - ) - - -def test_set_children(mock_substrate, subtensor, fake_wallet): - subtensor.set_children( - fake_wallet, - fake_wallet.hotkey.ss58_address, - netuid=1, - children=[ - ( - 1.0, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ), - ], - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="set_children", - call_params={ - "children": [ - ( - U64_MAX, - "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - ) - ], - "hotkey": fake_wallet.hotkey.ss58_address, - "netuid": 1, - }, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -def test_set_delegate_take_equal(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - - assert subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.18, - ).success - - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_set_delegate_take_increase(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - - assert subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.2, - ).success - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="increase_take", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "take": 13107, - }, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -def test_set_delegate_take_decrease(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) - - assert subtensor.set_delegate_take( - fake_wallet, - fake_wallet.hotkey.ss58_address, - 0.1, - ).success - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="decrease_take", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "take": 6553, - }, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -def test_subnet(mock_substrate, subtensor, mock_dynamic_info): - mock_substrate.runtime_call.return_value.decode.return_value = mock_dynamic_info - - result = subtensor.subnet(netuid=0) - - assert result == DynamicInfo( - netuid=0, - owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", - subnet_name="root", - symbol="Τ", - tempo=100, - last_step=4919910, - blocks_since_last_step=84234, - emission=Balance(0), - alpha_in=Balance(14723086336554), - alpha_out=Balance(6035890271491007), - tao_in=Balance(6035892206947246), - price=Balance.from_tao(1), - k=88866962081017766138079430284, - is_dynamic=False, - 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), - network_registered_at=0, - subnet_volume=Balance(2240411565906691), - subnet_identity=None, - moving_price=0.0, - ) - - mock_substrate.runtime_call.assert_called_once_with( - "SubnetInfoRuntimeApi", - "get_dynamic_info", - params=[0], - block_hash=None, - ) - - -def test_subtensor_contextmanager(mock_substrate, subtensor): - with subtensor: - pass - - mock_substrate.close.assert_called_once() - - -def test_swap_stake(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object(subtensor, "get_stake", return_value=Balance(1000)) - mocker.patch.object( - subtensor, - "get_hotkey_owner", - autospec=True, - return_value=fake_wallet.coldkeypub.ss58_address, - ) - - success, message = subtensor.swap_stake( - fake_wallet, - fake_wallet.hotkey.ss58_address, - origin_netuid=1, - destination_netuid=2, - amount=Balance(999), - ) - - assert success is True - assert message == "Success" - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="swap_stake", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "origin_netuid": 1, - "destination_netuid": 2, - "alpha_amount": 999, - }, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -@pytest.mark.parametrize( - "query,result", - ( - ( - None, - None, - ), - ( - { - "additional": "Additional", - "description": "Description", - "discord": "", - "github_repo": "https://github.com/opentensor/bittensor", - "image": "", - "name": "Chain Delegate", - "url": "https://www.example.com", - }, - ChainIdentity( - additional="Additional", - description="Description", - discord="", - github="https://github.com/opentensor/bittensor", - image="", - name="Chain Delegate", - url="https://www.example.com", - ), - ), - ), -) -def test_query_identity(mock_substrate, subtensor, query, result): - mock_substrate.query.return_value = query - - identity = subtensor.query_identity( - "coldkey_ss58", - ) - - assert identity == result - - mock_substrate.query.assert_called_once_with( - module="SubtensorModule", - storage_function="IdentitiesV2", - params=["coldkey_ss58"], - block_hash=None, - ) - - -def test_register(mock_substrate, subtensor, fake_wallet, mocker): - create_pow = mocker.patch( - "bittensor.core.extrinsics.registration.create_pow", - return_value=mocker.Mock( - **{ - "is_stale.return_value": False, - "seal": b"\1\2\3", - }, - ), - ) - mocker.patch.object( - subtensor, - "get_neuron_for_pubkey_and_subnet", - return_value=NeuronInfo.get_null_neuron(), - ) - - assert subtensor.register( - wallet=fake_wallet, - netuid=1, - ).success - - subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( - hotkey_ss58=fake_wallet.hotkey.ss58_address, - netuid=1, - block=mock_substrate.get_block_number.return_value, - ) - create_pow.assert_called_once_with( - subtensor=subtensor, - wallet=fake_wallet, - netuid=1, - output_in_place=True, - cuda=False, - num_processes=None, - update_interval=None, - log_verbose=False, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="register", - call_params={ - "block_number": create_pow.return_value.block_number, - "coldkey": fake_wallet.coldkeypub.ss58_address, - "hotkey": fake_wallet.hotkey.ss58_address, - "netuid": 1, - "nonce": create_pow.return_value.nonce, - "work": [1, 2, 3], - }, - ) - - -@pytest.mark.parametrize( - "success", - [ - True, - False, - ], -) -def test_register_subnet(mock_substrate, subtensor, fake_wallet, mocker, success): - mocker.patch.object(subtensor, "get_balance", return_value=Balance(100)) - mocker.patch.object(subtensor, "get_subnet_burn_cost", return_value=Balance(10)) - - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - is_success=success, - ) - - result = subtensor.register_subnet(fake_wallet) - - assert result.success is success - - assert_submit_signed_extrinsic( - substrate=mock_substrate, - keypair=fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="register_network", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "mechid": 1, - }, - ) - - -def test_register_subnet_insufficient_funds( - mock_substrate, subtensor, fake_wallet, mocker -): - 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) - - assert success is False - - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_root_register(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object( - subtensor, "get_balance", autospec=True, return_value=Balance(100) - ) - mocker.patch.object(subtensor, "get_hyperparameter", autospec=True, return_value=10) - mocker.patch.object( - subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=False - ) - - success, _ = subtensor.root_register(fake_wallet) - - assert success is True - - subtensor.get_balance.assert_called_once_with( - fake_wallet.coldkeypub.ss58_address, - block=mock_substrate.get_block_number.return_value, - ) - subtensor.get_hyperparameter.assert_called_once() - subtensor.is_hotkey_registered_on_subnet.assert_called_once_with( - fake_wallet.hotkey.ss58_address, - 0, - None, - ) - - assert_submit_signed_extrinsic( - substrate=mock_substrate, - keypair=fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="root_register", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - }, - ) - - -def test_root_register_is_already_registered( - mock_substrate, subtensor, fake_wallet, mocker -): - mocker.patch.object( - subtensor, "get_balance", autospec=True, return_value=Balance(100) - ) - mocker.patch.object(subtensor, "get_hyperparameter", autospec=True, return_value=10) - mocker.patch.object( - subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=True - ) - - success, _ = subtensor.root_register(fake_wallet) - - assert success is True - - subtensor.is_hotkey_registered_on_subnet.assert_called_once_with( - fake_wallet.hotkey.ss58_address, - 0, - None, - ) - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_sign_and_send_extrinsic(mock_substrate, subtensor, fake_wallet, mocker): - call = mocker.Mock() - - subtensor.sign_and_send_extrinsic( - call=call, - wallet=fake_wallet, - use_nonce=True, - period=10, - ) - - mock_substrate.get_account_next_index.assert_called_once_with( - fake_wallet.hotkey.ss58_address, - ) - mock_substrate.create_signed_extrinsic.assert_called_once_with( - call=call, - era={ - "period": 10, - }, - keypair=fake_wallet.coldkey, - nonce=mock_substrate.get_account_next_index.return_value, - ) - mock_substrate.submit_extrinsic.assert_called_once_with( - mock_substrate.create_signed_extrinsic.return_value, - wait_for_inclusion=True, - wait_for_finalization=False, - ) - - -def test_sign_and_send_extrinsic_raises_error( - mock_substrate, subtensor, fake_wallet, mocker -): - mock_substrate.submit_extrinsic.return_value = mocker.Mock( - error_message={ - "name": "Exception", - }, - is_success=False, - ) - - with pytest.raises( - async_substrate_interface.errors.SubstrateRequestException, - match="{'name': 'Exception'}", - ): - subtensor.sign_and_send_extrinsic( - call=mocker.Mock(), - wallet=fake_wallet, - raise_error=True, - ) - - -@pytest.mark.parametrize( - "wait,response_message", - ( - [True, "Success"], - [False, "Not waiting for finalization or inclusion."], - ), -) -def test_transfer_stake( - mock_substrate, subtensor, fake_wallet, mocker, wait, response_message -): - mocker.patch.object( - subtensor, - "get_hotkey_owner", - autospec=True, - return_value=fake_wallet.coldkeypub.ss58_address, - ) - - success, message = subtensor.transfer_stake( - fake_wallet, - "dest", - "hotkey_ss58", - origin_netuid=1, - destination_netuid=1, - amount=Balance(1), - wait_for_finalization=wait, - wait_for_inclusion=wait, - ) - - assert success is True - assert message == response_message - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, - call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": "dest", - "hotkey": "hotkey_ss58", - "origin_netuid": 1, - "destination_netuid": 1, - "alpha_amount": 1, - }, - wait_for_finalization=wait, - wait_for_inclusion=wait, - ) - - -def test_transfer_stake_insufficient_stake( - mock_substrate, subtensor, fake_wallet, mocker -): - mocker.patch.object( - subtensor, - "get_hotkey_owner", - autospec=True, - return_value=fake_wallet.coldkeypub.ss58_address, - ) - - with unittest.mock.patch.object( - subtensor, - "get_stake", - return_value=Balance(0), - ): - success, message = subtensor.transfer_stake( - fake_wallet, - "dest", - "hotkey_ss58", - origin_netuid=1, - destination_netuid=1, - amount=Balance(1), - ) - - assert success is False - assert "Insufficient stake in origin hotkey" in message - - mock_substrate.submit_extrinsic.assert_not_called() - - -def test_wait_for_block(mock_substrate, subtensor, mocker): - mock_subscription_handler = None - - def get_block_handler( - current_block_hash, - header_only, - subscription_handler, - ): - nonlocal mock_subscription_handler - mock_subscription_handler = mocker.Mock(wraps=subscription_handler) - - for block in range(1, 20): - if mock_subscription_handler( - { - "header": { - "number": block, - }, - } - ): - return - - assert False - - mock_substrate.get_block.side_effect = [ - { - "header": { - "number": 1, - }, - }, - ] - mock_substrate.get_block_handler.side_effect = get_block_handler - - subtensor.wait_for_block(block=9) - - assert mock_subscription_handler.call_count == 9 - - -def test_weights(mock_substrate, subtensor): - mock_substrate.query_map.return_value = [ - (1, unittest.mock.Mock(value=0.5)), - ] - - results = subtensor.weights( - netuid=1, - ) - - assert results == [ - ( - 1, - 0.5, - ), - ] - - mock_substrate.query_map.assert_called_once_with( - module="SubtensorModule", - storage_function="Weights", - params=[1], - block_hash=None, - ) +# import unittest.mock +# +# import async_substrate_interface.errors +# import pytest +# +# from bittensor.core.chain_data.axon_info import AxonInfo +# from bittensor.core.chain_data.chain_identity import ChainIdentity +# from bittensor.core.chain_data.delegate_info import DelegatedInfo, DelegateInfo +# from bittensor.core.chain_data.dynamic_info import DynamicInfo +# from bittensor.core.chain_data.neuron_info import NeuronInfo +# from bittensor.core.chain_data.neuron_info_lite import NeuronInfoLite +# from bittensor.core.chain_data.prometheus_info import PrometheusInfo +# from bittensor.core.chain_data.stake_info import StakeInfo +# 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 import subtensor as subtensor_module +# +# +# @pytest.fixture +# def mock_delegate_info(): +# return { +# "delegate_ss58": tuple(bytearray(32)), +# "total_stake": {}, +# "nominators": [], +# "owner_ss58": tuple(bytearray(32)), +# "take": U16_MAX, +# "validator_permits": [], +# "registrations": [], +# "return_per_1000": 2, +# "total_daily_return": 3, +# } +# +# +# @pytest.fixture +# def mock_dynamic_info(): +# return { +# "netuid": 0, +# "owner_hotkey": tuple(bytearray(32)), +# "owner_coldkey": tuple(bytearray(32)), +# "subnet_name": (114, 111, 111, 116), +# "token_symbol": (206, 164), +# "tempo": 100, +# "last_step": 4919910, +# "blocks_since_last_step": 84234, +# "emission": 0, +# "alpha_in": 14723086336554, +# "alpha_out": 6035890271491007, +# "tao_in": 6035892206947246, +# "alpha_out_emission": 0, +# "alpha_in_emission": 0, +# "tao_in_emission": 0, +# "pending_alpha_emission": 0, +# "pending_root_emission": 0, +# "subnet_volume": 2240411565906691, +# "network_registered_at": 0, +# "subnet_identity": None, +# "moving_price": {"bits": 0}, +# } +# +# +# @pytest.fixture +# def mock_neuron_info(): +# return { +# "active": 0, +# "axon_info": { +# "ip_type": 4, +# "ip": 2130706433, +# "placeholder1": 0, +# "placeholder2": 0, +# "port": 8080, +# "protocol": 0, +# "version": 1, +# }, +# "bonds": [], +# "coldkey": tuple(bytearray(32)), +# "consensus": 0.0, +# "dividends": 0.0, +# "emission": 0.0, +# "hotkey": tuple(bytearray(32)), +# "incentive": 0.0, +# "is_null": False, +# "last_update": 0, +# "netuid": 1, +# "prometheus_info": { +# "block": 0, +# "ip_type": 0, +# "ip": 0, +# "port": 0, +# "version": 1, +# }, +# "pruning_score": 0.0, +# "rank": 0.0, +# "stake_dict": {}, +# "stake": [], +# "total_stake": 1e12, +# "trust": 0.0, +# "uid": 1, +# "validator_permit": True, +# "validator_trust": 0.0, +# "weights": [], +# } +# +# +# def test_all_subnets(mock_substrate, subtensor, mock_dynamic_info): +# mock_substrate.runtime_call.return_value.decode.return_value = [ +# mock_dynamic_info, +# ] +# +# result = subtensor.all_subnets() +# +# assert result == [ +# DynamicInfo( +# netuid=0, +# owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# subnet_name="root", +# symbol="Τ", +# tempo=100, +# last_step=4919910, +# blocks_since_last_step=84234, +# emission=Balance(0), +# alpha_in=Balance(14723086336554), +# alpha_out=Balance(6035890271491007), +# tao_in=Balance(6035892206947246), +# price=Balance.from_tao(1), +# k=88866962081017766138079430284, +# is_dynamic=False, +# 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), +# network_registered_at=0, +# subnet_volume=Balance(2240411565906691), +# subnet_identity=None, +# moving_price=0.0, +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "SubnetInfoRuntimeApi", +# "get_all_dynamic_info", +# block_hash=None, +# ) +# +# +# def test_bonds(mock_substrate, subtensor, mocker): +# mock_substrate.query_map.return_value = [ +# (0, mocker.Mock(value=[(1, 100), (2, 200)])), +# (1, mocker.Mock(value=[(0, 150), (2, 250)])), +# (2, mocker.Mock(value=None)), +# ] +# +# result = subtensor.bonds(netuid=1) +# +# assert result == [ +# (0, [(1, 100), (2, 200)]), +# (1, [(0, 150), (2, 250)]), +# ] +# +# mock_substrate.query_map.assert_called_once_with( +# module="SubtensorModule", +# storage_function="Bonds", +# params=[1], +# block_hash=None, +# ) +# +# +# def test_burned_register(mock_substrate, subtensor, fake_wallet, mocker): +# mock_substrate.get_payment_info.return_value = {"partial_fee": 10} +# mocker.patch.object( +# subtensor, +# "get_neuron_for_pubkey_and_subnet", +# return_value=NeuronInfo.get_null_neuron(), +# ) +# mocker.patch.object(subtensor, "get_balance") +# +# success, _ = subtensor.burned_register( +# fake_wallet, +# netuid=1, +# ) +# +# assert success is True +# +# subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( +# fake_wallet.hotkey.ss58_address, +# netuid=1, +# block=mock_substrate.get_block_number.return_value, +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="burned_register", +# call_params={ +# "netuid": 1, +# "hotkey": fake_wallet.hotkey.ss58_address, +# }, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# +# +# def test_burned_register_on_root(mock_substrate, subtensor, fake_wallet, mocker): +# mocked_root_register_extrinsic = mocker.patch.object( +# subtensor_module, +# "root_register_extrinsic", +# ) +# result = subtensor.burned_register( +# wallet=fake_wallet, +# netuid=0, +# ) +# +# assert result == mocked_root_register_extrinsic.return_value +# +# +# def test_get_all_commitments(mock_substrate, subtensor): +# mock_substrate.query_map.return_value = [ +# ( +# (tuple(bytearray(32)),), +# { +# "info": { +# "fields": [ +# ( +# { +# "Raw4": (tuple(b"Test"),), +# }, +# ), +# ], +# }, +# }, +# ), +# ] +# +# result = subtensor.get_all_commitments(netuid=1) +# +# assert result == { +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": "Test", +# } +# +# mock_substrate.query_map.assert_called_once_with( +# module="Commitments", +# storage_function="CommitmentOf", +# params=[1], +# block_hash=None, +# ) +# +# +# def test_get_balance(mock_substrate, subtensor): +# mock_substrate.query.return_value = { +# "data": { +# "free": 123, +# }, +# } +# +# result = subtensor.get_balance( +# "hotkey_ss58", +# ) +# +# assert result == Balance(123) +# +# mock_substrate.query.assert_called_once_with( +# module="System", +# storage_function="Account", +# params=["hotkey_ss58"], +# block_hash=None, +# ) +# +# +# def test_get_balances(mock_substrate, subtensor, mocker): +# create_storage_keys = [ +# mocker.Mock(), +# mocker.Mock(), +# ] +# +# mock_substrate.create_storage_key.side_effect = create_storage_keys +# mock_substrate.query_multi.return_value = [ +# ( +# mocker.Mock( +# params=["hotkey1_ss58"], +# ), +# { +# "data": { +# "free": 1, +# }, +# }, +# ), +# ( +# mocker.Mock( +# params=["hotkey2_ss58"], +# ), +# { +# "data": { +# "free": 2, +# }, +# }, +# ), +# ] +# +# result = subtensor.get_balances( +# "hotkey1_ss58", +# "hotkey2_ss58", +# ) +# +# assert result == { +# "hotkey1_ss58": Balance(1), +# "hotkey2_ss58": Balance(2), +# } +# +# mock_substrate.query_multi.assert_called_once_with( +# create_storage_keys, +# block_hash=mock_substrate.get_chain_head.return_value, +# ) +# mock_substrate.create_storage_key.assert_has_calls( +# [ +# mocker.call( +# "System", +# "Account", +# ["hotkey1_ss58"], +# block_hash=mock_substrate.get_chain_head.return_value, +# ), +# mocker.call( +# "System", +# "Account", +# ["hotkey2_ss58"], +# block_hash=mock_substrate.get_chain_head.return_value, +# ), +# ] +# ) +# +# +# def test_get_block_hash_none(mock_substrate, subtensor): +# result = subtensor.get_block_hash(block=None) +# +# assert result == mock_substrate.get_chain_head.return_value +# +# mock_substrate.get_chain_head.assert_called_once() +# +# +# def test_get_children(mock_substrate, subtensor, fake_wallet): +# mock_substrate.query.return_value.value = [ +# ( +# U64_MAX, +# (tuple(bytearray(32)),), +# ), +# ] +# +# success, children, error = subtensor.get_children( +# "hotkey_ss58", +# netuid=1, +# ) +# +# assert success is True +# assert children == [ +# ( +# 1.0, +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# ] +# assert error == "" +# +# mock_substrate.query.assert_called_once_with( +# module="SubtensorModule", +# storage_function="ChildKeys", +# params=["hotkey_ss58", 1], +# block_hash=None, +# ) +# +# +# def test_get_children_pending(mock_substrate, subtensor): +# mock_substrate.query.return_value.value = [ +# [ +# ( +# U64_MAX, +# (tuple(bytearray(32)),), +# ), +# ], +# 123, +# ] +# +# children, cooldown = subtensor.get_children_pending( +# "hotkey_ss58", +# netuid=1, +# ) +# +# assert children == [ +# ( +# 1.0, +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# ] +# assert cooldown == 123 +# +# mock_substrate.query.assert_called_once_with( +# module="SubtensorModule", +# storage_function="PendingChildKeys", +# params=[1, "hotkey_ss58"], +# block_hash=None, +# ) +# +# +# def test_get_delegate_by_hotkey(mock_substrate, subtensor, mock_delegate_info): +# mock_substrate.runtime_call.return_value.value = mock_delegate_info +# +# result = subtensor.get_delegate_by_hotkey( +# "hotkey_ss58", +# ) +# +# assert result == DelegateInfo( +# hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# owner_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# take=1.0, +# validator_permits=[], +# registrations=[], +# return_per_1000=Balance(2), +# total_daily_return=Balance(3), +# total_stake={}, +# nominators={}, +# ) +# +# mock_substrate.runtime_call.assert_called_once_with( +# "DelegateInfoRuntimeApi", +# "get_delegate", +# ["hotkey_ss58"], +# None, +# ) +# +# +# def test_get_delegate_identities(mock_substrate, subtensor, mocker): +# mock_substrate.query_map.return_value = [ +# ( +# (tuple(bytearray(32)),), +# mocker.Mock( +# value={ +# "additional": "Additional", +# "description": "Description", +# "discord": "", +# "github_repo": "https://github.com/opentensor/bittensor", +# "image": "", +# "name": "Chain Delegate", +# "url": "https://www.example.com", +# }, +# ), +# ), +# ] +# +# result = subtensor.get_delegate_identities() +# +# assert result == { +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM": ChainIdentity( +# additional="Additional", +# description="Description", +# discord="", +# github="https://github.com/opentensor/bittensor", +# image="", +# name="Chain Delegate", +# url="https://www.example.com", +# ), +# } +# +# +# def test_get_delegated(mock_substrate, subtensor, mock_delegate_info): +# mock_substrate.runtime_call.return_value.value = [ +# ( +# mock_delegate_info, +# ( +# 0, +# 999, +# ), +# ), +# ] +# +# result = subtensor.get_delegated( +# "coldkey_ss58", +# ) +# +# assert result == [ +# DelegatedInfo( +# hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# owner_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# take=1.0, +# validator_permits=[], +# registrations=[], +# return_per_1000=Balance(2), +# total_daily_return=Balance(3), +# netuid=0, +# stake=Balance(999), +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "DelegateInfoRuntimeApi", +# "get_delegated", +# ["coldkey_ss58"], +# None, +# ) +# +# +# def test_get_neuron_certificate(mock_substrate, subtensor): +# mock_substrate.query.return_value = { +# "public_key": (tuple(b"CERTDATA"),), +# "algorithm": 63, +# } +# +# result = subtensor.get_neuron_certificate( +# "hotkey_ss58", +# netuid=1, +# ) +# +# assert result == "?CERTDATA" +# +# mock_substrate.query.assert_called_once_with( +# module="SubtensorModule", +# storage_function="NeuronCertificates", +# params=[1, "hotkey_ss58"], +# block_hash=None, +# ) +# +# +# def test_get_stake_for_coldkey(mock_substrate, subtensor): +# mock_substrate.runtime_call.return_value.value = [ +# { +# "coldkey": tuple(bytearray(32)), +# "drain": 0, +# "emission": 3, +# "hotkey": tuple(bytearray(32)), +# "is_registered": True, +# "locked": 2, +# "netuid": 1, +# "stake": 999, +# }, +# # filter out (stake=0): +# { +# "coldkey": tuple(bytearray(32)), +# "drain": 1000, +# "emission": 1000, +# "hotkey": tuple(bytearray(32)), +# "is_registered": True, +# "locked": 1000, +# "netuid": 2, +# "stake": 0, +# }, +# ] +# +# result = subtensor.get_stake_for_coldkey( +# "coldkey_ss58", +# ) +# +# assert result == [ +# StakeInfo( +# coldkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# drain=0, +# emission=Balance(3), +# hotkey_ss58="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# is_registered=True, +# locked=Balance(2), +# netuid=1, +# stake=Balance(999), +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "StakeInfoRuntimeApi", +# "get_stake_info_for_coldkey", +# ["coldkey_ss58"], +# None, +# ) +# +# +# def test_filter_netuids_by_registered_hotkeys( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# mock_substrate.query_map.return_value = mocker.MagicMock( +# **{ +# "__iter__.return_value": iter( +# [ +# ( +# 2, +# mocker.Mock( +# value=1, +# ), +# ), +# ( +# 3, +# mocker.Mock( +# value=1, +# ), +# ), +# ] +# ), +# }, +# ) +# +# result = subtensor.filter_netuids_by_registered_hotkeys( +# all_netuids=[0, 1, 2], +# filter_for_netuids=[2], +# all_hotkeys=[fake_wallet], +# block=10, +# ) +# +# assert result == [2] +# +# mock_substrate.get_block_hash.assert_called_once_with(10) +# mock_substrate.query_map.assert_called_once_with( +# module="SubtensorModule", +# storage_function="IsNetworkMember", +# params=[fake_wallet.hotkey.ss58_address], +# block_hash=mock_substrate.get_block_hash.return_value, +# ) +# +# +# def test_last_drand_round(mock_substrate, subtensor): +# mock_substrate.query.return_value.value = 123 +# +# result = subtensor.last_drand_round() +# +# assert result == 123 +# +# mock_substrate.query.assert_called_once_with( +# module="Drand", +# storage_function="LastStoredRound", +# ) +# +# +# @pytest.mark.parametrize( +# "wait,resp_message", +# ( +# [True, "Success"], +# [False, "Not waiting for finalization or inclusion."], +# ), +# ) +# def test_move_stake(mock_substrate, subtensor, fake_wallet, wait, resp_message): +# success, message = subtensor.move_stake( +# wallet=fake_wallet, +# origin_hotkey_ss58="origin_hotkey", +# origin_netuid=1, +# destination_hotkey_ss58="destination_hotkey", +# destination_netuid=2, +# amount=Balance(1), +# wait_for_finalization=wait, +# wait_for_inclusion=wait, +# ) +# +# assert success is True +# assert message == resp_message +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="move_stake", +# call_params={ +# "origin_hotkey": "origin_hotkey", +# "origin_netuid": 1, +# "destination_hotkey": "destination_hotkey", +# "destination_netuid": 2, +# "alpha_amount": 1, +# }, +# wait_for_finalization=wait, +# wait_for_inclusion=wait, +# ) +# +# +# def test_move_stake_insufficient_stake(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_stake", return_value=Balance(0)) +# +# success, message = subtensor.move_stake( +# fake_wallet, +# origin_hotkey_ss58="origin_hotkey", +# origin_netuid=1, +# destination_hotkey_ss58="destination_hotkey", +# destination_netuid=2, +# amount=Balance(1), +# ) +# +# assert success is False +# assert "Insufficient stake in origin hotkey" in message +# +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_move_stake_error(mock_substrate, subtensor, fake_wallet, mocker): +# mock_substrate.submit_extrinsic.return_value = mocker.Mock( +# error_message="ERROR", +# is_success=False, +# ) +# +# success, message = subtensor.move_stake( +# fake_wallet, +# origin_hotkey_ss58="origin_hotkey", +# origin_netuid=1, +# destination_hotkey_ss58="destination_hotkey", +# destination_netuid=2, +# amount=Balance(1), +# ) +# +# assert success is False +# assert ( +# message +# == "Subtensor returned `UnknownError(UnknownType)` error. This means: `Unknown Description`." +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="move_stake", +# call_params={ +# "origin_hotkey": "origin_hotkey", +# "origin_netuid": 1, +# "destination_hotkey": "destination_hotkey", +# "destination_netuid": 2, +# "alpha_amount": 1, +# }, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# +# +# def test_move_stake_exception(mock_substrate, subtensor, fake_wallet): +# mock_substrate.submit_extrinsic.side_effect = RuntimeError +# +# with pytest.raises(RuntimeError) as exc: +# subtensor.move_stake( +# fake_wallet, +# origin_hotkey_ss58="origin_hotkey", +# origin_netuid=1, +# destination_hotkey_ss58="destination_hotkey", +# destination_netuid=2, +# amount=Balance(1), +# raise_error=True, +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="move_stake", +# call_params={ +# "origin_hotkey": "origin_hotkey", +# "origin_netuid": 1, +# "destination_hotkey": "destination_hotkey", +# "destination_netuid": 2, +# "alpha_amount": 1, +# }, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# +# +# def test_neurons(mock_substrate, subtensor, mock_neuron_info): +# mock_substrate.runtime_call.return_value.value = [ +# mock_neuron_info, +# ] +# +# neurons = subtensor.neurons(netuid=1) +# +# assert neurons == [ +# NeuronInfo( +# axon_info=AxonInfo( +# version=1, +# ip="127.0.0.1", +# port=8080, +# ip_type=4, +# hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# active=0, +# bonds=[], +# coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# consensus=0.0, +# dividends=0.0, +# emission=0.0, +# hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# incentive=0.0, +# is_null=False, +# last_update=0, +# netuid=1, +# prometheus_info=PrometheusInfo( +# block=0, +# version=1, +# ip="0.0.0.0", +# port=0, +# ip_type=0, +# ), +# pruning_score=0.0, +# rank=0.0, +# stake_dict={}, +# stake=Balance(0), +# total_stake=Balance(0), +# trust=0.0, +# uid=1, +# validator_permit=True, +# validator_trust=0.0, +# weights=[], +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "NeuronInfoRuntimeApi", +# "get_neurons", +# [1], +# None, +# ) +# +# +# def test_neurons_lite(mock_substrate, subtensor, mock_neuron_info): +# mock_substrate.runtime_call.return_value.value = [ +# mock_neuron_info, +# ] +# +# result = subtensor.neurons_lite(netuid=1) +# +# assert result == [ +# NeuronInfoLite( +# axon_info=AxonInfo( +# version=1, +# ip="127.0.0.1", +# port=8080, +# ip_type=4, +# hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# active=0, +# coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# consensus=0.0, +# dividends=0.0, +# emission=0.0, +# hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# incentive=0.0, +# is_null=False, +# last_update=0, +# netuid=1, +# prometheus_info=PrometheusInfo( +# block=0, +# version=1, +# ip="0.0.0.0", +# port=0, +# ip_type=0, +# ), +# pruning_score=0.0, +# rank=0.0, +# stake_dict={}, +# stake=Balance(0), +# total_stake=Balance(0), +# trust=0.0, +# uid=1, +# validator_permit=True, +# validator_trust=0.0, +# ), +# ] +# +# mock_substrate.runtime_call.assert_called_once_with( +# "NeuronInfoRuntimeApi", +# "get_neurons_lite", +# [1], +# None, +# ) +# +# +# def test_set_children(mock_substrate, subtensor, fake_wallet): +# subtensor.set_children( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# netuid=1, +# children=[ +# ( +# 1.0, +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ), +# ], +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="set_children", +# call_params={ +# "children": [ +# ( +# U64_MAX, +# "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# ) +# ], +# "hotkey": fake_wallet.hotkey.ss58_address, +# "netuid": 1, +# }, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# def test_set_delegate_take_equal(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) +# +# assert subtensor.set_delegate_take( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# 0.18, +# ).success +# +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_set_delegate_take_increase(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) +# +# assert subtensor.set_delegate_take( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# 0.2, +# ).success +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="increase_take", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# "take": 13107, +# }, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# def test_set_delegate_take_decrease(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_delegate_take", return_value=0.18) +# +# assert subtensor.set_delegate_take( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# 0.1, +# ).success +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="decrease_take", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# "take": 6553, +# }, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# def test_subnet(mock_substrate, subtensor, mock_dynamic_info): +# mock_substrate.runtime_call.return_value.decode.return_value = mock_dynamic_info +# +# result = subtensor.subnet(netuid=0) +# +# assert result == DynamicInfo( +# netuid=0, +# owner_hotkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# owner_coldkey="5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", +# subnet_name="root", +# symbol="Τ", +# tempo=100, +# last_step=4919910, +# blocks_since_last_step=84234, +# emission=Balance(0), +# alpha_in=Balance(14723086336554), +# alpha_out=Balance(6035890271491007), +# tao_in=Balance(6035892206947246), +# price=Balance.from_tao(1), +# k=88866962081017766138079430284, +# is_dynamic=False, +# 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), +# network_registered_at=0, +# subnet_volume=Balance(2240411565906691), +# subnet_identity=None, +# moving_price=0.0, +# ) +# +# mock_substrate.runtime_call.assert_called_once_with( +# "SubnetInfoRuntimeApi", +# "get_dynamic_info", +# params=[0], +# block_hash=None, +# ) +# +# +# def test_subtensor_contextmanager(mock_substrate, subtensor): +# with subtensor: +# pass +# +# mock_substrate.close.assert_called_once() +# +# +# def test_swap_stake(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object(subtensor, "get_stake", return_value=Balance(1000)) +# mocker.patch.object( +# subtensor, +# "get_hotkey_owner", +# autospec=True, +# return_value=fake_wallet.coldkeypub.ss58_address, +# ) +# +# success, message = subtensor.swap_stake( +# fake_wallet, +# fake_wallet.hotkey.ss58_address, +# origin_netuid=1, +# destination_netuid=2, +# amount=Balance(999), +# ) +# +# assert success is True +# assert message == "Success" +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="swap_stake", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# "origin_netuid": 1, +# "destination_netuid": 2, +# "alpha_amount": 999, +# }, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# @pytest.mark.parametrize( +# "query,result", +# ( +# ( +# None, +# None, +# ), +# ( +# { +# "additional": "Additional", +# "description": "Description", +# "discord": "", +# "github_repo": "https://github.com/opentensor/bittensor", +# "image": "", +# "name": "Chain Delegate", +# "url": "https://www.example.com", +# }, +# ChainIdentity( +# additional="Additional", +# description="Description", +# discord="", +# github="https://github.com/opentensor/bittensor", +# image="", +# name="Chain Delegate", +# url="https://www.example.com", +# ), +# ), +# ), +# ) +# def test_query_identity(mock_substrate, subtensor, query, result): +# mock_substrate.query.return_value = query +# +# identity = subtensor.query_identity( +# "coldkey_ss58", +# ) +# +# assert identity == result +# +# mock_substrate.query.assert_called_once_with( +# module="SubtensorModule", +# storage_function="IdentitiesV2", +# params=["coldkey_ss58"], +# block_hash=None, +# ) +# +# +# def \ +# test_register(mock_substrate, subtensor, fake_wallet, mocker): +# create_pow = mocker.patch( +# "bittensor.core.extrinsics.registration.create_pow", +# return_value=mocker.Mock( +# **{ +# "is_stale.return_value": False, +# "seal": b"\1\2\3", +# }, +# ), +# ) +# mocker.patch.object( +# subtensor, +# "get_neuron_for _pubkey_and_subnet", +# return_value=NeuronInfo.get_null_neuron(), +# ) +# +# assert subtensor.register( +# wallet=fake_wallet, +# netuid=1, +# ).success +# +# subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( +# hotkey_ss58=fake_wallet.hotkey.ss58_address, +# netuid=1, +# block=mock_substrate.get_block_number.return_value, +# ) +# create_pow.assert_called_once_with( +# subtensor=subtensor, +# wallet=fake_wallet, +# netuid=1, +# output_in_place=True, +# cuda=False, +# num_processes=None, +# update_interval=None, +# log_verbose=False, +# ) +# +# assert_submit_signed_extrinsic( +# mock_substrate, +# fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="register", +# call_params={ +# "block_number": create_pow.return_value.block_number, +# "coldkey": fake_wallet.coldkeypub.ss58_address, +# "hotkey": fake_wallet.hotkey.ss58_address, +# "netuid": 1, +# "nonce": create_pow.return_value.nonce, +# "work": [1, 2, 3], +# }, +# ) +# +# +# @pytest.mark.parametrize( +# "success", +# [ +# True, +# False, +# ], +# ) +# def test_register_subnet(mock_substrate, subtensor, fake_wallet, mocker, success): +# mocker.patch.object(subtensor, "get_balance", return_value=Balance(100)) +# mocker.patch.object(subtensor, "get_subnet_burn_cost", return_value=Balance(10)) +# +# mock_substrate.submit_extrinsic.return_value = mocker.Mock( +# is_success=success, +# ) +# +# result = subtensor.register_subnet(fake_wallet) +# +# assert result.success is success +# +# assert_submit_signed_extrinsic( +# substrate=mock_substrate, +# keypair=fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="register_network", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# }, +# ) +# +# +# def test_register_subnet_insufficient_funds( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# 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) +# +# assert success is False +# +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_root_register(mock_substrate, subtensor, fake_wallet, mocker): +# mocker.patch.object( +# subtensor, "get_balance", autospec=True, return_value=Balance(100) +# ) +# mocker.patch.object(subtensor, "get_hyperparameter", autospec=True, return_value=10) +# mocker.patch.object( +# subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=False +# ) +# +# success, _ = subtensor.root_register(fake_wallet) +# +# assert success is True +# +# subtensor.get_balance.assert_called_once_with( +# fake_wallet.coldkeypub.ss58_address, +# block=mock_substrate.get_block_number.return_value, +# ) +# subtensor.get_hyperparameter.assert_called_once() +# subtensor.is_hotkey_registered_on_subnet.assert_called_once_with( +# fake_wallet.hotkey.ss58_address, +# 0, +# None, +# ) +# +# assert_submit_signed_extrinsic( +# substrate=mock_substrate, +# keypair=fake_wallet.coldkey, +# call_module="SubtensorModule", +# call_function="root_register", +# call_params={ +# "hotkey": fake_wallet.hotkey.ss58_address, +# }, +# ) +# +# +# def test_root_register_is_already_registered( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# mocker.patch.object( +# subtensor, "get_balance", autospec=True, return_value=Balance(100) +# ) +# mocker.patch.object(subtensor, "get_hyperparameter", autospec=True, return_value=10) +# mocker.patch.object( +# subtensor, "is_hotkey_registered_on_subnet", autospec=True, return_value=True +# ) +# +# success, _ = subtensor.root_register(fake_wallet) +# +# assert success is True +# +# subtensor.is_hotkey_registered_on_subnet.assert_called_once_with( +# fake_wallet.hotkey.ss58_address, +# 0, +# None, +# ) +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_sign_and_send_extrinsic(mock_substrate, subtensor, fake_wallet, mocker): +# call = mocker.Mock() +# +# subtensor.sign_and_send_extrinsic( +# call=call, +# wallet=fake_wallet, +# use_nonce=True, +# period=10, +# ) +# +# mock_substrate.get_account_next_index.assert_called_once_with( +# fake_wallet.hotkey.ss58_address, +# ) +# mock_substrate.create_signed_extrinsic.assert_called_once_with( +# call=call, +# era={ +# "period": 10, +# }, +# keypair=fake_wallet.coldkey, +# nonce=mock_substrate.get_account_next_index.return_value, +# ) +# mock_substrate.submit_extrinsic.assert_called_once_with( +# mock_substrate.create_signed_extrinsic.return_value, +# wait_for_inclusion=True, +# wait_for_finalization=False, +# ) +# +# +# def test_sign_and_send_extrinsic_raises_error( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# mock_substrate.submit_extrinsic.return_value = mocker.Mock( +# error_message={ +# "name": "Exception", +# }, +# is_success=False, +# ) +# +# with pytest.raises( +# async_substrate_interface.errors.SubstrateRequestException, +# match="{'name': 'Exception'}", +# ): +# subtensor.sign_and_send_extrinsic( +# call=mocker.Mock(), +# wallet=fake_wallet, +# raise_error=True, +# ) +# +# +# def test_transfer_stake(subtensor, mocker): +# # Preps +# wallet = mocker.Mock() +# destination_coldkey_ss58 = mocker.Mock() +# hotkey_ss58=mocker.Mock() +# origin_netuid=mocker.Mock() +# destination_netuid=mocker.Mock() +# amount = mocker.Mock() +# +# mocked_transfer_stake_extrinsic = mocker.patch.object( +# subtensor_module, "transfer_stake_extrinsic" +# ) +# +# # Call +# response = subtensor.transfer_stake( +# wallet=wallet, +# destination_coldkey_ss58=destination_coldkey_ss58, +# hotkey_ss58=hotkey_ss58, +# origin_netuid=origin_netuid, +# destination_netuid=destination_netuid, +# amount=amount, +# ) +# +# # Asserts +# assert response == mocked_transfer_stake_extrinsic.return_value +# +# mocked_transfer_stake_extrinsic.assert_called_once_with( +# subtensor=subtensor, +# wallet=wallet, +# destination_coldkey_ss58=destination_coldkey_ss58, +# hotkey_ss58=hotkey_ss58, +# origin_netuid=origin_netuid, +# destination_netuid=destination_netuid, +# amount=amount, +# period=None, +# raise_error=False, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# ) +# +# +# def test_transfer_stake_insufficient_stake( +# mock_substrate, subtensor, fake_wallet, mocker +# ): +# mocker.patch.object( +# subtensor, +# "get_hotkey_owner", +# autospec=True, +# return_value=fake_wallet.coldkeypub.ss58_address, +# ) +# +# with unittest.mock.patch.object( +# subtensor, +# "get_stake", +# return_value=Balance(0), +# ): +# success, message = subtensor.transfer_stake( +# fake_wallet, +# "dest", +# "hotkey_ss58", +# origin_netuid=1, +# destination_netuid=1, +# amount=Balance(1), +# ) +# +# assert success is False +# assert "Insufficient stake in origin hotkey" in message +# +# mock_substrate.submit_extrinsic.assert_not_called() +# +# +# def test_wait_for_block(mock_substrate, subtensor, mocker): +# mock_subscription_handler = None +# +# def get_block_handler( +# current_block_hash, +# header_only, +# subscription_handler, +# ): +# nonlocal mock_subscription_handler +# mock_subscription_handler = mocker.Mock(wraps=subscription_handler) +# +# for block in range(1, 20): +# if mock_subscription_handler( +# { +# "header": { +# "number": block, +# }, +# } +# ): +# return +# +# assert False +# +# mock_substrate.get_block.side_effect = [ +# { +# "header": { +# "number": 1, +# }, +# }, +# ] +# mock_substrate.get_block_handler.side_effect = get_block_handler +# +# subtensor.wait_for_block(block=9) +# +# assert mock_subscription_handler.call_count == 9 +# +# +# def test_weights(mock_substrate, subtensor): +# mock_substrate.query_map.return_value = [ +# (1, unittest.mock.Mock(value=0.5)), +# ] +# +# results = subtensor.weights( +# netuid=1, +# ) +# +# assert results == [ +# ( +# 1, +# 0.5, +# ), +# ] +# +# mock_substrate.query_map.assert_called_once_with( +# module="SubtensorModule", +# storage_function="Weights", +# params=[1], +# block_hash=None, +# ) From f6eabede3a82dd9b1a5c0b901d168173819cef9e Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:16:52 -0700 Subject: [PATCH 244/416] remove old files --- bittensor/core/extrinsics/asyncex/mechanism.py | 0 bittensor/core/extrinsics/mechanism.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 bittensor/core/extrinsics/asyncex/mechanism.py delete mode 100644 bittensor/core/extrinsics/mechanism.py diff --git a/bittensor/core/extrinsics/asyncex/mechanism.py b/bittensor/core/extrinsics/asyncex/mechanism.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/bittensor/core/extrinsics/mechanism.py b/bittensor/core/extrinsics/mechanism.py deleted file mode 100644 index e69de29bb2..0000000000 From b0a3b51a604b724e02e9b294462b6bf3cca9fdc5 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:17:14 -0700 Subject: [PATCH 245/416] update readme and license --- LICENSE | 3 ++- README.md | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 8d10866d56..6785ae26c3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,6 @@ The MIT License (MIT) -Copyright © 2021 Yuma Rao +Copyright © 2025 The Opentensor Foundation +Copyright © 2025 Yuma Rao 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 diff --git a/README.md b/README.md index e74a7e1490..8e117d2d65 100644 --- a/README.md +++ b/README.md @@ -297,7 +297,8 @@ Ready to contribute? Read the [contributing guide](./contrib/CONTRIBUTING.md) be ## License The MIT License (MIT) -Copyright © 2024 The Opentensor Foundation +Copyright © 2025 The Opentensor Foundation +Copyright © 2025 Yuma Rao 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: From 8909a1c8c7d3dde1119625bab79eb75f7696bf02 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:17:28 -0700 Subject: [PATCH 246/416] improve 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 c0e9e6d751..30c31ff7e1 100644 --- a/bittensor/core/subtensor_api/subnets.py +++ b/bittensor/core/subtensor_api/subnets.py @@ -44,6 +44,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.max_weight_limit = subtensor.max_weight_limit self.min_allowed_weights = subtensor.min_allowed_weights self.recycle = subtensor.recycle + self.register = subtensor.register self.register_subnet = subtensor.register_subnet self.set_subnet_identity = subtensor.set_subnet_identity self.subnet = subtensor.subnet From 5c9b518f55fd86df86b74ac76162336623aadceb Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:17:42 -0700 Subject: [PATCH 247/416] update subtensor classes --- bittensor/core/async_subtensor.py | 181 ++++++++++++++++-------------- bittensor/core/subtensor.py | 172 ++++++++++++++-------------- 2 files changed, 182 insertions(+), 171 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 9c18d5f763..32305695be 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -72,16 +72,14 @@ set_auto_stake_extrinsic, ) from bittensor.core.extrinsics.asyncex.start_call import start_call_extrinsic -from bittensor.core.extrinsics.asyncex.take import ( - decrease_take_extrinsic, - increase_take_extrinsic, -) +from bittensor.core.extrinsics.asyncex.take import set_take_extrinsic from bittensor.core.extrinsics.asyncex.transfer import transfer_extrinsic from bittensor.core.extrinsics.asyncex.unstaking import ( unstake_all_extrinsic, unstake_extrinsic, unstake_multiple_extrinsic, ) +from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee from bittensor.core.extrinsics.asyncex.weights import ( commit_timelocked_weights_extrinsic, commit_weights_extrinsic, @@ -102,11 +100,12 @@ Certificate, decode_hex_identity_dict, format_error_message, + get_caller_name, + get_transfer_fn_params, + get_mechid_storage_index, is_valid_ss58_address, u16_normalized_float, u64_normalized_float, - get_transfer_fn_params, - get_mechid_storage_index, ) from bittensor.utils.balance import ( Balance, @@ -1327,7 +1326,7 @@ async def get_block_hash(self, block: Optional[int] = None) -> str: async def get_parents( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, @@ -1337,7 +1336,7 @@ async def get_parents( storage function to get the children and formats them before returning as a tuple. Parameters: - hotkey: The child hotkey SS58. + hotkey_ss58: The child hotkey SS58. netuid: The netuid value. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. @@ -1350,7 +1349,7 @@ async def get_parents( parents = await self.substrate.query( module="SubtensorModule", storage_function="ParentKeys", - params=[hotkey, netuid], + params=[hotkey_ss58, netuid], block_hash=block_hash, reuse_block_hash=reuse_block, ) @@ -1367,7 +1366,7 @@ async def get_parents( async def get_children( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, @@ -1380,7 +1379,7 @@ async def get_children( distribution. Parameters: - hotkey: The hotkey value. + hotkey_ss58: The hotkey value. netuid: The netuid value. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. @@ -1403,7 +1402,7 @@ async def get_children( children = await self.substrate.query( module="SubtensorModule", storage_function="ChildKeys", - params=[hotkey, netuid], + params=[hotkey_ss58, netuid], block_hash=block_hash, reuse_block_hash=reuse_block, ) @@ -1422,7 +1421,7 @@ async def get_children( async def get_children_pending( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, @@ -1437,7 +1436,7 @@ async def get_children_pending( approval or in a cooldown period. These are children that have been proposed but not yet finalized. Parameters: - hotkey: The hotkey value. + hotkey_ss58: The hotkey value. netuid: The netuid value. block: The block number for which the children are to be retrieved. block_hash: The hash of the block to retrieve the subnet unique identifiers from. @@ -1451,7 +1450,7 @@ async def get_children_pending( response = await self.substrate.query( module="SubtensorModule", storage_function="PendingChildKeys", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=await self.determine_block_hash( block, block_hash, @@ -2124,7 +2123,7 @@ async def get_netuids_for_hotkey( async def get_neuron_certificate( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, @@ -2135,7 +2134,7 @@ async def get_neuron_certificate( subnet (netuid) of the Bittensor network. Parameters: - hotkey: The hotkey to query. + hotkey_ss58: The hotkey to query. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. block_hash: The hash of the block to retrieve the parameter from. Do not specify if using block or @@ -2155,7 +2154,7 @@ async def get_neuron_certificate( name="NeuronCertificates", block_hash=block_hash, reuse_block=reuse_block, - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], ), ) try: @@ -2863,8 +2862,8 @@ async def get_stake_for_coldkey_and_hotkey( results = await asyncio.gather( *[ self.query_runtime_api( - "StakeInfoRuntimeApi", - "get_stake_info_for_hotkey_coldkey_netuid", + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_hotkey_coldkey_netuid", params=[hotkey_ss58, coldkey_ss58, netuid], block_hash=block_hash, ) @@ -4231,7 +4230,11 @@ async def sign_and_send_extrinsic( Raises: SubstrateRequestException: Substrate request exception. """ - extrinsic_response = ExtrinsicResponse(extrinsic_function=calling_function) + extrinsic_response = ExtrinsicResponse( + extrinsic_function=calling_function + if calling_function + else get_caller_name() + ) possible_keys = ("coldkey", "hotkey", "coldkeypub") if sign_with not in possible_keys: raise AttributeError( @@ -4251,6 +4254,9 @@ async def sign_and_send_extrinsic( if period is not None: extrinsic_data["era"] = {"period": period} + extrinsic_response.extrinsic_fee = await get_extrinsic_fee( + subtensor=self, call=call, keypair=signing_keypair + ) extrinsic_response.extrinsic = await self.substrate.create_signed_extrinsic( **extrinsic_data ) @@ -4265,6 +4271,7 @@ async def sign_and_send_extrinsic( extrinsic_response.message = ( "Not waiting for finalization or inclusion." ) + logging.debug(extrinsic_response.message) return extrinsic_response if await response.is_success: @@ -4275,6 +4282,7 @@ async def sign_and_send_extrinsic( if raise_error: raise ChainError.from_error(response_error_message) + extrinsic_response.success = False extrinsic_response.message = format_error_message(response_error_message) extrinsic_response.error = response_error_message @@ -4404,8 +4412,8 @@ async def add_liquidity( async def add_stake_multiple( self, wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + hotkey_ss58s: list[str], amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, @@ -4418,8 +4426,8 @@ async def add_stake_multiple( Parameters: wallet: The wallet used for staking. - hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. netuids: List of subnet UIDs. + hotkey_ss58s: List of ``SS58`` addresses 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 @@ -4538,11 +4546,9 @@ async def commit_weights( See also: , """ retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to commit weights!" - ) + response = ExtrinsicResponse(False) - logging.info( + logging.debug( f"Committing weights with params: " f"netuid=[blue]{netuid}[/blue], uids=[blue]{uids}[/blue], weights=[blue]{weights}[/blue], " f"version_key=[blue]{version_key}[/blue]" @@ -4564,10 +4570,16 @@ async def commit_weights( raise_error=raise_error, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error committing weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 + if not response.success: + logging.debug( + "No one successful attempt made. " + "Perhaps it is too soon to commit weights!" + ) return response async def modify_liquidity( @@ -4880,9 +4892,7 @@ async def reveal_weights( See also: , """ retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to reveal weights!" - ) + response = ExtrinsicResponse(False) while retries < max_retries and response.success is False: try: @@ -4901,10 +4911,13 @@ async def reveal_weights( wait_for_finalization=wait_for_finalization, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error revealing weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 + if not response.success: + logging.debug("No attempt made. Perhaps it is too soon to reveal weights!") return response async def root_register( @@ -5019,7 +5032,7 @@ async def set_auto_stake( async def set_children( self, wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], period: Optional[int] = None, @@ -5032,7 +5045,7 @@ async def set_children( Parameters: wallet: bittensor wallet instance. - hotkey: The `SS58` address of the neuron's hotkey. + hotkey_ss58: The `SS58` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. period: The number of blocks during which the transaction will remain valid after it's @@ -5061,7 +5074,7 @@ async def set_children( return await set_children_extrinsic( subtensor=self, wallet=wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, children=children, period=period, @@ -5118,23 +5131,18 @@ async def set_delegate_take( current_take_u16 = int(current_take * 0xFFFF) if current_take_u16 == take_u16: - message = f"The take for {hotkey_ss58} is already set to {take}" - logging.info(f":white_heavy_check_mark: [green]{message}[/green].") + message = f"The take for {hotkey_ss58} is already set to {take}." + logging.debug(f"[green]{message}[/green].") return ExtrinsicResponse(True, message) - logging.info(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - - extrinsic_call = ( - increase_take_extrinsic - if current_take_u16 < take_u16 - else decrease_take_extrinsic - ) + logging.debug(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - response = await extrinsic_call( + response = await set_take_extrinsic( subtensor=self, wallet=wallet, hotkey_ss58=hotkey_ss58, take=take_u16, + action="increase_take" if current_take_u16 < take_u16 else "decrease_take", period=period, raise_error=raise_error, wait_for_finalization=wait_for_finalization, @@ -5142,8 +5150,9 @@ async def set_delegate_take( ) if response.success: - logging.info(":white_heavy_check_mark: [green]Take Updated[/green]") + return response + logging.error(f"[red]{response.message}[/red]") return response async def set_subnet_identity( @@ -5252,9 +5261,8 @@ async def _blocks_weight_limit() -> bool: return bslu > wrl retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to set weights!" - ) + response = ExtrinsicResponse(False) + if ( uid := await self.get_uid_for_hotkey_on_subnet( wallet.hotkey.ss58_address, netuid @@ -5262,7 +5270,7 @@ async def _blocks_weight_limit() -> bool: ) is None: return ExtrinsicResponse( False, - f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}", + f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}.", ) if await self.commit_reveal_enabled(netuid=netuid): @@ -5273,27 +5281,31 @@ async def _blocks_weight_limit() -> bool: and response.success is False and await _blocks_weight_limit() ): - logging.info( + logging.debug( f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) - response = await commit_timelocked_weights_extrinsic( - subtensor=self, - wallet=wallet, - netuid=netuid, - mechid=mechid, - 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: + response = await commit_timelocked_weights_extrinsic( + subtensor=self, + wallet=wallet, + netuid=netuid, + mechid=mechid, + 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 error: + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 - return response else: # go with `set_mechanism_weights_extrinsic` @@ -5303,7 +5315,7 @@ async def _blocks_weight_limit() -> bool: and await _blocks_weight_limit() ): try: - logging.info( + logging.debug( f"Setting weights for subnet #[blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) @@ -5321,11 +5333,16 @@ async def _blocks_weight_limit() -> bool: wait_for_finalization=wait_for_finalization, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error setting weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 - return response + if not response.success: + logging.debug( + "No one successful attempt made. Perhaps it is too soon to set weights!" + ) + return response async def serve_axon( self, @@ -5454,15 +5471,15 @@ async def set_reveal_commitment( Returns: ExtrinsicResponse: The result object of the extrinsic execution. - Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + Note: + A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + Successful extrinsic's the "data" field contains {"encrypted": encrypted, "reveal_round": reveal_round}. """ encrypted, reveal_round = get_encrypted_commitment( data, blocks_until_reveal, block_time ) - # increase reveal_round in return + 1 because we want to fetch data from the chain after that round was revealed - # and stored. data_ = {"encrypted": encrypted, "reveal_round": reveal_round} response = await publish_metadata_extrinsic( subtensor=self, @@ -5475,7 +5492,7 @@ async def set_reveal_commitment( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - response.data = {"reveal_round": reveal_round} + response.data = data_ return response async def start_call( @@ -5775,7 +5792,7 @@ async def unstake( async def unstake_all( self, wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, @@ -5787,7 +5804,7 @@ async def unstake_all( Parameters: wallet: The wallet of the stake owner. - hotkey: The SS58 address of the hotkey to unstake from. + hotkey_ss58: 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. @@ -5840,14 +5857,10 @@ async def unstake_all( ) print(result) """ - if netuid != 0: - logging.debug( - f"Unstaking without Alpha price control from subnet [blue]#{netuid}[/blue]." - ) return await unstake_all_extrinsic( subtensor=self, wallet=wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, rate_tolerance=rate_tolerance, period=period, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 2257b00696..7d9ae58754 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -71,16 +71,14 @@ set_auto_stake_extrinsic, ) from bittensor.core.extrinsics.start_call import start_call_extrinsic -from bittensor.core.extrinsics.take import ( - decrease_take_extrinsic, - increase_take_extrinsic, -) +from bittensor.core.extrinsics.take import set_take_extrinsic from bittensor.core.extrinsics.transfer import transfer_extrinsic from bittensor.core.extrinsics.unstaking import ( unstake_all_extrinsic, unstake_extrinsic, unstake_multiple_extrinsic, ) +from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.core.extrinsics.weights import ( commit_timelocked_weights_extrinsic, commit_weights_extrinsic, @@ -105,11 +103,12 @@ Certificate, decode_hex_identity_dict, format_error_message, + get_caller_name, + get_transfer_fn_params, + get_mechid_storage_index, is_valid_ss58_address, u16_normalized_float, u64_normalized_float, - get_transfer_fn_params, - get_mechid_storage_index, ) from bittensor.utils.balance import ( Balance, @@ -831,14 +830,14 @@ def get_hyperparameter( return getattr(result, "value", result) def get_parents( - self, hotkey: str, netuid: int, block: Optional[int] = None + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None ) -> list[tuple[float, str]]: """ This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys storage function to get the children and formats them before returning as a tuple. Parameters: - hotkey: The child hotkey SS58. + hotkey_ss58: The child hotkey SS58. netuid: The netuid. block: The block number for which the children are to be retrieved. @@ -848,7 +847,7 @@ def get_parents( parents = self.substrate.query( module="SubtensorModule", storage_function="ParentKeys", - params=[hotkey, netuid], + params=[hotkey_ss58, netuid], block_hash=self.determine_block_hash(block), ) if parents: @@ -863,14 +862,14 @@ def get_parents( return [] def get_children( - self, hotkey: str, netuid: int, block: Optional[int] = None + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None ) -> tuple[bool, list[tuple[float, str]], str]: """ This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys storage function to get the children and formats them before returning as a tuple. Parameters: - hotkey: The hotkey value. + hotkey_ss58: The hotkey value. netuid: The netuid value. block: The block number for which the children are to be retrieved. @@ -882,7 +881,7 @@ def get_children( children = self.substrate.query( module="SubtensorModule", storage_function="ChildKeys", - params=[hotkey, netuid], + params=[hotkey_ss58, netuid], block_hash=self.determine_block_hash(block), ) if children: @@ -900,7 +899,7 @@ def get_children( def get_children_pending( self, - hotkey: str, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, ) -> tuple[ @@ -912,7 +911,7 @@ def get_children_pending( It queries the SubtensorModule's PendingChildKeys storage function. Parameters: - hotkey: The hotkey value. + hotkey_ss58: The hotkey value. netuid: The netuid value. block: The block number for which the children are to be retrieved. @@ -924,7 +923,7 @@ def get_children_pending( children, cooldown = self.substrate.query( module="SubtensorModule", storage_function="PendingChildKeys", - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], block_hash=self.determine_block_hash(block), ).value @@ -1434,14 +1433,14 @@ def get_netuids_for_hotkey( return netuids def get_neuron_certificate( - self, hotkey: str, netuid: int, block: Optional[int] = None + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None ) -> Optional[Certificate]: """ Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a specified subnet (netuid) of the Bittensor network. Parameters: - hotkey: The hotkey to query. + hotkey_ss58: The hotkey to query. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. @@ -1454,7 +1453,7 @@ def get_neuron_certificate( module="SubtensorModule", name="NeuronCertificates", block=block, - params=[netuid, hotkey], + params=[netuid, hotkey_ss58], ) try: if certificate_query: @@ -1683,27 +1682,21 @@ def get_neuron_for_pubkey_and_subnet( attributes within a particular subnet of the Bittensor ecosystem. """ block_hash = self.determine_block_hash(block) - uid = self.substrate.query( + uid_query = self.substrate.query( module="SubtensorModule", storage_function="Uids", params=[netuid, hotkey_ss58], block_hash=block_hash, ) - if uid is None: + if (uid := getattr(uid_query, "value", None)) is None: return NeuronInfo.get_null_neuron() - result = self.query_runtime_api( - runtime_api="NeuronInfoRuntimeApi", - method="get_neuron", - params=[netuid, uid.value], + return self.neuron_for_uid( + uid=uid, + netuid=netuid, block=block, ) - if not result: - return NeuronInfo.get_null_neuron() - - return NeuronInfo.from_dict(result) - def get_next_epoch_start_block( self, netuid: int, block: Optional[int] = None ) -> Optional[int]: @@ -2069,8 +2062,8 @@ def get_stake_for_coldkey_and_hotkey( all_netuids = netuids results = [ self.query_runtime_api( - "StakeInfoRuntimeApi", - "get_stake_info_for_hotkey_coldkey_netuid", + runtime_api="StakeInfoRuntimeApi", + method="get_stake_info_for_hotkey_coldkey_netuid", params=[hotkey_ss58, coldkey_ss58, netuid], block=block, ) @@ -2088,11 +2081,11 @@ def get_stake_for_coldkey( Retrieves the stake information for a given coldkey. Parameters: - coldkey_ss58 The SS58 address of the coldkey. + coldkey_ss58: The SS58 address of the coldkey. block: The block number at which to query the stake information. Returns: - OA list of StakeInfo objects, or ``None`` if no stake information is found. + An optional list of StakeInfo objects, or ``None`` if no stake information is found. """ result = self.query_runtime_api( runtime_api="StakeInfoRuntimeApi", @@ -3114,7 +3107,11 @@ def sign_and_send_extrinsic( Raises: SubstrateRequestException: Substrate request exception. """ - extrinsic_response = ExtrinsicResponse(extrinsic_function=calling_function) + extrinsic_response = ExtrinsicResponse( + extrinsic_function=calling_function + if calling_function + else get_caller_name() + ) possible_keys = ("coldkey", "hotkey", "coldkeypub") if sign_with not in possible_keys: raise AttributeError( @@ -3135,6 +3132,9 @@ def sign_and_send_extrinsic( if period is not None: extrinsic_data["era"] = {"period": period} + extrinsic_response.extrinsic_fee = get_extrinsic_fee( + subtensor=self, call=call, keypair=signing_keypair + ) extrinsic_response.extrinsic = self.substrate.create_signed_extrinsic( **extrinsic_data ) @@ -3149,6 +3149,7 @@ def sign_and_send_extrinsic( extrinsic_response.message = ( "Not waiting for finalization or inclusion." ) + logging.debug(extrinsic_response.message) return extrinsic_response if response.is_success: @@ -3289,8 +3290,8 @@ def add_liquidity( def add_stake_multiple( self, wallet: "Wallet", - hotkey_ss58s: list[str], netuids: UIDs, + hotkey_ss58s: list[str], amounts: list[Balance], period: Optional[int] = None, raise_error: bool = False, @@ -3303,8 +3304,8 @@ def add_stake_multiple( Parameters: wallet: The wallet used for staking. - hotkey_ss58s: List of ``SS58`` addresses of hotkeys to stake to. netuids: List of subnet UIDs. + hotkey_ss58s: List of ``SS58`` addresses 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 @@ -3420,11 +3421,9 @@ def commit_weights( time, enhancing transparency and accountability within the Bittensor network. """ retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to commit weights!" - ) + response = ExtrinsicResponse(False) - logging.info( + logging.debug( f"Committing weights with params: " f"netuid=[blue]{netuid}[/blue], uids=[blue]{uids}[/blue], weights=[blue]{weights}[/blue], " f"version_key=[blue]{version_key}[/blue]" @@ -3445,13 +3444,17 @@ def commit_weights( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - if response.success: - break except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error committing weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 + if not response.success: + logging.debug( + "No one successful attempt made. " + "Perhaps it is too soon to commit weights!" + ) return response def modify_liquidity( @@ -3764,9 +3767,7 @@ def reveal_weights( See also: , """ retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to reveal weights!" - ) + response = ExtrinsicResponse(False) while retries < max_retries and response.success is False: try: @@ -3785,10 +3786,13 @@ def reveal_weights( wait_for_finalization=wait_for_finalization, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error revealing weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 + if not response.success: + logging.debug("No attempt made. Perhaps it is too soon to reveal weights!") return response def root_register( @@ -3903,7 +3907,7 @@ def set_auto_stake( def set_children( self, wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, children: list[tuple[float, str]], period: Optional[int] = None, @@ -3916,7 +3920,7 @@ def set_children( Parameters: wallet: bittensor wallet instance. - hotkey: The ``SS58`` address of the neuron's hotkey. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The netuid value. children: A list of children with their proportions. period: The number of blocks during which the transaction will remain valid after it's @@ -3932,7 +3936,7 @@ def set_children( return set_children_extrinsic( subtensor=self, wallet=wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, children=children, period=period, @@ -3989,23 +3993,18 @@ def set_delegate_take( current_take_u16 = int(current_take * 0xFFFF) if current_take_u16 == take_u16: - message = f"The take for {hotkey_ss58} is already set to {take}" - logging.info(f":white_heavy_check_mark: [green]{message}[/green].") + message = f"The take for {hotkey_ss58} is already set to {take}." + logging.debug(f"[green]{message}[/green].") return ExtrinsicResponse(True, message) - logging.info(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - - extrinsic_call = ( - increase_take_extrinsic - if current_take_u16 < take_u16 - else decrease_take_extrinsic - ) + logging.debug(f"Updating {hotkey_ss58} take: current={current_take} new={take}") - response = extrinsic_call( + response = set_take_extrinsic( subtensor=self, wallet=wallet, hotkey_ss58=hotkey_ss58, take=take_u16, + action="increase_take" if current_take_u16 < take_u16 else "decrease_take", period=period, raise_error=raise_error, wait_for_finalization=wait_for_finalization, @@ -4013,8 +4012,9 @@ def set_delegate_take( ) if response.success: - logging.info(":white_heavy_check_mark: [green]Take Updated[/green]") + return response + logging.error(f"[red]{response.message}[/red]") return response def set_subnet_identity( @@ -4118,15 +4118,14 @@ def _blocks_weight_limit() -> bool: return bslu > wrl retries = 0 - response = ExtrinsicResponse( - False, "No attempt made. Perhaps it is too soon to set weights!" - ) + response = ExtrinsicResponse(False) + if ( uid := self.get_uid_for_hotkey_on_subnet(wallet.hotkey.ss58_address, netuid) ) is None: return ExtrinsicResponse( False, - f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}", + f"Hotkey {wallet.hotkey.ss58_address} not registered in subnet {netuid}.", ) if self.commit_reveal_enabled(netuid=netuid): @@ -4137,7 +4136,7 @@ def _blocks_weight_limit() -> bool: and response.success is False and _blocks_weight_limit() ): - logging.info( + logging.debug( f"Committing weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) @@ -4157,13 +4156,11 @@ def _blocks_weight_limit() -> bool: wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) - except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error setting weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 - - return response else: # go with `set_mechanism_weights_extrinsic` @@ -4173,7 +4170,7 @@ def _blocks_weight_limit() -> bool: and _blocks_weight_limit() ): try: - logging.info( + logging.debug( f"Setting weights for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[/blue] of [green]{max_retries}[/green]." ) @@ -4191,11 +4188,16 @@ def _blocks_weight_limit() -> bool: wait_for_finalization=wait_for_finalization, ) except Exception as error: - response.error = error if not response.error else response.error - logging.error(f"Error setting weights: {error}") + return ExtrinsicResponse.from_exception( + raise_error=raise_error, error=error + ) retries += 1 - return response + if not response.success: + logging.debug( + "No one successful attempt made. Perhaps it is too soon to set weights!" + ) + return response def serve_axon( self, @@ -4325,15 +4327,15 @@ def set_reveal_commitment( Returns: ExtrinsicResponse: The result object of the extrinsic execution. - Note: A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + Note: + A commitment can be set once per subnet epoch and is reset at the next epoch in the chain automatically. + Successful extrinsic's the "data" field contains {"encrypted": encrypted, "reveal_round": reveal_round}. """ encrypted, reveal_round = get_encrypted_commitment( data, blocks_until_reveal, block_time ) - # increase reveal_round in return + 1 because we want to fetch data from the chain after that round was revealed - # and stored. data_ = {"encrypted": encrypted, "reveal_round": reveal_round} response = publish_metadata_extrinsic( subtensor=self, @@ -4647,7 +4649,7 @@ def unstake( def unstake_all( self, wallet: "Wallet", - hotkey: str, + hotkey_ss58: str, netuid: int, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, @@ -4659,7 +4661,7 @@ def unstake_all( Parameters: wallet: The wallet of the stake owner. - hotkey: The SS58 address of the hotkey to unstake from. + hotkey_ss58: 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. @@ -4711,14 +4713,10 @@ def unstake_all( ) print(result) """ - if netuid != 0: - logging.debug( - f"Unstaking without Alpha price control from subnet [blue]#{netuid}[/blue]." - ) return unstake_all_extrinsic( subtensor=self, wallet=wallet, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, netuid=netuid, rate_tolerance=rate_tolerance, period=period, From ef43b45557a8bdbb474effa719e967191744abff Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:17:53 -0700 Subject: [PATCH 248/416] pyproject.toml --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2683171b95..6bc44e8d40 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor" -version = "9.12.0" -description = "Bittensor" +version = "10.0.0rc" +description = "Bittensor SDK" readme = "README.md" authors = [ {name = "bittensor.com"} From c5d703e5890e072d53ce8efd56b2256b7b64073e Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 28 Sep 2025 20:18:02 -0700 Subject: [PATCH 249/416] update MIGRATION.md --- MIGRATION.md | 53 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index dd27b8f1e7..128f70ed28 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -13,7 +13,7 @@ 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. +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.~~ Just improved regarding usage. 6. ✅ Remove `unstake_all` parameter from `unstake_extrinsic` since we have `unstake_all_extrinsic`which is calles another subtensor function. @@ -21,7 +21,7 @@ 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`. +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`.~~ `get_transfer_fee` isn't `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. @@ -41,7 +41,7 @@ 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 +1. Reduce the amount of logging.info or transfer part of logging.info to logging.debug `(in progress)` 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. @@ -53,7 +53,7 @@ rename this variable in documentation. 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: +6. To be consistent throughout the SDK `(in progress)`: `hotkey`, `coldkey`, `hotkeypub`, and `coldkeypub` are keypairs `hotkey_ss58`, `coldkey_ss58`, `hotkeypub_ss58`, and `coldkeypub_ss58` are SS58 addresses of keypair. @@ -61,9 +61,9 @@ rename this variable in documentation. 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. +9. ✅ Remove all logic related to CRv3 as it will be removed from the chain next week. - [x] CRv3 extrinsics - - [ ] CRv3 logic related subtensor's calls + - [x] CRv3 logic related subtensor's calls 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. @@ -77,10 +77,10 @@ rename this variable in documentation. ## 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. Issue: https://github.com/opentensor/bittensor/issues/3017 -3. Implement Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 +3. ✅ Implement Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 ## Testing -1. When running tests via Docker, ensure no lingering processes occupy required ports before launch. +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). @@ -112,13 +112,13 @@ It must include: # Migration guide -- [x] `._do_commit_reveal_v3` logic is included in the main code `.commit_reveal_v3_extrinsic` -- [x] `revecommit_reveal_version` parameter with default value `4` added to `revecommit_reveal_version` +- [x] `._do_commit_reveal_v3` logic is included in the main code `.commit_timelocked_weights_extrinsic` + - [x] `commit_reveal_version` parameter with default value `4` added to `commit_timelocked_weights_extrinsic` - [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] `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` @@ -199,25 +199,38 @@ Additional changes in extrinsics: Removing deprecated extrinsics and replacing them with consistent ones: - `commit_reveal_extrinsic` (without mechanisms support) + related tests -- `bittensor.core.extrinsics.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_timelocked_weights_extrinsic` -- `bittensor.core.extrinsics.asyncex.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_timelocked_weights_extrinsic` + - `bittensor.core.extrinsics.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_timelocked_weights_extrinsic` + - `bittensor.core.extrinsics.asyncex.mechanism.commit_timelocked_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_timelocked_weights_extrinsic` - `commit_weights_extrinsic`(without mechanisms support) + related tests -- `bittensor.core.extrinsics.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_weights_extrinsic` -- `bittensor.core.extrinsics.asyncex.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_weights_extrinsic` + - `bittensor.core.extrinsics.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.commit_weights_extrinsic` + - `bittensor.core.extrinsics.asyncex.mechanism.commit_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.commit_weights_extrinsic` - `reveal_weights_extrinsic`(without mechanisms support) + related tests -- `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` -- `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` + - `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` + - `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` - `set_weights_extrinsic`(without mechanisms support) + related tests -- `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` -- `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` + - `bittensor.core.extrinsics.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.weights.reveal_weights_extrinsic` + - `bittensor.core.extrinsics.asyncex.mechanism.reveal_mechanism_weights_extrinsic` moved and renamed to `bittensor.core.extrinsics.asyncex.weights.reveal_weights_extrinsic` +- `decrease_take_extrinsic` and `increase_take_extrinsic` have been merged into a single set_take_extrinsic. The API now has a new `action: Literal["increase_take", "decrease_take"]` parameter (DRY rule). + +### Extrinsics has extra data in response's `data` field: +- `add_stake_extrinsic` +- `add_stake_multiple_extrinsic` +- `burned_register_extrinsic` +- `register_extrinsic` +- `transfer_extrinsic` +- `unstake_extrinsic` +- `unstake_multiple_extrinsic` ### Subtensor changes -- method `all_subnets` has renamed parameter from `block_number` to `block`. +- method `all_subnets` has renamed parameter from `block_number` to `block` (consistency in the codebase). +- The `hotkey` parameter, which meant ss58 key address, was renamed to `hotkey_ss58` in all methods (consistency in the codebase). +- The `coldkey` parameter, which meant ss58 key address, was renamed to `coldkey_ss58` in all methods (consistency in the codebase). - method `query_subtensor` has updated parameters order. - method `query_module` has updated parameters order. - method `query_map_subtensor` has updated parameters order. - method `query_map` has updated parameters order. +- method `add_stake_multiple` has updated parameters order. From 3ded4c6b3f386b3bdbf2e6bf9e0e8d8491ef62e0 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 00:02:48 -0700 Subject: [PATCH 250/416] fix and improve tests + ruff --- bittensor/core/types.py | 5 +- tests/e2e_tests/test_hotkeys.py | 593 ++++++++++----------- tests/e2e_tests/test_neuron_certificate.py | 4 +- 3 files changed, 298 insertions(+), 304 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 94dc91bd20..543ee8eb64 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -486,7 +486,10 @@ def from_exception(cls, raise_error: bool, error: Exception) -> "ExtrinsicRespon ).with_log() def with_log( - self, level: Literal["trace", "debug", "info", "warning", "error", "success"] = "error" + self, + level: Literal[ + "trace", "debug", "info", "warning", "error", "success" + ] = "error", ) -> "ExtrinsicResponse": """Logs provided message with provided level. diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index f5fe16e857..261703f83b 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -193,7 +193,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(RegistrationNotPermittedOnRootSubnet): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=0, children=[], raise_error=True, @@ -202,7 +202,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(NonAssociatedColdKey): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=1, children=[], raise_error=True, @@ -210,8 +210,8 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(SubnetNotExists): subtensor.extrinsics.set_children( - alice_wallet, - alice_wallet.hotkey.ss58_address, + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=3, children=[], raise_error=True, @@ -230,7 +230,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") success, children, error = subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -241,7 +241,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(InvalidChild): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -255,7 +255,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(TooManyChildren): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -270,7 +270,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(ProportionOverflow): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -288,7 +288,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(DuplicateChild): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -305,7 +305,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): success, message = subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -324,18 +324,17 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): # children not set yet (have to wait cool-down period) success, children, error = subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=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 = subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -349,10 +348,9 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2 + 1) success, children, error = subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) - assert error == "" assert success is True assert children == [(1.0, bob_wallet.hotkey.ss58_address)] @@ -365,7 +363,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): # pending queue is empty pending, cooldown = subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) assert pending == [] @@ -377,25 +375,25 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): set_children_block = subtensor.block subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, ) subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, ) # wait for rate limit to expire + 1 block to ensure that the rate limit is expired - subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT + 1) + subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT + 5) success, message = subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, @@ -408,7 +406,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): set_children_block = subtensor.block pending, cooldown = subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) @@ -417,12 +415,20 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): f"[orange]block: {subtensor.block}, cooldown: {cooldown}[/orange]" ) - subtensor.wait_for_block(cooldown + 1) + subtensor.wait_for_block(cooldown) - success, children, error = subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + # we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait. But no longer than one tempo. + children = [] + while not children: + success, children, error = subtensor.wallets.get_children( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + logging.console.info(f"block: {subtensor.block}") + if subtensor.block > cooldown + SET_TEMPO: + break + subtensor.wait_for_block() assert error == "" assert success is True @@ -442,7 +448,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): with pytest.raises(NotEnoughStakeToSetChildkeys): subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( @@ -455,11 +461,9 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): @pytest.mark.asyncio -async def test_children_async( - async_subtensor, alice_wallet, bob_wallet, dave_wallet, subtensor -): +async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): """ - Async tests: + Tests: - Get default children (empty list) - Call `root_set_pending_childkey_cooldown` extrinsic. - Update children list @@ -468,345 +472,332 @@ async def test_children_async( - Trigger rate limit - Clear children list """ - async with async_subtensor as async_subtensor: - # turn off admin freeze window limit for testing - window_response, is_fast_blocks = await asyncio.gather( - async_sudo.sudo_set_admin_freeze_window_extrinsic( - async_subtensor, alice_wallet, 0 - ), - async_subtensor.chain.is_fast_blocks(), + # turn off admin freeze window limit for testing + assert ( + await async_sudo.sudo_set_admin_freeze_window_extrinsic( + async_subtensor, alice_wallet, 0 ) + ).success - dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + SET_TEMPO = ( + FAST_RUNTIME_TEMPO + if await async_subtensor.chain.is_fast_blocks() + else NON_FAST_RUNTIME_TEMPO + ) - assert window_response.success, window_response.message + dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - SET_TEMPO = FAST_RUNTIME_TEMPO if is_fast_blocks else NON_FAST_RUNTIME_TEMPO + # Set cooldown + ( + success, + message, + ) = await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( + wallet=alice_wallet, cooldown=ROOT_COOLDOWN + ) + assert success is True, message + assert message == "Success" - cooldown_response = ( - await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( - wallet=alice_wallet, cooldown=ROOT_COOLDOWN - ) - ) - assert cooldown_response.success, cooldown_response.message + 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." + ) - assert await async_subtensor.subnets.register_subnet(dave_wallet) + assert await async_wait_to_start_call( + async_subtensor, dave_wallet, dave_subnet_netuid + ) - subnet_exists, start_call, tempo_call = await asyncio.gather( - async_subtensor.subnets.subnet_exists(dave_subnet_netuid), - async_wait_to_start_call(async_subtensor, dave_wallet, dave_subnet_netuid), - async_sudo.sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, - ), + # set the same tempo for both type of nodes (to avoid tests timeout) + assert ( + await async_sudo.sudo_call_extrinsic( + subtensor=async_subtensor, + wallet=alice_wallet, + call_function="sudo_set_tempo", + call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, ) - assert subnet_exists, "Subnet does not exist." - assert start_call, "Subnet did not start." + ).success + assert await async_subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO - tempo_call = await async_sudo.sudo_call_extrinsic( + assert ( + await async_sudo.sudo_call_extrinsic( subtensor=async_subtensor, wallet=alice_wallet, call_function="sudo_set_tx_rate_limit", call_params={"tx_rate_limit": 0}, ) + ).success - assert tempo_call.success, tempo_call.message - assert await async_subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO - - 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(SubnetNotExists): - await async_subtensor.extrinsics.set_children( - wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, - netuid=3, - children=[], - raise_error=True, - ) - - # we can't do more than one registration within one block (avoid gather) - # https://docs.learnbittensor.org/errors/subtensor#toomanyregistrationsthisblock - alice_register_call = await async_subtensor.subnets.burned_register( + with pytest.raises(RegistrationNotPermittedOnRootSubnet): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=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_ss58=alice_wallet.hotkey.ss58_address, + netuid=1, + children=[], + raise_error=True, + ) + + with pytest.raises(SubnetNotExists): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=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, ) - await async_subtensor.wait_for_block() + ).success + logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") - bob_register_call, (success, children, error) = await asyncio.gather( - async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ), - async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ), + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, ) + ).success + logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") + + success, children, error = await async_subtensor.wallets.get_children( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) + + assert error == "" + assert success is True + assert children == [] - assert alice_register_call.success, alice_register_call.message - logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") - - assert bob_register_call.success, bob_register_call.message - logging.console.success(f"Bob registered on subnet {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( + with pytest.raises(InvalidChild): + await async_subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[ ( 1.0, - bob_wallet.hotkey.ss58_address, + alice_wallet.hotkey.ss58_address, ), ], raise_error=True, - wait_for_inclusion=True, - wait_for_finalization=True, ) - assert success, message - assert message == "Success" - - 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, + with pytest.raises(TooManyChildren): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, + children=[ + ( + 0.1, + bob_wallet.hotkey.ss58_address, + ) + for _ in range(10) + ], + raise_error=True, ) - 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, + with pytest.raises(ProportionOverflow): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ( + 1.0, + "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + ), + ], + raise_error=True, ) - logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + with pytest.raises(DuplicateChild): + await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=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, ) - assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] + success, message = await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=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 success is True, message + assert message == "Success" - # we use `*2` to ensure that the chain has time to process - await async_subtensor.wait_for_block(cooldown + SET_CHILDREN_RATE_LIMIT * 2 + 1) + set_children_block = await async_subtensor.block - success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + # children not set yet (have to wait cool-down period) + success, children, error = await async_subtensor.wallets.get_children( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + block=set_children_block, + netuid=dave_subnet_netuid, + ) + assert success is True + assert children == [] + assert error == "" - # we need to wait some amount of blocks to ensure that children are posted on chain - # than slower the machine then longer need to wait - while not children: - 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, - ) - logging.console.info(f"block: {await async_subtensor.block}") + # children are in pending state + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) - assert error == "" - assert success is True - assert children == [(1.0, bob_wallet.hotkey.ss58_address)] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) - parent_ = await async_subtensor.wallets.get_parents( - bob_wallet.hotkey.ss58_address, dave_subnet_netuid - ) + assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] - assert parent_ == [(1.0, alice_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 + 1) - # 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]" - ) + success, children, error = await async_subtensor.wallets.get_children( + hotkey_ss58=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_ss58=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, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, + wait_for_finalization=False, ) await async_subtensor.extrinsics.set_children( wallet=alice_wallet, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, children=[], raise_error=True, + wait_for_finalization=False, ) - await async_subtensor.wait_for_block( - set_children_block + SET_CHILDREN_RATE_LIMIT + 1 - ) + await async_subtensor.wait_for_block( + set_children_block + SET_CHILDREN_RATE_LIMIT + 1 + ) - 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 success is True, message - assert message == "Success" + success, message = await async_subtensor.extrinsics.set_children( + wallet=alice_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[], + raise_error=True, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + assert success is True, message + assert message == "Success" - set_children_block = await async_subtensor.chain.get_current_block() + set_children_block = await async_subtensor.block - pending, cooldown = await async_subtensor.wallets.get_children_pending( - hotkey=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, - ) + pending, cooldown = await async_subtensor.wallets.get_children_pending( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + ) - assert pending == [] - logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" - ) + assert pending == [] + logging.console.info( + f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + ) - await async_subtensor.wait_for_block(cooldown + 1) + await async_subtensor.wait_for_block(cooldown) + # we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait. But no longer than one tempo. + children = [] + while not children: success, children, error = await async_subtensor.wallets.get_children( - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_subnet_netuid, ) + logging.console.info(f"block: {await async_subtensor.block}") + if await async_subtensor.block > cooldown + SET_TEMPO: + break + await async_subtensor.wait_for_block() - # we need to wait some amount of blocks to ensure that children are posted on chain - # than slower the machine then longer need to wait - while not children: - 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, - ) - logging.console.info(f"block: {await async_subtensor.block}") - - 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 - ) - - assert ( - await async_sudo.sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_stake_threshold", - call_params={ - "min_stake": 1_000_000_000_000, - }, - ) - ).success - - 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, - ) + 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.sudo_call_extrinsic( + subtensor=async_subtensor, + 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_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_subnet_netuid, + children=[ + ( + 1.0, + bob_wallet.hotkey.ss58_address, + ), + ], + raise_error=True, + ) diff --git a/tests/e2e_tests/test_neuron_certificate.py b/tests/e2e_tests/test_neuron_certificate.py index d4c80a286b..198e581ad0 100644 --- a/tests/e2e_tests/test_neuron_certificate.py +++ b/tests/e2e_tests/test_neuron_certificate.py @@ -44,7 +44,7 @@ async def test_neuron_certificate(subtensor, alice_wallet): assert ( subtensor.neurons.get_neuron_certificate( netuid=netuid, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, ) == encoded_certificate ) @@ -96,7 +96,7 @@ async def test_neuron_certificate_async(async_subtensor, alice_wallet): assert ( await async_subtensor.neurons.get_neuron_certificate( netuid=netuid, - hotkey=alice_wallet.hotkey.ss58_address, + hotkey_ss58=alice_wallet.hotkey.ss58_address, ) == encoded_certificate ) From 1b13039c6c67e9a8cedf0123abc29534e3ba61df Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 08:10:11 -0700 Subject: [PATCH 251/416] try faster runners --- .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 29b09650da..57b832d9f7 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: [self-hosted, type-ccx33] timeout-minutes: 45 strategy: diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index dc9d20dba8..dd2608fe12 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -21,7 +21,7 @@ on: jobs: # Looking for e2e tests find-tests: - runs-on: ubuntu-latest + runs-on: [self-hosted, type-ccx33] if: ${{ github.event.pull_request.draft == false }} outputs: test-files: ${{ steps.get-tests.outputs.test-files }} From c332b4479507b3542cd331083ee8ad4cd05c405e Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 08:16:41 -0700 Subject: [PATCH 252/416] nope --- .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 57b832d9f7..29b09650da 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: [self-hosted, type-ccx33] + runs-on: ubuntu-latest timeout-minutes: 45 strategy: diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index dd2608fe12..dc9d20dba8 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -21,7 +21,7 @@ on: jobs: # Looking for e2e tests find-tests: - runs-on: [self-hosted, type-ccx33] + runs-on: ubuntu-latest if: ${{ github.event.pull_request.draft == false }} outputs: test-files: ${{ steps.get-tests.outputs.test-files }} From b6708fd4d052badcf332fae0fa39fac4549d6742 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 09:05:57 -0700 Subject: [PATCH 253/416] more time to re-run docker --- .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 29b09650da..06eca8cc12 100644 --- a/.github/workflows/_run-e2e-single.yaml +++ b/.github/workflows/_run-e2e-single.yaml @@ -74,7 +74,7 @@ jobs: echo "::endgroup::" if [ "$i" -lt 3 ]; then echo "Retrying..." - sleep 5 + sleep 15 fi fi From 47fdf90878c45fb5213e7c9d0c4d357dac479cdf Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 09:06:16 -0700 Subject: [PATCH 254/416] fix if logic for fast block test --- tests/e2e_tests/test_staking.py | 3352 ++++++++++++++++--------------- 1 file changed, 1677 insertions(+), 1675 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 62d7f879b8..48368911ac 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -18,636 +18,636 @@ from tests.helpers.helpers import CloseInValue -def test_single_operation(subtensor, alice_wallet, bob_wallet): - """ - Tests: - - Staking using `add_stake` - - Unstaking using `unstake` - - Checks StakeInfo - """ - 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).success - - # Verify subnet 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 subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") - - stake = 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) - - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1), - period=16, - ).success - - 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.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 = 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 subtensor.chain.is_fast_blocks() - else [] - ) - - expected_stakes += fast_blocks_stake - - assert stakes == expected_stakes - assert ( - subtensor.staking.get_stake_for_coldkey - == subtensor.staking.get_stake_info_for_coldkey - ) - - stakes = 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 = 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 - response = subtensor.staking.unstake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=stake, - period=16, - ) - - assert response.success is True - - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - # all balances have been unstaked - assert stake == Balance(0).set_unit(alice_subnet_netuid) - - -@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 - """ - 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)).success - - # 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 - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ) - ).success - logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ) - ).success - 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) - - assert ( - await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1), - period=16, - ) - ).success - - 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, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - 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) - - -def test_batch_operations(subtensor, alice_wallet, bob_wallet): - """ - Tests: - - Staking using `add_stake_multiple` - - Unstaking using `unstake_multiple` - - Checks StakeInfo - - Checks Accounts Balance - """ - netuids = [ - 2, - 3, - ] - - for _ in netuids: - assert subtensor.subnets.register_subnet(alice_wallet).success - - # make sure we passed start_call limit for both subnets - for netuid in netuids: - assert wait_to_start_call(subtensor, alice_wallet, netuid) - - for netuid in netuids: - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=netuid, - ).success - - for netuid in netuids: - stake = subtensor.staking.get_stake( - alice_wallet.coldkey.ss58_address, - bob_wallet.hotkey.ss58_address, - netuid=netuid, - ) - - assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" - - balances = 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 - - alice_balance = balances[alice_wallet.coldkey.ss58_address] - - assert subtensor.staking.add_stake_multiple( - wallet=alice_wallet, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], - netuids=netuids, - amounts=[Balance.from_tao(10_000) for _ in netuids], - ).success - - stakes = [ - subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=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 = 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 = 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 = subtensor.substrate.get_payment_info( - call, alice_wallet.coldkeypub - ) - fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) - dynamic_info = subtensor.subnets.subnet(netuid) - fee_tao = dynamic_info.alpha_to_tao(fee_alpha) - expected_fee_paid += fee_tao - - response = subtensor.staking.unstake_multiple( - wallet=alice_wallet, - netuids=netuids, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], - amounts=[Balance.from_tao(100) for _ in netuids], - raise_error=True, - ) - logging.console.info(f">>> res {response}") - assert response.success, response.message - - for netuid, old_stake in zip(netuids, stakes): - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=netuid, - ) - - assert stake < old_stake, f"netuid={netuid} stake={stake}" - - balances = 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 - - -@pytest.mark.asyncio -async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet): - """ - Async tests: - - Staking using `add_stake_multiple` - - Unstaking using `unstake_multiple` - - Checks StakeInfo - - Checks Accounts Balance - """ - netuids = [ - 2, - 3, - ] - - for _ in netuids: - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - - # 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) - - for netuid in netuids: - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=netuid, - ) - ).success - - 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, - ) - - assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" - - 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 - - alice_balance = balances[alice_wallet.coldkey.ss58_address] - - response = await async_subtensor.staking.add_stake_multiple( - wallet=alice_wallet, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], - netuids=netuids, - amounts=[Balance.from_tao(10_000) for _ in netuids], - ) - assert response.success - - stakes = [ - await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=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 - - response = await async_subtensor.staking.unstake_multiple( - wallet=alice_wallet, - netuids=netuids, - hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], - amounts=[Balance.from_tao(100) for _ in netuids], - ) - assert response.success, response.message - - for netuid, old_stake in zip(netuids, stakes): - stake = await async_subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=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 +# def test_single_operation(subtensor, alice_wallet, bob_wallet): +# """ +# Tests: +# - Staking using `add_stake` +# - Unstaking using `unstake` +# - Checks StakeInfo +# """ +# 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).success +# +# # Verify subnet 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 subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ).success +# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ).success +# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") +# +# stake = 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) +# +# assert subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1), +# period=16, +# ).success +# +# 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.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 = 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 subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# expected_stakes += fast_blocks_stake +# +# assert stakes == expected_stakes +# assert ( +# subtensor.staking.get_stake_for_coldkey +# == subtensor.staking.get_stake_info_for_coldkey +# ) +# +# stakes = 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 = 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 +# response = subtensor.staking.unstake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=stake, +# period=16, +# ) +# +# assert response.success is True +# +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# # all balances have been unstaked +# assert stake == Balance(0).set_unit(alice_subnet_netuid) +# +# +# @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 +# """ +# 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)).success +# +# # 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 +# ) +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# 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) +# +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1), +# period=16, +# ) +# ).success +# +# 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, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# 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) +# +# +# def test_batch_operations(subtensor, alice_wallet, bob_wallet): +# """ +# Tests: +# - Staking using `add_stake_multiple` +# - Unstaking using `unstake_multiple` +# - Checks StakeInfo +# - Checks Accounts Balance +# """ +# netuids = [ +# 2, +# 3, +# ] +# +# for _ in netuids: +# assert subtensor.subnets.register_subnet(alice_wallet).success +# +# # make sure we passed start_call limit for both subnets +# for netuid in netuids: +# assert wait_to_start_call(subtensor, alice_wallet, netuid) +# +# for netuid in netuids: +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=netuid, +# ).success +# +# for netuid in netuids: +# stake = subtensor.staking.get_stake( +# alice_wallet.coldkey.ss58_address, +# bob_wallet.hotkey.ss58_address, +# netuid=netuid, +# ) +# +# assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" +# +# balances = 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 +# +# alice_balance = balances[alice_wallet.coldkey.ss58_address] +# +# assert subtensor.staking.add_stake_multiple( +# wallet=alice_wallet, +# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], +# netuids=netuids, +# amounts=[Balance.from_tao(10_000) for _ in netuids], +# ).success +# +# stakes = [ +# subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=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 = 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 = 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 = subtensor.substrate.get_payment_info( +# call, alice_wallet.coldkeypub +# ) +# fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) +# dynamic_info = subtensor.subnets.subnet(netuid) +# fee_tao = dynamic_info.alpha_to_tao(fee_alpha) +# expected_fee_paid += fee_tao +# +# response = subtensor.staking.unstake_multiple( +# wallet=alice_wallet, +# netuids=netuids, +# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], +# amounts=[Balance.from_tao(100) for _ in netuids], +# raise_error=True, +# ) +# logging.console.info(f">>> res {response}") +# assert response.success, response.message +# +# for netuid, old_stake in zip(netuids, stakes): +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=netuid, +# ) +# +# assert stake < old_stake, f"netuid={netuid} stake={stake}" +# +# balances = 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 +# +# +# @pytest.mark.asyncio +# async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet): +# """ +# Async tests: +# - Staking using `add_stake_multiple` +# - Unstaking using `unstake_multiple` +# - Checks StakeInfo +# - Checks Accounts Balance +# """ +# netuids = [ +# 2, +# 3, +# ] +# +# for _ in netuids: +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# +# # 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) +# +# for netuid in netuids: +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=netuid, +# ) +# ).success +# +# 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, +# ) +# +# assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" +# +# 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 +# +# alice_balance = balances[alice_wallet.coldkey.ss58_address] +# +# response = await async_subtensor.staking.add_stake_multiple( +# wallet=alice_wallet, +# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], +# netuids=netuids, +# amounts=[Balance.from_tao(10_000) for _ in netuids], +# ) +# assert response.success +# +# stakes = [ +# await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=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 +# +# response = await async_subtensor.staking.unstake_multiple( +# wallet=alice_wallet, +# netuids=netuids, +# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], +# amounts=[Balance.from_tao(100) for _ in netuids], +# ) +# assert response.success, response.message +# +# for netuid, old_stake in zip(netuids, stakes): +# stake = await async_subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=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 def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet): @@ -803,8 +803,9 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) 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" + # The current stake may be greater or equal, but not less. It may be greater due to rapid emissions. + assert current_stake >= full_stake, ( + "Stake should not change after failed unstake attempt." ) # 2. Partial allowed - should succeed partially @@ -1005,7 +1006,8 @@ async def test_safe_staking_scenarios_async( 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, ( + # The current stake may be greater or equal, but not less. It may be greater due to rapid emissions. + assert current_stake >= full_stake, ( "Stake should not change after failed unstake attempt" ) @@ -1044,1048 +1046,1048 @@ async def test_safe_staking_scenarios_async( assert response.success is True, "Unstake should succeed" -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%) - """ - # Create new subnet (netuid 2) and register Alice - origin_netuid = 2 - assert subtensor.subnets.register_subnet(bob_wallet).success - assert subtensor.subnets.subnet_exists(origin_netuid), ( - "Subnet wasn't created successfully" - ) - dest_netuid = 3 - assert subtensor.subnets.register_subnet(bob_wallet).success - 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 - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=origin_netuid, - ).success - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dest_netuid, - ).success - - # Add initial stake to swap from - initial_stake_amount = Balance.from_tao(10_000) - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=origin_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=initial_stake_amount, - ).success - - 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 - response = 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_swapping=True, - rate_tolerance=0.005, # 0.5% - allow_partial_stake=False, - ) - assert response.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) - response = 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_swapping=True, - rate_tolerance=0.3, # 30% - allow_partial_stake=True, - ) - assert response.success is True - - # Verify 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 be non-zero after successful swap" - ) - - -@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%) - """ - # Create new subnet (netuid 2) and register Alice - origin_netuid = 2 - assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success - 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)).success - 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 - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=origin_netuid, - ) - ).success - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dest_netuid, - ) - ).success - - # Add initial stake to swap from - initial_stake_amount = Balance.from_tao(10_000) - assert ( - await async_subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=origin_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=initial_stake_amount, - ) - ).success - - 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 - response = 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_swapping=True, - rate_tolerance=0.005, # 0.5% - allow_partial_stake=False, - ) - assert response.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) - response = 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_swapping=True, - rate_tolerance=0.3, # 30% - allow_partial_stake=True, - ) - assert response.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, - ) - assert dest_stake > Balance(0).set_unit(dest_netuid), ( - "Destination stake should be non-zero after successful swap" - ) - - -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. - """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet).success - 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, - netuid=alice_subnet_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1_000), - ).success - - 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 - assert subtensor.subnets.register_subnet(bob_wallet).success - assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success - - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - ).success - - response = subtensor.staking.move_stake( - wallet=alice_wallet, - origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=alice_subnet_netuid, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, - amount=stakes[0].stake, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - assert response.success is 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, - netuid=bob_subnet_netuid, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1000), - allow_partial_stake=True, - ).success - - 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) - ) - - response = subtensor.staking.move_stake( - wallet=dave_wallet, - origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, - origin_netuid=bob_subnet_netuid, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - move_all_stake=True, - ) - assert response.success is 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) - - -@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. - """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - 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 - ) - - 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(1_000), - ) - ).success - - stakes = await async_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 = await async_subtensor.subnets.get_total_subnets() # 3 - assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success - assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert await async_wait_to_start_call( - async_subtensor, bob_wallet, bob_subnet_netuid - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ) - ).success - - assert ( - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - ) - ).success - - response = await async_subtensor.staking.move_stake( - wallet=alice_wallet, - origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=alice_subnet_netuid, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, - amount=stakes[0].stake, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - assert response.success is True - - 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 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), - 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 await async_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 = 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 ( - await async_subtensor.staking.add_stake( - wallet=dave_wallet, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, - amount=Balance.from_tao(1000), - allow_partial_stake=True, - ) - ).success - - 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]") - - 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_) - - response = await async_subtensor.staking.move_stake( - wallet=dave_wallet, - origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, - origin_netuid=bob_subnet_netuid, - destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - move_all_stake=True, - ) - assert response.success is True - - 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 moving all: {dave_stake}[orange]") - - assert dave_stake.rao == CloseInValue(0, 0.00001) - - -def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): - """ - Tests: - - Adding stake - - Transferring stake from one coldkey-subnet pair to another - """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - - assert subtensor.subnets.register_subnet(alice_wallet).success - 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.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ).success - - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1_000), - ).success - - alice_stakes = 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 = subtensor.staking.get_stake_for_coldkey( - bob_wallet.coldkey.ss58_address - ) - - assert bob_stakes == [] - - dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet(dave_wallet).success - - assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ).success - - response = 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, - ) - assert response.success is True - - alice_stakes = 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 subtensor.chain.is_fast_blocks() - else [] - ) - - assert alice_stakes == expected_alice_stake - - bob_stakes = 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 - - -@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 - """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - 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 - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ) - ).success - - 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(1_000), - ) - ).success - - 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 - assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success - - assert await async_wait_to_start_call( - async_subtensor, dave_wallet, dave_subnet_netuid - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ) - ).success - - response = 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, - ) - assert response.success is 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 - - -# 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. -@pytest.mark.parametrize( - "rate_tolerance", - [None, 1.0], - ids=[ - "Without price limit", - "With price limit", - ], -) -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.""" - # Register first SN - alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet).success - 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) - - # Register Bob and Dave in SN2 - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid_2, - ).success - - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_2, - ).success - - # Register second SN - alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet(alice_wallet).success - 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) - - # Register Bob and Dave in SN3 - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid_3, - ).success - - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_3, - ).success - - # Check Bob's stakes are empty. - assert ( - subtensor.staking.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) - == [] - ) - - # Bob stakes to Dave in both SNs - - assert subtensor.staking.add_stake( - wallet=bob_wallet, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid_2, - amount=Balance.from_tao(10000), - period=16, - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" - - assert subtensor.staking.add_stake( - wallet=bob_wallet, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid_3, - amount=Balance.from_tao(15000), - period=16, - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" - - # Check that both stakes are presented in result - bob_stakes = 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" - ): - subtensor.staking.unstake_all( - wallet=bob_wallet, - netuid=bob_stakes[0].netuid, - hotkey_ss58=bob_stakes[0].hotkey_ss58, - rate_tolerance=rate_tolerance, - ) - else: - # Successful cases - for si in bob_stakes: - assert subtensor.staking.unstake_all( - wallet=bob_wallet, - netuid=si.netuid, - hotkey_ss58=si.hotkey_ss58, - rate_tolerance=rate_tolerance, - ).success - - # Make sure both unstake were successful. - 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.""" - # Register first SN - alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - 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, - ) - ).success - - assert ( - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_2, - ) - ).success - - # Register second SN - alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - 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, - ) - ).success - - assert ( - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_3, - ) - ).success - - # 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, - netuid=alice_subnet_netuid_2, - hotkey_ss58=dave_wallet.hotkey.ss58_address, - amount=Balance.from_tao(10000), - period=16, - ) - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" - - assert ( - await async_subtensor.staking.add_stake( - wallet=bob_wallet, - netuid=alice_subnet_netuid_3, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(15000), - period=16, - ) - ).success, 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, - netuid=bob_stakes[0].netuid, - hotkey_ss58=bob_stakes[0].hotkey_ss58, - rate_tolerance=rate_tolerance, - ) - else: - # Successful cases - for si in bob_stakes: - assert ( - await async_subtensor.staking.unstake_all( - wallet=bob_wallet, - hotkey_ss58=si.hotkey_ss58, - netuid=si.netuid, - rate_tolerance=rate_tolerance, - ) - ).success - - # 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 +# 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%) +# """ +# # Create new subnet (netuid 2) and register Alice +# origin_netuid = 2 +# assert subtensor.subnets.register_subnet(bob_wallet).success +# assert subtensor.subnets.subnet_exists(origin_netuid), ( +# "Subnet wasn't created successfully" +# ) +# dest_netuid = 3 +# assert subtensor.subnets.register_subnet(bob_wallet).success +# 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 +# assert subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=origin_netuid, +# ).success +# assert subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=dest_netuid, +# ).success +# +# # Add initial stake to swap from +# initial_stake_amount = Balance.from_tao(10_000) +# assert subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=origin_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=initial_stake_amount, +# ).success +# +# 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 +# response = 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_swapping=True, +# rate_tolerance=0.005, # 0.5% +# allow_partial_stake=False, +# ) +# assert response.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) +# response = 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_swapping=True, +# rate_tolerance=0.3, # 30% +# allow_partial_stake=True, +# ) +# assert response.success is True +# +# # Verify 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 be non-zero after successful swap" +# ) +# +# +# @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%) +# """ +# # Create new subnet (netuid 2) and register Alice +# origin_netuid = 2 +# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success +# 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)).success +# 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 +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=origin_netuid, +# ) +# ).success +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=dest_netuid, +# ) +# ).success +# +# # Add initial stake to swap from +# initial_stake_amount = Balance.from_tao(10_000) +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=origin_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=initial_stake_amount, +# ) +# ).success +# +# 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 +# response = 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_swapping=True, +# rate_tolerance=0.005, # 0.5% +# allow_partial_stake=False, +# ) +# assert response.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) +# response = 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_swapping=True, +# rate_tolerance=0.3, # 30% +# allow_partial_stake=True, +# ) +# assert response.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, +# ) +# assert dest_stake > Balance(0).set_unit(dest_netuid), ( +# "Destination stake should be non-zero after successful swap" +# ) +# +# +# 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. +# """ +# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 +# assert subtensor.subnets.register_subnet(alice_wallet).success +# 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, +# netuid=alice_subnet_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1_000), +# ).success +# +# 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 +# assert subtensor.subnets.register_subnet(bob_wallet).success +# assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) +# +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ).success +# +# assert subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid, +# ).success +# +# response = subtensor.staking.move_stake( +# wallet=alice_wallet, +# origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=alice_subnet_netuid, +# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, +# destination_netuid=bob_subnet_netuid, +# amount=stakes[0].stake, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# assert response.success is 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, +# netuid=bob_subnet_netuid, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1000), +# allow_partial_stake=True, +# ).success +# +# 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) +# ) +# +# response = subtensor.staking.move_stake( +# wallet=dave_wallet, +# origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, +# origin_netuid=bob_subnet_netuid, +# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, +# destination_netuid=bob_subnet_netuid, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# move_all_stake=True, +# ) +# assert response.success is 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) +# +# +# @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. +# """ +# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# 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 +# ) +# +# 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(1_000), +# ) +# ).success +# +# stakes = await async_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 = await async_subtensor.subnets.get_total_subnets() # 3 +# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success +# assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( +# "Subnet wasn't created successfully" +# ) +# +# assert await async_wait_to_start_call( +# async_subtensor, bob_wallet, bob_subnet_netuid +# ) +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# +# response = await async_subtensor.staking.move_stake( +# wallet=alice_wallet, +# origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, +# origin_netuid=alice_subnet_netuid, +# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, +# destination_netuid=bob_subnet_netuid, +# amount=stakes[0].stake, +# wait_for_finalization=True, +# wait_for_inclusion=True, +# ) +# assert response.success is True +# +# 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 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), +# 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 await async_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 = 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 ( +# await async_subtensor.staking.add_stake( +# wallet=dave_wallet, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# netuid=bob_subnet_netuid, +# amount=Balance.from_tao(1000), +# allow_partial_stake=True, +# ) +# ).success +# +# 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]") +# +# 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_) +# +# response = await async_subtensor.staking.move_stake( +# wallet=dave_wallet, +# origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, +# origin_netuid=bob_subnet_netuid, +# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, +# destination_netuid=bob_subnet_netuid, +# wait_for_inclusion=True, +# wait_for_finalization=True, +# move_all_stake=True, +# ) +# assert response.success is True +# +# 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 moving all: {dave_stake}[orange]") +# +# assert dave_stake.rao == CloseInValue(0, 0.00001) +# +# +# def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): +# """ +# Tests: +# - Adding stake +# - Transferring stake from one coldkey-subnet pair to another +# """ +# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 +# +# assert subtensor.subnets.register_subnet(alice_wallet).success +# 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.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ).success +# +# assert subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1_000), +# ).success +# +# alice_stakes = 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 = subtensor.staking.get_stake_for_coldkey( +# bob_wallet.coldkey.ss58_address +# ) +# +# assert bob_stakes == [] +# +# dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 +# assert subtensor.subnets.register_subnet(dave_wallet).success +# +# assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) +# +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=dave_subnet_netuid, +# ).success +# +# response = 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, +# ) +# assert response.success is True +# +# alice_stakes = 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 subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# assert alice_stakes == expected_alice_stake +# +# bob_stakes = 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 +# +# +# @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 +# """ +# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 +# +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# 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 +# ) +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ) +# ).success +# +# 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(1_000), +# ) +# ).success +# +# 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 +# assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success +# +# assert await async_wait_to_start_call( +# async_subtensor, dave_wallet, dave_subnet_netuid +# ) +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=dave_subnet_netuid, +# ) +# ).success +# +# response = 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, +# ) +# assert response.success is 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 +# +# +# # 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. +# @pytest.mark.parametrize( +# "rate_tolerance", +# [None, 1.0], +# ids=[ +# "Without price limit", +# "With price limit", +# ], +# ) +# 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.""" +# # Register first SN +# alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 +# assert subtensor.subnets.register_subnet(alice_wallet).success +# 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) +# +# # Register Bob and Dave in SN2 +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid_2, +# ).success +# +# assert subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid_2, +# ).success +# +# # Register second SN +# alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 +# assert subtensor.subnets.register_subnet(alice_wallet).success +# 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) +# +# # Register Bob and Dave in SN3 +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid_3, +# ).success +# +# assert subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid_3, +# ).success +# +# # Check Bob's stakes are empty. +# assert ( +# subtensor.staking.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) +# == [] +# ) +# +# # Bob stakes to Dave in both SNs +# +# assert subtensor.staking.add_stake( +# wallet=bob_wallet, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid_2, +# amount=Balance.from_tao(10000), +# period=16, +# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" +# +# assert subtensor.staking.add_stake( +# wallet=bob_wallet, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid_3, +# amount=Balance.from_tao(15000), +# period=16, +# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" +# +# # Check that both stakes are presented in result +# bob_stakes = 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" +# ): +# subtensor.staking.unstake_all( +# wallet=bob_wallet, +# netuid=bob_stakes[0].netuid, +# hotkey_ss58=bob_stakes[0].hotkey_ss58, +# rate_tolerance=rate_tolerance, +# ) +# else: +# # Successful cases +# for si in bob_stakes: +# assert subtensor.staking.unstake_all( +# wallet=bob_wallet, +# netuid=si.netuid, +# hotkey_ss58=si.hotkey_ss58, +# rate_tolerance=rate_tolerance, +# ).success +# +# # Make sure both unstake were successful. +# 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.""" +# # Register first SN +# alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# 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, +# ) +# ).success +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid_2, +# ) +# ).success +# +# # Register second SN +# alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 +# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success +# 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, +# ) +# ).success +# +# assert ( +# await async_subtensor.subnets.burned_register( +# wallet=dave_wallet, +# netuid=alice_subnet_netuid_3, +# ) +# ).success +# +# # 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, +# netuid=alice_subnet_netuid_2, +# hotkey_ss58=dave_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(10000), +# period=16, +# ) +# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" +# +# assert ( +# await async_subtensor.staking.add_stake( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid_3, +# hotkey_ss58=alice_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(15000), +# period=16, +# ) +# ).success, 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, +# netuid=bob_stakes[0].netuid, +# hotkey_ss58=bob_stakes[0].hotkey_ss58, +# rate_tolerance=rate_tolerance, +# ) +# else: +# # Successful cases +# for si in bob_stakes: +# assert ( +# await async_subtensor.staking.unstake_all( +# wallet=bob_wallet, +# hotkey_ss58=si.hotkey_ss58, +# netuid=si.netuid, +# rate_tolerance=rate_tolerance, +# ) +# ).success +# +# # 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 def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet): From e8a9e1df46885562921f784ffdc9baade8609cc2 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:01:24 -0700 Subject: [PATCH 255/416] add `ExtrinsicResponse.extrinsic_receipt` --- bittensor/core/async_subtensor.py | 1 + bittensor/core/subtensor.py | 1 + bittensor/core/types.py | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 32305695be..7c18ea305e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4266,6 +4266,7 @@ async def sign_and_send_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) + extrinsic_response.extrinsic_receipt = response # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: extrinsic_response.message = ( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 7d9ae58754..5fad72b657 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3144,6 +3144,7 @@ def sign_and_send_extrinsic( wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) + extrinsic_response.extrinsic_receipt = response # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: extrinsic_response.message = ( diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 543ee8eb64..73d962489b 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -23,6 +23,8 @@ from bittensor_wallet import Wallet from bittensor.utils.balance import Balance from scalecodec.types import GenericExtrinsic + from async_substrate_interface.sync_substrate import ExtrinsicReceipt + from async_substrate_interface.async_substrate import AsyncExtrinsicReceipt # Type annotations for UIDs and weights. @@ -302,6 +304,7 @@ class ExtrinsicResponse: extrinsic_function: The SDK extrinsic or external function name that was executed (e.g., "add_stake_extrinsic"). extrinsic: The raw extrinsic object used in the call, if available. extrinsic_fee: The fee charged by the extrinsic, if available. + extrinsic_receipt: The receipt object of the submitted extrinsic. transaction_fee: The fee charged by the transaction (e.g., fee for add_stake or transfer_stake), if available. error: Captures the underlying exception if the extrinsic failed, otherwise `None`. data: Arbitrary data returned from the extrinsic, such as decoded events, balance or another extra context. @@ -330,6 +333,7 @@ class ExtrinsicResponse: extrinsic_function: register_subnet_extrinsic extrinsic: {'account_id': '0xd43593c715fdd31c... extrinsic_fee: τ1.0 + extrinsic_receipt: Extrinsic Receipt data transaction_fee: τ1.0 error: None data: None @@ -350,6 +354,9 @@ class ExtrinsicResponse: extrinsic_function: Optional[str] = None extrinsic: Optional["GenericExtrinsic"] = None extrinsic_fee: Optional["Balance"] = None + extrinsic_receipt: Optional[Union["AsyncExtrinsicReceipt", "ExtrinsicReceipt"]] = ( + None + ) transaction_fee: Optional["Balance"] = None error: Optional[Exception] = None data: Optional[Any] = None @@ -366,6 +373,7 @@ def __str__(self): f"\textrinsic_function: {self.extrinsic_function}\n" f"\textrinsic: {self.extrinsic}\n" f"\textrinsic_fee: {self.extrinsic_fee}\n" + f"\textrinsic_receipt: ExtrinsicReceipt\n" f"\ttransaction_fee: {self.transaction_fee}\n" f"\tdata: {self.data}\n" f"\terror: {self.error}" @@ -385,6 +393,7 @@ def as_dict(self) -> dict: "transaction_fee": str(self.transaction_fee) if self.transaction_fee else None, + "extrinsic_receipt": self.extrinsic_receipt, "error": str(self.error) if self.error else None, "data": self.data, } @@ -400,6 +409,7 @@ def __eq__(self, other: Any) -> bool: and self.extrinsic == other.extrinsic and self.extrinsic_fee == other.extrinsic_fee and self.transaction_fee == other.transaction_fee + and self.extrinsic_receipt == other.extrinsic_receipt and self.error == other.error and self.data == other.data ) From e1fb62f49a114448e76cf64485bfef708d03eeba Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:10:19 -0700 Subject: [PATCH 256/416] check GPG --- bittensor/core/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 73d962489b..8204e00e58 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -333,7 +333,7 @@ class ExtrinsicResponse: extrinsic_function: register_subnet_extrinsic extrinsic: {'account_id': '0xd43593c715fdd31c... extrinsic_fee: τ1.0 - extrinsic_receipt: Extrinsic Receipt data + extrinsic_receipt: Extrinsic Receipt data of of the submitted extrinsic transaction_fee: τ1.0 error: None data: None From c6738c15735b769948a1b67869051159c3f9bd5e Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:44:32 -0700 Subject: [PATCH 257/416] fix field representation --- bittensor/core/types.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 8204e00e58..74e5d54787 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -366,6 +366,11 @@ def __iter__(self): yield self.message def __str__(self): + _extrinsic_receipt = ( + f"ExtrinsicReceipt\n" + if self.extrinsic_receipt + else f"{self.extrinsic_receipt}\n" + ) return ( f"{self.__class__.__name__}:\n" f"\tsuccess: {self.success}\n" @@ -373,7 +378,7 @@ def __str__(self): f"\textrinsic_function: {self.extrinsic_function}\n" f"\textrinsic: {self.extrinsic}\n" f"\textrinsic_fee: {self.extrinsic_fee}\n" - f"\textrinsic_receipt: ExtrinsicReceipt\n" + f"\textrinsic_receipt: {_extrinsic_receipt}" f"\ttransaction_fee: {self.transaction_fee}\n" f"\tdata: {self.data}\n" f"\terror: {self.error}" From 603b06b6a3742cdff3ea467ea1699f07849a07b8 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:52:01 -0700 Subject: [PATCH 258/416] uncomment tests --- tests/e2e_tests/test_staking.py | 3344 +++++++++++++++---------------- 1 file changed, 1672 insertions(+), 1672 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 48368911ac..4104537ebf 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -18,636 +18,636 @@ from tests.helpers.helpers import CloseInValue -# def test_single_operation(subtensor, alice_wallet, bob_wallet): -# """ -# Tests: -# - Staking using `add_stake` -# - Unstaking using `unstake` -# - Checks StakeInfo -# """ -# 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).success -# -# # Verify subnet 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 subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ).success -# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ).success -# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") -# -# stake = 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) -# -# assert subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1), -# period=16, -# ).success -# -# 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.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 = 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 subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# expected_stakes += fast_blocks_stake -# -# assert stakes == expected_stakes -# assert ( -# subtensor.staking.get_stake_for_coldkey -# == subtensor.staking.get_stake_info_for_coldkey -# ) -# -# stakes = 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 = 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 -# response = subtensor.staking.unstake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=stake, -# period=16, -# ) -# -# assert response.success is True -# -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# # all balances have been unstaked -# assert stake == Balance(0).set_unit(alice_subnet_netuid) -# -# -# @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 -# """ -# 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)).success -# -# # 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 -# ) -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# 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) -# -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1), -# period=16, -# ) -# ).success -# -# 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, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# 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) -# -# -# def test_batch_operations(subtensor, alice_wallet, bob_wallet): -# """ -# Tests: -# - Staking using `add_stake_multiple` -# - Unstaking using `unstake_multiple` -# - Checks StakeInfo -# - Checks Accounts Balance -# """ -# netuids = [ -# 2, -# 3, -# ] -# -# for _ in netuids: -# assert subtensor.subnets.register_subnet(alice_wallet).success -# -# # make sure we passed start_call limit for both subnets -# for netuid in netuids: -# assert wait_to_start_call(subtensor, alice_wallet, netuid) -# -# for netuid in netuids: -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=netuid, -# ).success -# -# for netuid in netuids: -# stake = subtensor.staking.get_stake( -# alice_wallet.coldkey.ss58_address, -# bob_wallet.hotkey.ss58_address, -# netuid=netuid, -# ) -# -# assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" -# -# balances = 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 -# -# alice_balance = balances[alice_wallet.coldkey.ss58_address] -# -# assert subtensor.staking.add_stake_multiple( -# wallet=alice_wallet, -# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], -# netuids=netuids, -# amounts=[Balance.from_tao(10_000) for _ in netuids], -# ).success -# -# stakes = [ -# subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=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 = 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 = 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 = subtensor.substrate.get_payment_info( -# call, alice_wallet.coldkeypub -# ) -# fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) -# dynamic_info = subtensor.subnets.subnet(netuid) -# fee_tao = dynamic_info.alpha_to_tao(fee_alpha) -# expected_fee_paid += fee_tao -# -# response = subtensor.staking.unstake_multiple( -# wallet=alice_wallet, -# netuids=netuids, -# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], -# amounts=[Balance.from_tao(100) for _ in netuids], -# raise_error=True, -# ) -# logging.console.info(f">>> res {response}") -# assert response.success, response.message -# -# for netuid, old_stake in zip(netuids, stakes): -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=netuid, -# ) -# -# assert stake < old_stake, f"netuid={netuid} stake={stake}" -# -# balances = 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 -# -# -# @pytest.mark.asyncio -# async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet): -# """ -# Async tests: -# - Staking using `add_stake_multiple` -# - Unstaking using `unstake_multiple` -# - Checks StakeInfo -# - Checks Accounts Balance -# """ -# netuids = [ -# 2, -# 3, -# ] -# -# for _ in netuids: -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# -# # 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) -# -# for netuid in netuids: -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=netuid, -# ) -# ).success -# -# 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, -# ) -# -# assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" -# -# 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 -# -# alice_balance = balances[alice_wallet.coldkey.ss58_address] -# -# response = await async_subtensor.staking.add_stake_multiple( -# wallet=alice_wallet, -# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], -# netuids=netuids, -# amounts=[Balance.from_tao(10_000) for _ in netuids], -# ) -# assert response.success -# -# stakes = [ -# await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=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 -# -# response = await async_subtensor.staking.unstake_multiple( -# wallet=alice_wallet, -# netuids=netuids, -# hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], -# amounts=[Balance.from_tao(100) for _ in netuids], -# ) -# assert response.success, response.message -# -# for netuid, old_stake in zip(netuids, stakes): -# stake = await async_subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=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 +def test_single_operation(subtensor, alice_wallet, bob_wallet): + """ + Tests: + - Staking using `add_stake` + - Unstaking using `unstake` + - Checks StakeInfo + """ + 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).success + + # Verify subnet 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 subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ).success + logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ).success + logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") + + stake = 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) + + assert subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1), + period=16, + ).success + + 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.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 = 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 subtensor.chain.is_fast_blocks() + else [] + ) + + expected_stakes += fast_blocks_stake + + assert stakes == expected_stakes + assert ( + subtensor.staking.get_stake_for_coldkey + == subtensor.staking.get_stake_info_for_coldkey + ) + + stakes = 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 = 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 + response = subtensor.staking.unstake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=stake, + period=16, + ) + + assert response.success is True + + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + # all balances have been unstaked + assert stake == Balance(0).set_unit(alice_subnet_netuid) + + +@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 + """ + 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)).success + + # 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 + ) + + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ) + ).success + logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ) + ).success + 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) + + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1), + period=16, + ) + ).success + + 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, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + 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) + + +def test_batch_operations(subtensor, alice_wallet, bob_wallet): + """ + Tests: + - Staking using `add_stake_multiple` + - Unstaking using `unstake_multiple` + - Checks StakeInfo + - Checks Accounts Balance + """ + netuids = [ + 2, + 3, + ] + + for _ in netuids: + assert subtensor.subnets.register_subnet(alice_wallet).success + + # make sure we passed start_call limit for both subnets + for netuid in netuids: + assert wait_to_start_call(subtensor, alice_wallet, netuid) + + for netuid in netuids: + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=netuid, + ).success + + for netuid in netuids: + stake = subtensor.staking.get_stake( + alice_wallet.coldkey.ss58_address, + bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + + assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" + + balances = 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 + + alice_balance = balances[alice_wallet.coldkey.ss58_address] + + assert subtensor.staking.add_stake_multiple( + wallet=alice_wallet, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + netuids=netuids, + amounts=[Balance.from_tao(10_000) for _ in netuids], + ).success + + stakes = [ + subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=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 = 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 = 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 = subtensor.substrate.get_payment_info( + call, alice_wallet.coldkeypub + ) + fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) + dynamic_info = subtensor.subnets.subnet(netuid) + fee_tao = dynamic_info.alpha_to_tao(fee_alpha) + expected_fee_paid += fee_tao + + response = subtensor.staking.unstake_multiple( + wallet=alice_wallet, + netuids=netuids, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + amounts=[Balance.from_tao(100) for _ in netuids], + raise_error=True, + ) + logging.console.info(f">>> res {response}") + assert response.success, response.message + + for netuid, old_stake in zip(netuids, stakes): + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=netuid, + ) + + assert stake < old_stake, f"netuid={netuid} stake={stake}" + + balances = 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 + + +@pytest.mark.asyncio +async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet): + """ + Async tests: + - Staking using `add_stake_multiple` + - Unstaking using `unstake_multiple` + - Checks StakeInfo + - Checks Accounts Balance + """ + netuids = [ + 2, + 3, + ] + + for _ in netuids: + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + + # 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) + + for netuid in netuids: + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=netuid, + ) + ).success + + 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, + ) + + assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" + + 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 + + alice_balance = balances[alice_wallet.coldkey.ss58_address] + + response = await async_subtensor.staking.add_stake_multiple( + wallet=alice_wallet, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + netuids=netuids, + amounts=[Balance.from_tao(10_000) for _ in netuids], + ) + assert response.success + + stakes = [ + await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=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 + + response = await async_subtensor.staking.unstake_multiple( + wallet=alice_wallet, + netuids=netuids, + hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], + amounts=[Balance.from_tao(100) for _ in netuids], + ) + assert response.success, response.message + + for netuid, old_stake in zip(netuids, stakes): + stake = await async_subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=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 def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet): @@ -1046,1048 +1046,1048 @@ async def test_safe_staking_scenarios_async( assert response.success is True, "Unstake should succeed" -# 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%) -# """ -# # Create new subnet (netuid 2) and register Alice -# origin_netuid = 2 -# assert subtensor.subnets.register_subnet(bob_wallet).success -# assert subtensor.subnets.subnet_exists(origin_netuid), ( -# "Subnet wasn't created successfully" -# ) -# dest_netuid = 3 -# assert subtensor.subnets.register_subnet(bob_wallet).success -# 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 -# assert subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=origin_netuid, -# ).success -# assert subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=dest_netuid, -# ).success -# -# # Add initial stake to swap from -# initial_stake_amount = Balance.from_tao(10_000) -# assert subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=origin_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=initial_stake_amount, -# ).success -# -# 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 -# response = 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_swapping=True, -# rate_tolerance=0.005, # 0.5% -# allow_partial_stake=False, -# ) -# assert response.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) -# response = 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_swapping=True, -# rate_tolerance=0.3, # 30% -# allow_partial_stake=True, -# ) -# assert response.success is True -# -# # Verify 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 be non-zero after successful swap" -# ) -# -# -# @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%) -# """ -# # Create new subnet (netuid 2) and register Alice -# origin_netuid = 2 -# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success -# 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)).success -# 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 -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=origin_netuid, -# ) -# ).success -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=dest_netuid, -# ) -# ).success -# -# # Add initial stake to swap from -# initial_stake_amount = Balance.from_tao(10_000) -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=origin_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=initial_stake_amount, -# ) -# ).success -# -# 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 -# response = 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_swapping=True, -# rate_tolerance=0.005, # 0.5% -# allow_partial_stake=False, -# ) -# assert response.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) -# response = 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_swapping=True, -# rate_tolerance=0.3, # 30% -# allow_partial_stake=True, -# ) -# assert response.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, -# ) -# assert dest_stake > Balance(0).set_unit(dest_netuid), ( -# "Destination stake should be non-zero after successful swap" -# ) -# -# -# 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. -# """ -# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 -# assert subtensor.subnets.register_subnet(alice_wallet).success -# 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, -# netuid=alice_subnet_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1_000), -# ).success -# -# 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 -# assert subtensor.subnets.register_subnet(bob_wallet).success -# assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) -# -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ).success -# -# assert subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid, -# ).success -# -# response = subtensor.staking.move_stake( -# wallet=alice_wallet, -# origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=alice_subnet_netuid, -# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, -# destination_netuid=bob_subnet_netuid, -# amount=stakes[0].stake, -# wait_for_finalization=True, -# wait_for_inclusion=True, -# ) -# assert response.success is 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, -# netuid=bob_subnet_netuid, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1000), -# allow_partial_stake=True, -# ).success -# -# 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) -# ) -# -# response = subtensor.staking.move_stake( -# wallet=dave_wallet, -# origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, -# origin_netuid=bob_subnet_netuid, -# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, -# destination_netuid=bob_subnet_netuid, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# move_all_stake=True, -# ) -# assert response.success is 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) -# -# -# @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. -# """ -# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# 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 -# ) -# -# 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(1_000), -# ) -# ).success -# -# stakes = await async_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 = await async_subtensor.subnets.get_total_subnets() # 3 -# assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success -# assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( -# "Subnet wasn't created successfully" -# ) -# -# assert await async_wait_to_start_call( -# async_subtensor, bob_wallet, bob_subnet_netuid -# ) -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# -# response = await async_subtensor.staking.move_stake( -# wallet=alice_wallet, -# origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, -# origin_netuid=alice_subnet_netuid, -# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, -# destination_netuid=bob_subnet_netuid, -# amount=stakes[0].stake, -# wait_for_finalization=True, -# wait_for_inclusion=True, -# ) -# assert response.success is True -# -# 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 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), -# 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 await async_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 = 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 ( -# await async_subtensor.staking.add_stake( -# wallet=dave_wallet, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# netuid=bob_subnet_netuid, -# amount=Balance.from_tao(1000), -# allow_partial_stake=True, -# ) -# ).success -# -# 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]") -# -# 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_) -# -# response = await async_subtensor.staking.move_stake( -# wallet=dave_wallet, -# origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, -# origin_netuid=bob_subnet_netuid, -# destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, -# destination_netuid=bob_subnet_netuid, -# wait_for_inclusion=True, -# wait_for_finalization=True, -# move_all_stake=True, -# ) -# assert response.success is True -# -# 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 moving all: {dave_stake}[orange]") -# -# assert dave_stake.rao == CloseInValue(0, 0.00001) -# -# -# def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): -# """ -# Tests: -# - Adding stake -# - Transferring stake from one coldkey-subnet pair to another -# """ -# alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 -# -# assert subtensor.subnets.register_subnet(alice_wallet).success -# 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.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ).success -# -# assert subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1_000), -# ).success -# -# alice_stakes = 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 = subtensor.staking.get_stake_for_coldkey( -# bob_wallet.coldkey.ss58_address -# ) -# -# assert bob_stakes == [] -# -# dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 -# assert subtensor.subnets.register_subnet(dave_wallet).success -# -# assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) -# -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=dave_subnet_netuid, -# ).success -# -# response = 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, -# ) -# assert response.success is True -# -# alice_stakes = 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 subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# assert alice_stakes == expected_alice_stake -# -# bob_stakes = 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 -# -# -# @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 -# """ -# alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 -# -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# 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 -# ) -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ) -# ).success -# -# 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(1_000), -# ) -# ).success -# -# 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 -# assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success -# -# assert await async_wait_to_start_call( -# async_subtensor, dave_wallet, dave_subnet_netuid -# ) -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=dave_subnet_netuid, -# ) -# ).success -# -# response = 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, -# ) -# assert response.success is 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 -# -# -# # 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. -# @pytest.mark.parametrize( -# "rate_tolerance", -# [None, 1.0], -# ids=[ -# "Without price limit", -# "With price limit", -# ], -# ) -# 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.""" -# # Register first SN -# alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 -# assert subtensor.subnets.register_subnet(alice_wallet).success -# 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) -# -# # Register Bob and Dave in SN2 -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid_2, -# ).success -# -# assert subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid_2, -# ).success -# -# # Register second SN -# alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 -# assert subtensor.subnets.register_subnet(alice_wallet).success -# 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) -# -# # Register Bob and Dave in SN3 -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid_3, -# ).success -# -# assert subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid_3, -# ).success -# -# # Check Bob's stakes are empty. -# assert ( -# subtensor.staking.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) -# == [] -# ) -# -# # Bob stakes to Dave in both SNs -# -# assert subtensor.staking.add_stake( -# wallet=bob_wallet, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid_2, -# amount=Balance.from_tao(10000), -# period=16, -# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" -# -# assert subtensor.staking.add_stake( -# wallet=bob_wallet, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid_3, -# amount=Balance.from_tao(15000), -# period=16, -# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" -# -# # Check that both stakes are presented in result -# bob_stakes = 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" -# ): -# subtensor.staking.unstake_all( -# wallet=bob_wallet, -# netuid=bob_stakes[0].netuid, -# hotkey_ss58=bob_stakes[0].hotkey_ss58, -# rate_tolerance=rate_tolerance, -# ) -# else: -# # Successful cases -# for si in bob_stakes: -# assert subtensor.staking.unstake_all( -# wallet=bob_wallet, -# netuid=si.netuid, -# hotkey_ss58=si.hotkey_ss58, -# rate_tolerance=rate_tolerance, -# ).success -# -# # Make sure both unstake were successful. -# 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.""" -# # Register first SN -# alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# 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, -# ) -# ).success -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid_2, -# ) -# ).success -# -# # Register second SN -# alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 -# assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success -# 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, -# ) -# ).success -# -# assert ( -# await async_subtensor.subnets.burned_register( -# wallet=dave_wallet, -# netuid=alice_subnet_netuid_3, -# ) -# ).success -# -# # 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, -# netuid=alice_subnet_netuid_2, -# hotkey_ss58=dave_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(10000), -# period=16, -# ) -# ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" -# -# assert ( -# await async_subtensor.staking.add_stake( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid_3, -# hotkey_ss58=alice_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(15000), -# period=16, -# ) -# ).success, 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, -# netuid=bob_stakes[0].netuid, -# hotkey_ss58=bob_stakes[0].hotkey_ss58, -# rate_tolerance=rate_tolerance, -# ) -# else: -# # Successful cases -# for si in bob_stakes: -# assert ( -# await async_subtensor.staking.unstake_all( -# wallet=bob_wallet, -# hotkey_ss58=si.hotkey_ss58, -# netuid=si.netuid, -# rate_tolerance=rate_tolerance, -# ) -# ).success -# -# # 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 +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%) + """ + # Create new subnet (netuid 2) and register Alice + origin_netuid = 2 + assert subtensor.subnets.register_subnet(bob_wallet).success + assert subtensor.subnets.subnet_exists(origin_netuid), ( + "Subnet wasn't created successfully" + ) + dest_netuid = 3 + assert subtensor.subnets.register_subnet(bob_wallet).success + 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 + assert subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=origin_netuid, + ).success + assert subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dest_netuid, + ).success + + # Add initial stake to swap from + initial_stake_amount = Balance.from_tao(10_000) + assert subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=origin_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=initial_stake_amount, + ).success + + 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 + response = 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_swapping=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ) + assert response.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) + response = 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_swapping=True, + rate_tolerance=0.3, # 30% + allow_partial_stake=True, + ) + assert response.success is True + + # Verify 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 be non-zero after successful swap" + ) + + +@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%) + """ + # Create new subnet (netuid 2) and register Alice + origin_netuid = 2 + assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success + 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)).success + 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 + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=origin_netuid, + ) + ).success + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=dest_netuid, + ) + ).success + + # Add initial stake to swap from + initial_stake_amount = Balance.from_tao(10_000) + assert ( + await async_subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=origin_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=initial_stake_amount, + ) + ).success + + 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 + response = 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_swapping=True, + rate_tolerance=0.005, # 0.5% + allow_partial_stake=False, + ) + assert response.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) + response = 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_swapping=True, + rate_tolerance=0.3, # 30% + allow_partial_stake=True, + ) + assert response.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, + ) + assert dest_stake > Balance(0).set_unit(dest_netuid), ( + "Destination stake should be non-zero after successful swap" + ) + + +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. + """ + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + assert subtensor.subnets.register_subnet(alice_wallet).success + 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, + netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1_000), + ).success + + 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 + assert subtensor.subnets.register_subnet(bob_wallet).success + assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) + + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ).success + + assert subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid, + ).success + + response = subtensor.staking.move_stake( + wallet=alice_wallet, + origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=alice_subnet_netuid, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + amount=stakes[0].stake, + wait_for_finalization=True, + wait_for_inclusion=True, + ) + assert response.success is 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, + netuid=bob_subnet_netuid, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1000), + allow_partial_stake=True, + ).success + + 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) + ) + + response = subtensor.staking.move_stake( + wallet=dave_wallet, + origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, + origin_netuid=bob_subnet_netuid, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + move_all_stake=True, + ) + assert response.success is 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) + + +@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. + """ + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + 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 + ) + + 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(1_000), + ) + ).success + + stakes = await async_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 = await async_subtensor.subnets.get_total_subnets() # 3 + assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success + assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( + "Subnet wasn't created successfully" + ) + + assert await async_wait_to_start_call( + async_subtensor, bob_wallet, bob_subnet_netuid + ) + + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ) + ).success + + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid, + ) + ).success + + response = await async_subtensor.staking.move_stake( + wallet=alice_wallet, + origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, + origin_netuid=alice_subnet_netuid, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + amount=stakes[0].stake, + wait_for_finalization=True, + wait_for_inclusion=True, + ) + assert response.success is True + + 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 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), + 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 await async_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 = 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 ( + await async_subtensor.staking.add_stake( + wallet=dave_wallet, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=bob_subnet_netuid, + amount=Balance.from_tao(1000), + allow_partial_stake=True, + ) + ).success + + 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]") + + 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_) + + response = await async_subtensor.staking.move_stake( + wallet=dave_wallet, + origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, + origin_netuid=bob_subnet_netuid, + destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, + destination_netuid=bob_subnet_netuid, + wait_for_inclusion=True, + wait_for_finalization=True, + move_all_stake=True, + ) + assert response.success is True + + 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 moving all: {dave_stake}[orange]") + + assert dave_stake.rao == CloseInValue(0, 0.00001) + + +def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): + """ + Tests: + - Adding stake + - Transferring stake from one coldkey-subnet pair to another + """ + alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + + assert subtensor.subnets.register_subnet(alice_wallet).success + 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.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ).success + + assert subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1_000), + ).success + + alice_stakes = 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 = subtensor.staking.get_stake_for_coldkey( + bob_wallet.coldkey.ss58_address + ) + + assert bob_stakes == [] + + dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 + assert subtensor.subnets.register_subnet(dave_wallet).success + + assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) + + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, + ).success + + response = 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, + ) + assert response.success is True + + alice_stakes = 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 subtensor.chain.is_fast_blocks() + else [] + ) + + assert alice_stakes == expected_alice_stake + + bob_stakes = 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 + + +@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 + """ + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + 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 + ) + + assert ( + await async_subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ) + ).success + + 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(1_000), + ) + ).success + + 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 + assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success + + assert await async_wait_to_start_call( + async_subtensor, dave_wallet, dave_subnet_netuid + ) + + assert ( + await async_subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=dave_subnet_netuid, + ) + ).success + + response = 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, + ) + assert response.success is 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 + + +# 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. +@pytest.mark.parametrize( + "rate_tolerance", + [None, 1.0], + ids=[ + "Without price limit", + "With price limit", + ], +) +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.""" + # Register first SN + alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 + assert subtensor.subnets.register_subnet(alice_wallet).success + 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) + + # Register Bob and Dave in SN2 + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_2, + ).success + + assert subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_2, + ).success + + # Register second SN + alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 + assert subtensor.subnets.register_subnet(alice_wallet).success + 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) + + # Register Bob and Dave in SN3 + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid_3, + ).success + + assert subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_3, + ).success + + # Check Bob's stakes are empty. + assert ( + subtensor.staking.get_stake_info_for_coldkey(bob_wallet.coldkey.ss58_address) + == [] + ) + + # Bob stakes to Dave in both SNs + + assert subtensor.staking.add_stake( + wallet=bob_wallet, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid_2, + amount=Balance.from_tao(10000), + period=16, + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + + assert subtensor.staking.add_stake( + wallet=bob_wallet, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid_3, + amount=Balance.from_tao(15000), + period=16, + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" + + # Check that both stakes are presented in result + bob_stakes = 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" + ): + subtensor.staking.unstake_all( + wallet=bob_wallet, + netuid=bob_stakes[0].netuid, + hotkey_ss58=bob_stakes[0].hotkey_ss58, + rate_tolerance=rate_tolerance, + ) + else: + # Successful cases + for si in bob_stakes: + assert subtensor.staking.unstake_all( + wallet=bob_wallet, + netuid=si.netuid, + hotkey_ss58=si.hotkey_ss58, + rate_tolerance=rate_tolerance, + ).success + + # Make sure both unstake were successful. + 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.""" + # Register first SN + alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + 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, + ) + ).success + + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_2, + ) + ).success + + # Register second SN + alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 + assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + 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, + ) + ).success + + assert ( + await async_subtensor.subnets.burned_register( + wallet=dave_wallet, + netuid=alice_subnet_netuid_3, + ) + ).success + + # 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, + netuid=alice_subnet_netuid_2, + hotkey_ss58=dave_wallet.hotkey.ss58_address, + amount=Balance.from_tao(10000), + period=16, + ) + ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + + assert ( + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=alice_subnet_netuid_3, + hotkey_ss58=alice_wallet.hotkey.ss58_address, + amount=Balance.from_tao(15000), + period=16, + ) + ).success, 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, + netuid=bob_stakes[0].netuid, + hotkey_ss58=bob_stakes[0].hotkey_ss58, + rate_tolerance=rate_tolerance, + ) + else: + # Successful cases + for si in bob_stakes: + assert ( + await async_subtensor.staking.unstake_all( + wallet=bob_wallet, + hotkey_ss58=si.hotkey_ss58, + netuid=si.netuid, + rate_tolerance=rate_tolerance, + ) + ).success + + # 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 def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet): From 2882b428876a69af006f3c9ea8539ab7f7d14e1a Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:54:35 -0700 Subject: [PATCH 259/416] test GPG --- tests/e2e_tests/test_staking.py | 336 ++++++++++++++++---------------- 1 file changed, 168 insertions(+), 168 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 4104537ebf..d5f600d113 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -18,174 +18,174 @@ from tests.helpers.helpers import CloseInValue -def test_single_operation(subtensor, alice_wallet, bob_wallet): - """ - Tests: - - Staking using `add_stake` - - Unstaking using `unstake` - - Checks StakeInfo - """ - 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).success - - # Verify subnet 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 subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") - - stake = 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) - - assert subtensor.staking.add_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=Balance.from_tao(1), - period=16, - ).success - - 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.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 = 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 subtensor.chain.is_fast_blocks() - else [] - ) - - expected_stakes += fast_blocks_stake - - assert stakes == expected_stakes - assert ( - subtensor.staking.get_stake_for_coldkey - == subtensor.staking.get_stake_info_for_coldkey - ) - - stakes = 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 = 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 - response = subtensor.staking.unstake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=stake, - period=16, - ) - - assert response.success is True - - stake = subtensor.staking.get_stake( - coldkey_ss58=alice_wallet.coldkey.ss58_address, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, - ) - - # all balances have been unstaked - assert stake == Balance(0).set_unit(alice_subnet_netuid) +# def test_single_operation(subtensor, alice_wallet, bob_wallet): +# """ +# Tests: +# - Staking using `add_stake` +# - Unstaking using `unstake` +# - Checks StakeInfo +# """ +# 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).success +# +# # Verify subnet 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 subtensor.subnets.burned_register( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# ).success +# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") +# assert subtensor.subnets.burned_register( +# wallet=bob_wallet, +# netuid=alice_subnet_netuid, +# ).success +# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") +# +# stake = 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) +# +# assert subtensor.staking.add_stake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=Balance.from_tao(1), +# period=16, +# ).success +# +# 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.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 = 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 subtensor.chain.is_fast_blocks() +# else [] +# ) +# +# expected_stakes += fast_blocks_stake +# +# assert stakes == expected_stakes +# assert ( +# subtensor.staking.get_stake_for_coldkey +# == subtensor.staking.get_stake_info_for_coldkey +# ) +# +# stakes = 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 = 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 +# response = subtensor.staking.unstake( +# wallet=alice_wallet, +# netuid=alice_subnet_netuid, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# amount=stake, +# period=16, +# ) +# +# assert response.success is True +# +# stake = subtensor.staking.get_stake( +# coldkey_ss58=alice_wallet.coldkey.ss58_address, +# hotkey_ss58=bob_wallet.hotkey.ss58_address, +# netuid=alice_subnet_netuid, +# ) +# +# # all balances have been unstaked +# assert stake == Balance(0).set_unit(alice_subnet_netuid) @pytest.mark.asyncio From b1f407bcd60f6af4da15434d7a986cea09657475 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 29 Sep 2025 11:56:34 -0700 Subject: [PATCH 260/416] uncomment one more test --- tests/e2e_tests/test_staking.py | 336 ++++++++++++++++---------------- 1 file changed, 168 insertions(+), 168 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index d5f600d113..4104537ebf 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -18,174 +18,174 @@ from tests.helpers.helpers import CloseInValue -# def test_single_operation(subtensor, alice_wallet, bob_wallet): -# """ -# Tests: -# - Staking using `add_stake` -# - Unstaking using `unstake` -# - Checks StakeInfo -# """ -# 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).success -# -# # Verify subnet 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 subtensor.subnets.burned_register( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# ).success -# logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") -# assert subtensor.subnets.burned_register( -# wallet=bob_wallet, -# netuid=alice_subnet_netuid, -# ).success -# logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") -# -# stake = 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) -# -# assert subtensor.staking.add_stake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=Balance.from_tao(1), -# period=16, -# ).success -# -# 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.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 = 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 subtensor.chain.is_fast_blocks() -# else [] -# ) -# -# expected_stakes += fast_blocks_stake -# -# assert stakes == expected_stakes -# assert ( -# subtensor.staking.get_stake_for_coldkey -# == subtensor.staking.get_stake_info_for_coldkey -# ) -# -# stakes = 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 = 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 -# response = subtensor.staking.unstake( -# wallet=alice_wallet, -# netuid=alice_subnet_netuid, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# amount=stake, -# period=16, -# ) -# -# assert response.success is True -# -# stake = subtensor.staking.get_stake( -# coldkey_ss58=alice_wallet.coldkey.ss58_address, -# hotkey_ss58=bob_wallet.hotkey.ss58_address, -# netuid=alice_subnet_netuid, -# ) -# -# # all balances have been unstaked -# assert stake == Balance(0).set_unit(alice_subnet_netuid) +def test_single_operation(subtensor, alice_wallet, bob_wallet): + """ + Tests: + - Staking using `add_stake` + - Unstaking using `unstake` + - Checks StakeInfo + """ + 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).success + + # Verify subnet 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 subtensor.subnets.burned_register( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + ).success + logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") + assert subtensor.subnets.burned_register( + wallet=bob_wallet, + netuid=alice_subnet_netuid, + ).success + logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") + + stake = 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) + + assert subtensor.staking.add_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=Balance.from_tao(1), + period=16, + ).success + + 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.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 = 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 subtensor.chain.is_fast_blocks() + else [] + ) + + expected_stakes += fast_blocks_stake + + assert stakes == expected_stakes + assert ( + subtensor.staking.get_stake_for_coldkey + == subtensor.staking.get_stake_info_for_coldkey + ) + + stakes = 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 = 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 + response = subtensor.staking.unstake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=stake, + period=16, + ) + + assert response.success is True + + stake = subtensor.staking.get_stake( + coldkey_ss58=alice_wallet.coldkey.ss58_address, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + netuid=alice_subnet_netuid, + ) + + # all balances have been unstaked + assert stake == Balance(0).set_unit(alice_subnet_netuid) @pytest.mark.asyncio From 53ab89ca085a959e9c4870c6eb9f5fb2f35e77ac Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 12:47:26 -0700 Subject: [PATCH 261/416] e2e tests conftest Finished --- tests/e2e_tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 579d7dcd6c..9fa8dfe8d4 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -326,4 +326,4 @@ def log_test_start_and_end(request): test_name = request.node.nodeid logging.console.info(f"🏁[green]Testing[/green] [yellow]{test_name}[/yellow]") yield - logging.console.success(f"✅ [green]Passed[/green] [yellow]{test_name}[/yellow]") + logging.console.success(f"✅ [green]Finished[/green] [yellow]{test_name}[/yellow]") From 788559c1513a22f124fd46cad73fe9150af9d88b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 14:40:52 -0700 Subject: [PATCH 262/416] mechid related changes --- bittensor/core/async_subtensor.py | 48 ++++++++++----------- bittensor/core/chain_data/metagraph_info.py | 3 +- bittensor/core/subtensor.py | 47 ++++++++++---------- 3 files changed, 47 insertions(+), 51 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 7c18ea305e..2c33f0442a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -880,10 +880,10 @@ async def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int] async def bonds( self, netuid: int, + mechid: int = 0, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - mechid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """Retrieves the bond distribution set by subnet validators within a specific subnet. @@ -893,10 +893,10 @@ async def bonds( Parameters: netuid: Subnet identifier. + mechid: Subnet mechanism identifier. block: The block number for this query. Do not specify if using block_hash or reuse_block. block_hash: The hash of the block for the query. Do not specify if using reuse_block or block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - mechid: Subnet mechanism identifier. Returns: List of tuples mapping each neuron's UID to its bonds with other neurons. @@ -1968,15 +1968,16 @@ async def get_minimum_required_stake(self): return Balance.from_rao(getattr(result, "value", 0)) - # TODO: update parameters order in SDKv10, rename `field_indices` to `selected_indices` async def get_metagraph_info( self, netuid: int, - field_indices: Optional[Union[list[SelectiveMetagraphIndex], list[int]]] = None, + mechid: int = 0, + selected_indices: Optional[ + Union[list[SelectiveMetagraphIndex], list[int]] + ] = None, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - mechid: int = 0, ) -> Optional[MetagraphInfo]: """ Retrieves full or partial metagraph information for the specified subnet (netuid). @@ -1987,7 +1988,7 @@ async def get_metagraph_info( Parameters: netuid: The unique identifier of the subnet to query. - field_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. + selected_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. If not provided, all available fields will be returned. block: The blockchain block number for the query. block_hash: The hash of the blockchain block number at which to perform the query. @@ -2007,14 +2008,14 @@ async def get_metagraph_info( # Retrieve selective data from the metagraph from subnet 2 mechanism 0 partial_meta_info = subtensor.get_metagraph_info( netuid=2, - field_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] ) # Retrieve selective data from the metagraph from subnet 2 mechanism 1 partial_meta_info = subtensor.get_metagraph_info( netuid=2, mechid=1, - field_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] ) Notes: @@ -2029,9 +2030,9 @@ async def get_metagraph_info( indexes = ( [ f.value if isinstance(f, SelectiveMetagraphIndex) else f - for f in field_indices + for f in selected_indices ] - if field_indices is not None + if selected_indices is not None else [f for f in range(len(SelectiveMetagraphIndex))] ) @@ -2741,24 +2742,23 @@ async def get_subnet_prices( prices.update({0: Balance.from_tao(1)}) return prices - # TODO: update order in SDKv10 async def get_timelocked_weight_commits( self, netuid: int, + mechid: int = 0, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - mechid: int = 0, ) -> list[tuple[str, int, str, int]]: """ Retrieves CRv4 weight commit information for a specific subnet. Parameters: netuid: Subnet identifier. - block (Optional[int]): The blockchain block number for the query. + mechid: Subnet mechanism identifier. + block: The blockchain block number for the query. block_hash: The hash of the block to retrieve the stake 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. - mechid: Subnet mechanism identifier. Returns: A list of commit details, where each item contains: @@ -3585,13 +3585,12 @@ async def max_weight_limit( ) return None if call is None else u16_normalized_float(int(call)) - # TODO: update parameters order in SDKv10 async def metagraph( self, netuid: int, + mechid: int = 0, lite: bool = True, block: Optional[int] = None, - mechid: int = 0, ) -> "AsyncMetagraph": """ Returns a synced metagraph for a specified subnet within the Bittensor network. @@ -3599,9 +3598,9 @@ async def metagraph( Parameters: netuid: The network UID of the subnet to query. + mechid: Subnet mechanism identifier. lite: If true, returns a metagraph using a lightweight sync (no weights, no bonds). block: Block number for synchronization, or `None` for the latest block. - mechid: Subnet mechanism identifier. Returns: The metagraph representing the subnet's structure and neuron relationships. @@ -4053,14 +4052,13 @@ async def handler(block_data: dict): ) return True - # TODO: update order in SDKv10 async def weights( self, netuid: int, + mechid: int = 0, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - mechid: int = 0, ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the weight distribution set by neurons within a specific subnet of the Bittensor network. @@ -4509,13 +4507,13 @@ async def commit_weights( salt: Salt, uids: UIDs, weights: Weights, + mechid: int = 0, version_key: int = version_as_int, max_retries: int = 5, period: Optional[int] = 16, raise_error: bool = True, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, - mechid: int = 0, ) -> ExtrinsicResponse: """ Commits a hash of the subnet validator's weight vector to the Bittensor blockchain using the provided wallet. @@ -4527,6 +4525,7 @@ async def commit_weights( 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. + mechid: The subnet mechanism unique identifier. 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 @@ -4535,7 +4534,6 @@ async def commit_weights( 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. - mechid: The subnet mechanism unique identifier. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4856,13 +4854,13 @@ async def reveal_weights( uids: UIDs, weights: Weights, salt: Salt, + mechid: int = 0, max_retries: int = 5, version_key: int = version_as_int, period: Optional[int] = 16, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - mechid: int = 0, ) -> ExtrinsicResponse: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -4874,6 +4872,7 @@ async def reveal_weights( uids: NumPy array of neuron UIDs for which weights are being revealed. weights: NumPy array of weight values corresponding to each UID. salt: NumPy array of salt values corresponding to the hash function. + mechid: The subnet mechanism unique identifier. 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 @@ -4882,7 +4881,6 @@ async def reveal_weights( 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. - mechid: The subnet mechanism unique identifier. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -5208,6 +5206,7 @@ async def set_weights( netuid: int, uids: UIDs, weights: Weights, + mechid: int = 0, block_time: float = 12.0, commit_reveal_version: int = 4, max_retries: int = 5, @@ -5216,7 +5215,6 @@ async def set_weights( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - mechid: int = 0, ) -> ExtrinsicResponse: """ Sets the weight vector for a neuron acting as a validator, specifying the weights assigned to subnet miners @@ -5232,6 +5230,7 @@ async def set_weights( 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. + mechid: The subnet mechanism unique identifier. 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. @@ -5242,7 +5241,6 @@ async def set_weights( 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. - mechid: The subnet mechanism unique identifier. Returns: ExtrinsicResponse: The result object of the extrinsic execution. diff --git a/bittensor/core/chain_data/metagraph_info.py b/bittensor/core/chain_data/metagraph_info.py index 69b170c1c1..d82292fb1e 100644 --- a/bittensor/core/chain_data/metagraph_info.py +++ b/bittensor/core/chain_data/metagraph_info.py @@ -70,6 +70,7 @@ def process_nested( class MetagraphInfo(InfoBase): # Subnet index netuid: int + mechid: int # Name and symbol name: str @@ -173,8 +174,6 @@ class MetagraphInfo(InfoBase): commitments: Optional[tuple[tuple[str, str]]] - mechid: int = 0 - @classmethod def _from_dict(cls, decoded: dict) -> "MetagraphInfo": """Returns a MetagraphInfo object from decoded chain data.""" diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 5fad72b657..a5c7cd7224 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -503,8 +503,8 @@ def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int]: def bonds( self, netuid: int, - block: Optional[int] = None, mechid: int = 0, + block: Optional[int] = None, ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the bond distribution set by neurons within a specific subnet of the Bittensor network. @@ -514,8 +514,8 @@ def bonds( Parameters: netuid: Subnet identifier. - block: the block number for this query. mechid: Subnet mechanism identifier. + block: the block number for this query. Returns: List of tuples mapping each neuron's UID to its bonds with other neurons. @@ -1303,11 +1303,12 @@ def get_minimum_required_stake(self) -> Balance: return Balance.from_rao(getattr(result, "value", 0)) - # TODO: update parameters order in SDKv10, rename `field_indices` to `selected_indices` def get_metagraph_info( self, netuid: int, - field_indices: Optional[Union[list[SelectiveMetagraphIndex], list[int]]] = None, + selected_indices: Optional[ + Union[list[SelectiveMetagraphIndex], list[int]] + ] = None, block: Optional[int] = None, mechid: int = 0, ) -> Optional[MetagraphInfo]: @@ -1316,10 +1317,10 @@ def get_metagraph_info( Parameters: netuid: Subnet unique identifier. - field_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. + mechid: Subnet mechanism unique identifier. + selected_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. If not provided, all available fields will be returned. block: The block number at which to query the data. - mechid: Subnet mechanism unique identifier. Returns: MetagraphInfo object with the requested subnet mechanism data, None if the subnet mechanism does not exist. @@ -1334,14 +1335,14 @@ def get_metagraph_info( # Retrieve selective data from the metagraph from subnet 2 mechanism 0 partial_meta_info = subtensor.get_metagraph_info( netuid=2, - field_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] ) # Retrieve selective data from the metagraph from subnet 2 mechanism 1 partial_meta_info = subtensor.get_metagraph_info( netuid=2, mechid=1, - field_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] ) Notes: @@ -1354,9 +1355,9 @@ def get_metagraph_info( indexes = ( [ f.value if isinstance(f, SelectiveMetagraphIndex) else f - for f in field_indices + for f in selected_indices ] - if field_indices is not None + if selected_indices is not None else [f for f in range(len(SelectiveMetagraphIndex))] ) @@ -1960,20 +1961,19 @@ def get_subnet_prices( prices.update({0: Balance.from_tao(1)}) return prices - # TODO: update order in SDKv10 def get_timelocked_weight_commits( self, netuid: int, - block: Optional[int] = None, mechid: int = 0, + block: Optional[int] = None, ) -> list[tuple[str, int, str, int]]: """ Retrieves CRv4 weight commit information for a specific subnet. Parameters: netuid: Subnet identifier. - block: The blockchain block number for the query. mechid: Subnet mechanism identifier. + block: The blockchain block number for the query. Returns: A list of commit details, where each item contains: @@ -2621,9 +2621,9 @@ def max_weight_limit( def metagraph( self, netuid: int, + mechid: int = 0, lite: bool = True, block: Optional[int] = None, - mechid: int = 0, ) -> "Metagraph": """ Returns a synced metagraph for a specified subnet within the Bittensor network. @@ -2631,9 +2631,9 @@ def metagraph( Parameters: netuid: The network UID of the subnet to query. + mechid: Subnet mechanism identifier. lite: If true, returns a metagraph using a lightweight sync (no weights, no bonds). block: Block number for synchronization, or `None` for the latest block. - mechid: Subnet mechanism identifier. Returns: The metagraph representing the subnet's structure and neuron relationships. @@ -2963,12 +2963,11 @@ def handler(block_data: dict): ) return True - # TODO: update order in SDKv10 def weights( self, netuid: int, - block: Optional[int] = None, mechid: int = 0, + block: Optional[int] = None, ) -> list[tuple[int, list[tuple[int, int]]]]: """ Retrieves the weight distribution set by neurons within a specific subnet of the Bittensor network. @@ -2977,8 +2976,8 @@ def weights( Parameters: netuid: The network UID of the subnet to query. - block: Block number for synchronization, or ``None`` for the latest block. mechid: Subnet mechanism identifier. + block: Block number for synchronization, or ``None`` for the latest block. Returns: A list of tuples mapping each neuron's UID to its assigned weights. @@ -3387,13 +3386,13 @@ def commit_weights( salt: Salt, uids: UIDs, weights: Weights, + mechid: int = 0, version_key: int = version_as_int, max_retries: int = 5, period: Optional[int] = 16, raise_error: bool = True, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, - mechid: int = 0, ) -> ExtrinsicResponse: """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. @@ -3405,6 +3404,7 @@ def commit_weights( 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. + mechid: Subnet mechanism unique identifier. 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 @@ -3413,7 +3413,6 @@ def commit_weights( 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. - mechid: Subnet mechanism unique identifier. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -3731,13 +3730,13 @@ def reveal_weights( uids: UIDs, weights: Weights, salt: Salt, + mechid: int = 0, max_retries: int = 5, version_key: int = version_as_int, period: Optional[int] = 16, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - mechid: int = 0, ) -> ExtrinsicResponse: """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. @@ -3749,6 +3748,7 @@ def reveal_weights( uids: NumPy array of neuron UIDs for which weights are being revealed. weights: NumPy array of weight values corresponding to each UID. salt: NumPy array of salt values corresponding to the hash function. + mechid: The subnet mechanism unique identifier. 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 @@ -3757,7 +3757,6 @@ def reveal_weights( 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. - mechid: The subnet mechanism unique identifier. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -4070,6 +4069,7 @@ def set_weights( netuid: int, uids: UIDs, weights: Weights, + mechid: int = 0, block_time: float = 12.0, commit_reveal_version: int = 4, max_retries: int = 5, @@ -4078,7 +4078,6 @@ def set_weights( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - mechid: int = 0, ) -> ExtrinsicResponse: """ Sets the interneuronal weights for the specified neuron. This process involves specifying the influence or trust @@ -4091,6 +4090,7 @@ def set_weights( 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. + mechid: The subnet mechanism unique identifier. 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. @@ -4101,7 +4101,6 @@ def set_weights( 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. - mechid: The subnet mechanism unique identifier. Returns: ExtrinsicResponse: The result object of the extrinsic execution. From a84f8d3a95ecd00d2f7d336c57f13aaa3a36dfe8 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 14:41:00 -0700 Subject: [PATCH 263/416] fix tests --- tests/e2e_tests/test_incentive.py | 4 ++-- tests/e2e_tests/test_metagraph.py | 12 ++++++------ tests/unit_tests/test_async_subtensor.py | 4 ++-- tests/unit_tests/test_subtensor.py | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index ed1c180a54..e0e0ce7329 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -134,7 +134,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): subtensor.wait_for_block(next_epoch_start_block + tempo + 1) validators = subtensor.metagraphs.get_metagraph_info( - alice_subnet_netuid, field_indices=[72] + alice_subnet_netuid, selected_indices=[72] ).validators alice_uid = subtensor.subnets.get_uid_for_hotkey_on_subnet( @@ -320,7 +320,7 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal validators = ( await async_subtensor.metagraphs.get_metagraph_info( - alice_subnet_netuid, field_indices=[72] + alice_subnet_netuid, selected_indices=[72] ) ).validators diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index aed0270286..32d7c374a9 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -891,7 +891,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 assert subtensor.subnets.register_subnet(alice_wallet) - field_indices = [ + selected_indices = [ SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.Active, SelectiveMetagraphIndex.OwnerHotkey, @@ -900,7 +900,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): ] metagraph_info = subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, field_indices=field_indices + netuid=alice_subnet_netuid, selected_indices=selected_indices ) assert metagraph_info == MetagraphInfo( @@ -1007,7 +1007,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): ] metagraph_info = subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, field_indices=fields + netuid=alice_subnet_netuid, selected_indices=fields ) assert metagraph_info == MetagraphInfo( @@ -1125,7 +1125,7 @@ async def test_metagraph_info_with_indexes_async( alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 assert await async_subtensor.subnets.register_subnet(alice_wallet) - field_indices = [ + selected_indices = [ SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.Active, SelectiveMetagraphIndex.OwnerHotkey, @@ -1134,7 +1134,7 @@ async def test_metagraph_info_with_indexes_async( ] metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, field_indices=field_indices + netuid=alice_subnet_netuid, selected_indices=selected_indices ) assert metagraph_info == MetagraphInfo( @@ -1245,7 +1245,7 @@ async def test_metagraph_info_with_indexes_async( ] metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, field_indices=fields + netuid=alice_subnet_netuid, selected_indices=fields ) assert metagraph_info == MetagraphInfo( diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 52fe385f47..e0a25e5a09 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2962,7 +2962,7 @@ async def test_get_metagraph_info_all_fields(subtensor, mocker): # Call result = await subtensor.get_metagraph_info( - netuid=netuid, field_indices=[f for f in range(len(SelectiveMetagraphIndex))] + netuid=netuid, selected_indices=[f for f in range(len(SelectiveMetagraphIndex))] ) # Asserts @@ -2995,7 +2995,7 @@ async def test_get_metagraph_info_specific_fields(subtensor, mocker): ) # Call - result = await subtensor.get_metagraph_info(netuid=netuid, field_indices=fields) + result = await subtensor.get_metagraph_info(netuid=netuid, selected_indices=fields) # Asserts assert result == "parsed_metagraph" diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 0d2f5830be..ce942ce397 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1023,7 +1023,7 @@ def test_metagraph(subtensor, mocker): mocked_metagraph = mocker.patch.object(subtensor_module, "Metagraph") # Call - result = subtensor.metagraph(fake_netuid, fake_lite) + result = subtensor.metagraph(fake_netuid, lite=fake_lite) # Asserts mocked_metagraph.assert_called_once_with( @@ -3162,7 +3162,7 @@ def test_get_metagraph_info_all_fields(subtensor, mocker): # Call result = subtensor.get_metagraph_info( - netuid=netuid, field_indices=[f for f in range(len(SelectiveMetagraphIndex))] + netuid=netuid, selected_indices=[f for f in range(len(SelectiveMetagraphIndex))] ) # Asserts @@ -3194,7 +3194,7 @@ def test_get_metagraph_info_specific_fields(subtensor, mocker): ) # Call - result = subtensor.get_metagraph_info(netuid=netuid, field_indices=fields) + result = subtensor.get_metagraph_info(netuid=netuid, selected_indices=fields) # Asserts assert result == "parsed_metagraph" From ba633be50b954081a98a4561ad1dd685b53d8c37 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 14:41:09 -0700 Subject: [PATCH 264/416] update MIGRATION.md --- MIGRATION.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 128f70ed28..9aaf09bbbe 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -234,3 +234,20 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `query_map_subtensor` has updated parameters order. - method `query_map` has updated parameters order. - method `add_stake_multiple` has updated parameters order. + +### Mechid related changes: +In the next subtensor methods got updated the parameters order: + - `bonds` + - `get_metagraph_info` + - `get_timelocked_weight_commits` + - `metagraph` + - `weights` + - `commit_weights` + - `reveal_weights` + - `set_weights` + +Additional: + - `bittensor.core.chain_data.metagraph_info.MetagraphInfo` got required attribute `mechid: int`. + +### Renames parameters: +- `get_metagraph_info`: `field_indices` -> `selected_indices` (to be consistent) \ No newline at end of file From 3a3ed121302bed52beb2b4f979de957c3c63e567 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 15:10:20 -0700 Subject: [PATCH 265/416] opps e2e test fix --- MIGRATION.md | 2 +- tests/e2e_tests/test_metagraph.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 9aaf09bbbe..5065daceaf 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -250,4 +250,4 @@ Additional: - `bittensor.core.chain_data.metagraph_info.MetagraphInfo` got required attribute `mechid: int`. ### Renames parameters: -- `get_metagraph_info`: `field_indices` -> `selected_indices` (to be consistent) \ No newline at end of file +- `get_metagraph_info`: `field_indices` -> `selected_indices` (to be consistent) diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 32d7c374a9..9553fda968 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -380,6 +380,7 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): expected_metagraph_info = MetagraphInfo( netuid=1, + mechid=0, name="apex", symbol="α", identity=None, @@ -477,6 +478,7 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): expected_metagraph_infos = [ MetagraphInfo( netuid=0, + mechid=0, name="root", symbol="Τ", identity=None, @@ -636,6 +638,7 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): expected_metagraph_info = MetagraphInfo( netuid=1, + mechid=0, name="apex", symbol="α", identity=None, @@ -733,6 +736,7 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): expected_metagraph_infos = [ MetagraphInfo( netuid=0, + mechid=0, name="root", symbol="Τ", identity=None, @@ -905,6 +909,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): assert metagraph_info == MetagraphInfo( netuid=alice_subnet_netuid, + mechid=0, name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, owner_coldkey=alice_wallet.coldkey.ss58_address, @@ -1012,6 +1017,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): assert metagraph_info == MetagraphInfo( netuid=alice_subnet_netuid, + mechid=0, name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, owner_coldkey=alice_wallet.coldkey.ss58_address, @@ -1139,6 +1145,7 @@ async def test_metagraph_info_with_indexes_async( assert metagraph_info == MetagraphInfo( netuid=alice_subnet_netuid, + mechid=0, name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, owner_coldkey=alice_wallet.coldkey.ss58_address, @@ -1250,6 +1257,7 @@ async def test_metagraph_info_with_indexes_async( assert metagraph_info == MetagraphInfo( netuid=alice_subnet_netuid, + mechid=0, name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, owner_coldkey=alice_wallet.coldkey.ss58_address, From eee0c64694d487cd71687c084b407982100f5179 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 15:55:18 -0700 Subject: [PATCH 266/416] method `get_stake_for_coldkey` removed, bc this is the same as `get_stake_info_for_coldkey` --- MIGRATION.md | 5 +-- bittensor/core/async_subtensor.py | 4 +-- bittensor/core/extrinsics/asyncex/staking.py | 2 +- .../core/extrinsics/asyncex/unstaking.py | 2 +- bittensor/core/extrinsics/staking.py | 2 +- bittensor/core/extrinsics/unstaking.py | 2 +- bittensor/core/subtensor.py | 4 +-- bittensor/core/subtensor_api/staking.py | 1 - bittensor/core/subtensor_api/utils.py | 1 - bittensor/core/subtensor_api/wallets.py | 1 - tests/e2e_tests/test_staking.py | 36 ++++++++----------- tests/unit_tests/extrinsics/test_staking.py | 2 +- 12 files changed, 24 insertions(+), 38 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 5065daceaf..d73a6377da 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -27,9 +27,9 @@ 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`. +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. +6. ~~Add `hotkey_ss58` parameter to `get_liquidity_list` method. One wallet can have many HKs. Currently, the mentioned method uses default HK only.~~ wrong idea ## Metagraph 1. Remove verbose archival node warnings for blocks older than 300. Some users complained about many messages for them. @@ -234,6 +234,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `query_map_subtensor` has updated parameters order. - method `query_map` has updated parameters order. - method `add_stake_multiple` has updated parameters order. +- method `get_stake_for_coldkey` removed, bc this is the same as `get_stake_info_for_coldkey` ### Mechid related changes: In the next subtensor methods got updated the parameters order: diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 2c33f0442a..7b5a1a7781 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2875,7 +2875,7 @@ async def get_stake_for_coldkey_and_hotkey( for (netuid, result) in zip(all_netuids, results) } - async def get_stake_for_coldkey( + async def get_stake_info_for_coldkey( self, coldkey_ss58: str, block: Optional[int] = None, @@ -2909,8 +2909,6 @@ async def get_stake_for_coldkey( stakes: list[StakeInfo] = StakeInfo.list_from_dicts(result) return [stake for stake in stakes if stake.stake > 0] - get_stake_info_for_coldkey = get_stake_for_coldkey - async def get_stake_for_hotkey( self, hotkey_ss58: str, diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 865b17f806..34b3229bb4 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -269,7 +269,7 @@ async def add_stake_multiple_extrinsic( block_hash = await subtensor.substrate.get_chain_head() - all_stakes = await subtensor.get_stake_for_coldkey( + all_stakes = await subtensor.get_stake_info_for_coldkey( coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash ) old_stakes: list[Balance] = get_old_stakes( diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index f6f5fce8bd..d3acf3f9bc 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -352,7 +352,7 @@ async def unstake_multiple_extrinsic( block_hash = await subtensor.substrate.get_chain_head() all_stakes, old_balance = await asyncio.gather( - subtensor.get_stake_for_coldkey( + subtensor.get_stake_info_for_coldkey( coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash ), subtensor.get_balance( diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index ba53409f17..dd982bcf67 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -262,7 +262,7 @@ def add_stake_multiple_extrinsic( return ExtrinsicResponse(True, "Success") block = subtensor.get_current_block() - all_stakes = subtensor.get_stake_for_coldkey( + all_stakes = subtensor.get_stake_info_for_coldkey( coldkey_ss58=wallet.coldkeypub.ss58_address, ) old_stakes: list[Balance] = get_old_stakes( diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 6e2d2bdb27..45bda590d0 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -349,7 +349,7 @@ def unstake_multiple_extrinsic( old_balance = subtensor.get_balance( address=wallet.coldkeypub.ss58_address, block=block ) - all_stakes = subtensor.get_stake_for_coldkey( + all_stakes = subtensor.get_stake_info_for_coldkey( coldkey_ss58=wallet.coldkeypub.ss58_address, block=block, ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index a5c7cd7224..a7a0f7c790 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2074,7 +2074,7 @@ def get_stake_for_coldkey_and_hotkey( for (netuid, result) in zip(all_netuids, results) } - def get_stake_for_coldkey( + def get_stake_info_for_coldkey( self, coldkey_ss58: str, block: Optional[int] = None ) -> list["StakeInfo"]: """ @@ -2099,8 +2099,6 @@ def get_stake_for_coldkey( stakes: list[StakeInfo] = StakeInfo.list_from_dicts(result) return [stake for stake in stakes if stake.stake > 0] - get_stake_info_for_coldkey = get_stake_for_coldkey - def get_stake_for_hotkey( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None ) -> Balance: diff --git a/bittensor/core/subtensor_api/staking.py b/bittensor/core/subtensor_api/staking.py index 9c2733822c..470bd9eeb2 100644 --- a/bittensor/core/subtensor_api/staking.py +++ b/bittensor/core/subtensor_api/staking.py @@ -14,7 +14,6 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_minimum_required_stake = subtensor.get_minimum_required_stake 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 self.get_stake_for_coldkey_and_hotkey = ( subtensor.get_stake_for_coldkey_and_hotkey ) diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index ec5785e88d..a07292d76a 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -75,7 +75,6 @@ def add_legacy_methods(subtensor: "SubtensorApi"): ) subtensor.get_stake = subtensor._subtensor.get_stake subtensor.get_stake_add_fee = subtensor._subtensor.get_stake_add_fee - subtensor.get_stake_for_coldkey = subtensor._subtensor.get_stake_for_coldkey subtensor.get_stake_for_coldkey_and_hotkey = ( subtensor._subtensor.get_stake_for_coldkey_and_hotkey ) diff --git a/bittensor/core/subtensor_api/wallets.py b/bittensor/core/subtensor_api/wallets.py index 9b3a3a058b..3ad195ae45 100644 --- a/bittensor/core/subtensor_api/wallets.py +++ b/bittensor/core/subtensor_api/wallets.py @@ -30,7 +30,6 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): 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 self.get_stake_for_coldkey_and_hotkey = ( subtensor.get_stake_for_coldkey_and_hotkey ) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 4104537ebf..78859e4038 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -80,7 +80,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.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + stakes = subtensor.staking.get_stake_info_for_coldkey(alice_wallet.coldkey.ss58_address) expected_stakes = [ StakeInfo( @@ -117,10 +117,6 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): expected_stakes += fast_blocks_stake assert stakes == expected_stakes - assert ( - subtensor.staking.get_stake_for_coldkey - == subtensor.staking.get_stake_info_for_coldkey - ) stakes = subtensor.staking.get_stake_for_coldkey_and_hotkey( alice_wallet.coldkey.ss58_address, @@ -259,7 +255,7 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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( + stakes = await async_subtensor.staking.get_stake_info_for_coldkey( alice_wallet.coldkey.ss58_address ) @@ -298,10 +294,6 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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, @@ -1287,7 +1279,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): amount=Balance.from_tao(1_000), ).success - stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + stakes = subtensor.staking.get_stake_info_for_coldkey(alice_wallet.coldkey.ss58_address) assert stakes == [ StakeInfo( @@ -1332,7 +1324,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ) assert response.success is True - stakes = subtensor.staking.get_stake_for_coldkey(alice_wallet.coldkey.ss58_address) + stakes = subtensor.staking.get_stake_info_for_coldkey(alice_wallet.coldkey.ss58_address) expected_stakes = [ StakeInfo( @@ -1446,7 +1438,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ ) ).success - stakes = await async_subtensor.staking.get_stake_for_coldkey( + stakes = await async_subtensor.staking.get_stake_info_for_coldkey( alice_wallet.coldkey.ss58_address ) @@ -1499,7 +1491,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ ) assert response.success is True - stakes = await async_subtensor.staking.get_stake_for_coldkey( + stakes = await async_subtensor.staking.get_stake_info_for_coldkey( alice_wallet.coldkey.ss58_address ) @@ -1618,7 +1610,7 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): amount=Balance.from_tao(1_000), ).success - alice_stakes = subtensor.staking.get_stake_for_coldkey( + alice_stakes = subtensor.staking.get_stake_info_for_coldkey( alice_wallet.coldkey.ss58_address ) @@ -1637,7 +1629,7 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ), ] - bob_stakes = subtensor.staking.get_stake_for_coldkey( + bob_stakes = subtensor.staking.get_stake_info_for_coldkey( bob_wallet.coldkey.ss58_address ) @@ -1665,7 +1657,7 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ) assert response.success is True - alice_stakes = subtensor.staking.get_stake_for_coldkey( + alice_stakes = subtensor.staking.get_stake_info_for_coldkey( alice_wallet.coldkey.ss58_address ) @@ -1692,7 +1684,7 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): assert alice_stakes == expected_alice_stake - bob_stakes = subtensor.staking.get_stake_for_coldkey( + bob_stakes = subtensor.staking.get_stake_info_for_coldkey( bob_wallet.coldkey.ss58_address ) @@ -1749,7 +1741,7 @@ async def test_transfer_stake_async( ) ).success - alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( alice_wallet.coldkey.ss58_address ) @@ -1768,7 +1760,7 @@ async def test_transfer_stake_async( ), ] - bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( + bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( bob_wallet.coldkey.ss58_address ) @@ -1800,7 +1792,7 @@ async def test_transfer_stake_async( ) assert response.success is True - alice_stakes = await async_subtensor.staking.get_stake_for_coldkey( + alice_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( alice_wallet.coldkey.ss58_address ) @@ -1827,7 +1819,7 @@ async def test_transfer_stake_async( assert alice_stakes == expected_alice_stake - bob_stakes = await async_subtensor.staking.get_stake_for_coldkey( + bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( bob_wallet.coldkey.ss58_address ) diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 0ec04007e5..35d4677cf2 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -62,7 +62,7 @@ def test_add_stake_multiple_extrinsic(subtensor, mocker, fake_wallet): """Verify that sync `add_stake_multiple_extrinsic` method calls proper async method.""" # Preps mocked_get_stake_for_coldkey = mocker.patch.object( - subtensor, "get_stake_for_coldkey", return_value=[Balance(1.1), Balance(0.3)] + subtensor, "get_stake_info_for_coldkey", return_value=[Balance(1.1), Balance(0.3)] ) mocked_get_balance = mocker.patch.object( subtensor, "get_balance", return_value=Balance.from_tao(10) From 5bde1ea9d33f5458a018e3cff4085d59c5933103 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 16:10:27 -0700 Subject: [PATCH 267/416] `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` --- MIGRATION.md | 4 ++++ bittensor/core/addons/__init__.py | 10 ++++++++++ bittensor/core/{ => addons}/subtensor_api/__init__.py | 0 bittensor/core/{ => addons}/subtensor_api/chain.py | 0 .../core/{ => addons}/subtensor_api/commitments.py | 0 bittensor/core/{ => addons}/subtensor_api/delegates.py | 0 .../core/{ => addons}/subtensor_api/extrinsics.py | 0 .../core/{ => addons}/subtensor_api/metagraphs.py | 0 bittensor/core/{ => addons}/subtensor_api/neurons.py | 0 bittensor/core/{ => addons}/subtensor_api/queries.py | 0 bittensor/core/{ => addons}/subtensor_api/staking.py | 0 bittensor/core/{ => addons}/subtensor_api/subnets.py | 0 bittensor/core/{ => addons}/subtensor_api/utils.py | 0 bittensor/core/{ => addons}/subtensor_api/wallets.py | 0 bittensor/utils/easy_imports.py | 3 +-- tests/unit_tests/test_subtensor_api.py | 5 +++-- 16 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 bittensor/core/addons/__init__.py rename bittensor/core/{ => addons}/subtensor_api/__init__.py (100%) rename bittensor/core/{ => addons}/subtensor_api/chain.py (100%) rename bittensor/core/{ => addons}/subtensor_api/commitments.py (100%) rename bittensor/core/{ => addons}/subtensor_api/delegates.py (100%) rename bittensor/core/{ => addons}/subtensor_api/extrinsics.py (100%) rename bittensor/core/{ => addons}/subtensor_api/metagraphs.py (100%) rename bittensor/core/{ => addons}/subtensor_api/neurons.py (100%) rename bittensor/core/{ => addons}/subtensor_api/queries.py (100%) rename bittensor/core/{ => addons}/subtensor_api/staking.py (100%) rename bittensor/core/{ => addons}/subtensor_api/subnets.py (100%) rename bittensor/core/{ => addons}/subtensor_api/utils.py (100%) rename bittensor/core/{ => addons}/subtensor_api/wallets.py (100%) diff --git a/MIGRATION.md b/MIGRATION.md index d73a6377da..a4a3d454c1 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -236,6 +236,10 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `add_stake_multiple` has updated parameters order. - method `get_stake_for_coldkey` removed, bc this is the same as `get_stake_info_for_coldkey` +Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. + - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` + - + ### Mechid related changes: In the next subtensor methods got updated the parameters order: - `bonds` diff --git a/bittensor/core/addons/__init__.py b/bittensor/core/addons/__init__.py new file mode 100644 index 0000000000..daa1ce0ff0 --- /dev/null +++ b/bittensor/core/addons/__init__.py @@ -0,0 +1,10 @@ +""" +The `addons` sub-package contains optional extensions and logic augmentations for the core functionality of the project. + +Modules placed in this package may include experimental features, alternative implementations, developer tools, or +enhancements that extend or customize core behavior. These components are not always critical for the main application, +but can be enabled or imported as needed for advanced use cases, internal tooling, or feature expansion. + +Use this package to keep optional, modular, or feature-gated logic separate from the primary codebase while maintaining +discoverability and structure. +""" diff --git a/bittensor/core/subtensor_api/__init__.py b/bittensor/core/addons/subtensor_api/__init__.py similarity index 100% rename from bittensor/core/subtensor_api/__init__.py rename to bittensor/core/addons/subtensor_api/__init__.py diff --git a/bittensor/core/subtensor_api/chain.py b/bittensor/core/addons/subtensor_api/chain.py similarity index 100% rename from bittensor/core/subtensor_api/chain.py rename to bittensor/core/addons/subtensor_api/chain.py diff --git a/bittensor/core/subtensor_api/commitments.py b/bittensor/core/addons/subtensor_api/commitments.py similarity index 100% rename from bittensor/core/subtensor_api/commitments.py rename to bittensor/core/addons/subtensor_api/commitments.py diff --git a/bittensor/core/subtensor_api/delegates.py b/bittensor/core/addons/subtensor_api/delegates.py similarity index 100% rename from bittensor/core/subtensor_api/delegates.py rename to bittensor/core/addons/subtensor_api/delegates.py diff --git a/bittensor/core/subtensor_api/extrinsics.py b/bittensor/core/addons/subtensor_api/extrinsics.py similarity index 100% rename from bittensor/core/subtensor_api/extrinsics.py rename to bittensor/core/addons/subtensor_api/extrinsics.py diff --git a/bittensor/core/subtensor_api/metagraphs.py b/bittensor/core/addons/subtensor_api/metagraphs.py similarity index 100% rename from bittensor/core/subtensor_api/metagraphs.py rename to bittensor/core/addons/subtensor_api/metagraphs.py diff --git a/bittensor/core/subtensor_api/neurons.py b/bittensor/core/addons/subtensor_api/neurons.py similarity index 100% rename from bittensor/core/subtensor_api/neurons.py rename to bittensor/core/addons/subtensor_api/neurons.py diff --git a/bittensor/core/subtensor_api/queries.py b/bittensor/core/addons/subtensor_api/queries.py similarity index 100% rename from bittensor/core/subtensor_api/queries.py rename to bittensor/core/addons/subtensor_api/queries.py diff --git a/bittensor/core/subtensor_api/staking.py b/bittensor/core/addons/subtensor_api/staking.py similarity index 100% rename from bittensor/core/subtensor_api/staking.py rename to bittensor/core/addons/subtensor_api/staking.py diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/addons/subtensor_api/subnets.py similarity index 100% rename from bittensor/core/subtensor_api/subnets.py rename to bittensor/core/addons/subtensor_api/subnets.py diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/addons/subtensor_api/utils.py similarity index 100% rename from bittensor/core/subtensor_api/utils.py rename to bittensor/core/addons/subtensor_api/utils.py diff --git a/bittensor/core/subtensor_api/wallets.py b/bittensor/core/addons/subtensor_api/wallets.py similarity index 100% rename from bittensor/core/subtensor_api/wallets.py rename to bittensor/core/addons/subtensor_api/wallets.py diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index de3399aaa5..31bd503b56 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -28,6 +28,7 @@ from bittensor_wallet.wallet import Wallet from bittensor.core import settings, timelock +from bittensor.core.addons.subtensor_api import SubtensorApi from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.core.axon import Axon from bittensor.core.chain_data import ( @@ -99,7 +100,6 @@ from bittensor.core.settings import BLOCKTIME from bittensor.core.stream import StreamingSynapse from bittensor.core.subtensor import Subtensor -from bittensor.core.subtensor_api import SubtensorApi from bittensor.core.synapse import TerminalInfo, Synapse from bittensor.core.tensor import Tensor from bittensor.core.threadpool import PriorityThreadPoolExecutor @@ -119,7 +119,6 @@ from bittensor.utils.mock.subtensor_mock import MockSubtensor from bittensor.utils.subnets import SubnetsAPI - # Backwards compatibility with previous bittensor versions. async_subtensor = AsyncSubtensor axon = Axon diff --git a/tests/unit_tests/test_subtensor_api.py b/tests/unit_tests/test_subtensor_api.py index 5007370d96..785aab0926 100644 --- a/tests/unit_tests/test_subtensor_api.py +++ b/tests/unit_tests/test_subtensor_api.py @@ -1,7 +1,8 @@ -from bittensor.core.subtensor import Subtensor -from bittensor.core.subtensor_api import SubtensorApi import pytest +from bittensor.core.addons.subtensor_api import SubtensorApi +from bittensor.core.subtensor import Subtensor + def test_properties_methods_comparable(other_class: "Subtensor" = None): """Verifies that methods in SubtensorApi and its properties contains all Subtensors methods.""" From 39b8eb2cc820f83cd6c5d538bc5d214a17e96408 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 16:16:57 -0700 Subject: [PATCH 268/416] fix imports + move timelock impl to addons --- bittensor/core/addons/__init__.py | 9 +++++++++ bittensor/core/addons/subtensor_api/utils.py | 2 +- bittensor/core/{ => addons}/timelock.py | 0 bittensor/utils/easy_imports.py | 4 ++-- tests/e2e_tests/conftest.py | 2 +- tests/e2e_tests/test_transfer.py | 2 +- tests/e2e_tests/utils/chain_interactions.py | 2 +- tests/e2e_tests/utils/e2e_test_utils.py | 2 +- tests/integration_tests/test_timelock.py | 2 +- 9 files changed, 17 insertions(+), 8 deletions(-) rename bittensor/core/{ => addons}/timelock.py (100%) diff --git a/bittensor/core/addons/__init__.py b/bittensor/core/addons/__init__.py index daa1ce0ff0..3e3dd14b8f 100644 --- a/bittensor/core/addons/__init__.py +++ b/bittensor/core/addons/__init__.py @@ -8,3 +8,12 @@ Use this package to keep optional, modular, or feature-gated logic separate from the primary codebase while maintaining discoverability and structure. """ + +from bittensor.core.addons.subtensor_api import SubtensorApi +from bittensor.core.addons import timelock + + +__all__ = [ + "timelock", + "SubtensorApi", +] diff --git a/bittensor/core/addons/subtensor_api/utils.py b/bittensor/core/addons/subtensor_api/utils.py index a07292d76a..c03d599c93 100644 --- a/bittensor/core/addons/subtensor_api/utils.py +++ b/bittensor/core/addons/subtensor_api/utils.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from bittensor.core.subtensor_api import SubtensorApi + from bittensor.core.addons import SubtensorApi def add_legacy_methods(subtensor: "SubtensorApi"): diff --git a/bittensor/core/timelock.py b/bittensor/core/addons/timelock.py similarity index 100% rename from bittensor/core/timelock.py rename to bittensor/core/addons/timelock.py diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index 31bd503b56..e84a84d786 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -27,8 +27,8 @@ ) from bittensor_wallet.wallet import Wallet -from bittensor.core import settings, timelock -from bittensor.core.addons.subtensor_api import SubtensorApi +from bittensor.core import settings +from bittensor.core.addons import timelock, SubtensorApi from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.core.axon import Axon from bittensor.core.chain_data import ( diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 9fa8dfe8d4..dffcc510f6 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -14,7 +14,7 @@ import pytest_asyncio from async_substrate_interface import SubstrateInterface -from bittensor.core.subtensor_api import SubtensorApi +from bittensor.core.addons import SubtensorApi from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.e2e_test_utils import ( Templates, diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py index 4f65a5828d..d372f4d33d 100644 --- a/tests/e2e_tests/test_transfer.py +++ b/tests/e2e_tests/test_transfer.py @@ -7,7 +7,7 @@ from bittensor import logging if typing.TYPE_CHECKING: - from bittensor.core.subtensor_api import SubtensorApi + from bittensor.core.addons import SubtensorApi def test_transfer(subtensor, alice_wallet): diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index 43af7a1532..8fc0ecfe53 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -15,7 +15,7 @@ # for typing purposes if TYPE_CHECKING: from bittensor import Wallet - from bittensor.core.subtensor_api import SubtensorApi + from bittensor.core.addons import SubtensorApi from async_substrate_interface import ( AsyncSubstrateInterface, AsyncExtrinsicReceipt, diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index 3ad0b896c9..e0a19ada7e 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -6,7 +6,7 @@ from bittensor_wallet import Keypair, Wallet -from bittensor.core.subtensor_api import SubtensorApi +from bittensor.core.addons import SubtensorApi from bittensor.utils.btlogging import logging template_path = os.getcwd() + "/neurons/" diff --git a/tests/integration_tests/test_timelock.py b/tests/integration_tests/test_timelock.py index 33e31db782..b0545e3517 100644 --- a/tests/integration_tests/test_timelock.py +++ b/tests/integration_tests/test_timelock.py @@ -3,7 +3,7 @@ import pytest -from bittensor.core import timelock +from bittensor.core.addons import timelock def test_encrypt_returns_valid_tuple(): From ecd8aac6070a9435c77f912d6668372ea7e9753a Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 16:19:57 -0700 Subject: [PATCH 269/416] more --- MIGRATION.md | 2 +- bittensor/core/addons/__init__.py | 3 +-- tests/e2e_tests/conftest.py | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index a4a3d454c1..aa5ea513b0 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -238,7 +238,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` - - + - `bittensor.core.timelock` moved to `bittensor.core.addons.timelock` ### Mechid related changes: In the next subtensor methods got updated the parameters order: diff --git a/bittensor/core/addons/__init__.py b/bittensor/core/addons/__init__.py index 3e3dd14b8f..b84bb3ae90 100644 --- a/bittensor/core/addons/__init__.py +++ b/bittensor/core/addons/__init__.py @@ -9,9 +9,8 @@ discoverability and structure. """ -from bittensor.core.addons.subtensor_api import SubtensorApi from bittensor.core.addons import timelock - +from bittensor.core.addons.subtensor_api import SubtensorApi __all__ = [ "timelock", diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index dffcc510f6..319b9d646a 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -1,4 +1,3 @@ -import asyncio import os import re import shlex @@ -12,7 +11,6 @@ import pytest import pytest_asyncio -from async_substrate_interface import SubstrateInterface from bittensor.core.addons import SubtensorApi from bittensor.utils.btlogging import logging From 4fab1140ee117bf2913e3d2ce7c83af59a17a7a6 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 16:24:57 -0700 Subject: [PATCH 270/416] rename `SubtensorApi._subtensor` -> `SubtensorApi.inner_subtensor` --- .../core/addons/subtensor_api/__init__.py | 50 +-- bittensor/core/addons/subtensor_api/utils.py | 308 ++++++++++-------- tests/e2e_tests/test_metagraph.py | 12 +- 3 files changed, 196 insertions(+), 174 deletions(-) diff --git a/bittensor/core/addons/subtensor_api/__init__.py b/bittensor/core/addons/subtensor_api/__init__.py index 63bfd137c3..f729998c9e 100644 --- a/bittensor/core/addons/subtensor_api/__init__.py +++ b/bittensor/core/addons/subtensor_api/__init__.py @@ -94,24 +94,24 @@ def __init__( # assigned only for async instance self.initialize = None - self._subtensor = self._get_subtensor() + self.inner_subtensor = self._get_subtensor() # fix naming collision - self._neurons = _Neurons(self._subtensor) + self._neurons = _Neurons(self.inner_subtensor) # define empty fields - self.substrate = self._subtensor.substrate - self.chain_endpoint = self._subtensor.chain_endpoint - self.close = self._subtensor.close - self.config = self._subtensor.config - self.setup_config = self._subtensor.setup_config - self.help = self._subtensor.help - - self.determine_block_hash = self._subtensor.determine_block_hash - self.encode_params = self._subtensor.encode_params - self.sign_and_send_extrinsic = self._subtensor.sign_and_send_extrinsic - self.start_call = self._subtensor.start_call - self.wait_for_block = self._subtensor.wait_for_block + self.substrate = self.inner_subtensor.substrate + self.chain_endpoint = self.inner_subtensor.chain_endpoint + self.close = self.inner_subtensor.close + self.config = self.inner_subtensor.config + self.setup_config = self.inner_subtensor.setup_config + self.help = self.inner_subtensor.help + + self.determine_block_hash = self.inner_subtensor.determine_block_hash + self.encode_params = self.inner_subtensor.encode_params + self.sign_and_send_extrinsic = self.inner_subtensor.sign_and_send_extrinsic + self.start_call = self.inner_subtensor.start_call + self.wait_for_block = self.inner_subtensor.wait_for_block # adds all Subtensor methods into main level os SubtensorApi class if legacy_methods: @@ -178,7 +178,7 @@ async def __aenter__(self): raise NotImplementedError( "Sync version of SubtensorApi cannot be used with async context manager." ) - await self._subtensor.__aenter__() + await self.inner_subtensor.__aenter__() return self async def __aexit__(self, exc_type, exc_val, exc_tb): @@ -195,32 +195,32 @@ def add_args(cls, parser): @property def block(self): """Returns current chain block number.""" - return self._subtensor.block + return self.inner_subtensor.block @property def chain(self): """Property of interaction with chain methods.""" - return _Chain(self._subtensor) + return _Chain(self.inner_subtensor) @property def commitments(self): """Property to access commitments methods.""" - return _Commitments(self._subtensor) + return _Commitments(self.inner_subtensor) @property def delegates(self): """Property to access delegates methods.""" - return _Delegates(self._subtensor) + return _Delegates(self.inner_subtensor) @property def extrinsics(self): """Property to access extrinsics methods.""" - return _Extrinsics(self._subtensor) + return _Extrinsics(self.inner_subtensor) @property def metagraphs(self): """Property to access metagraphs methods.""" - return _Metagraphs(self._subtensor) + return _Metagraphs(self.inner_subtensor) @property def neurons(self): @@ -235,19 +235,19 @@ def neurons(self, value): @property def queries(self): """Property to access subtensor queries methods.""" - return _Queries(self._subtensor) + return _Queries(self.inner_subtensor) @property def staking(self): """Property to access staking methods.""" - return _Staking(self._subtensor) + return _Staking(self.inner_subtensor) @property def subnets(self): """Property of interaction with subnets methods.""" - return _Subnets(self._subtensor) + return _Subnets(self.inner_subtensor) @property def wallets(self): """Property of interaction methods with cold/hotkeys, and balances, etc.""" - return _Wallets(self._subtensor) + return _Wallets(self.inner_subtensor) diff --git a/bittensor/core/addons/subtensor_api/utils.py b/bittensor/core/addons/subtensor_api/utils.py index c03d599c93..4b511db11e 100644 --- a/bittensor/core/addons/subtensor_api/utils.py +++ b/bittensor/core/addons/subtensor_api/utils.py @@ -6,177 +6,199 @@ def add_legacy_methods(subtensor: "SubtensorApi"): """If SubtensorApi get `subtensor_fields=True` arguments, then all classic Subtensor fields added to root level.""" - subtensor.add_liquidity = subtensor._subtensor.add_liquidity - subtensor.add_stake = subtensor._subtensor.add_stake - subtensor.add_stake_multiple = subtensor._subtensor.add_stake_multiple - subtensor.all_subnets = subtensor._subtensor.all_subnets - subtensor.blocks_since_last_step = subtensor._subtensor.blocks_since_last_step - subtensor.blocks_since_last_update = subtensor._subtensor.blocks_since_last_update - subtensor.bonds = subtensor._subtensor.bonds - subtensor.burned_register = subtensor._subtensor.burned_register - subtensor.chain_endpoint = subtensor._subtensor.chain_endpoint - 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 - subtensor.difficulty = subtensor._subtensor.difficulty - subtensor.does_hotkey_exist = subtensor._subtensor.does_hotkey_exist - subtensor.encode_params = subtensor._subtensor.encode_params + subtensor.add_liquidity = subtensor.inner_subtensor.add_liquidity + subtensor.add_stake = subtensor.inner_subtensor.add_stake + subtensor.add_stake_multiple = subtensor.inner_subtensor.add_stake_multiple + subtensor.all_subnets = subtensor.inner_subtensor.all_subnets + subtensor.blocks_since_last_step = subtensor.inner_subtensor.blocks_since_last_step + subtensor.blocks_since_last_update = ( + subtensor.inner_subtensor.blocks_since_last_update + ) + subtensor.bonds = subtensor.inner_subtensor.bonds + subtensor.burned_register = subtensor.inner_subtensor.burned_register + subtensor.chain_endpoint = subtensor.inner_subtensor.chain_endpoint + subtensor.commit_reveal_enabled = subtensor.inner_subtensor.commit_reveal_enabled + subtensor.commit_weights = subtensor.inner_subtensor.commit_weights + subtensor.determine_block_hash = subtensor.inner_subtensor.determine_block_hash + subtensor.difficulty = subtensor.inner_subtensor.difficulty + subtensor.does_hotkey_exist = subtensor.inner_subtensor.does_hotkey_exist + subtensor.encode_params = subtensor.inner_subtensor.encode_params subtensor.filter_netuids_by_registered_hotkeys = ( - subtensor._subtensor.filter_netuids_by_registered_hotkeys + subtensor.inner_subtensor.filter_netuids_by_registered_hotkeys + ) + subtensor.get_admin_freeze_window = ( + subtensor.inner_subtensor.get_admin_freeze_window + ) + subtensor.get_all_commitments = subtensor.inner_subtensor.get_all_commitments + subtensor.get_all_metagraphs_info = ( + subtensor.inner_subtensor.get_all_metagraphs_info ) - subtensor.get_admin_freeze_window = subtensor._subtensor.get_admin_freeze_window - subtensor.get_all_commitments = subtensor._subtensor.get_all_commitments - subtensor.get_all_metagraphs_info = subtensor._subtensor.get_all_metagraphs_info subtensor.get_all_neuron_certificates = ( - subtensor._subtensor.get_all_neuron_certificates + subtensor.inner_subtensor.get_all_neuron_certificates ) subtensor.get_all_revealed_commitments = ( - subtensor._subtensor.get_all_revealed_commitments - ) - subtensor.get_all_subnets_info = subtensor._subtensor.get_all_subnets_info - subtensor.get_auto_stakes = subtensor._subtensor.get_auto_stakes - subtensor.get_balance = subtensor._subtensor.get_balance - subtensor.get_balances = subtensor._subtensor.get_balances - subtensor.get_block_hash = subtensor._subtensor.get_block_hash - subtensor.get_parents = subtensor._subtensor.get_parents - subtensor.get_children = subtensor._subtensor.get_children - subtensor.get_children_pending = subtensor._subtensor.get_children_pending - subtensor.get_commitment = subtensor._subtensor.get_commitment - subtensor.get_current_block = subtensor._subtensor.get_current_block + subtensor.inner_subtensor.get_all_revealed_commitments + ) + subtensor.get_all_subnets_info = subtensor.inner_subtensor.get_all_subnets_info + subtensor.get_auto_stakes = subtensor.inner_subtensor.get_auto_stakes + subtensor.get_balance = subtensor.inner_subtensor.get_balance + subtensor.get_balances = subtensor.inner_subtensor.get_balances + subtensor.get_block_hash = subtensor.inner_subtensor.get_block_hash + subtensor.get_parents = subtensor.inner_subtensor.get_parents + subtensor.get_children = subtensor.inner_subtensor.get_children + subtensor.get_children_pending = subtensor.inner_subtensor.get_children_pending + subtensor.get_commitment = subtensor.inner_subtensor.get_commitment + subtensor.get_current_block = subtensor.inner_subtensor.get_current_block subtensor.get_last_commitment_bonds_reset_block = ( - subtensor._subtensor.get_last_commitment_bonds_reset_block - ) - subtensor.get_delegate_by_hotkey = subtensor._subtensor.get_delegate_by_hotkey - subtensor.get_delegate_identities = subtensor._subtensor.get_delegate_identities - subtensor.get_delegate_take = subtensor._subtensor.get_delegate_take - subtensor.get_delegated = subtensor._subtensor.get_delegated - subtensor.get_delegates = subtensor._subtensor.get_delegates - subtensor.get_existential_deposit = subtensor._subtensor.get_existential_deposit - subtensor.get_hotkey_owner = subtensor._subtensor.get_hotkey_owner - subtensor.get_hotkey_stake = subtensor._subtensor.get_hotkey_stake - subtensor.get_hyperparameter = subtensor._subtensor.get_hyperparameter - subtensor.get_liquidity_list = subtensor._subtensor.get_liquidity_list - subtensor.get_metagraph_info = subtensor._subtensor.get_metagraph_info + subtensor.inner_subtensor.get_last_commitment_bonds_reset_block + ) + subtensor.get_delegate_by_hotkey = subtensor.inner_subtensor.get_delegate_by_hotkey + subtensor.get_delegate_identities = ( + subtensor.inner_subtensor.get_delegate_identities + ) + subtensor.get_delegate_take = subtensor.inner_subtensor.get_delegate_take + subtensor.get_delegated = subtensor.inner_subtensor.get_delegated + subtensor.get_delegates = subtensor.inner_subtensor.get_delegates + subtensor.get_existential_deposit = ( + subtensor.inner_subtensor.get_existential_deposit + ) + subtensor.get_hotkey_owner = subtensor.inner_subtensor.get_hotkey_owner + subtensor.get_hotkey_stake = subtensor.inner_subtensor.get_hotkey_stake + subtensor.get_hyperparameter = subtensor.inner_subtensor.get_hyperparameter + subtensor.get_liquidity_list = subtensor.inner_subtensor.get_liquidity_list + subtensor.get_metagraph_info = subtensor.inner_subtensor.get_metagraph_info subtensor.get_minimum_required_stake = ( - subtensor._subtensor.get_minimum_required_stake + subtensor.inner_subtensor.get_minimum_required_stake ) - subtensor.get_netuids_for_hotkey = subtensor._subtensor.get_netuids_for_hotkey - subtensor.get_neuron_certificate = subtensor._subtensor.get_neuron_certificate + subtensor.get_netuids_for_hotkey = subtensor.inner_subtensor.get_netuids_for_hotkey + subtensor.get_neuron_certificate = subtensor.inner_subtensor.get_neuron_certificate subtensor.get_neuron_for_pubkey_and_subnet = ( - subtensor._subtensor.get_neuron_for_pubkey_and_subnet + subtensor.inner_subtensor.get_neuron_for_pubkey_and_subnet ) subtensor.get_next_epoch_start_block = ( - subtensor._subtensor.get_next_epoch_start_block + subtensor.inner_subtensor.get_next_epoch_start_block + ) + subtensor.get_owned_hotkeys = subtensor.inner_subtensor.get_owned_hotkeys + subtensor.get_revealed_commitment = ( + subtensor.inner_subtensor.get_revealed_commitment ) - subtensor.get_owned_hotkeys = subtensor._subtensor.get_owned_hotkeys - subtensor.get_revealed_commitment = subtensor._subtensor.get_revealed_commitment subtensor.get_revealed_commitment_by_hotkey = ( - subtensor._subtensor.get_revealed_commitment_by_hotkey + subtensor.inner_subtensor.get_revealed_commitment_by_hotkey ) - subtensor.get_stake = subtensor._subtensor.get_stake - subtensor.get_stake_add_fee = subtensor._subtensor.get_stake_add_fee + subtensor.get_stake = subtensor.inner_subtensor.get_stake + subtensor.get_stake_add_fee = subtensor.inner_subtensor.get_stake_add_fee subtensor.get_stake_for_coldkey_and_hotkey = ( - subtensor._subtensor.get_stake_for_coldkey_and_hotkey + subtensor.inner_subtensor.get_stake_for_coldkey_and_hotkey ) - subtensor.get_stake_for_hotkey = subtensor._subtensor.get_stake_for_hotkey + subtensor.get_stake_for_hotkey = subtensor.inner_subtensor.get_stake_for_hotkey subtensor.get_stake_info_for_coldkey = ( - subtensor._subtensor.get_stake_info_for_coldkey + subtensor.inner_subtensor.get_stake_info_for_coldkey + ) + subtensor.get_stake_movement_fee = subtensor.inner_subtensor.get_stake_movement_fee + subtensor.get_stake_operations_fee = ( + subtensor.inner_subtensor.get_stake_operations_fee ) - subtensor.get_stake_movement_fee = subtensor._subtensor.get_stake_movement_fee - subtensor.get_stake_operations_fee = subtensor._subtensor.get_stake_operations_fee - subtensor.get_stake_weight = subtensor._subtensor.get_stake_weight + subtensor.get_stake_weight = subtensor.inner_subtensor.get_stake_weight subtensor.get_mechanism_emission_split = ( - subtensor._subtensor.get_mechanism_emission_split + subtensor.inner_subtensor.get_mechanism_emission_split ) - subtensor.get_mechanism_count = subtensor._subtensor.get_mechanism_count - subtensor.get_subnet_burn_cost = subtensor._subtensor.get_subnet_burn_cost + subtensor.get_mechanism_count = subtensor.inner_subtensor.get_mechanism_count + subtensor.get_subnet_burn_cost = subtensor.inner_subtensor.get_subnet_burn_cost subtensor.get_subnet_hyperparameters = ( - subtensor._subtensor.get_subnet_hyperparameters + subtensor.inner_subtensor.get_subnet_hyperparameters + ) + subtensor.get_subnet_info = subtensor.inner_subtensor.get_subnet_info + subtensor.get_subnet_price = subtensor.inner_subtensor.get_subnet_price + subtensor.get_subnet_prices = subtensor.inner_subtensor.get_subnet_prices + subtensor.get_subnet_owner_hotkey = ( + subtensor.inner_subtensor.get_subnet_owner_hotkey ) - subtensor.get_subnet_info = subtensor._subtensor.get_subnet_info - subtensor.get_subnet_price = subtensor._subtensor.get_subnet_price - subtensor.get_subnet_prices = subtensor._subtensor.get_subnet_prices - subtensor.get_subnet_owner_hotkey = subtensor._subtensor.get_subnet_owner_hotkey subtensor.get_subnet_reveal_period_epochs = ( - subtensor._subtensor.get_subnet_reveal_period_epochs + subtensor.inner_subtensor.get_subnet_reveal_period_epochs ) subtensor.get_subnet_validator_permits = ( - subtensor._subtensor.get_subnet_validator_permits + subtensor.inner_subtensor.get_subnet_validator_permits ) - subtensor.get_subnets = subtensor._subtensor.get_subnets + subtensor.get_subnets = subtensor.inner_subtensor.get_subnets subtensor.get_timelocked_weight_commits = ( - subtensor._subtensor.get_timelocked_weight_commits + subtensor.inner_subtensor.get_timelocked_weight_commits ) - subtensor.get_timestamp = subtensor._subtensor.get_timestamp - subtensor.get_total_subnets = subtensor._subtensor.get_total_subnets - subtensor.get_transfer_fee = subtensor._subtensor.get_transfer_fee + subtensor.get_timestamp = subtensor.inner_subtensor.get_timestamp + subtensor.get_total_subnets = subtensor.inner_subtensor.get_total_subnets + subtensor.get_transfer_fee = subtensor.inner_subtensor.get_transfer_fee subtensor.get_uid_for_hotkey_on_subnet = ( - subtensor._subtensor.get_uid_for_hotkey_on_subnet - ) - subtensor.get_unstake_fee = subtensor._subtensor.get_unstake_fee - subtensor.get_vote_data = subtensor._subtensor.get_vote_data - subtensor.immunity_period = subtensor._subtensor.immunity_period - subtensor.is_fast_blocks = subtensor._subtensor.is_fast_blocks - subtensor.is_hotkey_delegate = subtensor._subtensor.is_hotkey_delegate - subtensor.is_hotkey_registered = subtensor._subtensor.is_hotkey_registered - subtensor.is_hotkey_registered_any = subtensor._subtensor.is_hotkey_registered_any + subtensor.inner_subtensor.get_uid_for_hotkey_on_subnet + ) + subtensor.get_unstake_fee = subtensor.inner_subtensor.get_unstake_fee + subtensor.get_vote_data = subtensor.inner_subtensor.get_vote_data + subtensor.immunity_period = subtensor.inner_subtensor.immunity_period + subtensor.is_fast_blocks = subtensor.inner_subtensor.is_fast_blocks + subtensor.is_hotkey_delegate = subtensor.inner_subtensor.is_hotkey_delegate + subtensor.is_hotkey_registered = subtensor.inner_subtensor.is_hotkey_registered + subtensor.is_hotkey_registered_any = ( + subtensor.inner_subtensor.is_hotkey_registered_any + ) subtensor.is_hotkey_registered_on_subnet = ( - subtensor._subtensor.is_hotkey_registered_on_subnet - ) - subtensor.is_in_admin_freeze_window = subtensor._subtensor.is_in_admin_freeze_window - subtensor.is_subnet_active = subtensor._subtensor.is_subnet_active - subtensor.last_drand_round = subtensor._subtensor.last_drand_round - subtensor.log_verbose = subtensor._subtensor.log_verbose - subtensor.max_weight_limit = subtensor._subtensor.max_weight_limit - subtensor.metagraph = subtensor._subtensor.metagraph - subtensor.min_allowed_weights = subtensor._subtensor.min_allowed_weights - subtensor.modify_liquidity = subtensor._subtensor.modify_liquidity - subtensor.move_stake = subtensor._subtensor.move_stake - subtensor.network = subtensor._subtensor.network - subtensor.neurons = subtensor._subtensor.neurons - subtensor.neuron_for_uid = subtensor._subtensor.neuron_for_uid - subtensor.neurons_lite = subtensor._subtensor.neurons_lite - subtensor.query_constant = subtensor._subtensor.query_constant - subtensor.query_identity = subtensor._subtensor.query_identity - subtensor.query_map = subtensor._subtensor.query_map - subtensor.query_map_subtensor = subtensor._subtensor.query_map_subtensor - subtensor.query_module = subtensor._subtensor.query_module - subtensor.query_runtime_api = subtensor._subtensor.query_runtime_api - subtensor.query_subtensor = subtensor._subtensor.query_subtensor - subtensor.recycle = subtensor._subtensor.recycle - subtensor.remove_liquidity = subtensor._subtensor.remove_liquidity - subtensor.register = subtensor._subtensor.register - subtensor.register_subnet = subtensor._subtensor.register_subnet - subtensor.reveal_weights = subtensor._subtensor.reveal_weights - subtensor.root_register = subtensor._subtensor.root_register + subtensor.inner_subtensor.is_hotkey_registered_on_subnet + ) + subtensor.is_in_admin_freeze_window = ( + subtensor.inner_subtensor.is_in_admin_freeze_window + ) + subtensor.is_subnet_active = subtensor.inner_subtensor.is_subnet_active + subtensor.last_drand_round = subtensor.inner_subtensor.last_drand_round + subtensor.log_verbose = subtensor.inner_subtensor.log_verbose + subtensor.max_weight_limit = subtensor.inner_subtensor.max_weight_limit + subtensor.metagraph = subtensor.inner_subtensor.metagraph + subtensor.min_allowed_weights = subtensor.inner_subtensor.min_allowed_weights + subtensor.modify_liquidity = subtensor.inner_subtensor.modify_liquidity + subtensor.move_stake = subtensor.inner_subtensor.move_stake + subtensor.network = subtensor.inner_subtensor.network + subtensor.neurons = subtensor.inner_subtensor.neurons + subtensor.neuron_for_uid = subtensor.inner_subtensor.neuron_for_uid + subtensor.neurons_lite = subtensor.inner_subtensor.neurons_lite + subtensor.query_constant = subtensor.inner_subtensor.query_constant + subtensor.query_identity = subtensor.inner_subtensor.query_identity + subtensor.query_map = subtensor.inner_subtensor.query_map + subtensor.query_map_subtensor = subtensor.inner_subtensor.query_map_subtensor + subtensor.query_module = subtensor.inner_subtensor.query_module + subtensor.query_runtime_api = subtensor.inner_subtensor.query_runtime_api + subtensor.query_subtensor = subtensor.inner_subtensor.query_subtensor + subtensor.recycle = subtensor.inner_subtensor.recycle + subtensor.remove_liquidity = subtensor.inner_subtensor.remove_liquidity + subtensor.register = subtensor.inner_subtensor.register + subtensor.register_subnet = subtensor.inner_subtensor.register_subnet + subtensor.reveal_weights = subtensor.inner_subtensor.reveal_weights + subtensor.root_register = subtensor.inner_subtensor.root_register subtensor.root_set_pending_childkey_cooldown = ( - subtensor._subtensor.root_set_pending_childkey_cooldown - ) - subtensor.serve_axon = subtensor._subtensor.serve_axon - subtensor.set_auto_stake = subtensor._subtensor.set_auto_stake - subtensor.set_children = subtensor._subtensor.set_children - subtensor.set_commitment = subtensor._subtensor.set_commitment - subtensor.set_delegate_take = subtensor._subtensor.set_delegate_take - subtensor.set_reveal_commitment = subtensor._subtensor.set_reveal_commitment - subtensor.set_subnet_identity = subtensor._subtensor.set_subnet_identity - subtensor.set_weights = subtensor._subtensor.set_weights - subtensor.setup_config = subtensor._subtensor.setup_config - subtensor.sign_and_send_extrinsic = subtensor._subtensor.sign_and_send_extrinsic - subtensor.start_call = subtensor._subtensor.start_call - subtensor.state_call = subtensor._subtensor.state_call - subtensor.subnet = subtensor._subtensor.subnet - subtensor.subnet_exists = subtensor._subtensor.subnet_exists - subtensor.subnetwork_n = subtensor._subtensor.subnetwork_n - subtensor.substrate = subtensor._subtensor.substrate - subtensor.swap_stake = subtensor._subtensor.swap_stake - subtensor.tempo = subtensor._subtensor.tempo - subtensor.toggle_user_liquidity = subtensor._subtensor.toggle_user_liquidity - subtensor.transfer = subtensor._subtensor.transfer - subtensor.transfer_stake = subtensor._subtensor.transfer_stake - subtensor.tx_rate_limit = subtensor._subtensor.tx_rate_limit - subtensor.unstake = subtensor._subtensor.unstake - subtensor.unstake_all = subtensor._subtensor.unstake_all - subtensor.unstake_multiple = subtensor._subtensor.unstake_multiple - subtensor.wait_for_block = subtensor._subtensor.wait_for_block - subtensor.weights = subtensor._subtensor.weights - subtensor.weights_rate_limit = subtensor._subtensor.weights_rate_limit + subtensor.inner_subtensor.root_set_pending_childkey_cooldown + ) + subtensor.serve_axon = subtensor.inner_subtensor.serve_axon + subtensor.set_auto_stake = subtensor.inner_subtensor.set_auto_stake + subtensor.set_children = subtensor.inner_subtensor.set_children + subtensor.set_commitment = subtensor.inner_subtensor.set_commitment + subtensor.set_delegate_take = subtensor.inner_subtensor.set_delegate_take + subtensor.set_reveal_commitment = subtensor.inner_subtensor.set_reveal_commitment + subtensor.set_subnet_identity = subtensor.inner_subtensor.set_subnet_identity + subtensor.set_weights = subtensor.inner_subtensor.set_weights + subtensor.setup_config = subtensor.inner_subtensor.setup_config + subtensor.sign_and_send_extrinsic = ( + subtensor.inner_subtensor.sign_and_send_extrinsic + ) + subtensor.start_call = subtensor.inner_subtensor.start_call + subtensor.state_call = subtensor.inner_subtensor.state_call + subtensor.subnet = subtensor.inner_subtensor.subnet + subtensor.subnet_exists = subtensor.inner_subtensor.subnet_exists + subtensor.subnetwork_n = subtensor.inner_subtensor.subnetwork_n + subtensor.substrate = subtensor.inner_subtensor.substrate + subtensor.swap_stake = subtensor.inner_subtensor.swap_stake + subtensor.tempo = subtensor.inner_subtensor.tempo + subtensor.toggle_user_liquidity = subtensor.inner_subtensor.toggle_user_liquidity + subtensor.transfer = subtensor.inner_subtensor.transfer + subtensor.transfer_stake = subtensor.inner_subtensor.transfer_stake + subtensor.tx_rate_limit = subtensor.inner_subtensor.tx_rate_limit + subtensor.unstake = subtensor.inner_subtensor.unstake + subtensor.unstake_all = subtensor.inner_subtensor.unstake_all + subtensor.unstake_multiple = subtensor.inner_subtensor.unstake_multiple + subtensor.wait_for_block = subtensor.inner_subtensor.wait_for_block + subtensor.weights = subtensor.inner_subtensor.weights + subtensor.weights_rate_limit = subtensor.inner_subtensor.weights_rate_limit diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 9553fda968..404c7d91f9 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -79,7 +79,7 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): ) logging.console.info("Refresh the metagraph") - metagraph.sync(subtensor=subtensor._subtensor) + metagraph.sync(subtensor=subtensor.inner_subtensor) logging.console.info("Assert metagraph has Alice and Bob neurons") assert len(metagraph.uids) == 2, "Metagraph doesn't have exactly 2 neurons" @@ -118,7 +118,7 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): dave_wallet, alice_subnet_netuid ).success, "Unable to register Dave as a neuron" - metagraph.sync(subtensor=subtensor._subtensor) + metagraph.sync(subtensor=subtensor.inner_subtensor) logging.console.info("Assert metagraph now includes Dave's neuron") assert len(metagraph.uids) == 3, ( @@ -147,7 +147,7 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): ).success, "Failed to add stake for Bob" logging.console.info("Assert stake is added after updating metagraph") - metagraph.sync(subtensor=subtensor._subtensor) + metagraph.sync(subtensor=subtensor.inner_subtensor) assert 0.95 < metagraph.neurons[1].stake.rao / alpha.rao < 1.05, ( "Bob's stake not updated in metagraph" ) @@ -242,7 +242,7 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w ).success, "Unable to register Bob as a neuron" logging.console.info("Refresh the metagraph") - await metagraph.sync(subtensor=async_subtensor._subtensor) + await metagraph.sync(subtensor=async_subtensor.inner_subtensor) logging.console.info("Assert metagraph has Alice and Bob neurons") assert len(metagraph.uids) == 2, "Metagraph doesn't have exactly 2 neurons" @@ -287,7 +287,7 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w ) ).success, "Unable to register Dave as a neuron" - await metagraph.sync(subtensor=async_subtensor._subtensor) + await metagraph.sync(subtensor=async_subtensor.inner_subtensor) logging.console.info("Assert metagraph now includes Dave's neuron") assert len(metagraph.uids) == 3, ( @@ -320,7 +320,7 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w ).success, "Failed to add stake for Bob" logging.console.info("Assert stake is added after updating metagraph") - await metagraph.sync(subtensor=async_subtensor._subtensor) + await metagraph.sync(subtensor=async_subtensor.inner_subtensor) assert 0.95 < metagraph.neurons[1].stake.rao / alpha.rao < 1.05, ( "Bob's stake not updated in metagraph" ) From 1ea45d46416ba9626601ddf6d72938e424786f30 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 16:28:03 -0700 Subject: [PATCH 271/416] updated order --- bittensor/core/addons/subtensor_api/chain.py | 4 +- bittensor/core/addons/subtensor_api/utils.py | 66 +++++++------------- 2 files changed, 24 insertions(+), 46 deletions(-) diff --git a/bittensor/core/addons/subtensor_api/chain.py b/bittensor/core/addons/subtensor_api/chain.py index 9f4c312f13..8a45169169 100644 --- a/bittensor/core/addons/subtensor_api/chain.py +++ b/bittensor/core/addons/subtensor_api/chain.py @@ -13,10 +13,10 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_delegate_identities = subtensor.get_delegate_identities self.get_existential_deposit = subtensor.get_existential_deposit self.get_minimum_required_stake = subtensor.get_minimum_required_stake - self.get_vote_data = subtensor.get_vote_data self.get_timestamp = subtensor.get_timestamp - self.is_in_admin_freeze_window = subtensor.is_in_admin_freeze_window + self.get_vote_data = subtensor.get_vote_data self.is_fast_blocks = subtensor.is_fast_blocks + self.is_in_admin_freeze_window = subtensor.is_in_admin_freeze_window self.last_drand_round = subtensor.last_drand_round self.state_call = subtensor.state_call self.tx_rate_limit = subtensor.tx_rate_limit diff --git a/bittensor/core/addons/subtensor_api/utils.py b/bittensor/core/addons/subtensor_api/utils.py index 4b511db11e..b605541e07 100644 --- a/bittensor/core/addons/subtensor_api/utils.py +++ b/bittensor/core/addons/subtensor_api/utils.py @@ -11,9 +11,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.add_stake_multiple = subtensor.inner_subtensor.add_stake_multiple subtensor.all_subnets = subtensor.inner_subtensor.all_subnets subtensor.blocks_since_last_step = subtensor.inner_subtensor.blocks_since_last_step - subtensor.blocks_since_last_update = ( - subtensor.inner_subtensor.blocks_since_last_update - ) + subtensor.blocks_since_last_update = subtensor.inner_subtensor.blocks_since_last_update subtensor.bonds = subtensor.inner_subtensor.bonds subtensor.burned_register = subtensor.inner_subtensor.burned_register subtensor.chain_endpoint = subtensor.inner_subtensor.chain_endpoint @@ -26,13 +24,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.filter_netuids_by_registered_hotkeys = ( subtensor.inner_subtensor.filter_netuids_by_registered_hotkeys ) - subtensor.get_admin_freeze_window = ( - subtensor.inner_subtensor.get_admin_freeze_window - ) + subtensor.get_admin_freeze_window = subtensor.inner_subtensor.get_admin_freeze_window subtensor.get_all_commitments = subtensor.inner_subtensor.get_all_commitments - subtensor.get_all_metagraphs_info = ( - subtensor.inner_subtensor.get_all_metagraphs_info - ) + subtensor.get_all_metagraphs_info = subtensor.inner_subtensor.get_all_metagraphs_info subtensor.get_all_neuron_certificates = ( subtensor.inner_subtensor.get_all_neuron_certificates ) @@ -44,28 +38,27 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_balance = subtensor.inner_subtensor.get_balance subtensor.get_balances = subtensor.inner_subtensor.get_balances subtensor.get_block_hash = subtensor.inner_subtensor.get_block_hash - subtensor.get_parents = subtensor.inner_subtensor.get_parents subtensor.get_children = subtensor.inner_subtensor.get_children subtensor.get_children_pending = subtensor.inner_subtensor.get_children_pending subtensor.get_commitment = subtensor.inner_subtensor.get_commitment subtensor.get_current_block = subtensor.inner_subtensor.get_current_block - subtensor.get_last_commitment_bonds_reset_block = ( - subtensor.inner_subtensor.get_last_commitment_bonds_reset_block - ) subtensor.get_delegate_by_hotkey = subtensor.inner_subtensor.get_delegate_by_hotkey - subtensor.get_delegate_identities = ( - subtensor.inner_subtensor.get_delegate_identities - ) + subtensor.get_delegate_identities = subtensor.inner_subtensor.get_delegate_identities subtensor.get_delegate_take = subtensor.inner_subtensor.get_delegate_take subtensor.get_delegated = subtensor.inner_subtensor.get_delegated subtensor.get_delegates = subtensor.inner_subtensor.get_delegates - subtensor.get_existential_deposit = ( - subtensor.inner_subtensor.get_existential_deposit - ) + subtensor.get_existential_deposit = subtensor.inner_subtensor.get_existential_deposit subtensor.get_hotkey_owner = subtensor.inner_subtensor.get_hotkey_owner subtensor.get_hotkey_stake = subtensor.inner_subtensor.get_hotkey_stake subtensor.get_hyperparameter = subtensor.inner_subtensor.get_hyperparameter + subtensor.get_last_commitment_bonds_reset_block = ( + subtensor.inner_subtensor.get_last_commitment_bonds_reset_block + ) subtensor.get_liquidity_list = subtensor.inner_subtensor.get_liquidity_list + subtensor.get_mechanism_count = subtensor.inner_subtensor.get_mechanism_count + subtensor.get_mechanism_emission_split = ( + subtensor.inner_subtensor.get_mechanism_emission_split + ) subtensor.get_metagraph_info = subtensor.inner_subtensor.get_metagraph_info subtensor.get_minimum_required_stake = ( subtensor.inner_subtensor.get_minimum_required_stake @@ -79,9 +72,8 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.inner_subtensor.get_next_epoch_start_block ) subtensor.get_owned_hotkeys = subtensor.inner_subtensor.get_owned_hotkeys - subtensor.get_revealed_commitment = ( - subtensor.inner_subtensor.get_revealed_commitment - ) + subtensor.get_parents = subtensor.inner_subtensor.get_parents + subtensor.get_revealed_commitment = subtensor.inner_subtensor.get_revealed_commitment subtensor.get_revealed_commitment_by_hotkey = ( subtensor.inner_subtensor.get_revealed_commitment_by_hotkey ) @@ -95,24 +87,16 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.inner_subtensor.get_stake_info_for_coldkey ) subtensor.get_stake_movement_fee = subtensor.inner_subtensor.get_stake_movement_fee - subtensor.get_stake_operations_fee = ( - subtensor.inner_subtensor.get_stake_operations_fee - ) + subtensor.get_stake_operations_fee = subtensor.inner_subtensor.get_stake_operations_fee subtensor.get_stake_weight = subtensor.inner_subtensor.get_stake_weight - subtensor.get_mechanism_emission_split = ( - subtensor.inner_subtensor.get_mechanism_emission_split - ) - subtensor.get_mechanism_count = subtensor.inner_subtensor.get_mechanism_count subtensor.get_subnet_burn_cost = subtensor.inner_subtensor.get_subnet_burn_cost subtensor.get_subnet_hyperparameters = ( subtensor.inner_subtensor.get_subnet_hyperparameters ) subtensor.get_subnet_info = subtensor.inner_subtensor.get_subnet_info + subtensor.get_subnet_owner_hotkey = subtensor.inner_subtensor.get_subnet_owner_hotkey subtensor.get_subnet_price = subtensor.inner_subtensor.get_subnet_price subtensor.get_subnet_prices = subtensor.inner_subtensor.get_subnet_prices - subtensor.get_subnet_owner_hotkey = ( - subtensor.inner_subtensor.get_subnet_owner_hotkey - ) subtensor.get_subnet_reveal_period_epochs = ( subtensor.inner_subtensor.get_subnet_reveal_period_epochs ) @@ -135,15 +119,11 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.is_fast_blocks = subtensor.inner_subtensor.is_fast_blocks subtensor.is_hotkey_delegate = subtensor.inner_subtensor.is_hotkey_delegate subtensor.is_hotkey_registered = subtensor.inner_subtensor.is_hotkey_registered - subtensor.is_hotkey_registered_any = ( - subtensor.inner_subtensor.is_hotkey_registered_any - ) + subtensor.is_hotkey_registered_any = subtensor.inner_subtensor.is_hotkey_registered_any subtensor.is_hotkey_registered_on_subnet = ( subtensor.inner_subtensor.is_hotkey_registered_on_subnet ) - subtensor.is_in_admin_freeze_window = ( - subtensor.inner_subtensor.is_in_admin_freeze_window - ) + subtensor.is_in_admin_freeze_window = subtensor.inner_subtensor.is_in_admin_freeze_window subtensor.is_subnet_active = subtensor.inner_subtensor.is_subnet_active subtensor.last_drand_round = subtensor.inner_subtensor.last_drand_round subtensor.log_verbose = subtensor.inner_subtensor.log_verbose @@ -152,10 +132,10 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.min_allowed_weights = subtensor.inner_subtensor.min_allowed_weights subtensor.modify_liquidity = subtensor.inner_subtensor.modify_liquidity subtensor.move_stake = subtensor.inner_subtensor.move_stake - subtensor.network = subtensor.inner_subtensor.network - subtensor.neurons = subtensor.inner_subtensor.neurons subtensor.neuron_for_uid = subtensor.inner_subtensor.neuron_for_uid + subtensor.neurons = subtensor.inner_subtensor.neurons subtensor.neurons_lite = subtensor.inner_subtensor.neurons_lite + subtensor.network = subtensor.inner_subtensor.network subtensor.query_constant = subtensor.inner_subtensor.query_constant subtensor.query_identity = subtensor.inner_subtensor.query_identity subtensor.query_map = subtensor.inner_subtensor.query_map @@ -164,9 +144,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.query_runtime_api = subtensor.inner_subtensor.query_runtime_api subtensor.query_subtensor = subtensor.inner_subtensor.query_subtensor subtensor.recycle = subtensor.inner_subtensor.recycle - subtensor.remove_liquidity = subtensor.inner_subtensor.remove_liquidity subtensor.register = subtensor.inner_subtensor.register subtensor.register_subnet = subtensor.inner_subtensor.register_subnet + subtensor.remove_liquidity = subtensor.inner_subtensor.remove_liquidity subtensor.reveal_weights = subtensor.inner_subtensor.reveal_weights subtensor.root_register = subtensor.inner_subtensor.root_register subtensor.root_set_pending_childkey_cooldown = ( @@ -181,9 +161,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.set_subnet_identity = subtensor.inner_subtensor.set_subnet_identity subtensor.set_weights = subtensor.inner_subtensor.set_weights subtensor.setup_config = subtensor.inner_subtensor.setup_config - subtensor.sign_and_send_extrinsic = ( - subtensor.inner_subtensor.sign_and_send_extrinsic - ) + subtensor.sign_and_send_extrinsic = subtensor.inner_subtensor.sign_and_send_extrinsic subtensor.start_call = subtensor.inner_subtensor.start_call subtensor.state_call = subtensor.inner_subtensor.state_call subtensor.subnet = subtensor.inner_subtensor.subnet From 5afe8121a44b927ea464b26eca270b9a2fbd0f05 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 16:45:33 -0700 Subject: [PATCH 272/416] - method `get_subnets` renamed to `get_all_subnets_netuid` (more obvious) --- MIGRATION.md | 5 +- .../core/addons/subtensor_api/subnets.py | 2 +- bittensor/core/addons/subtensor_api/utils.py | 46 ++++++++++++++----- bittensor/core/async_subtensor.py | 4 +- bittensor/core/metagraph.py | 5 +- bittensor/core/subtensor.py | 4 +- tests/e2e_tests/test_subtensor_functions.py | 10 ++-- tests/helpers/integration_websocket_data.py | 2 +- tests/unit_tests/test_async_subtensor.py | 6 +-- tests/unit_tests/test_subtensor.py | 16 +++---- 10 files changed, 62 insertions(+), 38 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index aa5ea513b0..ba81c79179 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -25,8 +25,8 @@ ## 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. +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.~~ wrong idea @@ -235,6 +235,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `query_map` has updated parameters order. - method `add_stake_multiple` has updated parameters order. - method `get_stake_for_coldkey` removed, bc this is the same as `get_stake_info_for_coldkey` +- method `get_subnets` renamed to `get_all_subnets_netuid` (more obvious) Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` diff --git a/bittensor/core/addons/subtensor_api/subnets.py b/bittensor/core/addons/subtensor_api/subnets.py index 30c31ff7e1..75b8adbf13 100644 --- a/bittensor/core/addons/subtensor_api/subnets.py +++ b/bittensor/core/addons/subtensor_api/subnets.py @@ -16,6 +16,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.commit_reveal_enabled = subtensor.commit_reveal_enabled self.difficulty = subtensor.difficulty self.get_all_subnets_info = subtensor.get_all_subnets_info + self.get_all_subnets_netuid = subtensor.get_all_subnets_netuid self.get_parents = subtensor.get_parents self.get_children = subtensor.get_children self.get_children_pending = subtensor.get_children_pending @@ -35,7 +36,6 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_subnet_owner_hotkey = subtensor.get_subnet_owner_hotkey self.get_subnet_reveal_period_epochs = subtensor.get_subnet_reveal_period_epochs self.get_subnet_validator_permits = subtensor.get_subnet_validator_permits - self.get_subnets = subtensor.get_subnets self.get_total_subnets = subtensor.get_total_subnets self.get_uid_for_hotkey_on_subnet = subtensor.get_uid_for_hotkey_on_subnet self.immunity_period = subtensor.immunity_period diff --git a/bittensor/core/addons/subtensor_api/utils.py b/bittensor/core/addons/subtensor_api/utils.py index b605541e07..09ed8fc302 100644 --- a/bittensor/core/addons/subtensor_api/utils.py +++ b/bittensor/core/addons/subtensor_api/utils.py @@ -11,7 +11,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.add_stake_multiple = subtensor.inner_subtensor.add_stake_multiple subtensor.all_subnets = subtensor.inner_subtensor.all_subnets subtensor.blocks_since_last_step = subtensor.inner_subtensor.blocks_since_last_step - subtensor.blocks_since_last_update = subtensor.inner_subtensor.blocks_since_last_update + subtensor.blocks_since_last_update = ( + subtensor.inner_subtensor.blocks_since_last_update + ) subtensor.bonds = subtensor.inner_subtensor.bonds subtensor.burned_register = subtensor.inner_subtensor.burned_register subtensor.chain_endpoint = subtensor.inner_subtensor.chain_endpoint @@ -24,9 +26,13 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.filter_netuids_by_registered_hotkeys = ( subtensor.inner_subtensor.filter_netuids_by_registered_hotkeys ) - subtensor.get_admin_freeze_window = subtensor.inner_subtensor.get_admin_freeze_window + subtensor.get_admin_freeze_window = ( + subtensor.inner_subtensor.get_admin_freeze_window + ) subtensor.get_all_commitments = subtensor.inner_subtensor.get_all_commitments - subtensor.get_all_metagraphs_info = subtensor.inner_subtensor.get_all_metagraphs_info + subtensor.get_all_metagraphs_info = ( + subtensor.inner_subtensor.get_all_metagraphs_info + ) subtensor.get_all_neuron_certificates = ( subtensor.inner_subtensor.get_all_neuron_certificates ) @@ -43,11 +49,15 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_commitment = subtensor.inner_subtensor.get_commitment subtensor.get_current_block = subtensor.inner_subtensor.get_current_block subtensor.get_delegate_by_hotkey = subtensor.inner_subtensor.get_delegate_by_hotkey - subtensor.get_delegate_identities = subtensor.inner_subtensor.get_delegate_identities + subtensor.get_delegate_identities = ( + subtensor.inner_subtensor.get_delegate_identities + ) subtensor.get_delegate_take = subtensor.inner_subtensor.get_delegate_take subtensor.get_delegated = subtensor.inner_subtensor.get_delegated subtensor.get_delegates = subtensor.inner_subtensor.get_delegates - subtensor.get_existential_deposit = subtensor.inner_subtensor.get_existential_deposit + subtensor.get_existential_deposit = ( + subtensor.inner_subtensor.get_existential_deposit + ) subtensor.get_hotkey_owner = subtensor.inner_subtensor.get_hotkey_owner subtensor.get_hotkey_stake = subtensor.inner_subtensor.get_hotkey_stake subtensor.get_hyperparameter = subtensor.inner_subtensor.get_hyperparameter @@ -73,7 +83,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): ) subtensor.get_owned_hotkeys = subtensor.inner_subtensor.get_owned_hotkeys subtensor.get_parents = subtensor.inner_subtensor.get_parents - subtensor.get_revealed_commitment = subtensor.inner_subtensor.get_revealed_commitment + subtensor.get_revealed_commitment = ( + subtensor.inner_subtensor.get_revealed_commitment + ) subtensor.get_revealed_commitment_by_hotkey = ( subtensor.inner_subtensor.get_revealed_commitment_by_hotkey ) @@ -87,14 +99,18 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.inner_subtensor.get_stake_info_for_coldkey ) subtensor.get_stake_movement_fee = subtensor.inner_subtensor.get_stake_movement_fee - subtensor.get_stake_operations_fee = subtensor.inner_subtensor.get_stake_operations_fee + subtensor.get_stake_operations_fee = ( + subtensor.inner_subtensor.get_stake_operations_fee + ) subtensor.get_stake_weight = subtensor.inner_subtensor.get_stake_weight subtensor.get_subnet_burn_cost = subtensor.inner_subtensor.get_subnet_burn_cost subtensor.get_subnet_hyperparameters = ( subtensor.inner_subtensor.get_subnet_hyperparameters ) subtensor.get_subnet_info = subtensor.inner_subtensor.get_subnet_info - subtensor.get_subnet_owner_hotkey = subtensor.inner_subtensor.get_subnet_owner_hotkey + subtensor.get_subnet_owner_hotkey = ( + subtensor.inner_subtensor.get_subnet_owner_hotkey + ) subtensor.get_subnet_price = subtensor.inner_subtensor.get_subnet_price subtensor.get_subnet_prices = subtensor.inner_subtensor.get_subnet_prices subtensor.get_subnet_reveal_period_epochs = ( @@ -103,7 +119,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_subnet_validator_permits = ( subtensor.inner_subtensor.get_subnet_validator_permits ) - subtensor.get_subnets = subtensor.inner_subtensor.get_subnets + subtensor.get_all_subnets_netuid = subtensor.inner_subtensor.get_all_subnets_netuid subtensor.get_timelocked_weight_commits = ( subtensor.inner_subtensor.get_timelocked_weight_commits ) @@ -119,11 +135,15 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.is_fast_blocks = subtensor.inner_subtensor.is_fast_blocks subtensor.is_hotkey_delegate = subtensor.inner_subtensor.is_hotkey_delegate subtensor.is_hotkey_registered = subtensor.inner_subtensor.is_hotkey_registered - subtensor.is_hotkey_registered_any = subtensor.inner_subtensor.is_hotkey_registered_any + subtensor.is_hotkey_registered_any = ( + subtensor.inner_subtensor.is_hotkey_registered_any + ) subtensor.is_hotkey_registered_on_subnet = ( subtensor.inner_subtensor.is_hotkey_registered_on_subnet ) - subtensor.is_in_admin_freeze_window = subtensor.inner_subtensor.is_in_admin_freeze_window + subtensor.is_in_admin_freeze_window = ( + subtensor.inner_subtensor.is_in_admin_freeze_window + ) subtensor.is_subnet_active = subtensor.inner_subtensor.is_subnet_active subtensor.last_drand_round = subtensor.inner_subtensor.last_drand_round subtensor.log_verbose = subtensor.inner_subtensor.log_verbose @@ -161,7 +181,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.set_subnet_identity = subtensor.inner_subtensor.set_subnet_identity subtensor.set_weights = subtensor.inner_subtensor.set_weights subtensor.setup_config = subtensor.inner_subtensor.setup_config - subtensor.sign_and_send_extrinsic = subtensor.inner_subtensor.sign_and_send_extrinsic + subtensor.sign_and_send_extrinsic = ( + subtensor.inner_subtensor.sign_and_send_extrinsic + ) subtensor.start_call = subtensor.inner_subtensor.start_call subtensor.state_call = subtensor.inner_subtensor.state_call subtensor.subnet = subtensor.inner_subtensor.subnet diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 7b5a1a7781..ebda3c15ed 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2856,7 +2856,7 @@ async def get_stake_for_coldkey_and_hotkey( elif not block_hash: block_hash = await self.substrate.get_chain_head() if netuids is None: - all_netuids = await self.get_subnets(block_hash=block_hash) + all_netuids = await self.get_all_subnets_netuid(block_hash=block_hash) else: all_netuids = netuids results = await asyncio.gather( @@ -3081,7 +3081,7 @@ async def get_subnet_reveal_period_epochs( param_name="RevealPeriodEpochs", block_hash=block_hash, netuid=netuid ) - async def get_subnets( + async def get_all_subnets_netuid( self, block: Optional[int] = None, block_hash: Optional[str] = None, diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 0231411df5..494ef24909 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -1556,7 +1556,8 @@ async def _process_root_weights( """ data_array = [] n_subnets_, subnets = await asyncio.gather( - subtensor.get_total_subnets(block=block), subtensor.get_subnets(block=block) + subtensor.get_total_subnets(block=block), + subtensor.get_all_subnets_netuid(block=block), ) n_subnets = n_subnets_ or 0 for item in data: @@ -1867,7 +1868,7 @@ def _process_root_weights( """ data_array = [] n_subnets = subtensor.get_total_subnets(block=block) or 0 - subnets = subtensor.get_subnets(block=block) + subnets = subtensor.get_all_subnets_netuid(block=block) for item in data: if len(item) == 0: if use_torch(): diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index a7a0f7c790..8d0d34cac2 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2057,7 +2057,7 @@ def get_stake_for_coldkey_and_hotkey( A {netuid: StakeInfo} pairing of all stakes across all subnets. """ if netuids is None: - all_netuids = self.get_subnets(block=block) + all_netuids = self.get_all_subnets_netuid(block=block) else: all_netuids = netuids results = [ @@ -2231,7 +2231,7 @@ def get_subnet_reveal_period_epochs( ), ) - def get_subnets(self, block: Optional[int] = None) -> UIDs: + def get_all_subnets_netuid(self, block: Optional[int] = None) -> UIDs: """ Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network. diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 654f69c035..570171da8c 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -20,7 +20,7 @@ """ Verifies: -* get_subnets() +* get_all_subnets_netuid() * get_total_subnets() * subnet_exists() * get_netuids_for_hotkey() @@ -59,7 +59,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall existential_deposit = Balance.from_tao(0.000_000_500) # Subnets 0 and 1 are bootstrapped from the start - assert subtensor.subnets.get_subnets() == [0, 1] + assert subtensor.subnets.get_all_subnets_netuid() == [0, 1] assert subtensor.subnets.get_total_subnets() == 2 # Assert correct balance is fetched for Alice @@ -93,7 +93,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall ), "Balance is the same even after registering a subnet." # Subnet 2 is added after registration - assert subtensor.subnets.get_subnets() == [0, 1, 2] + assert subtensor.subnets.get_all_subnets_netuid() == [0, 1, 2] assert subtensor.subnets.get_total_subnets() == 3 # Verify subnet 2 created successfully @@ -245,7 +245,7 @@ async def test_subtensor_extrinsics_async( 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_all_subnets_netuid() == [0, 1] assert await async_subtensor.subnets.get_total_subnets() == 2 # Assert correct balance is fetched for Alice @@ -281,7 +281,7 @@ async def test_subtensor_extrinsics_async( ), "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_all_subnets_netuid() == [0, 1, 2] assert await async_subtensor.subnets.get_total_subnets() == 3 # Verify subnet 2 created successfully diff --git a/tests/helpers/integration_websocket_data.py b/tests/helpers/integration_websocket_data.py index ce450ad03c..c90b75ad48 100644 --- a/tests/helpers/integration_websocket_data.py +++ b/tests/helpers/integration_websocket_data.py @@ -5303,7 +5303,7 @@ }, "system_chain": {"[]": {"jsonrpc": "2.0", "result": "Bittensor"}}, }, - "get_subnets": { + "get_all_subnets_netuid": { "chain_getHead": { "[]": { "jsonrpc": "2.0", diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index e0a25e5a09..f36342a396 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -390,7 +390,7 @@ async def test_get_total_subnets(subtensor, mocker): ) @pytest.mark.asyncio async def test_get_subnets(subtensor, mocker, records, response): - """Tests get_subnets method with any return.""" + """Tests get_all_subnets_netuid method with any return.""" # Preps fake_result = mocker.AsyncMock(autospec=list) fake_result.records = records @@ -405,7 +405,7 @@ async def test_get_subnets(subtensor, mocker, records, response): fake_block_hash = None # Call - result = await subtensor.get_subnets(block_hash=fake_block_hash) + result = await subtensor.get_all_subnets_netuid(block_hash=fake_block_hash) # Asserts mocked_substrate_query_map.assert_called_once_with( @@ -559,7 +559,7 @@ async def test_get_stake_for_coldkey_and_hotkey(subtensor, mocker): subtensor.substrate, "get_chain_head", return_value=block_hash ) mocked_get_subnets = mocker.patch.object( - subtensor, "get_subnets", return_value=netuids + subtensor, "get_all_subnets_netuid", return_value=netuids ) result = await subtensor.get_stake_for_coldkey_and_hotkey( diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index ce942ce397..c53769abfc 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -759,9 +759,9 @@ def test_get_total_subnets_no_block(mocker, subtensor): subtensor.substrate.get_block_hash.assert_not_called() -# `get_subnets` tests +# `get_all_subnets_netuid` tests def test_get_subnets_success(mocker, subtensor): - """Test get_subnets returns correct list when subnet information is found.""" + """Test get_all_subnets_netuid returns correct list when subnet information is found.""" # Prep block = 123 mock_result = mocker.MagicMock() @@ -770,7 +770,7 @@ def test_get_subnets_success(mocker, subtensor): mocker.patch.object(subtensor.substrate, "query_map", return_value=mock_result) # Call - result = subtensor.get_subnets(block) + result = subtensor.get_all_subnets_netuid(block) # Asserts assert result == [1, 2] @@ -783,7 +783,7 @@ def test_get_subnets_success(mocker, subtensor): def test_get_subnets_no_data(mocker, subtensor): - """Test get_subnets returns empty list when no subnet information is found.""" + """Test get_all_subnets_netuid returns empty list when no subnet information is found.""" # Prep block = 123 mock_result = mocker.MagicMock() @@ -791,7 +791,7 @@ def test_get_subnets_no_data(mocker, subtensor): mocker.patch.object(subtensor.substrate, "query_map", return_value=mock_result) # Call - result = subtensor.get_subnets(block) + result = subtensor.get_all_subnets_netuid(block) # Asserts assert result == [] @@ -804,7 +804,7 @@ def test_get_subnets_no_data(mocker, subtensor): def test_get_subnets_no_block_specified(mocker, subtensor): - """Test get_subnets with no block specified.""" + """Test get_all_subnets_netuid with no block specified.""" # Prep mock_result = mocker.MagicMock() mock_result.records = [(1, True), (2, True)] @@ -812,7 +812,7 @@ def test_get_subnets_no_block_specified(mocker, subtensor): mocker.patch.object(subtensor.substrate, "query_map", return_value=mock_result) # Call - result = subtensor.get_subnets() + result = subtensor.get_all_subnets_netuid() # Asserts assert result == [1, 2] @@ -2087,7 +2087,7 @@ def test_get_stake_for_coldkey_and_hotkey(subtensor, mocker): subtensor, "query_runtime_api", side_effect=query_fetcher ) mocked_get_subnets = mocker.patch.object( - subtensor, "get_subnets", return_value=netuids + subtensor, "get_all_subnets_netuid", return_value=netuids ) result = subtensor.get_stake_for_coldkey_and_hotkey( From 348bc74ebba1dff19d32e11f53d25c77598ae9a3 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 16:50:09 -0700 Subject: [PATCH 273/416] method `get_owned_hotkeys` get rid `reuse_block` parameter to be consistent with other sync methods. --- MIGRATION.md | 3 ++- bittensor/core/subtensor.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index ba81c79179..608b109cf5 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -53,7 +53,7 @@ rename this variable in documentation. 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 `(in progress)`: +6. ✅ To be consistent throughout the SDK `(in progress)`: `hotkey`, `coldkey`, `hotkeypub`, and `coldkeypub` are keypairs `hotkey_ss58`, `coldkey_ss58`, `hotkeypub_ss58`, and `coldkeypub_ss58` are SS58 addresses of keypair. @@ -236,6 +236,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `add_stake_multiple` has updated parameters order. - method `get_stake_for_coldkey` removed, bc this is the same as `get_stake_info_for_coldkey` - method `get_subnets` renamed to `get_all_subnets_netuid` (more obvious) +- method `get_owned_hotkeys` get rid `reuse_block` parameter to be consistent with other sync methods. Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 8d0d34cac2..c2f350e248 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1727,7 +1727,6 @@ def get_owned_hotkeys( self, coldkey_ss58: str, block: Optional[int] = None, - reuse_block: bool = False, ) -> list[str]: """ Retrieves all hotkeys owned by a specific coldkey address. @@ -1735,7 +1734,6 @@ def get_owned_hotkeys( Parameters: coldkey_ss58: The SS58 address of the coldkey to query. block: The blockchain block number for the query. - reuse_block: Whether to reuse the last-used blockchain block hash. Returns: list[str]: A list of hotkey SS58 addresses owned by the coldkey. @@ -1746,7 +1744,6 @@ def get_owned_hotkeys( storage_function="OwnedHotkeys", params=[coldkey_ss58], block_hash=block_hash, - reuse_block_hash=reuse_block, ) return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []] From d9111d5512b88d19c5c1fa529e578bad5d8400d9 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 16:59:17 -0700 Subject: [PATCH 274/416] All liquidity methods and related extrinsics received a renamed parameter: `hotkey` -> `hotkey_ss58` --- MIGRATION.md | 4 ++-- bittensor/core/async_subtensor.py | 18 +++++++++--------- bittensor/core/extrinsics/asyncex/liquidity.py | 18 +++++++++--------- bittensor/core/extrinsics/liquidity.py | 18 +++++++++--------- bittensor/core/subtensor.py | 18 +++++++++--------- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 608b109cf5..bc1fda3408 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -28,7 +28,7 @@ 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. +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.~~ wrong idea ## Metagraph @@ -227,7 +227,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: ### Subtensor changes - method `all_subnets` has renamed parameter from `block_number` to `block` (consistency in the codebase). -- The `hotkey` parameter, which meant ss58 key address, was renamed to `hotkey_ss58` in all methods (consistency in the codebase). +- The `hotkey` parameter, which meant ss58 key address, was renamed to `hotkey_ss58` in all methods and related extrinsics (consistency in the codebase). - The `coldkey` parameter, which meant ss58 key address, was renamed to `coldkey_ss58` in all methods (consistency in the codebase). - method `query_subtensor` has updated parameters order. - method `query_module` has updated parameters order. diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ebda3c15ed..708b8c4635 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4363,7 +4363,7 @@ async def add_liquidity( liquidity: Balance, price_low: Balance, price_high: Balance, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -4378,7 +4378,7 @@ async def add_liquidity( 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. + hotkey_ss58: 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. @@ -4399,7 +4399,7 @@ async def add_liquidity( liquidity=liquidity, price_low=price_low, price_high=price_high, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -4585,7 +4585,7 @@ async def modify_liquidity( netuid: int, position_id: int, liquidity_delta: Balance, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -4598,7 +4598,7 @@ async def modify_liquidity( 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. + hotkey_ss58: 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. @@ -4644,7 +4644,7 @@ async def modify_liquidity( netuid=netuid, position_id=position_id, liquidity_delta=liquidity_delta, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -4805,7 +4805,7 @@ async def remove_liquidity( wallet: "Wallet", netuid: int, position_id: int, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -4817,7 +4817,7 @@ async def remove_liquidity( 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. + hotkey_ss58: 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. @@ -4838,7 +4838,7 @@ async def remove_liquidity( wallet=wallet, netuid=netuid, position_id=position_id, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index f655da7a21..eb42ce2679 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -16,7 +16,7 @@ async def add_liquidity_extrinsic( liquidity: Balance, price_low: Balance, price_high: Balance, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -32,7 +32,7 @@ async def add_liquidity_extrinsic( 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. + hotkey_ss58: 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. @@ -59,7 +59,7 @@ async def add_liquidity_extrinsic( call_module="Swap", call_function="add_liquidity", call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, + "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, "netuid": netuid, "tick_low": tick_low, "tick_high": tick_high, @@ -85,7 +85,7 @@ async def modify_liquidity_extrinsic( netuid: int, position_id: int, liquidity_delta: Balance, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -99,7 +99,7 @@ async def modify_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. 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. + hotkey_ss58: 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. @@ -123,7 +123,7 @@ async def modify_liquidity_extrinsic( call_module="Swap", call_function="modify_position", call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, + "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, "netuid": netuid, "position_id": position_id, "liquidity_delta": liquidity_delta.rao, @@ -147,7 +147,7 @@ async def remove_liquidity_extrinsic( wallet: "Wallet", netuid: int, position_id: int, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -160,7 +160,7 @@ async def remove_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. 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. + hotkey_ss58: 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. @@ -184,7 +184,7 @@ async def remove_liquidity_extrinsic( call_module="Swap", call_function="remove_liquidity", call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, + "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, "netuid": netuid, "position_id": position_id, }, diff --git a/bittensor/core/extrinsics/liquidity.py b/bittensor/core/extrinsics/liquidity.py index 64ebc8c674..f86604a202 100644 --- a/bittensor/core/extrinsics/liquidity.py +++ b/bittensor/core/extrinsics/liquidity.py @@ -16,7 +16,7 @@ def add_liquidity_extrinsic( liquidity: Balance, price_low: Balance, price_high: Balance, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -32,7 +32,7 @@ def add_liquidity_extrinsic( 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. + hotkey_ss58: 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. @@ -59,7 +59,7 @@ def add_liquidity_extrinsic( call_module="Swap", call_function="add_liquidity", call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, + "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, "netuid": netuid, "tick_low": tick_low, "tick_high": tick_high, @@ -85,7 +85,7 @@ def modify_liquidity_extrinsic( netuid: int, position_id: int, liquidity_delta: Balance, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -99,7 +99,7 @@ def modify_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. 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. + hotkey_ss58: 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. @@ -123,7 +123,7 @@ def modify_liquidity_extrinsic( call_module="Swap", call_function="modify_position", call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, + "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, "netuid": netuid, "position_id": position_id, "liquidity_delta": liquidity_delta.rao, @@ -147,7 +147,7 @@ def remove_liquidity_extrinsic( wallet: "Wallet", netuid: int, position_id: int, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -160,7 +160,7 @@ def remove_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. 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. + hotkey_ss58: 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. @@ -184,7 +184,7 @@ def remove_liquidity_extrinsic( call_module="Swap", call_function="remove_liquidity", call_params={ - "hotkey": hotkey or wallet.hotkey.ss58_address, + "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, "netuid": netuid, "position_id": position_id, }, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index c2f350e248..4316df526c 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3239,7 +3239,7 @@ def add_liquidity( liquidity: Balance, price_low: Balance, price_high: Balance, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -3254,7 +3254,7 @@ def add_liquidity( 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. + hotkey_ss58: 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. @@ -3275,7 +3275,7 @@ def add_liquidity( liquidity=liquidity, price_low=price_low, price_high=price_high, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -3458,7 +3458,7 @@ def modify_liquidity( netuid: int, position_id: int, liquidity_delta: Balance, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -3471,7 +3471,7 @@ def modify_liquidity( 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. + hotkey_ss58: 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. @@ -3517,7 +3517,7 @@ def modify_liquidity( netuid=netuid, position_id=position_id, liquidity_delta=liquidity_delta, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, @@ -3678,7 +3678,7 @@ def remove_liquidity( wallet: "Wallet", netuid: int, position_id: int, - hotkey: Optional[str] = None, + hotkey_ss58: Optional[str] = None, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, @@ -3690,7 +3690,7 @@ def remove_liquidity( 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. + hotkey_ss58: 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. @@ -3711,7 +3711,7 @@ def remove_liquidity( wallet=wallet, netuid=netuid, position_id=position_id, - hotkey=hotkey, + hotkey_ss58=hotkey_ss58, period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, From b9c3dcc5ddf852825e2f44bbc06bacddfce6cc6b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 17:06:22 -0700 Subject: [PATCH 275/416] deprecated `bittensor.utils.version.version_checking` removed --- bittensor/utils/__init__.py | 3 +-- bittensor/utils/easy_imports.py | 2 -- bittensor/utils/version.py | 20 -------------------- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index c75a0b7936..0ffcdb37c1 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -18,7 +18,7 @@ from bittensor.core.settings import SS58_FORMAT from bittensor.utils.btlogging import logging from .registration import torch, use_torch -from .version import version_checking, check_version, VersionCheckError +from .version import check_version, VersionCheckError if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -36,7 +36,6 @@ logging = logging torch = torch use_torch = use_torch -version_checking = version_checking check_version = check_version VersionCheckError = VersionCheckError ss58_decode = ss58_decode diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index e84a84d786..57bc9ae2bc 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -105,7 +105,6 @@ from bittensor.core.threadpool import PriorityThreadPoolExecutor from bittensor.utils import ( ss58_to_vec_u8, - version_checking, strtobool, get_explorer_url_for_network, ss58_address_to_bytes, @@ -273,7 +272,6 @@ def info(on: bool = True): "Tensor", "PriorityThreadPoolExecutor", "ss58_to_vec_u8", - "version_checking", "strtobool", "get_explorer_url_for_network", "ss58_address_to_bytes", diff --git a/bittensor/utils/version.py b/bittensor/utils/version.py index 3f920e8333..e78fd109e8 100644 --- a/bittensor/utils/version.py +++ b/bittensor/utils/version.py @@ -98,26 +98,6 @@ def check_version(timeout: int = 15): raise VersionCheckError("Version check failed") from e -def version_checking(timeout: int = 15): - """Deprecated, kept for backwards compatibility. Use check_version() instead. - - Parameters: - timeout: The timeout for calling :func:``check_version`` function. - """ - - from warnings import warn - - warn( - "version_checking() is deprecated, please use check_version() instead", - DeprecationWarning, - ) - - try: - check_version(timeout) - except VersionCheckError: - logging.exception("Version check failed") - - def check_latest_version_in_pypi(): """Check for the latest version of the package on PyPI.""" package_name = __name__ From 63b1183306e9eabd363b9e279b0b3aceaf0db731 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 17:07:01 -0700 Subject: [PATCH 276/416] rename `hotkey` > `hotkey_ss58` in liquidity methods and extrinsics + fixed unit tests --- tests/unit_tests/test_async_subtensor.py | 10 +++++----- tests/unit_tests/test_subtensor.py | 8 +++----- tests/unit_tests/utils/test_version.py | 17 ----------------- 3 files changed, 8 insertions(+), 27 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index f36342a396..16c1e531a3 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3499,8 +3499,8 @@ async def test_add_liquidity(subtensor, fake_wallet, mocker): wallet=fake_wallet, netuid=netuid, liquidity=Balance.from_tao(150), - price_low=Balance.from_tao(180).rao, - price_high=Balance.from_tao(130).rao, + price_low=Balance.from_tao(180), + price_high=Balance.from_tao(130), ) # Asserts @@ -3511,7 +3511,7 @@ async def test_add_liquidity(subtensor, fake_wallet, mocker): liquidity=Balance.from_tao(150), price_low=Balance.from_tao(180).rao, price_high=Balance.from_tao(130).rao, - hotkey=None, + hotkey_ss58=None, wait_for_inclusion=True, wait_for_finalization=True, period=None, @@ -3545,7 +3545,7 @@ async def test_modify_liquidity(subtensor, fake_wallet, mocker): netuid=netuid, position_id=position_id, liquidity_delta=Balance.from_tao(150), - hotkey=None, + hotkey_ss58=None, wait_for_inclusion=True, wait_for_finalization=True, period=None, @@ -3577,7 +3577,7 @@ async def test_remove_liquidity(subtensor, fake_wallet, mocker): wallet=fake_wallet, netuid=netuid, position_id=position_id, - hotkey=None, + hotkey_ss58=None, wait_for_inclusion=True, wait_for_finalization=True, period=None, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index c53769abfc..ab69d03c86 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3094,7 +3094,6 @@ def test_get_owned_hotkeys_happy_path(subtensor, mocker): storage_function="OwnedHotkeys", params=[fake_coldkey], block_hash=None, - reuse_block_hash=False, ) assert result == [mocked_decode_account_id.return_value] mocked_decode_account_id.assert_called_once_with(fake_hotkey) @@ -3116,7 +3115,6 @@ def test_get_owned_hotkeys_return_empty(subtensor, mocker): storage_function="OwnedHotkeys", params=[fake_coldkey], block_hash=None, - reuse_block_hash=False, ) assert result == [] @@ -3738,7 +3736,7 @@ def test_add_liquidity(subtensor, fake_wallet, mocker): liquidity=Balance.from_tao(150), price_low=Balance.from_tao(180).rao, price_high=Balance.from_tao(130).rao, - hotkey=None, + hotkey_ss58=None, wait_for_inclusion=True, wait_for_finalization=True, period=None, @@ -3771,7 +3769,7 @@ def test_modify_liquidity(subtensor, fake_wallet, mocker): netuid=netuid, position_id=position_id, liquidity_delta=Balance.from_tao(150), - hotkey=None, + hotkey_ss58=None, wait_for_inclusion=True, wait_for_finalization=True, period=None, @@ -3802,7 +3800,7 @@ def test_remove_liquidity(subtensor, fake_wallet, mocker): wallet=fake_wallet, netuid=netuid, position_id=position_id, - hotkey=None, + hotkey_ss58=None, wait_for_inclusion=True, wait_for_finalization=True, period=None, diff --git a/tests/unit_tests/utils/test_version.py b/tests/unit_tests/utils/test_version.py index 6d1785b0bd..6d8d535d18 100644 --- a/tests/unit_tests/utils/test_version.py +++ b/tests/unit_tests/utils/test_version.py @@ -133,20 +133,3 @@ def test_check_version_up_to_date( assert captured.out == "" - -def test_version_checking(mocker: MockerFixture): - mock = mocker.patch("bittensor.utils.version.check_version") - - version.version_checking() - - mock.assert_called_once() - - -def test_version_checking_exception(mocker: MockerFixture): - mock = mocker.patch( - "bittensor.utils.version.check_version", side_effect=version.VersionCheckError - ) - - version.version_checking() - - mock.assert_called_once() From a84677c626641ba05a4b5f5e152ea544d6b89d16 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 17:24:17 -0700 Subject: [PATCH 277/416] method `blocks_since_last_update` improved. Currently it can be used to get historical data from archive node. --- MIGRATION.md | 1 + bittensor/core/async_subtensor.py | 24 +++++++++++++++++++++--- bittensor/core/subtensor.py | 12 +++++++++--- tests/unit_tests/test_async_subtensor.py | 24 +++++++++++++++++------- tests/unit_tests/test_subtensor.py | 2 +- 5 files changed, 49 insertions(+), 14 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index bc1fda3408..c11fb34aa9 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -237,6 +237,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `get_stake_for_coldkey` removed, bc this is the same as `get_stake_info_for_coldkey` - method `get_subnets` renamed to `get_all_subnets_netuid` (more obvious) - method `get_owned_hotkeys` get rid `reuse_block` parameter to be consistent with other sync methods. +- method `blocks_since_last_update` improved. Currently it can be used to get historical data from archive node. Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 708b8c4635..1c6f6796ed 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -857,12 +857,22 @@ async def blocks_since_last_step( ) return query.value if query is not None and hasattr(query, "value") else query - async def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int]: + async def blocks_since_last_update( + self, + netuid: int, + uid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional[int]: """Returns the number of blocks since the last update, or ``None`` if the subnetwork or UID does not exist. Parameters: netuid: The unique identifier of the subnetwork. uid: The unique identifier of the neuron. + block: The block number for this query. Do not specify if using block_hash or reuse_block. + block_hash: The hash of the block for the query. Do not specify if using reuse_block or block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: The number of blocks since the last update, or None if the subnetwork or UID does not exist. @@ -874,8 +884,16 @@ async def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int] # Check if neuron needs updating blocks_since_update = await subtensor.blocks_since_last_update(netuid=1, uid=10) """ - call = await self.get_hyperparameter(param_name="LastUpdate", netuid=netuid) - return None if call is None else await self.get_current_block() - int(call[uid]) + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + block = block or await self.substrate.get_block_number(block_hash) + call = await self.get_hyperparameter( + param_name="LastUpdate", + netuid=netuid, + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + return None if call is None else (block - int(call[uid])) async def bonds( self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 4316df526c..fb46b16260 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -486,19 +486,25 @@ def blocks_since_last_step( ) return query.value if query is not None and hasattr(query, "value") else query - def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int]: + def blocks_since_last_update( + self, netuid: int, uid: int, block: Optional[int] = None + ) -> Optional[int]: """ Returns the number of blocks since the last update for a specific UID in the subnetwork. Parameters: netuid: The unique identifier of the subnetwork. uid: The unique identifier of the neuron. + block: the block number for this query. Returns: The number of blocks since the last update, or ``None`` if the subnetwork or UID does not exist. """ - call = self.get_hyperparameter(param_name="LastUpdate", netuid=netuid) - return None if not call else (self.get_current_block() - int(call[uid])) + block = block or self.get_current_block() + call = self.get_hyperparameter( + param_name="LastUpdate", netuid=netuid, block=block + ) + return None if not call else (block - int(call[uid])) def bonds( self, diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 16c1e531a3..76da3967b0 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2508,23 +2508,26 @@ async def test_blocks_since_last_update_success(subtensor, mocker): current_block = 100 fake_blocks_since_update = current_block - last_update_block + mocker.patch.object( + subtensor.substrate, "get_block_number", return_value=current_block, + ) mocked_get_hyperparameter = mocker.patch.object( subtensor, "get_hyperparameter", return_value={fake_uid: last_update_block}, ) - mocked_get_current_block = mocker.AsyncMock(return_value=current_block) - subtensor.get_current_block = mocked_get_current_block - # Call result = await subtensor.blocks_since_last_update(netuid=fake_netuid, uid=fake_uid) # Asserts mocked_get_hyperparameter.assert_called_once_with( - param_name="LastUpdate", netuid=fake_netuid + param_name="LastUpdate", + netuid=fake_netuid, + block=subtensor.substrate.get_block_number.return_value, + block_hash=None, + reuse_block=False, ) - mocked_get_current_block.assert_called_once() assert result == fake_blocks_since_update @@ -2543,11 +2546,18 @@ async def test_blocks_since_last_update_no_last_update(subtensor, mocker): ) # Call - result = await subtensor.blocks_since_last_update(netuid=fake_netuid, uid=fake_uid) + result = await subtensor.blocks_since_last_update( + netuid=fake_netuid, + uid=fake_uid, + ) # Asserts mocked_get_hyperparameter.assert_called_once_with( - param_name="LastUpdate", netuid=fake_netuid + param_name="LastUpdate", + netuid=fake_netuid, + block=subtensor.substrate.get_block_number.return_value, + block_hash=None, + reuse_block=False, ) assert result is None diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index ab69d03c86..6c78d7f893 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -368,7 +368,7 @@ def test_blocks_since_last_update_success_calls(subtensor, mocker): # Assertions mocked_get_current_block.assert_called_once() - mocked_get_hyperparameter.assert_called_once_with(param_name="LastUpdate", netuid=7) + mocked_get_hyperparameter.assert_called_once_with(param_name="LastUpdate", netuid=7, block=mocked_current_block) assert result == 1 # if we change the methods logic in the future we have to be make sure the returned type is correct assert isinstance(result, int) From 000894481c335b3c04238b3a5669cb445b8e1852 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 17:40:43 -0700 Subject: [PATCH 278/416] method `blocks_since_last_update` improved. Currently it can be used to get historical data from archive node. --- MIGRATION.md | 1 + bittensor/core/async_subtensor.py | 26 ++++++++++++++++++++---- tests/unit_tests/test_async_subtensor.py | 8 ++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index c11fb34aa9..20095fd196 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -238,6 +238,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `get_subnets` renamed to `get_all_subnets_netuid` (more obvious) - method `get_owned_hotkeys` get rid `reuse_block` parameter to be consistent with other sync methods. - method `blocks_since_last_update` improved. Currently it can be used to get historical data from archive node. +- methods (async) `get_subnet_validator_permits` and `get_subnet_owner_hotkey` got `block_hash` and `reuse_block` parameters. Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 1c6f6796ed..b5532ba5c1 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4167,7 +4167,11 @@ async def get_timestamp( return datetime.fromtimestamp(unix / 1000, tz=timezone.utc) async def get_subnet_owner_hotkey( - self, netuid: int, block: Optional[int] = None + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Optional[str]: """ Retrieves the hotkey of the subnet owner for a given network UID. @@ -4177,17 +4181,27 @@ async def get_subnet_owner_hotkey( Parameters: netuid: The network UID of the subnet to fetch the owner's hotkey for. - block: The specific block number to query the data from. + block: The blockchain block number for the query. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: The hotkey of the subnet owner if available; None otherwise. """ return await self.query_subtensor( - name="SubnetOwnerHotkey", params=[netuid], block=block + name="SubnetOwnerHotkey", + params=[netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, ) async def get_subnet_validator_permits( - self, netuid: int, block: Optional[int] = None + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Optional[list[bool]]: """ Retrieves the list of validator permits for a given subnet as boolean values. @@ -4195,6 +4209,8 @@ async def get_subnet_validator_permits( Parameters: netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: A list of boolean values representing validator permits, or None if not available. @@ -4203,6 +4219,8 @@ async def get_subnet_validator_permits( name="ValidatorPermit", params=[netuid], block=block, + block_hash=block_hash, + reuse_block=reuse_block, ) return query.value if query is not None and hasattr(query, "value") else query diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 76da3967b0..2587e27adf 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3113,6 +3113,8 @@ async def test_get_subnet_owner_hotkey_has_return(subtensor, mocker): name="SubnetOwnerHotkey", block=block, params=[netuid], + block_hash=None, + reuse_block=False, ) assert result == expected_owner_hotkey @@ -3135,6 +3137,8 @@ async def test_get_subnet_owner_hotkey_is_none(subtensor, mocker): name="SubnetOwnerHotkey", block=block, params=[netuid], + block_hash=None, + reuse_block=False, ) assert result is None @@ -3158,6 +3162,8 @@ async def test_get_subnet_validator_permits_has_values(subtensor, mocker): name="ValidatorPermit", block=block, params=[netuid], + block_hash=None, + reuse_block=False, ) assert result == expected_validator_permits @@ -3181,6 +3187,8 @@ async def test_get_subnet_validator_permits_is_none(subtensor, mocker): name="ValidatorPermit", block=block, params=[netuid], + block_hash=None, + reuse_block=False, ) assert result is None From 073ec663ba4cd04397405ff2e873d70325320cd8 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 17:48:35 -0700 Subject: [PATCH 279/416] attribute `DelegateInfo/lite.total_daily_return` has been deleted (Vune confirmed that we shouldn't use it) --- MIGRATION.md | 1 + bittensor/core/chain_data/delegate_info.py | 6 ------ bittensor/core/chain_data/delegate_info_lite.py | 3 --- 3 files changed, 1 insertion(+), 9 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 20095fd196..4776e2bd95 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -239,6 +239,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `get_owned_hotkeys` get rid `reuse_block` parameter to be consistent with other sync methods. - method `blocks_since_last_update` improved. Currently it can be used to get historical data from archive node. - methods (async) `get_subnet_validator_permits` and `get_subnet_owner_hotkey` got `block_hash` and `reuse_block` parameters. +- attribute `DelegateInfo/lite.total_daily_return` has been deleted (Vune confirmed that we shouldn't use it) Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` diff --git a/bittensor/core/chain_data/delegate_info.py b/bittensor/core/chain_data/delegate_info.py index cfc4e50654..91301b6034 100644 --- a/bittensor/core/chain_data/delegate_info.py +++ b/bittensor/core/chain_data/delegate_info.py @@ -18,7 +18,6 @@ class DelegateInfoBase(InfoBase): validator_permits: List of subnets that the delegate is allowed to validate on. registrations: List of subnets that the delegate is registered on. return_per_1000: Return per 1000 tao of the delegate over a day. - total_daily_return: Total daily return of the delegate. """ hotkey_ss58: str # Hotkey of delegate @@ -29,7 +28,6 @@ class DelegateInfoBase(InfoBase): ] # List of subnets that the delegate is allowed to validate on registrations: list[int] # list of subnets that the delegate is registered on return_per_1000: Balance # Return per 1000 tao of the delegate over a day - total_daily_return: Balance # Total daily return of the delegate @dataclass @@ -77,7 +75,6 @@ def _from_dict(cls, decoded: dict) -> Optional["DelegateInfo"]: validator_permits=list(decoded.get("validator_permits", [])), registrations=list(decoded.get("registrations", [])), return_per_1000=Balance.from_rao(decoded.get("return_per_1000")), - total_daily_return=Balance.from_rao(decoded.get("total_daily_return")), ) @@ -108,9 +105,6 @@ def _from_dict( validator_permits=list(delegate_info.get("validator_permits", [])), registrations=list(delegate_info.get("registrations", [])), return_per_1000=Balance.from_rao(delegate_info.get("return_per_1000")), - total_daily_return=Balance.from_rao( - delegate_info.get("total_daily_return") - ), netuid=int(netuid), stake=Balance.from_rao(int(stake)).set_unit(int(netuid)), ) diff --git a/bittensor/core/chain_data/delegate_info_lite.py b/bittensor/core/chain_data/delegate_info_lite.py index aa61f44abd..06666769dc 100644 --- a/bittensor/core/chain_data/delegate_info_lite.py +++ b/bittensor/core/chain_data/delegate_info_lite.py @@ -19,7 +19,6 @@ class DelegateInfoLite(InfoBase): registrations: List of subnets that the delegate is registered on. validator_permits: List of subnets that the delegate is allowed to validate on. return_per_1000: Return per 1000 TAO, for the delegate over a day. - total_daily_return: Total daily return of the delegate. """ delegate_ss58: str # Hotkey of delegate @@ -31,7 +30,6 @@ class DelegateInfoLite(InfoBase): int ] # List of subnets that the delegate is allowed to validate on return_per_1000: Balance # Return per 1000 tao for the delegate over a day - total_daily_return: Balance # Total daily return of the delegate @classmethod def _from_dict(cls, decoded: dict) -> "DelegateInfoLite": @@ -43,5 +41,4 @@ def _from_dict(cls, decoded: dict) -> "DelegateInfoLite": registrations=decoded["registrations"], validator_permits=decoded["validator_permits"], return_per_1000=Balance.from_rao(decoded["return_per_1000"]), - total_daily_return=Balance.from_rao(decoded["total_daily_return"]), ) From abb94213533d4de9a15d8746b64634f41224bd19 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 17:48:48 -0700 Subject: [PATCH 280/416] fix integration tests --- tests/helpers/integration_websocket_data.py | 47 ++++++++++--------- .../test_subtensor_integration.py | 2 +- 2 files changed, 27 insertions(+), 22 deletions(-) diff --git a/tests/helpers/integration_websocket_data.py b/tests/helpers/integration_websocket_data.py index c90b75ad48..3bbf359819 100644 --- a/tests/helpers/integration_websocket_data.py +++ b/tests/helpers/integration_websocket_data.py @@ -9,48 +9,48 @@ "blocks_since_last_update": { "chain_getHead": { "[]": { - "result": "0xf658002393b00476606e6b6e1eb8c0a2979e53eacb8318b4b5f73fa73ff20398" + "result": "0x2830271e820b419253b79cbafea83f726354bbef26eb318835ffa774288de9ff" } }, "chain_getHeader": { - '["0xf658002393b00476606e6b6e1eb8c0a2979e53eacb8318b4b5f73fa73ff20398"]': { + '["0x2830271e820b419253b79cbafea83f726354bbef26eb318835ffa774288de9ff"]': { "result": { - "parentHash": "0xf620bf7b416a49079f0817a15fe5b1243bb5200416bf1b6c7934114551504517", - "number": "0x63856c", - "stateRoot": "0xb05a9a4ab12adda8c91d63242f9925da3735e5fa79b31094d2c61a6ac85db9a4", - "extrinsicsRoot": "0xedf4302417e5be22e2eff506e624db7807e5c98f1e4941d31d4a4e54527222ec", + "parentHash": "0xb5a3b72cc914b520a12e45e9f73fc3076b86fbae1be20b191fb7da4967c6ac47", + "number": "0x63fe87", + "stateRoot": "0x800d3090504b36e496fc579edf9db972a9cbc7522d52d96d4804e3396b21be7d", + "extrinsicsRoot": "0xe3ccef094f2e6176bc4a2d2f42a2d0b0c5f815c8506cf4299c8390c0eac62d39", "digest": { "logs": [ - "0x066175726120ec74bc0800000000", - "0x0466726f6e88011d87918002e1c26ed6d9a77885b14f77e4077cd9188cf398549cc978a295ad2b00", - "0x05617572610101961dd0050612d51f0aab580ee3ca9c7441ac91a7055716c1db496c418fc9d8431f51fe358b2833d613e6f69d5d783a0e89438511bc848a43845e8daa3d4ed980", + "0x06617572612007eebc0800000000", + "0x0466726f6e8801e4783b7f433d5462d1b84cfc851cbcf2fe886103b02fab1cce30f82cdaf0d37000", + "0x056175726101015e77e14c4e02e720bbd213a708dac67de5ea3494b41b6a87f03eee2d2d71557f2c960bd462e14433e758c6fe2584abec9b22b0d550bd25c76f836b413c526b8d", ] }, } }, "[null]": { "result": { - "parentHash": "0xf620bf7b416a49079f0817a15fe5b1243bb5200416bf1b6c7934114551504517", - "number": "0x63856c", - "stateRoot": "0xb05a9a4ab12adda8c91d63242f9925da3735e5fa79b31094d2c61a6ac85db9a4", - "extrinsicsRoot": "0xedf4302417e5be22e2eff506e624db7807e5c98f1e4941d31d4a4e54527222ec", + "parentHash": "0xb5a3b72cc914b520a12e45e9f73fc3076b86fbae1be20b191fb7da4967c6ac47", + "number": "0x63fe87", + "stateRoot": "0x800d3090504b36e496fc579edf9db972a9cbc7522d52d96d4804e3396b21be7d", + "extrinsicsRoot": "0xe3ccef094f2e6176bc4a2d2f42a2d0b0c5f815c8506cf4299c8390c0eac62d39", "digest": { "logs": [ - "0x066175726120ec74bc0800000000", - "0x0466726f6e88011d87918002e1c26ed6d9a77885b14f77e4077cd9188cf398549cc978a295ad2b00", - "0x05617572610101961dd0050612d51f0aab580ee3ca9c7441ac91a7055716c1db496c418fc9d8431f51fe358b2833d613e6f69d5d783a0e89438511bc848a43845e8daa3d4ed980", + "0x06617572612007eebc0800000000", + "0x0466726f6e8801e4783b7f433d5462d1b84cfc851cbcf2fe886103b02fab1cce30f82cdaf0d37000", + "0x056175726101015e77e14c4e02e720bbd213a708dac67de5ea3494b41b6a87f03eee2d2d71557f2c960bd462e14433e758c6fe2584abec9b22b0d550bd25c76f836b413c526b8d", ] }, } }, }, "state_getRuntimeVersion": { - '["0xf620bf7b416a49079f0817a15fe5b1243bb5200416bf1b6c7934114551504517"]': { + '["0xb5a3b72cc914b520a12e45e9f73fc3076b86fbae1be20b191fb7da4967c6ac47"]': { "result": { "specName": "node-subtensor", "implName": "node-subtensor", "authoringVersion": 1, - "specVersion": 315, + "specVersion": 320, "implVersion": 1, "apis": [ ["0xdf6acb689907609b", 5], @@ -81,12 +81,17 @@ } } }, + "chain_getBlockHash": { + "[6553223]": { + "result": "0x2830271e820b419253b79cbafea83f726354bbef26eb318835ffa774288de9ff" + } + }, "state_getStorageAt": { - '["0x658faa385070e074c85bf6b568cf05550e30450fc4d507a846032a7fa65d9a430100", null]': { + '["0x658faa385070e074c85bf6b568cf05550e30450fc4d507a846032a7fa65d9a430100", "0x2830271e820b419253b79cbafea83f726354bbef26eb318835ffa774288de9ff"]': { "result": "0x01" }, - '["0x658faa385070e074c85bf6b568cf0555696e262a16e52255a69d8acd793541460100", null]': { - "result": "0x0110a1cf2600000000009bc7260000000000b6fc2d0000000000b43c40000000000036b9260000000000e9445e00000000004ec5330000000000a2d02600000000008dc726000000000057d02600000000002ac826000000000069ca260000000000988a470000000000a8812600000000009cce26000000000080d026000000000057ca26000000000060cb26000000000059ce260000000000a9cc2600000000003ad02600000000007fd0260000000000c5c9260000000000fccf260000000000218563000000000011c726000000000015be26000000000039cf260000000000a3d02600000000007bb226000000000057ce2600000000006783260000000000bb093b0000000000b3d026000000000094ce2600000000004a8f2500000000000cc726000000000095c626000000000013b9260000000000fec926000000000070d026000000000078cf26000000000028d026000000000052cb2600000000004aaa240000000000a4c526000000000046ce260000000000c5cd26000000000004ce260000000000f2cc260000000000bcb72b00000000006acc260000000000c7cc260000000000d11c2600000000000dcf2600000000001fcb26000000000000cf26000000000030c1260000000000f3cd2600000000004dd0260000000000ea134b0000000000323d300000000000afcf26000000000016b426000000000087134b000000000094d026000000000012cc2600000000009dc3260000000000c9c3260000000000ecba260000000000f9c6260000000000e0c8260000000000218f25000000000051c8260000000000d2ce2600000000005dce280000000000a2ca260000000000f0cc26000000000089bf260000000000d8cb260000000000ba2526000000000076ab260000000000f8c9260000000000cdc8260000000000b3b32600000000005ed0260000000000f2b4260000000000a4cc26000000000016c926000000000019c6260000000000f1cc260000000000b3d0260000000000f4cc2600000000008db62600000000004fcf2600000000002bc52600000000009a27260000000000848726000000000020c4260000000000ebcb260000000000ea0d290000000000edc4260000000000a2c026000000000078c6260000000000111b2600000000009fc226000000000091d026000000000036c726000000000014c6290000000000a1cc260000000000dfa5260000000000ebc8260000000000ff782600000000001c8f250000000000fe6d260000000000aec0260000000000f5ca260000000000d7cb260000000000a3bd2600000000005dc82600000000009485260000000000c87826000000000090c72600000000002acf260000000000b2c626000000000030ba26000000000010c926000000000035c2260000000000f1c92600000000003acc26000000000095d02600000000001cab2600000000000fce2600000000000fce260000000000bdcc2600000000008fcc260000000000b45f3d000000000085ce26000000000056c326000000000053ce260000000000aec526000000000021bc260000000000acc82600000000009bd02600000000005ad026000000000038c426000000000019c3260000000000e1cd260000000000b0ce26000000000034c526000000000020be2600000000000b7d320000000000aecd26000000000048c6260000000000c1cf26000000000063d026000000000003cd260000000000ddc62600000000000d2b280000000000b3aa2400000000001ac82600000000001c522600000000008ac726000000000042ce2600000000004fce260000000000aabf26000000000017cc2600000000009e753300000000009cce2600000000008ecc26000000000009ca26000000000044cd26000000000024d02600000000009fbb260000000000a7c72600000000009cc6260000000000a6d026000000000061d0260000000000eac326000000000084b526000000000086cd2600000000000cc2260000000000d9c62600000000003ec32600000000002ec8260000000000c3cb26000000000085cf260000000000d7ce2600000000009dc226000000000033cc26000000000037ca26000000000055ce26000000000085ec44000000000095d0260000000000c9c6260000000000b7ca260000000000fcbd26000000000024d026000000000075bb260000000000e4b7260000000000087d32000000000020cf26000000000019ce26000000000054e644000000000022c62600000000009cc72600000000009dd0260000000000c8af4e000000000062ce260000000000019e26000000000049cc260000000000abcb260000000000c05026000000000066cb260000000000adcf260000000000aa9626000000000072cd2600000000003dc226000000000040573b00000000000dca260000000000adcc2600000000006bd0260000000000accc260000000000ccc9260000000000638f250000000000ea7c2600000000008b4a290000000000adcd26000000000061c126000000000079cf2600000000009dc5260000000000cece2600000000004dcd260000000000b2d026000000000014cc26000000000004ce2600000000003cce26000000000075cf260000000000d2ba2600000000000ecb2600000000009093260000000000a1c526000000000028ce2600000000007bd026000000000088aa2400000000001f85630000000000acc926000000000008ca260000000000cfc926000000000069cc260000000000c4c42600000000001bd02600000000005ed0260000000000a4cc2600000000008ace26000000000005bc260000000000d5c7260000000000f5c02600000000001dd0260000000000cccb2600000000006bcc260000000000cfce26000000000043ca260000000000b9ca26000000000054c226000000000015cf26000000000005f6250000000000c8cb4000000000009f93260000000000bacd260000000000ecb1260000000000c6c6260000000000f1cd2600000000000ec026000000000052cd2600000000000ed02600000000009ccd260000000000dac52600000000002ec226000000000088d0260000000000eccd26000000000008cf260000000000a6ce26000000000006c5260000000000e6c12600000000002fc7260000000000a9d026000000000036d02600000000009ac826000000000061cf260000000000aac826000000000028c3260000000000029f2600000000000cd02600000000009733260000000000692b3400000000007dc326000000000064cf2600000000001ac5260000000000a86d260000000000f9c926000000000019d02600000000009ec7260000000000a9f52400000000000dc6260000000000e6cf260000000000edce26000000000016c42600000000009bf52400000000009888260000000000acaa240000000000b2cd26000000000011cd260000000000ffba260000000000f2c9260000000000a6073c0000000000bf3d26000000000077aa240000000000dc94260000000000cbf5240000000000a6d02600000000005fc5260000000000b273260000000000b7b62600000000003cc6260000000000b3c72600000000001ecc260000000000669e260000000000cacf26000000000039ca2600000000006ecd260000000000f9a7260000000000eb3326000000000020c4260000000000a9cb26000000000076cf26000000000067cc26000000000023f02900000000008bd0260000000000bfc9260000000000c4c72600000000001fd0260000000000a21026000000000097c72600000000006e9a2600000000007e932600000000009acf26000000000076ce260000000000819226000000000014ca2600000000008ecb260000000000becb2600000000000acc2600000000003b453a000000000039cf260000000000e5772600000000009fcb260000000000bf344e00000000005ace26000000000077d0260000000000f4bf260000000000cbcf260000000000e0cf260000000000e8ea2b000000000084ce26000000000012c9260000000000f2c7260000000000a5d0260000000000b9c926000000000018c826000000000093592600000000009f8a260000000000afd0260000000000a9c726000000000091ce26000000000000cd26000000000045aa2400000000006acc260000000000da9f6100000000001acf260000000000a1ac25000000000059cf260000000000ceca260000000000bc2726000000000028aa26000000000072c4260000000000ac7626000000000084c726000000000057c426000000000073c2260000000000389e260000000000ff8b26000000000051d02600000000000ec5260000000000428f25000000000029c42600000000003d8f25000000000048ca26000000000098c8260000000000f896260000000000ffb7260000000000c1cc26000000000018ca260000000000f0c42600000000006993260000000000b0cc260000000000a5c726000000000031cf26000000000081cf2600000000003c73270000000000edc02600000000001ac426000000000000cc26000000000022ce26000000000014cd260000000000e45226000000000010cf26000000000002cf260000000000d9cb26000000000075cb2600000000005dd02600000000003193260000000000a1ca26000000000056cd260000000000cccb2600000000000cc826000000000096d02600000000009fd02600000000004ec526000000000026d026000000000081d026000000000078cf260000000000becf2600000000009ace26000000000042d026000000000060c326000000000097cf26000000000088cd2600000000004b3d260000000000c8cf2600000000000abd2600000000001dcc2600000000007bd02600000000008bb726000000000011c526000000000059d02600000000009078260000000000fbcb26000000000055c3260000000000e6c62600000000009bd0260000000000f8cb2600000000000a7926000000000055cd260000000000d8cd260000000000a4cd260000000000afcc260000000000eac826000000000036cf26000000000053ce2600000000009cca260000000000f0d92a000000000054ce26000000000021c426000000000030cd26000000000020cc26000000000041c226000000000052cc260000000000f2ce260000000000f87626000000000094b92600000000004ec8260000000000e5ca260000000000e5bb260000000000bdce2600000000000dcd26000000000058d026000000000018c726000000000040cf260000000000228f2500000000007eca2600000000007bcd26000000000078b926000000000088d026000000000016ca260000000000fcb326000000000082cb260000000000d5cc2600000000004dc1260000000000bbc9260000000000c9cf2600000000005dd026000000000053cd2600000000000ace260000000000b7ae3100000000005acf260000000000dfca260000000000e5cf2600000000002dc72600000000008fc626000000000024f6250000000000f4c92600000000002b856300000000001fc3260000000000cfbe260000000000decf2600000000006ece26000000000022a02600000000003cc3260000000000b4cd26000000000078512600000000004fcd26000000000097d02600000000004dcc26000000000051cf260000000000438f2600000000004bc626000000000049ca2600000000005f856300000000007f3b61000000000063b2560000000000b6c92600000000008ebb2600000000004bce260000000000f7cd2600000000002ccd260000000000f0cf260000000000c7cd26000000000004c526000000000098cc2600000000004dd02600000000003dbd2600000000006ecd26000000000029cf260000000000b9c326000000000081be260000000000f6ce26000000000056c826000000000096cc2600000000001b3d30000000000061d026000000000002b8260000000000ff90260000000000c1c42600000000002ccc260000000000d0c926000000000035c6260000000000eccb26000000000061cc26000000000072cc260000000000eccd2600000000005fcc2600000000004dd026000000000091cc2600000000009ec0260000000000049e26000000000060cd26000000000094d02600000000006dce260000000000b8cf370000000000bbca26000000000051c326000000000068ca260000000000f6852600000000002f61490000000000cccc26000000000065b926000000000061cc260000000000b4aa2400000000002fcd260000000000b23c30000000000002c7260000000000c78926000000000004be260000000000ffcc2600000000004bb83400000000008cc72600000000003ed0260000000000b5cb2600000000005a474000000000007dc32600000000001ec92600000000002ad0260000000000d8cd260000000000e4cb260000000000ac95260000000000cbcf26000000000002c82600000000009dd0260000000000a683260000000000bdc8260000000000bf092600000000009f7b2600000000000bce260000000000241526000000000097ce260000000000accc260000000000d3ee3900000000006bcd2600000000002b4f26000000000091c62600000000003f8f25000000000044c82600000000001ecb260000000000a9d026000000000050c826000000000051ce260000000000bbdd4200000000006b8226000000000089cb6100000000008bd0260000000000b2cd26000000000076cb260000000000f9cc26000000000029d02600000000007bc7260000000000fbcd2600000000007fca26000000000020b72400000000005bc526000000000016cf26000000000086be26000000000045d0260000000000c3cb26000000000092c4260000000000f8cf26000000000066cd260000000000d9cf260000000000c1873300000000002dc92600000000005fca26000000000082c72600000000003cc72600000000002dd02600000000002a85630000000000aff5240000000000c58226000000000097ca26000000000038cf260000000000937426000000000002ca26000000000022c6260000000000d0cf26000000000080cb26000000000029cd260000000000afcf26000000000034ca260000000000e0c72600000000002cce260000000000bacd26000000000065c82600000000000bcf2600000000009bcc260000000000454e260000000000f0cd2600000000003fcc26000000000004cc26000000000051d026000000000008c126000000000031ce26000000000022be260000000000cacf260000000000cb772600000000009c3e2b000000000076aa240000000000facb2600000000009bc326000000000059b7260000000000b1ca26000000000039cd260000000000aeb42600000000007ed02600000000002bd026000000000018cb260000000000010c590000000000decd260000000000c7c726000000000046d0260000000000b5c8260000000000facc26000000000058c826000000000012b626000000000094cd260000000000d5b92600000000003ed026000000000087d026000000000086d02600000000000fc0260000000000a1c726000000000063ce2600000000001cc02600000000007fc5260000000000e9cd2600000000004ed0260000000000a0b2260000000000aaca260000000000357a260000000000658563000000000048cd260000000000f5f52400000000004dc72600000000006ac426000000000098c226000000000043cc2600000000006293260000000000a1c2260000000000cfc426000000000068af260000000000f2b9260000000000a1d0260000000000ccc9260000000000f0cd26000000000072cd2600000000005fbf2600000000005ebf2600000000005ecf2600000000006e4d2600000000006e0b31000000000011bb26000000000002be26000000000041ca260000000000b6cd260000000000c0c42600000000007ece26000000000081cc260000000000deb12600000000008ccf26000000000063cc260000000000cfad2600000000009cd02600000000003dd026000000000045c9260000000000e4cb2600000000008d8b26000000000040d026000000000069d0260000000000d047260000000000fbcb26000000000021c7260000000000c9ce26000000000012b9260000000000aec7260000000000d2cc2600000000008bc4260000000000deee330000000000962726000000000056c226000000000099cf2600000000008e8f250000000000cbcb26000000000090c2260000000000adcd26000000000094a1260000000000d3b42600000000009cca260000000000ddf524000000000023182f0000000000b04026000000000064d0260000000000a5d026000000000069ca26000000000008ce2600000000000dce26000000000054cc2600000000009dcf26000000000043c5260000000000b9cd2600000000007dd02600000000005ad026000000000086d02600000000003fd0260000000000fd63260000000000228f2500000000004dd0260000000000bdcf260000000000a2cb260000000000c0c92600000000002ccf260000000000d19244000000000015d0260000000000becf26000000000008c72600000000000cd0260000000000abc3260000000000cdbc260000000000a7c82600000000007ac826000000000004792600000000000e9e490000000000e5be2600000000001f83280000000000bbbc2600000000007cd02600000000007dd0260000000000bccd26000000000006c8260000000000fac7260000000000f1c2260000000000f3ce26000000000067c726000000000068445d0000000000ecce260000000000d3c9260000000000e0c226000000000029cf2600000000000ccd260000000000becf26000000000048cb2600000000006186280000000000a7d02600000000007bc9260000000000dadf530000000000b3d026000000000083cf260000000000b1cf26000000000060424b0000000000eacb260000000000fbc826000000000045c3260000000000cdcf260000000000e1ce2600000000002fcb2600000000004dc426000000000095cd26000000000087362700000000004bcb260000000000afc4260000000000f5c626000000000038cd260000000000f9ce260000000000f6733a0000000000cbc9260000000000b4cd260000000000b4cb26000000000014cc26000000000000cf260000000000aac4260000000000ddc72600000000002efc340000000000b7b12600000000008479260000000000f3cf260000000000b5bd2600000000005ace260000000000d6c7260000000000e0c226000000000050ca260000000000f3c3260000000000f18e2600000000000bcc260000000000232e2600000000004b8f2500000000008fc726000000000096cc26000000000004cf2600000000006fc926000000000094ca26000000000024ac26000000000008cb260000000000d2c7260000000000c1cf26000000000079cc260000000000313d30000000000029ce2600000000006cc9260000000000c8cc260000000000e1c626000000000049cb260000000000b1ce260000000000afc5260000000000e1c6260000000000b3c8260000000000fbce2600000000009e733600000000000dcf26000000000079c826000000000030c5260000000000a7aa2400000000006cd026000000000098c6260000000000a4c4260000000000e9ce260000000000d3ce26000000000069cd260000000000f5c526000000000088cd260000000000d8cd260000000000c2cd260000000000df7b260000000000afc8260000000000943f2600000000000253280000000000dbcd2600000000006d7b260000000000dec7260000000000858126000000000073b526000000000025c4260000000000f0bc2600000000000dc126000000000087b726000000000002bd260000000000c7ce26000000000044c4260000000000d5cf260000000000aacd260000000000c5c826000000000028c126000000000078c52600000000004eef5400000000003ccf260000000000ef32300000000000c25145000000000067b52600000000009dc3260000000000def5240000000000e2ce260000000000108126000000000073c5260000000000ebcd26000000000034c8260000000000d2c12600000000006bcc260000000000f69c26000000000026c9260000000000909426000000000021cc260000000000ffc2260000000000fec72600000000007ecc260000000000adc7260000000000773e4000000000009ac226000000000063c5260000000000f7c3260000000000d0cf260000000000c4d9390000000000ea492900000000007acb26000000000017cb2600000000006abd260000000000eec4260000000000a2c726000000000008d026000000000099c826000000000022c72600000000009fca2600000000004ecc2600000000004cd026000000000007d026000000000000ca2600000000008dd026000000000055c226000000000091d02600000000004184260000000000e2cb26000000000006be2600000000002079260000000000cdc72600000000004ac42600000000003fc426000000000040a73e000000000070aa240000000000d3bd26000000000045ce260000000000bbf5240000000000328563000000000002ce260000000000bdcb2600000000007eaa24000000000059c226000000000061bc260000000000f6cd260000000000d6d93400000000005210340000000000f74a31000000000013c8260000000000fd8f260000000000bbc0260000000000a6d02600000000001dcd26000000000019ca260000000000e9cb26000000000068ce26000000000007bc260000000000b6cc260000000000e9ce260000000000cacf26000000000013cc260000000000a319260000000000b174260000000000b9cc260000000000b3cb260000000000d28463000000000048cd26000000000055b826000000000089c726000000000070d02600000000004ccc260000000000bac9260000000000a9d0260000000000c2cf26000000000044cc260000000000b9ee4b000000000098bb260000000000aacc26000000000062ca2600000000009163250000000000a1b42600000000008dcc2600000000008ace260000000000b9ce2600000000008bcd2600000000002bc22600000000005ed0260000000000fdc8260000000000ecc72600000000000dcd26000000000071cb26000000000056cd260000000000a9ca26000000000064c92600000000005a902d00000000002dcf260000000000" + '["0x658faa385070e074c85bf6b568cf0555696e262a16e52255a69d8acd793541460100", "0x2830271e820b419253b79cbafea83f726354bbef26eb318835ffa774288de9ff"]': { + "result": "0x0110a1cf2600000000009bc7260000000000b6fc2d0000000000b43c40000000000036b9260000000000e9445e00000000004ec5330000000000a2d02600000000008dc726000000000057d02600000000002ac826000000000069ca260000000000988a470000000000a8812600000000009cce26000000000080d026000000000057ca26000000000060cb26000000000059ce260000000000a9cc2600000000003ad02600000000007fd0260000000000c5c9260000000000fccf2600000000000efe63000000000011c726000000000015be26000000000039cf260000000000a3d02600000000007bb226000000000057ce2600000000006783260000000000bb093b0000000000b3d026000000000094ce2600000000004a8f2500000000000cc726000000000095c626000000000013b9260000000000fec926000000000070d026000000000078cf26000000000028d026000000000052cb2600000000004aaa240000000000a4c526000000000046ce260000000000c5cd26000000000004ce260000000000f2cc260000000000bcb72b00000000006acc260000000000c7cc260000000000d11c2600000000000dcf2600000000001fcb26000000000000cf26000000000030c1260000000000f3cd2600000000004dd0260000000000ea134b0000000000323d300000000000afcf26000000000016b426000000000087134b000000000094d026000000000012cc2600000000009dc3260000000000c9c3260000000000ecba260000000000f9c6260000000000e0c8260000000000218f25000000000051c8260000000000d2ce2600000000005dce280000000000a2ca260000000000f0cc26000000000089bf260000000000d8cb260000000000ba2526000000000076ab260000000000f8c9260000000000cdc8260000000000b3b32600000000005ed0260000000000f2b4260000000000a4cc26000000000016c926000000000019c6260000000000f1cc260000000000b3d0260000000000f4cc2600000000008db62600000000004fcf2600000000002bc52600000000009a27260000000000848726000000000020c4260000000000ebcb260000000000ea0d290000000000edc4260000000000a2c026000000000078c6260000000000111b2600000000009fc226000000000091d026000000000036c726000000000014c6290000000000a1cc260000000000dfa5260000000000ebc8260000000000ff782600000000001c8f250000000000fe6d260000000000aec0260000000000f5ca260000000000d7cb260000000000a3bd2600000000005dc82600000000009485260000000000c87826000000000090c72600000000002acf260000000000b2c626000000000030ba26000000000010c926000000000035c2260000000000f1c92600000000003acc26000000000095d02600000000001cab2600000000000fce2600000000000fce260000000000bdcc2600000000008fcc260000000000b45f3d000000000085ce26000000000056c326000000000053ce260000000000aec526000000000021bc260000000000acc82600000000009bd02600000000005ad026000000000038c426000000000019c3260000000000e1cd260000000000b0ce26000000000034c526000000000020be2600000000000b7d320000000000aecd26000000000048c6260000000000c1cf26000000000063d026000000000003cd260000000000ddc62600000000000d2b280000000000b3aa2400000000001ac82600000000001c522600000000008ac726000000000042ce2600000000004fce260000000000aabf26000000000017cc2600000000009e753300000000009cce2600000000008ecc26000000000009ca26000000000044cd26000000000024d02600000000009fbb260000000000a7c72600000000009cc6260000000000a6d026000000000061d0260000000000eac326000000000084b526000000000086cd2600000000000cc2260000000000d9c62600000000003ec32600000000002ec8260000000000c3cb26000000000085cf260000000000d7ce2600000000009dc226000000000033cc26000000000037ca26000000000055ce26000000000085ec44000000000095d0260000000000c9c6260000000000b7ca260000000000fcbd26000000000024d026000000000075bb260000000000e4b7260000000000087d32000000000020cf26000000000019ce26000000000054e644000000000022c62600000000009cc72600000000009dd0260000000000c8af4e000000000062ce260000000000019e26000000000049cc260000000000abcb260000000000c05026000000000066cb260000000000adcf260000000000aa9626000000000072cd2600000000003dc226000000000040573b00000000000dca260000000000adcc2600000000006bd0260000000000accc260000000000ccc9260000000000638f250000000000ea7c2600000000008b4a290000000000adcd26000000000061c126000000000079cf2600000000009dc5260000000000cece2600000000004dcd260000000000b2d026000000000014cc26000000000004ce2600000000003cce26000000000075cf260000000000d2ba2600000000000ecb2600000000009093260000000000a1c526000000000028ce2600000000007bd026000000000088aa2400000000001bfe630000000000acc926000000000008ca260000000000cfc926000000000069cc260000000000c4c42600000000001bd02600000000005ed0260000000000a4cc2600000000008ace26000000000005bc260000000000d5c7260000000000f5c02600000000001dd0260000000000cccb2600000000006bcc260000000000cfce26000000000043ca260000000000b9ca26000000000054c226000000000015cf26000000000005f6250000000000c8cb4000000000009f93260000000000bacd260000000000ecb1260000000000c6c6260000000000f1cd2600000000000ec026000000000052cd2600000000000ed02600000000009ccd260000000000dac52600000000002ec226000000000088d0260000000000eccd26000000000008cf260000000000a6ce26000000000006c5260000000000e6c12600000000002fc7260000000000a9d026000000000036d02600000000009ac826000000000061cf260000000000aac826000000000028c3260000000000029f2600000000000cd02600000000009733260000000000692b3400000000007dc326000000000064cf2600000000001ac5260000000000a86d260000000000f9c926000000000019d02600000000009ec7260000000000a9f52400000000000dc6260000000000e6cf260000000000edce26000000000016c42600000000009bf52400000000009888260000000000acaa240000000000b2cd26000000000011cd260000000000ffba260000000000f2c9260000000000a6073c0000000000bf3d26000000000077aa240000000000dc94260000000000cbf5240000000000a6d02600000000005fc5260000000000b273260000000000b7b62600000000003cc6260000000000b3c72600000000001ecc260000000000669e260000000000cacf26000000000039ca2600000000006ecd260000000000f9a7260000000000eb3326000000000020c4260000000000a9cb26000000000076cf26000000000067cc26000000000023f02900000000008bd0260000000000bfc9260000000000c4c72600000000001fd0260000000000a21026000000000097c72600000000006e9a2600000000007e932600000000009acf26000000000076ce260000000000819226000000000014ca2600000000008ecb260000000000becb2600000000000acc2600000000003b453a000000000039cf260000000000e5772600000000009fcb260000000000bf344e00000000005ace26000000000077d0260000000000f4bf260000000000cbcf260000000000e0cf260000000000e8ea2b000000000084ce26000000000012c9260000000000f2c7260000000000a5d0260000000000b9c926000000000018c826000000000093592600000000009f8a260000000000afd0260000000000a9c726000000000091ce26000000000000cd26000000000045aa2400000000006acc260000000000da9f6100000000001acf260000000000a1ac25000000000059cf260000000000ceca260000000000bc2726000000000028aa26000000000072c4260000000000ac7626000000000084c726000000000057c426000000000073c2260000000000389e260000000000ff8b26000000000051d02600000000000ec5260000000000428f25000000000029c42600000000003d8f25000000000048ca26000000000098c8260000000000f896260000000000ffb7260000000000c1cc26000000000018ca260000000000f0c42600000000006993260000000000b0cc260000000000a5c726000000000031cf26000000000081cf2600000000003c73270000000000edc02600000000001ac426000000000000cc26000000000022ce26000000000014cd260000000000e45226000000000010cf26000000000002cf260000000000d9cb26000000000075cb2600000000005dd02600000000003193260000000000a1ca26000000000056cd260000000000cccb2600000000000cc826000000000096d02600000000009fd02600000000004ec526000000000026d026000000000081d026000000000078cf260000000000becf2600000000009ace26000000000042d026000000000060c326000000000097cf26000000000088cd2600000000004b3d260000000000c8cf2600000000000abd2600000000001dcc2600000000007bd02600000000008bb726000000000011c526000000000059d0260000000000907826000000000017fe63000000000055c3260000000000e6c62600000000009bd0260000000000f8cb2600000000000a7926000000000055cd260000000000d8cd260000000000a4cd260000000000afcc260000000000eac826000000000036cf26000000000053ce2600000000009cca260000000000f0d92a000000000054ce26000000000021c426000000000030cd26000000000020cc26000000000041c226000000000052cc260000000000f2ce260000000000f87626000000000094b92600000000004ec8260000000000e5ca260000000000e5bb260000000000bdce2600000000000dcd26000000000058d026000000000018c726000000000040cf260000000000228f2500000000007eca2600000000007bcd26000000000078b926000000000088d026000000000016ca260000000000fcb326000000000082cb260000000000d5cc2600000000004dc1260000000000bbc9260000000000c9cf2600000000005dd026000000000053cd2600000000000ace260000000000b7ae3100000000005acf260000000000dfca260000000000e5cf2600000000002dc72600000000008fc626000000000024f6250000000000f4c926000000000026fe6300000000001fc3260000000000cfbe260000000000decf2600000000006ece26000000000022a02600000000003cc3260000000000b4cd26000000000078512600000000004fcd26000000000097d02600000000004dcc26000000000051cf260000000000438f2600000000004bc626000000000049ca26000000000037fe6300000000007f3b61000000000063b2560000000000b6c92600000000008ebb2600000000004bce260000000000f7cd2600000000002ccd260000000000f0cf260000000000c7cd26000000000004c526000000000098cc2600000000004dd02600000000003dbd2600000000006ecd26000000000029cf260000000000b9c326000000000081be260000000000f6ce26000000000056c826000000000096cc2600000000001b3d30000000000061d026000000000002b8260000000000ff90260000000000c1c42600000000002ccc260000000000d0c926000000000035c6260000000000eccb26000000000061cc26000000000072cc260000000000eccd2600000000005fcc2600000000004dd026000000000091cc2600000000009ec0260000000000049e26000000000060cd26000000000094d02600000000006dce260000000000b8cf370000000000bbca26000000000051c326000000000068ca260000000000f6852600000000002f61490000000000cccc26000000000065b926000000000061cc260000000000b4aa2400000000002fcd260000000000b23c30000000000002c7260000000000c78926000000000004be260000000000ffcc2600000000004bb83400000000008cc72600000000003ed0260000000000b5cb2600000000005a474000000000007dc32600000000001ec92600000000002ad0260000000000d8cd260000000000e4cb260000000000ac95260000000000cbcf26000000000002c82600000000009dd0260000000000a683260000000000bdc8260000000000bf092600000000009f7b2600000000000bce260000000000241526000000000097ce260000000000accc260000000000d3ee3900000000006bcd2600000000002b4f26000000000091c62600000000003f8f25000000000044c82600000000001ecb260000000000a9d026000000000050c826000000000051ce260000000000bbdd4200000000006b8226000000000089cb6100000000008bd0260000000000b2cd26000000000076cb260000000000f9cc26000000000029d02600000000007bc7260000000000fbcd2600000000007fca26000000000020b72400000000005bc526000000000016cf26000000000086be26000000000045d0260000000000c3cb26000000000092c4260000000000f8cf26000000000066cd260000000000d9cf260000000000c1873300000000002dc92600000000005fca26000000000082c72600000000003cc72600000000002dd02600000000009d85630000000000aff5240000000000c58226000000000097ca26000000000038cf260000000000937426000000000002ca26000000000022c6260000000000d0cf26000000000080cb26000000000029cd260000000000afcf26000000000034ca260000000000e0c72600000000002cce260000000000bacd26000000000065c82600000000000bcf2600000000009bcc260000000000454e260000000000f0cd2600000000003fcc26000000000004cc26000000000051d026000000000008c126000000000031ce26000000000022be260000000000cacf260000000000cb772600000000009c3e2b000000000076aa240000000000facb2600000000009bc326000000000059b7260000000000b1ca26000000000039cd260000000000aeb42600000000007ed02600000000002bd026000000000018cb260000000000010c590000000000decd260000000000c7c726000000000046d0260000000000b5c8260000000000facc26000000000058c826000000000012b626000000000094cd260000000000d5b92600000000003ed026000000000087d026000000000086d02600000000000fc0260000000000a1c726000000000063ce2600000000001cc02600000000007fc5260000000000e9cd2600000000004ed0260000000000a0b2260000000000aaca260000000000357a26000000000019fe63000000000048cd260000000000f5f52400000000004dc72600000000006ac426000000000098c226000000000043cc2600000000006293260000000000a1c2260000000000cfc426000000000068af260000000000f2b9260000000000a1d0260000000000ccc9260000000000f0cd26000000000072cd2600000000005fbf2600000000005ebf2600000000005ecf2600000000006e4d2600000000006e0b31000000000011bb26000000000002be26000000000041ca260000000000b6cd260000000000c0c42600000000007ece26000000000081cc260000000000deb12600000000008ccf26000000000063cc260000000000cfad2600000000009cd02600000000003dd026000000000045c9260000000000e4cb2600000000008d8b26000000000040d026000000000069d0260000000000d047260000000000fbcb26000000000021c7260000000000c9ce26000000000012b9260000000000aec7260000000000d2cc2600000000008bc4260000000000deee330000000000962726000000000056c226000000000099cf2600000000008e8f250000000000cbcb26000000000090c2260000000000adcd26000000000094a1260000000000d3b42600000000009cca260000000000ddf524000000000023182f0000000000b04026000000000064d0260000000000a5d026000000000069ca26000000000008ce2600000000000dce26000000000054cc2600000000009dcf26000000000043c5260000000000b9cd2600000000007dd02600000000005ad026000000000086d02600000000003fd0260000000000fd63260000000000228f2500000000004dd0260000000000bdcf260000000000a2cb260000000000c0c92600000000002ccf260000000000d19244000000000015d0260000000000becf26000000000008c72600000000000cd0260000000000abc3260000000000cdbc260000000000a7c82600000000007ac826000000000004792600000000000e9e490000000000e5be2600000000001f83280000000000bbbc2600000000007cd02600000000007dd0260000000000bccd26000000000006c8260000000000fac7260000000000f1c2260000000000f3ce26000000000067c726000000000068445d0000000000ecce260000000000d3c9260000000000e0c226000000000029cf2600000000000ccd260000000000becf26000000000048cb2600000000006186280000000000a7d02600000000007bc9260000000000dadf530000000000b3d026000000000083cf260000000000b1cf26000000000060424b0000000000eacb260000000000fbc826000000000045c3260000000000cdcf260000000000e1ce2600000000002fcb2600000000004dc426000000000095cd26000000000087362700000000004bcb260000000000afc4260000000000f5c626000000000038cd260000000000f9ce260000000000f6733a0000000000cbc9260000000000b4cd260000000000b4cb26000000000014cc26000000000000cf260000000000aac4260000000000ddc72600000000002efc340000000000b7b12600000000008479260000000000f3cf260000000000b5bd2600000000005ace260000000000d6c7260000000000e0c226000000000050ca260000000000f3c3260000000000f18e2600000000000bcc260000000000232e2600000000004b8f2500000000008fc726000000000096cc26000000000004cf2600000000006fc926000000000094ca26000000000024ac26000000000008cb260000000000d2c7260000000000c1cf26000000000079cc260000000000313d30000000000029ce2600000000006cc9260000000000c8cc260000000000e1c626000000000049cb260000000000b1ce260000000000afc5260000000000e1c6260000000000b3c8260000000000fbce2600000000009e733600000000000dcf26000000000079c826000000000030c5260000000000a7aa2400000000006cd026000000000098c6260000000000a4c4260000000000e9ce260000000000d3ce26000000000069cd260000000000f5c526000000000088cd260000000000d8cd260000000000c2cd260000000000df7b260000000000afc8260000000000943f2600000000000253280000000000dbcd2600000000006d7b260000000000dec7260000000000858126000000000073b526000000000025c4260000000000f0bc2600000000000dc126000000000087b726000000000002bd260000000000c7ce26000000000044c4260000000000d5cf260000000000aacd260000000000c5c826000000000028c126000000000078c52600000000004eef5400000000003ccf260000000000ef32300000000000c25145000000000067b52600000000009dc3260000000000def5240000000000e2ce260000000000108126000000000073c5260000000000ebcd26000000000034c8260000000000d2c12600000000006bcc260000000000f69c26000000000026c9260000000000909426000000000021cc260000000000ffc2260000000000fec72600000000007ecc260000000000adc7260000000000773e4000000000009ac226000000000063c5260000000000f7c3260000000000d0cf260000000000c4d9390000000000ea492900000000007acb26000000000017cb2600000000006abd260000000000eec4260000000000a2c726000000000008d026000000000099c826000000000022c72600000000009fca2600000000004ecc2600000000004cd026000000000007d026000000000000ca2600000000008dd026000000000055c226000000000091d02600000000004184260000000000e2cb26000000000006be2600000000002079260000000000cdc72600000000004ac42600000000003fc426000000000040a73e000000000070aa240000000000d3bd26000000000045ce260000000000bbf524000000000059fe63000000000002ce260000000000bdcb2600000000007eaa24000000000059c226000000000061bc260000000000f6cd260000000000d6d93400000000005210340000000000f74a31000000000013c8260000000000fd8f260000000000bbc0260000000000a6d02600000000001dcd26000000000019ca260000000000e9cb26000000000068ce26000000000007bc260000000000b6cc260000000000e9ce260000000000cacf26000000000013cc260000000000a319260000000000b174260000000000b9cc260000000000b3cb2600000000004efe63000000000048cd26000000000055b826000000000089c726000000000070d02600000000004ccc260000000000bac9260000000000a9d0260000000000c2cf26000000000044cc260000000000b9ee4b000000000098bb260000000000aacc26000000000062ca2600000000009163250000000000a1b42600000000008dcc2600000000008ace260000000000b9ce2600000000008bcd2600000000002bc22600000000005ed0260000000000fdc8260000000000ecc72600000000000dcd26000000000071cb26000000000056cd260000000000a9ca26000000000064c92600000000005a902d00000000002dcf260000000000" }, }, }, diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index d907890389..fac4b907de 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -96,7 +96,7 @@ async def test_is_hotkey_registered(mocker): async def test_blocks_since_last_update(mocker): subtensor = await prepare_test(mocker, "blocks_since_last_update") result = subtensor.blocks_since_last_update(1, 0) - assert result == 3978699 + assert result == 4009702 @pytest.mark.asyncio From f66cd688d543dc0aed305e10463b5cf330c5e391 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 17:55:20 -0700 Subject: [PATCH 281/416] fix integration tests + - `Async/Subtensor` parameter `_mock` renamed to `mock`, also moved to last one in order. Community can use mocked `Async/Subtensor` in their tests in the same way as in we use it in the codebase. --- MIGRATION.md | 3 ++- bittensor/core/async_subtensor.py | 6 +++--- bittensor/core/subtensor.py | 6 +++--- tests/integration_tests/test_subtensor_integration.py | 2 +- tests/unit_tests/conftest.py | 2 +- tests/unit_tests/test_subtensor.py | 4 ++-- tests/unit_tests/test_subtensor_api.py | 6 +++--- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 4776e2bd95..e1bfcc3218 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -240,7 +240,8 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `blocks_since_last_update` improved. Currently it can be used to get historical data from archive node. - methods (async) `get_subnet_validator_permits` and `get_subnet_owner_hotkey` got `block_hash` and `reuse_block` parameters. - attribute `DelegateInfo/lite.total_daily_return` has been deleted (Vune confirmed that we shouldn't use it) - +- `Async/Subtensor` parameter `_mock` renamed to `mock`, also moved to last one in order. Community can use mocked `Async/Subtensor` in their tests in the same way as in we use it in the codebase. + Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` - `bittensor.core.timelock` moved to `bittensor.core.addons.timelock` diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index b5532ba5c1..36e0ef9d8a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -147,9 +147,9 @@ def __init__( log_verbose: bool = False, fallback_endpoints: Optional[list[str]] = None, retry_forever: bool = False, - _mock: bool = False, archive_endpoints: Optional[list[str]] = None, websocket_shutdown_timer: float = 5.0, + mock: bool = False, ): """Initializes an AsyncSubtensor instance for blockchain interaction. @@ -160,7 +160,7 @@ def __init__( log_verbose: Enables or disables verbose logging. fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. retry_forever: Whether to retry forever on connection errors. - _mock: Whether this is a mock instance. Mainly for testing purposes. + mock: Whether this is a mock instance. Mainly for testing purposes. archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases where you are requesting a block that is too old for your current (presumably lite) node. websocket_shutdown_timer: Amount of time, in seconds, to wait after the last response from the chain to @@ -199,7 +199,7 @@ async def main(): self.substrate = self._get_substrate( fallback_endpoints=fallback_endpoints, retry_forever=retry_forever, - _mock=_mock, + _mock=mock, archive_endpoints=archive_endpoints, ws_shutdown_timer=websocket_shutdown_timer, ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index fb46b16260..c03d99aef4 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -144,8 +144,8 @@ def __init__( log_verbose: bool = False, fallback_endpoints: Optional[list[str]] = None, retry_forever: bool = False, - _mock: bool = False, archive_endpoints: Optional[list[str]] = None, + mock: bool = False, ): """ Initializes an instance of the Subtensor class. @@ -156,9 +156,9 @@ def __init__( log_verbose: Enables or disables verbose logging. fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. retry_forever: Whether to retry forever on connection errors. - _mock: Whether this is a mock instance. Mainly just for use in testing. archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in cases where you are requesting a block that is too old for your current (presumably lite) node. + mock: Whether this is a mock instance. Mainly just for use in testing. Raises: Any exceptions raised during the setup, configuration, or connection process. @@ -178,7 +178,7 @@ def __init__( self.substrate = self._get_substrate( fallback_endpoints=fallback_endpoints, retry_forever=retry_forever, - _mock=_mock, + _mock=mock, archive_endpoints=archive_endpoints, ) if self.log_verbose: diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index fac4b907de..c95c15a1af 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -25,7 +25,7 @@ async def prepare_test(mocker, seed, **subtensor_args): "async_substrate_interface.sync_substrate.connect", mocker.Mock(return_value=FakeWebsocket(seed=seed)), ) - subtensor = Subtensor("unknown", _mock=True, **subtensor_args) + subtensor = Subtensor("unknown", mock=True, **subtensor_args) return subtensor diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index bed6f28bb3..7b13c7893e 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -28,7 +28,7 @@ def mock_substrate(mocker): @pytest.fixture def subtensor(mock_substrate): - return bittensor.core.subtensor.Subtensor(_mock=True) + return bittensor.core.subtensor.Subtensor(mock=True) @pytest.fixture diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 6c78d7f893..524a653984 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -61,8 +61,8 @@ def call_params_with_certificate(): def test_methods_comparable(mock_substrate): """Verifies that methods in sync and async Subtensors are comparable.""" # Preps - subtensor = Subtensor(_mock=True) - async_subtensor = AsyncSubtensor(_mock=True) + subtensor = Subtensor(mock=True) + async_subtensor = AsyncSubtensor(mock=True) # methods which lives in async subtensor only excluded_async_subtensor_methods = ["initialize"] diff --git a/tests/unit_tests/test_subtensor_api.py b/tests/unit_tests/test_subtensor_api.py index 785aab0926..aead8c7ce1 100644 --- a/tests/unit_tests/test_subtensor_api.py +++ b/tests/unit_tests/test_subtensor_api.py @@ -8,9 +8,9 @@ def test_properties_methods_comparable(other_class: "Subtensor" = None): """Verifies that methods in SubtensorApi and its properties contains all Subtensors methods.""" # Preps subtensor = ( - other_class(network="latent-lite", _mock=True) + other_class(network="latent-lite", mock=True) if other_class - else Subtensor(network="latent-lite", _mock=True) + else Subtensor(network="latent-lite", mock=True) ) subtensor_api = SubtensorApi(network="latent-lite", mock=True) @@ -70,7 +70,7 @@ def test__methods_comparable_with_passed_legacy_methods( subtensor = ( other_class(network="latent-lite", mock=True) if other_class - else Subtensor(network="latent-lite", _mock=True) + else Subtensor(network="latent-lite", mock=True) ) subtensor_api = SubtensorApi(network="latent-lite", mock=True, legacy_methods=True) From 781ae4ed1d65fba193becac0803caf637ba538bb Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 17:55:59 -0700 Subject: [PATCH 282/416] update MIGRATION.md --- MIGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index e1bfcc3218..725b353f46 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -67,7 +67,7 @@ rename this variable in documentation. 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` +11. ✅ Remove deprecated `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. From 0c5c9b4e50e8c9d9480556a7d77cb24f58a55048 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 17:56:45 -0700 Subject: [PATCH 283/416] update MIGRATION.md --- MIGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 725b353f46..515f9b1c12 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -51,7 +51,7 @@ rename this variable in documentation. 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. +5. ~~Rename `non-/fast-blocks` to `non-/fast-runtime` in related places to be consistent with subtensor repo. Related with testing, subtensor scripts, documentation.~~ done across many PRs. 6. ✅ To be consistent throughout the SDK `(in progress)`: `hotkey`, `coldkey`, `hotkeypub`, and `coldkeypub` are keypairs From 39cc0c6f72df0988f6f687e5435e79708145ec29 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 18:07:13 -0700 Subject: [PATCH 284/416] ops, _mock --- bittensor/core/addons/subtensor_api/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/core/addons/subtensor_api/__init__.py b/bittensor/core/addons/subtensor_api/__init__.py index f729998c9e..7f9a8e500a 100644 --- a/bittensor/core/addons/subtensor_api/__init__.py +++ b/bittensor/core/addons/subtensor_api/__init__.py @@ -126,7 +126,7 @@ def _get_subtensor(self) -> Union["_Subtensor", "_AsyncSubtensor"]: log_verbose=self.log_verbose, fallback_endpoints=self._fallback_endpoints, retry_forever=self._retry_forever, - _mock=self._mock, + mock=self._mock, archive_endpoints=self._archive_endpoints, websocket_shutdown_timer=self._ws_shutdown_timer, ) @@ -139,7 +139,7 @@ def _get_subtensor(self) -> Union["_Subtensor", "_AsyncSubtensor"]: log_verbose=self.log_verbose, fallback_endpoints=self._fallback_endpoints, retry_forever=self._retry_forever, - _mock=self._mock, + mock=self._mock, archive_endpoints=self._archive_endpoints, ) From a91cc864e082ed9108e737767a0c2e4fcd00d73d Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 18:19:00 -0700 Subject: [PATCH 285/416] fix `tests/e2e_tests/test_delegate.py` --- tests/e2e_tests/test_delegate.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 176522dea8..9659985413 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -389,7 +389,6 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): validator_permits=[], registrations=[0], return_per_1000=Balance(0), - total_daily_return=Balance(0), total_stake={}, nominators={}, ) @@ -405,7 +404,6 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): validator_permits=[], registrations=[0], return_per_1000=Balance(0), - total_daily_return=Balance(0), total_stake={}, nominators={}, ) @@ -461,9 +459,6 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): 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), ), @@ -549,7 +544,6 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): validator_permits=[], registrations=[0], return_per_1000=Balance(0), - total_daily_return=Balance(0), total_stake={}, nominators={}, ) @@ -565,7 +559,6 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): validator_permits=[], registrations=[0], return_per_1000=Balance(0), - total_daily_return=Balance(0), total_stake={}, nominators={}, ) @@ -631,9 +624,6 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): 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), ), From 94df7c6da7da6bc8491ec8a3357087c89d30a2f7 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 20:34:00 -0700 Subject: [PATCH 286/416] BT_SUBTENSOR_CHAIN_ENDPOINT + MIGRATION.md --- MIGRATION.md | 11 ++++++----- bittensor/core/settings.py | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 515f9b1c12..ea71bd58f7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -24,7 +24,7 @@ 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`.~~ `get_transfer_fee` isn't `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. +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`. @@ -41,17 +41,17 @@ 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 `(in progress)` +1. ✅ Reduce the amount of logging.info or transfer part of logging.info to logging.debug `(in progress)` -2. To be consistent across all SDK regarding local environment variables name: +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`. +3. ~~Move `bittensor.utils.get_transfer_fn_params` to `bittensor.core.extrinsics.utils`.~~ it's on the right place. 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.~~ done across many PRs. +5. ✅ ~~Rename `non-/fast-blocks` to `non-/fast-runtime` in related places to be consistent with subtensor repo. Related with testing, subtensor scripts, documentation.~~ done across many PRs. 6. ✅ To be consistent throughout the SDK `(in progress)`: `hotkey`, `coldkey`, `hotkeypub`, and `coldkeypub` are keypairs @@ -245,6 +245,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` - `bittensor.core.timelock` moved to `bittensor.core.addons.timelock` + - local env variable `BT_CHAIN_ENDPOINT` replaced with `BT_SUBTENSOR_CHAIN_ENDPOINT`. ### Mechid related changes: In the next subtensor methods got updated the parameters order: diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index 3e5edae0d6..3bb6c4a447 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -121,7 +121,7 @@ "maxsize": int(_BT_PRIORITY_MAXSIZE) if _BT_PRIORITY_MAXSIZE else 10, }, "subtensor": { - "chain_endpoint": os.getenv("BT_CHAIN_ENDPOINT") or DEFAULT_ENDPOINT, + "chain_endpoint": os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT") or DEFAULT_ENDPOINT, "network": os.getenv("BT_NETWORK") or DEFAULT_NETWORK, "_mock": False, }, From 4c32471b8b45ed9a14dce6da3566e95078e9ae75 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 20:35:00 -0700 Subject: [PATCH 287/416] ruff --- bittensor/core/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index 3bb6c4a447..0407e80001 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -121,7 +121,8 @@ "maxsize": int(_BT_PRIORITY_MAXSIZE) if _BT_PRIORITY_MAXSIZE else 10, }, "subtensor": { - "chain_endpoint": os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT") or DEFAULT_ENDPOINT, + "chain_endpoint": os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT") + or DEFAULT_ENDPOINT, "network": os.getenv("BT_NETWORK") or DEFAULT_NETWORK, "_mock": False, }, From 43998af5e6b300fa4bc0b41cde960544e4404836 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 22:21:37 -0700 Subject: [PATCH 288/416] fix `get_async_subtensor` --- bittensor/core/async_subtensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 36e0ef9d8a..03c786e385 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5954,14 +5954,14 @@ async def unstake_multiple( async def get_async_subtensor( network: Optional[str] = None, config: Optional["Config"] = None, - _mock: bool = False, + mock: bool = False, log_verbose: bool = False, ) -> "AsyncSubtensor": """Factory method to create an initialized AsyncSubtensor. Mainly useful for when you don't want to run `await subtensor.initialize()` after instantiation. """ sub = AsyncSubtensor( - network=network, config=config, _mock=_mock, log_verbose=log_verbose + network=network, config=config, mock=mock, log_verbose=log_verbose ) await sub.initialize() return sub From bbae83988e40f172c765ca26b9348311e50e7c24 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 22:58:18 -0700 Subject: [PATCH 289/416] improve `easy_import.py`: removing inconsistent class names + references to overridden subpackages --- MIGRATION.md | 22 ++++++ bittensor/utils/btlogging/levels.py | 42 ++++++++++++ bittensor/utils/easy_imports.py | 100 +++++----------------------- tests/unit_tests/test_config.py | 12 ++-- tests/unit_tests/test_deprecated.py | 34 ---------- 5 files changed, 88 insertions(+), 122 deletions(-) create mode 100644 bittensor/utils/btlogging/levels.py delete mode 100644 tests/unit_tests/test_deprecated.py diff --git a/MIGRATION.md b/MIGRATION.md index ea71bd58f7..1361c3cc03 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -263,3 +263,25 @@ Additional: ### Renames parameters: - `get_metagraph_info`: `field_indices` -> `selected_indices` (to be consistent) + + +### `easy_import.py` module +Added: +- `from bittenosor import extrinsics` +- `from bittenosor import mock` +- `from bittenosor import get_async_subtensor` + +Next variables removed: +- `async_subtensor` not -> `AsyncSubtensor` +- `axon` not -> `Axon` +- `config` not -> `Config` +- `dendrite` not -> `Dendrite` +- `keyfile` not -> `Keyfile` +- `metagraph` not -> `Metagraph` +- `wallet` not -> `Wallet` +- `subtensor` not -> `Subtensor` +- `synapse` not -> `Synapse` + +Links to subpackages removed: +- `bittensor.mock` (available in `bittensor.core.mock`) +- `bittensor.extrinsics` (available in `bittensor.core.extrinsics`) diff --git a/bittensor/utils/btlogging/levels.py b/bittensor/utils/btlogging/levels.py new file mode 100644 index 0000000000..6115de1473 --- /dev/null +++ b/bittensor/utils/btlogging/levels.py @@ -0,0 +1,42 @@ +from bittensor.utils.btlogging import logging + + +# Logging level setup helpers. +def trace(on: bool = True): + """ + Enables or disables trace logging. + + Parameters: + on: If True, enables trace logging. If False, disables trace logging. + """ + logging.set_trace(on) + + +def debug(on: bool = True): + """ + Enables or disables debug logging. + + Parameters: + on: If True, enables debug logging. If False, disables debug logging. + """ + logging.set_debug(on) + + +def warning(on: bool = True): + """ + Enables or disables warning logging. + + Parameters: + on: If True, enables warning logging. If False, disables warning logging and sets default (WARNING) level. + """ + logging.set_warning(on) + + +def info(on: bool = True): + """ + Enables or disables info logging. + + Parameters: + on: If True, enables info logging. If False, disables info logging and sets default (WARNING) level. + """ + logging.set_info(on) diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index 57bc9ae2bc..b0f9578656 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -1,13 +1,15 @@ """ -The Bittensor Compatibility Module is designed to ensure seamless integration and functionality with legacy versions of -the Bittensor framework, specifically up to and including version 7.3.0. This module addresses changes and deprecated -features in recent versions, allowing users to maintain compatibility with older systems and projects. -""" +The Bittensor Compatibility Module serves as a centralized import hub for internal and external classes, functions, +constants, and utilities that are frequently accessed via the top-level `bittensor` namespace +(e.g., `from bittensor import Wallet`). + +It consolidates these widely used symbols into `bittensor/__init__.py`, enabling a cleaner and more intuitive public API +for developers and the broader community. -import importlib -import sys +Note: + Direct imports from their respective submodules are recommended for improved clarity and long-term maintainability. +""" -from bittensor_wallet import Keypair from bittensor_wallet.errors import KeyFileError from bittensor_wallet.keyfile import ( serialized_keypair_to_keyfile_data, @@ -25,11 +27,12 @@ decrypt_keyfile_data, Keyfile, ) +from bittensor_wallet.keypair import Keypair from bittensor_wallet.wallet import Wallet -from bittensor.core import settings +from bittensor.core import settings, extrinsics from bittensor.core.addons import timelock, SubtensorApi -from bittensor.core.async_subtensor import AsyncSubtensor +from bittensor.core.async_subtensor import AsyncSubtensor, get_async_subtensor from bittensor.core.axon import Axon from bittensor.core.chain_data import ( AxonInfo, @@ -104,6 +107,7 @@ from bittensor.core.tensor import Tensor from bittensor.core.threadpool import PriorityThreadPoolExecutor from bittensor.utils import ( + mock, ss58_to_vec_u8, strtobool, get_explorer_url_for_network, @@ -112,72 +116,12 @@ u64_normalized_float, get_hash, ) -from bittensor.utils.balance import Balance -from bittensor.utils.balance import tao, rao +from bittensor.utils.balance import Balance, tao, rao from bittensor.utils.btlogging import logging +from bittensor.utils.btlogging.levels import trace, debug, warning, info from bittensor.utils.mock.subtensor_mock import MockSubtensor from bittensor.utils.subnets import SubnetsAPI -# Backwards compatibility with previous bittensor versions. -async_subtensor = AsyncSubtensor -axon = Axon -config = Config -dendrite = Dendrite -keyfile = Keyfile -metagraph = Metagraph -wallet = Wallet -subtensor = Subtensor -synapse = Synapse - -# Makes the `bittensor.utils.mock` subpackage available as `bittensor.mock` for backwards compatibility. -mock_subpackage = importlib.import_module("bittensor.utils.mock") -sys.modules["bittensor.mock"] = mock_subpackage - -# Makes the `bittensor.core.extrinsics` subpackage available as `bittensor.extrinsics` for backwards compatibility. -extrinsics_subpackage = importlib.import_module("bittensor.core.extrinsics") -sys.modules["bittensor.extrinsics"] = extrinsics_subpackage - - -# Logging helpers. -def trace(on: bool = True): - """ - Enables or disables trace logging. - - Parameters: - on: If True, enables trace logging. If False, disables trace logging. - """ - logging.set_trace(on) - - -def debug(on: bool = True): - """ - Enables or disables debug logging. - - Parameters: - on: If True, enables debug logging. If False, disables debug logging. - """ - logging.set_debug(on) - - -def warning(on: bool = True): - """ - Enables or disables warning logging. - - Parameters: - on: If True, enables warning logging. If False, disables warning logging and sets default (WARNING) level. - """ - logging.set_warning(on) - - -def info(on: bool = True): - """ - Enables or disables info logging. - - Parameters: - on: If True, enables info logging. If False, disables info logging and sets default (WARNING) level. - """ - logging.set_info(on) - __all__ = [ "Keypair", @@ -284,19 +228,11 @@ def info(on: bool = True): "logging", "MockSubtensor", "SubnetsAPI", - "async_subtensor", - "axon", - "config", - "dendrite", - "keyfile", - "metagraph", - "wallet", - "subtensor", - "synapse", "trace", "debug", "warning", "info", - "mock_subpackage", - "extrinsics_subpackage", + "extrinsics", + "mock", + "get_async_subtensor", ] diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py index 5c1c2a0edf..18b8ee36d5 100644 --- a/tests/unit_tests/test_config.py +++ b/tests/unit_tests/test_config.py @@ -6,12 +6,12 @@ def test_py_config_parsed_successfully_rust_wallet(): """Verify that python based config object is successfully parsed with rust-based wallet object.""" parser = argparse.ArgumentParser() - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - bittensor.axon.add_args(parser) + bittensor.Wallet.add_args(parser) + bittensor.Subtensor.add_args(parser) + bittensor.Axon.add_args(parser) bittensor.logging.add_args(parser) - config = bittensor.config(parser) + config = bittensor.Config(parser) # override config manually since we can't apply mocking to rust objects easily config.wallet.name = "new_wallet_name" @@ -19,13 +19,13 @@ def test_py_config_parsed_successfully_rust_wallet(): config.wallet.path = "/some/not_default/path" # Pass in the whole bittensor config - wallet = bittensor.wallet(config=config) + wallet = bittensor.Wallet(config=config) assert wallet.name == config.wallet.name assert wallet.hotkey_str == config.wallet.hotkey assert wallet.path == config.wallet.path # Pass in only the btwallet's config - wallet_two = bittensor.wallet(config=config.wallet) + wallet_two = bittensor.Wallet(config=config.wallet) assert wallet_two.name == config.wallet.name assert wallet_two.hotkey_str == config.wallet.hotkey assert wallet_two.path == config.wallet.path diff --git a/tests/unit_tests/test_deprecated.py b/tests/unit_tests/test_deprecated.py deleted file mode 100644 index f47337e019..0000000000 --- a/tests/unit_tests/test_deprecated.py +++ /dev/null @@ -1,34 +0,0 @@ -import sys - - -def test_mock_import(): - """ - Tests that `bittensor.mock` can be imported and is the same as `bittensor.utils.mock`. - """ - import bittensor.mock as redirected_mock - import bittensor.utils.mock as real_mock - - assert "bittensor.mock" in sys.modules - assert redirected_mock is real_mock - - -def test_extrinsics_import(): - """Tests that `bittensor.extrinsics` can be imported and is the same as `bittensor.utils.deprecated.extrinsics`.""" - import bittensor.extrinsics as redirected_extrinsics - import bittensor.core.extrinsics as real_extrinsics - - assert "bittensor.extrinsics" in sys.modules - assert redirected_extrinsics is real_extrinsics - - -def test_object_aliases_are_correctly_mapped(): - """Ensures all object aliases correctly map to their respective classes in Bittensor package.""" - import bittensor - - assert issubclass(bittensor.axon, bittensor.Axon) - assert issubclass(bittensor.config, bittensor.Config) - assert issubclass(bittensor.dendrite, bittensor.Dendrite) - assert issubclass(bittensor.keyfile, bittensor.Keyfile) - assert issubclass(bittensor.metagraph, bittensor.Metagraph) - assert issubclass(bittensor.wallet, bittensor.Wallet) - assert issubclass(bittensor.synapse, bittensor.Synapse) From 796157a515097efd3ebaee45747b60ae7301995e Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 23:00:11 -0700 Subject: [PATCH 290/416] update MIGRATION.md --- MIGRATION.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MIGRATION.md b/MIGRATION.md index 1361c3cc03..bd0d064ee4 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -285,3 +285,9 @@ Next variables removed: Links to subpackages removed: - `bittensor.mock` (available in `bittensor.core.mock`) - `bittensor.extrinsics` (available in `bittensor.core.extrinsics`) + + +New subpackage `bittensor.core.addons` created to host optional extensions and experimental logic enhancing the core functionality. +Currently it contains: +- `bittensor.core.addons.subtensor_api` +- `bittensor.core.addons.timelock` \ No newline at end of file From 56bfa649efc5ae82d6d3df3b0ab57ef8c2a692b9 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 23:21:44 -0700 Subject: [PATCH 291/416] use updated `subnet-template` --- tests/e2e_tests/utils/e2e_test_utils.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index e0a19ada7e..9d3671ce58 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -214,7 +214,10 @@ async def _reader(self): self.set_weights.set() def __init__(self): - self.dir = clone_or_update_templates() + # self.dir = clone_or_update_templates() + # keep it until https://github.com/opentensor/subnet-template/commits/feat/roman/no-low-case-classes-anymore/ + # merged to main + self.dir = clone_or_update_templates(specific_commit="d972b47ba870c2a9f32760a83bdb7041c2752f01") def __enter__(self): return self From db78c24d25170d695fdbd8def9cefb3f5972dd23 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 29 Sep 2025 23:32:06 -0700 Subject: [PATCH 292/416] use regular `subnet-template` (merged) --- tests/e2e_tests/utils/e2e_test_utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index 9d3671ce58..e0a19ada7e 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -214,10 +214,7 @@ async def _reader(self): self.set_weights.set() def __init__(self): - # self.dir = clone_or_update_templates() - # keep it until https://github.com/opentensor/subnet-template/commits/feat/roman/no-low-case-classes-anymore/ - # merged to main - self.dir = clone_or_update_templates(specific_commit="d972b47ba870c2a9f32760a83bdb7041c2752f01") + self.dir = clone_or_update_templates() def __enter__(self): return self From 05aa6544738785cbe5698f718b4a15a9b239056c Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 30 Sep 2025 08:50:26 -0700 Subject: [PATCH 293/416] mode addons from `bittensor.core.addons` to `bittensor.addons` (logically correct place) --- MIGRATION.md | 14 ++++----- bittensor/{core => }/addons/__init__.py | 4 +-- .../addons/subtensor_api/__init__.py | 0 .../{core => }/addons/subtensor_api/chain.py | 0 .../addons/subtensor_api/commitments.py | 0 .../addons/subtensor_api/delegates.py | 0 .../addons/subtensor_api/extrinsics.py | 0 .../addons/subtensor_api/metagraphs.py | 0 .../addons/subtensor_api/neurons.py | 0 .../addons/subtensor_api/queries.py | 0 .../addons/subtensor_api/staking.py | 0 .../addons/subtensor_api/subnets.py | 0 .../{core => }/addons/subtensor_api/utils.py | 2 +- .../addons/subtensor_api/wallets.py | 0 bittensor/{core => }/addons/timelock.py | 0 bittensor/utils/balance.py | 29 +++++++++---------- bittensor/utils/easy_imports.py | 3 +- tests/e2e_tests/conftest.py | 2 +- tests/e2e_tests/utils/chain_interactions.py | 2 +- tests/e2e_tests/utils/e2e_test_utils.py | 2 +- tests/integration_tests/test_timelock.py | 2 +- .../extrinsics/asyncex/test_unstaking.py | 22 +++++++------- tests/unit_tests/extrinsics/test_unstaking.py | 22 +++++++------- tests/unit_tests/test_async_subtensor.py | 2 +- tests/unit_tests/test_subtensor.py | 2 +- tests/unit_tests/test_subtensor_api.py | 2 +- 26 files changed, 54 insertions(+), 56 deletions(-) rename bittensor/{core => }/addons/__init__.py (86%) rename bittensor/{core => }/addons/subtensor_api/__init__.py (100%) rename bittensor/{core => }/addons/subtensor_api/chain.py (100%) rename bittensor/{core => }/addons/subtensor_api/commitments.py (100%) rename bittensor/{core => }/addons/subtensor_api/delegates.py (100%) rename bittensor/{core => }/addons/subtensor_api/extrinsics.py (100%) rename bittensor/{core => }/addons/subtensor_api/metagraphs.py (100%) rename bittensor/{core => }/addons/subtensor_api/neurons.py (100%) rename bittensor/{core => }/addons/subtensor_api/queries.py (100%) rename bittensor/{core => }/addons/subtensor_api/staking.py (100%) rename bittensor/{core => }/addons/subtensor_api/subnets.py (100%) rename bittensor/{core => }/addons/subtensor_api/utils.py (99%) rename bittensor/{core => }/addons/subtensor_api/wallets.py (100%) rename bittensor/{core => }/addons/timelock.py (100%) diff --git a/MIGRATION.md b/MIGRATION.md index bd0d064ee4..4143843435 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -41,19 +41,19 @@ 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 `(in progress)` +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`.~~ it's on the right place. +3. ✅ ~~Move `bittensor.utils.get_transfer_fn_params` to `bittensor.core.extrinsics.utils`.~~ it's on the right place. 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.~~ done across many PRs. -6. ✅ To be consistent throughout the SDK `(in progress)`: +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. @@ -235,7 +235,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `query_map` has updated parameters order. - method `add_stake_multiple` has updated parameters order. - method `get_stake_for_coldkey` removed, bc this is the same as `get_stake_info_for_coldkey` -- method `get_subnets` renamed to `get_all_subnets_netuid` (more obvious) +- method `get_subnets` renamed to `get_all_subnets_netuid` (obvious name, consistent with existing names) - method `get_owned_hotkeys` get rid `reuse_block` parameter to be consistent with other sync methods. - method `blocks_since_last_update` improved. Currently it can be used to get historical data from archive node. - methods (async) `get_subnet_validator_permits` and `get_subnet_owner_hotkey` got `block_hash` and `reuse_block` parameters. @@ -287,7 +287,7 @@ Links to subpackages removed: - `bittensor.extrinsics` (available in `bittensor.core.extrinsics`) -New subpackage `bittensor.core.addons` created to host optional extensions and experimental logic enhancing the core functionality. +New subpackage `bittensor.addons` created to host optional extensions and experimental logic enhancing the core functionality. Currently it contains: -- `bittensor.core.addons.subtensor_api` -- `bittensor.core.addons.timelock` \ No newline at end of file +- `bittensor.addons.subtensor_api` +- `bittensor.addons.timelock` \ No newline at end of file diff --git a/bittensor/core/addons/__init__.py b/bittensor/addons/__init__.py similarity index 86% rename from bittensor/core/addons/__init__.py rename to bittensor/addons/__init__.py index b84bb3ae90..0f7295f24a 100644 --- a/bittensor/core/addons/__init__.py +++ b/bittensor/addons/__init__.py @@ -9,8 +9,8 @@ discoverability and structure. """ -from bittensor.core.addons import timelock -from bittensor.core.addons.subtensor_api import SubtensorApi +from bittensor.addons import timelock +from bittensor.addons.subtensor_api import SubtensorApi __all__ = [ "timelock", diff --git a/bittensor/core/addons/subtensor_api/__init__.py b/bittensor/addons/subtensor_api/__init__.py similarity index 100% rename from bittensor/core/addons/subtensor_api/__init__.py rename to bittensor/addons/subtensor_api/__init__.py diff --git a/bittensor/core/addons/subtensor_api/chain.py b/bittensor/addons/subtensor_api/chain.py similarity index 100% rename from bittensor/core/addons/subtensor_api/chain.py rename to bittensor/addons/subtensor_api/chain.py diff --git a/bittensor/core/addons/subtensor_api/commitments.py b/bittensor/addons/subtensor_api/commitments.py similarity index 100% rename from bittensor/core/addons/subtensor_api/commitments.py rename to bittensor/addons/subtensor_api/commitments.py diff --git a/bittensor/core/addons/subtensor_api/delegates.py b/bittensor/addons/subtensor_api/delegates.py similarity index 100% rename from bittensor/core/addons/subtensor_api/delegates.py rename to bittensor/addons/subtensor_api/delegates.py diff --git a/bittensor/core/addons/subtensor_api/extrinsics.py b/bittensor/addons/subtensor_api/extrinsics.py similarity index 100% rename from bittensor/core/addons/subtensor_api/extrinsics.py rename to bittensor/addons/subtensor_api/extrinsics.py diff --git a/bittensor/core/addons/subtensor_api/metagraphs.py b/bittensor/addons/subtensor_api/metagraphs.py similarity index 100% rename from bittensor/core/addons/subtensor_api/metagraphs.py rename to bittensor/addons/subtensor_api/metagraphs.py diff --git a/bittensor/core/addons/subtensor_api/neurons.py b/bittensor/addons/subtensor_api/neurons.py similarity index 100% rename from bittensor/core/addons/subtensor_api/neurons.py rename to bittensor/addons/subtensor_api/neurons.py diff --git a/bittensor/core/addons/subtensor_api/queries.py b/bittensor/addons/subtensor_api/queries.py similarity index 100% rename from bittensor/core/addons/subtensor_api/queries.py rename to bittensor/addons/subtensor_api/queries.py diff --git a/bittensor/core/addons/subtensor_api/staking.py b/bittensor/addons/subtensor_api/staking.py similarity index 100% rename from bittensor/core/addons/subtensor_api/staking.py rename to bittensor/addons/subtensor_api/staking.py diff --git a/bittensor/core/addons/subtensor_api/subnets.py b/bittensor/addons/subtensor_api/subnets.py similarity index 100% rename from bittensor/core/addons/subtensor_api/subnets.py rename to bittensor/addons/subtensor_api/subnets.py diff --git a/bittensor/core/addons/subtensor_api/utils.py b/bittensor/addons/subtensor_api/utils.py similarity index 99% rename from bittensor/core/addons/subtensor_api/utils.py rename to bittensor/addons/subtensor_api/utils.py index 09ed8fc302..6f7a2b3412 100644 --- a/bittensor/core/addons/subtensor_api/utils.py +++ b/bittensor/addons/subtensor_api/utils.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from bittensor.core.addons import SubtensorApi + from bittensor.addons import SubtensorApi def add_legacy_methods(subtensor: "SubtensorApi"): diff --git a/bittensor/core/addons/subtensor_api/wallets.py b/bittensor/addons/subtensor_api/wallets.py similarity index 100% rename from bittensor/core/addons/subtensor_api/wallets.py rename to bittensor/addons/subtensor_api/wallets.py diff --git a/bittensor/core/addons/timelock.py b/bittensor/addons/timelock.py similarity index 100% rename from bittensor/core/addons/timelock.py rename to bittensor/addons/timelock.py diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 5a84960152..37aafe1e32 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -1,4 +1,3 @@ -import warnings from typing import Union, TypedDict, Optional from scalecodec import ScaleType @@ -13,26 +12,23 @@ def _check_currencies(self, other): A warning is raised if the netuids differ. Example: - >>> balance1 = Balance.from_rao(1000).set_unit(12) - >>> balance2 = Balance.from_rao(500).set_unit(12) - >>> balance1 + balance2 # No warning + balance1 = Balance.from_rao(1000).set_unit(14) + balance2 = Balance.from_tao(500).set_unit(14) + balance1 + balance2 # No error. - >>> balance3 = Balance.from_rao(200).set_unit(15) - >>> balance1 + balance3 # Raises DeprecationWarning + balance3 = Balance.from_tao(200).set_unit(14) + balance1 + balance3 # Raises ValueError. In this example: - - `from_rao` creates a Balance instance from the amount in rao (smallest unit). - - `set_unit(12)` sets the unit to correspond to subnet 12 (i.e., Alpha from netuid 12). + - `from_rao` creates a Balance instance from the amount in rao. + - `set_unit(14)` sets the unit to correspond to subnet 14 (i.e., Alpha from netuid 14). """ if self.netuid != other.netuid: - warnings.simplefilter("default", DeprecationWarning) - warnings.warn( - "Balance objects must have the same netuid (Alpha currency) to perform arithmetic operations.\n" - f"First balance is `{self}`. Second balance is `{other}`.\n\n" - "To create a Balance instance with the correct netuid, use:\n" - "Balance.from_rao(1000).set_unit(12) # 1000 rao in subnet 12", - category=DeprecationWarning, - stacklevel=2, + raise TypeError( + f"Cannot perform arithmetic between balances of different currencies: {self} and {other}. " + "Both Balance objects must reference the same netuid (Alpha currency). " + "For example, to create a Balance instance for subnet 12 you can use: " + "Balance.from_tao(10).set_unit(14), which corresponds to 10 TAO in subnet 14." ) @@ -349,6 +345,7 @@ class FixedPoint(TypedDict): def fixed_to_float( fixed: Union[FixedPoint, ScaleType], frac_bits: int = 64, total_bits: int = 128 ) -> float: + """Converts a fixed-point value (e.g., U64F64) into a floating-point number.""" # By default, this is a U64F64 # which is 64 bits of integer and 64 bits of fractional data: int = fb.value if isinstance((fb := fixed["bits"]), ScaleType) else fb diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index b0f9578656..9eea34055a 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -30,8 +30,8 @@ from bittensor_wallet.keypair import Keypair from bittensor_wallet.wallet import Wallet +from bittensor.addons import timelock, SubtensorApi from bittensor.core import settings, extrinsics -from bittensor.core.addons import timelock, SubtensorApi from bittensor.core.async_subtensor import AsyncSubtensor, get_async_subtensor from bittensor.core.axon import Axon from bittensor.core.chain_data import ( @@ -122,7 +122,6 @@ from bittensor.utils.mock.subtensor_mock import MockSubtensor from bittensor.utils.subnets import SubnetsAPI - __all__ = [ "Keypair", "KeyFileError", diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 319b9d646a..f59dda1d9d 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -12,7 +12,7 @@ import pytest import pytest_asyncio -from bittensor.core.addons import SubtensorApi +from bittensor.addons import SubtensorApi from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.e2e_test_utils import ( Templates, diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index 8fc0ecfe53..ee827b2083 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -15,7 +15,7 @@ # for typing purposes if TYPE_CHECKING: from bittensor import Wallet - from bittensor.core.addons import SubtensorApi + from bittensor.addons import SubtensorApi from async_substrate_interface import ( AsyncSubstrateInterface, AsyncExtrinsicReceipt, diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index e0a19ada7e..b561bbdd9f 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -6,7 +6,7 @@ from bittensor_wallet import Keypair, Wallet -from bittensor.core.addons import SubtensorApi +from bittensor.addons import SubtensorApi from bittensor.utils.btlogging import logging template_path = os.getcwd() + "/neurons/" diff --git a/tests/integration_tests/test_timelock.py b/tests/integration_tests/test_timelock.py index b0545e3517..2b7ddf9823 100644 --- a/tests/integration_tests/test_timelock.py +++ b/tests/integration_tests/test_timelock.py @@ -3,7 +3,7 @@ import pytest -from bittensor.core.addons import timelock +from bittensor.addons import timelock def test_encrypt_returns_valid_tuple(): diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 8b0032a79e..0721adfca2 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -7,23 +7,23 @@ @pytest.mark.asyncio async def test_unstake_extrinsic(fake_wallet, mocker): + # Preps fake_substrate = mocker.AsyncMock( **{"get_payment_info.return_value": {"partial_fee": 10}} ) - # Preps + fake_netuid = 14 fake_subtensor = mocker.AsyncMock( **{ "get_hotkey_owner.return_value": "hotkey_owner", - "get_stake_for_coldkey_and_hotkey.return_value": Balance(10.0), + "get_stake_for_coldkey_and_hotkey.return_value": Balance.from_tao(10.0, fake_netuid), "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), - "get_stake.return_value": Balance(10.0), + "get_stake.return_value": Balance.from_tao(10.0, fake_netuid), "substrate": fake_substrate, } ) fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58 = "hotkey" - fake_netuid = 1 amount = Balance.from_tao(1.1) wait_for_inclusion = True wait_for_finalization = True @@ -48,7 +48,7 @@ async def test_unstake_extrinsic(fake_wallet, mocker): call_params={ "hotkey": "hotkey", "amount_unstaked": 1100000000, - "netuid": 1, + "netuid": fake_netuid, }, ) fake_subtensor.sign_and_send_extrinsic.assert_awaited_once_with( @@ -113,6 +113,9 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): async def test_unstake_multiple_extrinsic_some_unstake_is_happy(fake_wallet, mocker): """Verify that sync `unstake_multiple_extrinsic` method calls proper async method.""" # Preps + sn_5 = 5 + sn_14 = 14 + fake_netuids = [sn_5, sn_14] fake_substrate = mocker.AsyncMock( **{"get_payment_info.return_value": {"partial_fee": 10}} ) @@ -125,12 +128,11 @@ async def test_unstake_multiple_extrinsic_some_unstake_is_happy(fake_wallet, moc unstaking, "unstake_extrinsic", return_value=ExtrinsicResponse(True, "") ) mocker.patch.object( - unstaking, "get_old_stakes", return_value=[Balance(1.1), Balance(0.3)] + unstaking, "get_old_stakes", return_value=[Balance.from_tao(1.1, sn_5), Balance.from_tao(0.3, sn_14)] ) fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58s = ["hotkey1", "hotkey2"] - fake_netuids = [1, 2] - amounts = [Balance.from_tao(1.1), Balance.from_tao(1.2)] + amounts = [Balance.from_tao(1.1, sn_5), Balance.from_tao(1.2, sn_14)] wait_for_inclusion = True wait_for_finalization = True @@ -146,15 +148,13 @@ async def test_unstake_multiple_extrinsic_some_unstake_is_happy(fake_wallet, moc ) # Asserts - print(">>> result", response) - print(">>> result.success", response.success) assert response.success is False assert response.message == "Some unstake were successful." assert len(response.data) == 2 mocked_unstake_extrinsic.assert_awaited_once_with( subtensor=fake_subtensor, wallet=fake_wallet, - amount=Balance.from_tao(1.1), + amount=Balance.from_tao(1.1, sn_5), hotkey_ss58=hotkey_ss58s[0], netuid=fake_netuids[0], period=None, diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 6c8320ff8d..4a88a5c8ba 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -4,22 +4,22 @@ def test_unstake_extrinsic(fake_wallet, mocker): + # Preps fake_substrate = mocker.Mock( **{"get_payment_info.return_value": {"partial_fee": 10}} ) - # Preps + fake_netuid = 14 fake_subtensor = mocker.Mock( **{ "get_hotkey_owner.return_value": "hotkey_owner", - "get_stake_for_coldkey_and_hotkey.return_value": Balance(10.0), + "get_stake_for_coldkey_and_hotkey.return_value": Balance.from_tao(10.0, fake_netuid), "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), - "get_stake.return_value": Balance(10.0), + "get_stake.return_value": Balance.from_tao(10.0, fake_netuid), "substrate": fake_substrate, } ) fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58 = "hotkey" - fake_netuid = 1 amount = Balance.from_tao(1.1) wait_for_inclusion = True wait_for_finalization = True @@ -44,7 +44,7 @@ def test_unstake_extrinsic(fake_wallet, mocker): call_params={ "hotkey": "hotkey", "amount_unstaked": 1100000000, - "netuid": 1, + "netuid": fake_netuid, }, ) fake_subtensor.sign_and_send_extrinsic.assert_called_once_with( @@ -107,6 +107,9 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): def test_unstake_multiple_extrinsic(subtensor, fake_wallet, mocker): """Tests when out of 2 unstakes 1 is completed and 1 is not.""" # Preps + sn_5 = 5 + sn_14 = 14 + fake_netuids = [sn_5, sn_14] mocked_balance = mocker.patch.object( subtensor, "get_balance", return_value=Balance.from_tao(1.0) ) @@ -119,12 +122,11 @@ def test_unstake_multiple_extrinsic(subtensor, fake_wallet, mocker): mocker.patch.object( unstaking, "get_old_stakes", - return_value=[Balance.from_tao(10), Balance.from_tao(0.3)], + return_value=[Balance.from_tao(10, sn_5), Balance.from_tao(0.3, sn_14)], ) fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58s = ["hotkey1", "hotkey2"] - fake_netuids = [1, 2] - amounts = [Balance.from_tao(1.1), Balance.from_tao(1.2)] + amounts = [Balance.from_tao(1.1, sn_5), Balance.from_tao(1.2, sn_14)] wait_for_inclusion = True wait_for_finalization = True @@ -151,9 +153,9 @@ def test_unstake_multiple_extrinsic(subtensor, fake_wallet, mocker): mocked_unstake_extrinsic.assert_called_once_with( subtensor=subtensor, wallet=fake_wallet, - netuid=1, + netuid=sn_5, hotkey_ss58="hotkey1", - amount=Balance.from_tao(1.1), + amount=Balance.from_tao(1.1, sn_5), period=None, raise_error=False, wait_for_inclusion=wait_for_inclusion, diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 2587e27adf..09b233247c 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3801,7 +3801,7 @@ async def test_get_stake_operations_fee(subtensor, mocker): params=[netuid], block_hash=mocked_determine_block_hash.return_value, ) - assert result == Balance.from_rao(299076829).set_unit(netuid) + assert result == Balance.from_rao(299076829) @pytest.mark.asyncio diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 524a653984..b149473eaa 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -3986,7 +3986,7 @@ def test_get_stake_operations_fee(subtensor, mocker): params=[netuid], block_hash=mocked_determine_block_hash.return_value, ) - assert result == Balance.from_rao(299076829).set_unit(netuid) + assert result == Balance.from_rao(299076829) def test_get_stake_add_fee(subtensor, mocker): diff --git a/tests/unit_tests/test_subtensor_api.py b/tests/unit_tests/test_subtensor_api.py index aead8c7ce1..39cc918439 100644 --- a/tests/unit_tests/test_subtensor_api.py +++ b/tests/unit_tests/test_subtensor_api.py @@ -1,6 +1,6 @@ import pytest -from bittensor.core.addons.subtensor_api import SubtensorApi +from bittensor.addons.subtensor_api import SubtensorApi from bittensor.core.subtensor import Subtensor From 0fad17157fcb7cc944fa8a3b253420aab3bbe368 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 30 Sep 2025 09:46:30 -0700 Subject: [PATCH 294/416] Balance has strict check operations between instances regarding units. + fixed tests --- MIGRATION.md | 8 ++++---- bittensor/utils/balance.py | 21 ++++++++++++++++++++- tests/e2e_tests/test_delegate.py | 4 ++-- tests/e2e_tests/test_staking.py | 19 +++++++++++-------- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 4143843435..c1ced148a4 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -36,7 +36,7 @@ 2. Reconsider entire metagraph module logic. ## Balance -1. In `bittensor.utils.balance._check_currencies` raise the error instead of `warnings.warn`. +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. @@ -65,7 +65,7 @@ rename this variable in documentation. - [x] CRv3 extrinsics - [x] CRv3 logic related subtensor's calls -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. +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 deprecated `bittensor.utils.version.version_checking` @@ -102,8 +102,8 @@ To implement the above changes and prepare for the v10 release, the following st 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. It must include: - - [ ] All change categories (Extrinsics, Subtensor, Metagraph, etc.) - - [ ] Per-PR breakdown of what was added, removed, renamed, or refactored. + - [x] All change categories (Extrinsics, Subtensor, Metagraph, etc.) + - [x] 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. diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 37aafe1e32..2210dce1f2 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -25,7 +25,7 @@ def _check_currencies(self, other): """ if self.netuid != other.netuid: raise TypeError( - f"Cannot perform arithmetic between balances of different currencies: {self} and {other}. " + f"Cannot perform any operations between balances of different currencies: {self} and {other}. " "Both Balance objects must reference the same netuid (Alpha currency). " "For example, to create a Balance instance for subnet 12 you can use: " "Balance.from_tao(10).set_unit(14), which corresponds to 10 TAO in subnet 14." @@ -43,6 +43,25 @@ class Balance: rao_unit (str): A string representing the symbol for the rao unit. rao (int): An integer that stores the balance in rao units. tao (float): A float property that gives the balance in tao units. + + Note: + To ensure arithmetic operations between `Balance` instances work correctly, they must set the same unit for each + using the `netuid`. + + Examples: + + balance_wallet_default = Balance.from_tao(10, netuid=14) + balance_wallet_secret = Balance.from_tao(2, netuid=14) + total_balance = balance_wallet_default + balance_wallet_secret + + # or + + balance_wallet_default = Balance.from_tao(10).set_unit(netuid=14) + balance_wallet_secret = Balance.from_tao(2).set_unit(netuid=14) + total_balance = balance_wallet_default + balance_wallet_secret + + The `from_tao()` and `from_rao()` methods accept the `netuid` parameter + to set the appropriate unit symbol. """ unit: str = settings.TAO_SYMBOL # This is the tao unit diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 9659985413..3ebb405653 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -694,7 +694,7 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, ) - assert stake == Balance(0) + assert stake == Balance.from_tao(0, alice_subnet_netuid) @pytest.mark.asyncio @@ -774,7 +774,7 @@ async def test_nominator_min_required_stake_async( hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, ) - assert stake == Balance(0) + assert stake == Balance.from_tao(0, alice_subnet_netuid) def test_get_vote_data(subtensor, alice_wallet): diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 78859e4038..f40fcdda98 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -751,7 +751,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) assert partial_stake > Balance(0).set_unit(alice_subnet_netuid), ( "Partial stake should be added" ) - assert partial_stake < stake_amount, ( + assert partial_stake < Balance.from_tao(stake_amount.tao).set_unit(alice_subnet_netuid), ( "Partial stake should be less than requested amount" ) @@ -952,7 +952,7 @@ async def test_safe_staking_scenarios_async( assert partial_stake > Balance(0).set_unit(alice_subnet_netuid), ( "Partial stake should be added" ) - assert partial_stake < stake_amount, ( + assert partial_stake < Balance.from_tao(stake_amount.tao).set_unit(alice_subnet_netuid), ( "Partial stake should be less than requested amount" ) @@ -1333,9 +1333,9 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): 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), + 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, ) @@ -1359,6 +1359,9 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): ) expected_stakes += fast_block_stake + logging.console.info(f"[orange]FS: {fast_block_stake}[/orange]") + logging.console.info(f"[orange]RS: {stakes}[/orange]") + logging.console.info(f"[orange]ES: {expected_stakes}[/orange]") assert stakes == expected_stakes # test move_stake with move_all_stake=True @@ -1502,9 +1505,9 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ netuid=alice_subnet_netuid 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), - emission=get_dynamic_balance(stakes[0].emission.rao, bob_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, ) From 95c0187e18b4c627b795246bd610e2594cddbef9 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 30 Sep 2025 09:52:54 -0700 Subject: [PATCH 295/416] add `hex_to_ss58` and `ss58_to_hex` to `bittensor.utils.__init__` --- MIGRATION.md | 6 +++--- bittensor/utils/__init__.py | 13 +++++++++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index c1ced148a4..3f0015150f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -75,9 +75,9 @@ rename this variable in documentation. 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`) -2. Implement Crowdloan logic. Issue: https://github.com/opentensor/bittensor/issues/3017 -3. ✅ Implement Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 +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 Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 +3. Implement `Crowdloan` logic. Issue: https://github.com/opentensor/bittensor/issues/3017 ## Testing 1. ✅ When running tests via Docker, ensure no lingering processes occupy required ports before launch. diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 0ffcdb37c1..7d58d94de3 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -1,18 +1,23 @@ import ast import decimal import hashlib +import inspect import warnings 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, ) from bittensor_wallet import Keypair from bittensor_wallet.errors import KeyFileError, PasswordError -from scalecodec import ss58_decode, is_valid_ss58_address as _is_valid_ss58_address +from scalecodec import ( + ss58_decode, + ss58_encode, + is_valid_ss58_address as _is_valid_ss58_address, +) from bittensor.core import settings from bittensor.core.settings import SS58_FORMAT @@ -24,6 +29,10 @@ from bittensor_wallet import Wallet from bittensor.utils.balance import Balance +# keep save from import analyzer as obvious aliases +hex_to_ss58 = ss58_encode +ss58_to_hex = ss58_decode + BT_DOCS_LINK = "https://docs.bittensor.com" RAOPERTAO = 1e9 U16_MAX = 65535 From a0d516459c00d7dd453f3347bed98bad315f8d5c Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 30 Sep 2025 11:27:07 -0700 Subject: [PATCH 296/416] new custom errors for balance check --- bittensor/core/errors.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bittensor/core/errors.py b/bittensor/core/errors.py index 15eb9e0446..b1d2c510eb 100644 --- a/bittensor/core/errors.py +++ b/bittensor/core/errors.py @@ -212,3 +212,11 @@ def __init__( ): self.message = message super().__init__(self.message, synapse) + + +class BalanceUnitMismatchError(Exception): + """Raised when operations is attempted between Balance objects with different units (netuid).""" + + +class BalanceTypeError(Exception): + """Raised when an unsupported type is used instead of Balance amount.""" From 13b2dffdce50c6e79a1753d9f8dc8aedb362523f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 30 Sep 2025 11:27:43 -0700 Subject: [PATCH 297/416] `check_and_convert_to_balance` renamed to `check_balance_amount` + new logic across the code --- MIGRATION.md | 13 +++++++-- bittensor/core/async_subtensor.py | 16 +++++------ bittensor/core/subtensor.py | 16 +++++------ bittensor/utils/balance.py | 46 ++++++++++++++++++------------- bittensor/utils/easy_imports.py | 4 +++ 5 files changed, 58 insertions(+), 37 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 3f0015150f..1521ac5fe0 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -37,7 +37,7 @@ ## 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`. +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 @@ -290,4 +290,13 @@ Links to subpackages removed: New subpackage `bittensor.addons` created to host optional extensions and experimental logic enhancing the core functionality. Currently it contains: - `bittensor.addons.subtensor_api` -- `bittensor.addons.timelock` \ No newline at end of file +- `bittensor.addons.timelock` + +### Balance (bittensor/utils/balance.py) and related changes +- [x] Added 2 custom errors: + - `bittensor.core.errors.BalanceUnitMismatchError` + - `bittensor.core.errors.BalanceTypeError` +- [x] `check_balance` renamed to `check_balance_amount` +- [x] `check_and_convert_to_balance` renamed to `check_balance_amount` +- [x] `check_balance_amount` raised `BalanceTypeError` error instead of deprecated warning message. +- [x] private function `bittensor.utils.balance._check_currencies` raises `BalanceUnitMismatchError` error instead of deprecated warning message. This function is used inside the Balance class to check if units match during various mathematical and logical operations. \ No newline at end of file diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 03c786e385..1e909970d4 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -110,7 +110,7 @@ from bittensor.utils.balance import ( Balance, fixed_to_float, - check_and_convert_to_balance, + check_balance_amount, ) from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import ( @@ -3189,7 +3189,7 @@ async def get_transfer_fee( a crucial tool for managing financial operations within the Bittensor network. """ if value is not None: - value = check_and_convert_to_balance(value) + value = check_balance_amount(value) call_params: dict[str, Union[int, str, bool]] call_function, call_params = get_transfer_fn_params(value, dest, keep_alive) @@ -4376,7 +4376,7 @@ async def add_stake( 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) + amount = check_balance_amount(amount) return await add_stake_extrinsic( subtensor=self, wallet=wallet, @@ -4722,7 +4722,7 @@ async def move_stake( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount = check_and_convert_to_balance(amount) + amount = check_balance_amount(amount) return await move_stake_extrinsic( subtensor=self, wallet=wallet, @@ -5613,7 +5613,7 @@ async def swap_stake( - With allow_partial_stake=True: A partial amount will be swapped up to the point where the price ratio would increase by rate_tolerance. """ - amount = check_and_convert_to_balance(amount) + amount = check_balance_amount(amount) return await swap_stake_extrinsic( subtensor=self, wallet=wallet, @@ -5701,7 +5701,7 @@ async def transfer( ExtrinsicResponse: The result object of the extrinsic execution. """ if amount is not None: - amount = check_and_convert_to_balance(amount) + amount = check_balance_amount(amount) return await transfer_extrinsic( subtensor=self, wallet=wallet, @@ -5748,7 +5748,7 @@ async def transfer_stake( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount = check_and_convert_to_balance(amount) + amount = check_balance_amount(amount) return await transfer_stake_extrinsic( subtensor=self, wallet=wallet, @@ -5806,7 +5806,7 @@ async def unstake( This function supports flexible stake management, allowing neurons to adjust their network participation and potential reward accruals. """ - amount = check_and_convert_to_balance(amount) + amount = check_balance_amount(amount) return await unstake_extrinsic( subtensor=self, wallet=wallet, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index c03d99aef4..8caa7a9922 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -114,7 +114,7 @@ Balance, fixed_to_float, FixedPoint, - check_and_convert_to_balance, + check_balance_amount, ) from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import ( @@ -2308,7 +2308,7 @@ def get_transfer_fee( crucial tool for managing financial operations within the Bittensor network. """ if value is not None: - value = check_and_convert_to_balance(value) + value = check_balance_amount(value) call_params: dict[str, Union[int, str, bool]] call_function, call_params = get_transfer_fn_params(value, dest, keep_alive) @@ -3222,7 +3222,7 @@ def add_stake( 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) + amount = check_balance_amount(amount) return add_stake_extrinsic( subtensor=self, wallet=wallet, @@ -3565,7 +3565,7 @@ def move_stake( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount = check_and_convert_to_balance(amount) + amount = check_balance_amount(amount) return move_stake_extrinsic( subtensor=self, wallet=wallet, @@ -4437,7 +4437,7 @@ def swap_stake( - With allow_partial_stake=True: A partial amount will be swapped up to the point where the price ratio would increase by rate_tolerance """ - amount = check_and_convert_to_balance(amount) + amount = check_balance_amount(amount) return swap_stake_extrinsic( subtensor=self, wallet=wallet, @@ -4525,7 +4525,7 @@ def transfer( ExtrinsicResponse: The result object of the extrinsic execution. """ if amount is not None: - amount = check_and_convert_to_balance(amount) + amount = check_balance_amount(amount) return transfer_extrinsic( subtensor=self, wallet=wallet, @@ -4572,7 +4572,7 @@ def transfer_stake( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount = check_and_convert_to_balance(amount) + amount = check_balance_amount(amount) return transfer_stake_extrinsic( subtensor=self, wallet=wallet, @@ -4631,7 +4631,7 @@ def unstake( 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) + amount = check_balance_amount(amount) return unstake_extrinsic( subtensor=self, wallet=wallet, diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 2210dce1f2..e68f3a0e1a 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -1,9 +1,9 @@ -from typing import Union, TypedDict, Optional +from typing import Any, TypedDict, Union from scalecodec import ScaleType from bittensor.core import settings -from bittensor.utils import deprecated_message +from bittensor.core.errors import BalanceTypeError, BalanceUnitMismatchError def _check_currencies(self, other): @@ -24,7 +24,7 @@ def _check_currencies(self, other): - `set_unit(14)` sets the unit to correspond to subnet 14 (i.e., Alpha from netuid 14). """ if self.netuid != other.netuid: - raise TypeError( + raise BalanceUnitMismatchError( f"Cannot perform any operations between balances of different currencies: {self} and {other}. " "Both Balance objects must reference the same netuid (Alpha currency). " "For example, to create a Balance instance for subnet 12 you can use: " @@ -35,8 +35,9 @@ def _check_currencies(self, other): class Balance: """ Represents the bittensor balance of the wallet, stored as rao (int). - This class provides a way to interact with balances in two different units: rao and tao. - It provides methods to convert between these units, as well as to perform arithmetic and comparison operations. + + This class provides a way to interact with balances in two different units: rao and tao. It provides methods to + convert between these units, as well as to perform arithmetic and comparison operations. Attributes: unit (str): A string representing the symbol for the tao unit. @@ -60,8 +61,19 @@ class Balance: balance_wallet_secret = Balance.from_tao(2).set_unit(netuid=14) total_balance = balance_wallet_default + balance_wallet_secret - The `from_tao()` and `from_rao()` methods accept the `netuid` parameter - to set the appropriate unit symbol. + The `from_tao()` and `from_rao()` methods accept the `netuid` parameter to set the appropriate unit symbol. + + Note: + When performing arithmetic or comparison operations where the first operand is a `Balance` instance and the + second operand is not, the second operand is implicitly interpreted as a raw amount in `rao`, using the same + unit (netuid) as the first operand. This allows interoperability with integer or float values, but may result in + unexpected behavior if the caller assumes the second operand is in `tao`. + + Example: + balance = Balance.from_tao(10, netuid=14) + result = balance + 5000 # 5 will be treated as 5000 rao, not 5 tao + print(result) + output: τ10.000005000 """ unit: str = settings.TAO_SYMBOL # This is the tao unit @@ -839,17 +851,13 @@ def rao(amount: int, netuid: int = 0) -> Balance: return Balance.from_rao(amount).set_unit(netuid) -def check_and_convert_to_balance( - amount: Union[float, int, Optional[Balance]], -) -> Balance: - """ - Helper function to check and convert the amount type to a Balance object. - This is used to support backwards compatibility while also providing a deprecation notice. - """ - if isinstance(amount, (float, int)): - deprecated_message( - "Detected a non-balance amount. Converting to Balance from Tao for backwards compatibility." - "Please update your code to use tao(amount) or Balance.from_tao(amount) for the main release 10.0.0." +def check_balance_amount(amount: Any) -> Balance: + """""" + if not isinstance(amount, Balance): + raise BalanceTypeError( + "Invalid type detected: expected a Balance instance. " + "Passing non-Balance types may lead to incorrect calculations. " + "Please update your code to explicitly construct Balance instances " + "(e.g., Balance.from_tao(value)) before using this function." ) - amount = tao(amount) return amount diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index 9eea34055a..8d7689bf81 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -62,6 +62,8 @@ from bittensor.core.config import Config from bittensor.core.dendrite import Dendrite from bittensor.core.errors import ( + BalanceTypeError, + BalanceUnitMismatchError, BlacklistedException, ChainConnectionError, ChainError, @@ -169,6 +171,8 @@ "WeightCommitInfo", "Config", "Dendrite", + "BalanceTypeError", + "BalanceUnitMismatchError", "BlacklistedException", "ChainConnectionError", "ChainError", From 1750422b022bd27a578bd576f6054feec702914f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 30 Sep 2025 11:27:50 -0700 Subject: [PATCH 298/416] improve tests --- tests/unit_tests/test_async_subtensor.py | 14 +++++------ tests/unit_tests/test_subtensor.py | 20 +++++++-------- tests/unit_tests/utils/test_balance.py | 32 ++++++++++++++++++++++-- 3 files changed, 46 insertions(+), 20 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 09b233247c..1a6ee3e81d 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -1,6 +1,6 @@ import datetime import unittest.mock as mock - +from bittensor.core.errors import BalanceTypeError import pytest from async_substrate_interface.types import ScaleObj from bittensor_wallet import Wallet @@ -710,13 +710,11 @@ async def test_get_transfer_with_exception(subtensor, mocker): subtensor.substrate.compose_call = mocked_compose_call subtensor.substrate.get_payment_info.side_effect = Exception - # Call - result = await subtensor.get_transfer_fee( - wallet=mocker.Mock(), dest=mocker.Mock(), value=fake_value - ) - - # Assertions - assert result == async_subtensor.Balance.from_rao(int(2e7)) + # Call + Assertions + with pytest.raises(BalanceTypeError): + await subtensor.get_transfer_fee( + wallet=mocker.Mock(), dest=mocker.Mock(), value=fake_value + ) @pytest.mark.asyncio diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index b149473eaa..f34dabe433 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1252,7 +1252,7 @@ def test_transfer(subtensor, fake_wallet, mocker): """Tests successful transfer call.""" # Prep fake_dest = "SS58PUBLICKEY" - fake_amount = 1.1 + fake_amount = Balance.from_tao(1.1) fake_wait_for_inclusion = True fake_wait_for_finalization = True mocked_transfer_extrinsic = mocker.patch.object( @@ -1273,7 +1273,7 @@ def test_transfer(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, destination=fake_dest, - amount=Balance(fake_amount), + amount=fake_amount, transfer_all=False, wait_for_inclusion=fake_wait_for_inclusion, wait_for_finalization=fake_wait_for_finalization, @@ -2727,7 +2727,7 @@ def test_unstake_success(mocker, subtensor, fake_wallet): # Preps fake_hotkey_ss58 = "hotkey_1" fake_netuid = 1 - fake_amount = 10.0 + fake_amount = Balance.from_tao(10.0) mock_unstake_extrinsic = mocker.patch.object(subtensor_module, "unstake_extrinsic") @@ -2750,7 +2750,7 @@ def test_unstake_success(mocker, subtensor, fake_wallet): wallet=fake_wallet, netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, - amount=Balance.from_rao(fake_amount), + amount=fake_amount, safe_unstaking=False, allow_partial_stake=False, rate_tolerance=0.005, @@ -2765,7 +2765,7 @@ def test_unstake_success(mocker, subtensor, fake_wallet): 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_amount = Balance.from_tao(10.0) fake_netuid = 14 fake_rate_tolerance = 0.01 # 1% threshold @@ -2790,7 +2790,7 @@ def test_unstake_with_safe_unstaking(mocker, subtensor, fake_wallet): wallet=fake_wallet, netuid=fake_netuid, hotkey_ss58=fake_hotkey_ss58, - amount=Balance.from_rao(fake_amount), + amount=fake_amount, safe_unstaking=True, allow_partial_stake=True, rate_tolerance=fake_rate_tolerance, @@ -2808,7 +2808,7 @@ def test_swap_stake_success(mocker, subtensor, fake_wallet): fake_hotkey_ss58 = "hotkey_1" fake_origin_netuid = 1 fake_destination_netuid = 2 - fake_amount = 10.0 + fake_amount = Balance.from_tao(10.0) mock_swap_stake_extrinsic = mocker.patch.object( subtensor_module, "swap_stake_extrinsic" @@ -2835,7 +2835,7 @@ def test_swap_stake_success(mocker, subtensor, fake_wallet): hotkey_ss58=fake_hotkey_ss58, origin_netuid=fake_origin_netuid, destination_netuid=fake_destination_netuid, - amount=Balance.from_rao(fake_amount), + amount=fake_amount, wait_for_inclusion=True, wait_for_finalization=False, safe_swapping=False, @@ -2853,7 +2853,7 @@ def test_swap_stake_with_safe_staking(mocker, subtensor, fake_wallet): fake_hotkey_ss58 = "hotkey_1" fake_origin_netuid = 1 fake_destination_netuid = 2 - fake_amount = 10.0 + fake_amount = Balance.from_tao(10.0) fake_rate_tolerance = 0.01 # 1% threshold mock_swap_stake_extrinsic = mocker.patch.object( @@ -2881,7 +2881,7 @@ def test_swap_stake_with_safe_staking(mocker, subtensor, fake_wallet): hotkey_ss58=fake_hotkey_ss58, origin_netuid=fake_origin_netuid, destination_netuid=fake_destination_netuid, - amount=Balance.from_rao(fake_amount), + amount=fake_amount, wait_for_inclusion=True, wait_for_finalization=False, safe_swapping=True, diff --git a/tests/unit_tests/utils/test_balance.py b/tests/unit_tests/utils/test_balance.py index 4ff97bdb81..fab62002c3 100644 --- a/tests/unit_tests/utils/test_balance.py +++ b/tests/unit_tests/utils/test_balance.py @@ -5,8 +5,8 @@ import pytest from hypothesis import given from hypothesis import strategies as st - -from bittensor.utils.balance import Balance +from bittensor.core.errors import BalanceTypeError, BalanceUnitMismatchError +from bittensor.utils.balance import Balance, check_balance_amount from tests.helpers import CloseInValue @@ -518,3 +518,31 @@ def test_from_float(): def test_from_rao(): """Tests from_rao method call.""" assert Balance.from_tao(1) == Balance(1000000000) + + +@pytest.mark.parametrize( + "first, second", + [ + (Balance.from_tao(1), Balance.from_tao(1).set_unit(2)), + (Balance.from_tao(1).set_unit(2), Balance.from_tao(1)), + ], +) +def test_balance_raise_errors(first, second): + """Tests any cases with balance raise errors.""" + with pytest.raises(BalanceUnitMismatchError): + _ = first + second + + +@pytest.mark.parametrize( + "amount", + [ + 100, + 100.1, + "100", + "10.2" + ], +) +def test_check_balance_amount_raise_error(amount): + """Tests Balance.check_rao_value method.""" + with pytest.raises(BalanceTypeError): + check_balance_amount(amount) From 454920c23aee2c4dedd39f8804803b3d880e360b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 30 Sep 2025 12:18:36 -0700 Subject: [PATCH 299/416] add `check_balance_amount` across subtensor's and extrinsics codebase + fixed unit tests --- MIGRATION.md | 5 ++-- bittensor/core/async_subtensor.py | 26 +++++++++--------- bittensor/core/extrinsics/asyncex/staking.py | 6 +++-- .../core/extrinsics/asyncex/unstaking.py | 3 ++- bittensor/core/extrinsics/staking.py | 3 ++- bittensor/core/extrinsics/unstaking.py | 3 ++- bittensor/core/subtensor.py | 26 +++++++++--------- bittensor/utils/balance.py | 27 +++++++++++++++---- tests/unit_tests/test_async_subtensor.py | 14 +++++----- tests/unit_tests/test_subtensor.py | 8 +++--- 10 files changed, 74 insertions(+), 47 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 1521ac5fe0..e7c3bc3f3f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -100,11 +100,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. -- [ ] 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. +- [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: - [x] All change categories (Extrinsics, Subtensor, Metagraph, etc.) - [x] Per-PR breakdown of what was added, removed, renamed, or refactored. - - [ ] Justifications and migration notes for users (if API behavior changed). + - [x] 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. @@ -241,6 +241,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - methods (async) `get_subnet_validator_permits` and `get_subnet_owner_hotkey` got `block_hash` and `reuse_block` parameters. - attribute `DelegateInfo/lite.total_daily_return` has been deleted (Vune confirmed that we shouldn't use it) - `Async/Subtensor` parameter `_mock` renamed to `mock`, also moved to last one in order. Community can use mocked `Async/Subtensor` in their tests in the same way as in we use it in the codebase. +- method `get_traansfer_fee` has renamed parameter `value` to `amount` Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 1e909970d4..3358700ce0 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2591,6 +2591,7 @@ async def get_stake_add_fee( Returns: The calculated stake fee as a Balance object """ + check_balance_amount(amount) return await self.get_stake_operations_fee( amount=amount, netuid=netuid, block=block ) @@ -2819,6 +2820,7 @@ async def get_unstake_fee( Returns: The calculated stake fee as a Balance object """ + check_balance_amount(amount) return await self.get_stake_operations_fee( amount=amount, netuid=netuid, block=block ) @@ -2841,6 +2843,7 @@ async def get_stake_movement_fee( Returns: The calculated stake fee as a Balance object """ + check_balance_amount(amount) return await self.get_stake_operations_fee( amount=amount, netuid=origin_netuid, block=block ) @@ -2978,6 +2981,7 @@ async def get_stake_operations_fee( Returns: The calculated stake fee as a Balance object. """ + check_balance_amount(amount) block_hash = await self.determine_block_hash( block=block, block_hash=block_hash, reuse_block=reuse_block ) @@ -3165,7 +3169,7 @@ async def get_total_subnets( # TODO: update related with fee calculation async def get_transfer_fee( - self, wallet: "Wallet", dest: str, value: Balance, keep_alive: bool = True + self, wallet: "Wallet", dest: str, amount: Balance, keep_alive: bool = True ) -> Balance: """ Calculates the transaction fee for transferring tokens from a wallet to a specified destination address. This @@ -3175,7 +3179,7 @@ async def get_transfer_fee( Parameters: wallet: The wallet from which the transfer is initiated. dest: The ``SS58`` address of the destination account. - value: The amount of tokens to be transferred, specified as a Balance object, or in Tao (float) or Rao + amount: The amount of tokens to be transferred, specified as a Balance object, or in Tao (float) or Rao (int) units. keep_alive: Whether the transfer fee should be calculated based on keeping the wallet alive (existential deposit) or not. @@ -3188,10 +3192,9 @@ async def get_transfer_fee( wallet has sufficient funds to cover both the transfer amount and the associated costs. This function provides a crucial tool for managing financial operations within the Bittensor network. """ - if value is not None: - value = check_balance_amount(value) + check_balance_amount(amount) call_params: dict[str, Union[int, str, bool]] - call_function, call_params = get_transfer_fn_params(value, dest, keep_alive) + call_function, call_params = get_transfer_fn_params(amount, dest, keep_alive) call = await self.substrate.compose_call( call_module="Balances", @@ -4376,7 +4379,7 @@ async def add_stake( 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_balance_amount(amount) + check_balance_amount(amount) return await add_stake_extrinsic( subtensor=self, wallet=wallet, @@ -4722,7 +4725,7 @@ async def move_stake( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount = check_balance_amount(amount) + check_balance_amount(amount) return await move_stake_extrinsic( subtensor=self, wallet=wallet, @@ -5613,7 +5616,7 @@ async def swap_stake( - With allow_partial_stake=True: A partial amount will be swapped up to the point where the price ratio would increase by rate_tolerance. """ - amount = check_balance_amount(amount) + check_balance_amount(amount) return await swap_stake_extrinsic( subtensor=self, wallet=wallet, @@ -5700,8 +5703,7 @@ async def transfer( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if amount is not None: - amount = check_balance_amount(amount) + check_balance_amount(amount) return await transfer_extrinsic( subtensor=self, wallet=wallet, @@ -5748,7 +5750,7 @@ async def transfer_stake( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount = check_balance_amount(amount) + check_balance_amount(amount) return await transfer_stake_extrinsic( subtensor=self, wallet=wallet, @@ -5806,7 +5808,7 @@ async def unstake( This function supports flexible stake management, allowing neurons to adjust their network participation and potential reward accruals. """ - amount = check_balance_amount(amount) + check_balance_amount(amount) return await unstake_extrinsic( subtensor=self, wallet=wallet, diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 34b3229bb4..7a05e5f504 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -2,9 +2,11 @@ from typing import Optional, Sequence, TYPE_CHECKING from async_substrate_interface.errors import SubstrateRequestException + +from bittensor.core.errors import BalanceTypeError from bittensor.core.extrinsics.utils import get_old_stakes -from bittensor.utils import format_error_message from bittensor.core.types import ExtrinsicResponse, UIDs +from bittensor.utils import format_error_message from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -257,7 +259,7 @@ async def add_stake_multiple_extrinsic( raise TypeError("`hotkey_ss58s` must be a list of str.") if not all(isinstance(a, Balance) for a in amounts): - raise TypeError("Each `amount` must be an instance of Balance.") + raise BalanceTypeError("Each `amount` must be an instance of Balance.") new_amounts: Sequence[Optional[Balance]] = [ amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index d3acf3f9bc..d666716b56 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -3,6 +3,7 @@ from async_substrate_interface.errors import SubstrateRequestException +from bittensor.core.errors import BalanceTypeError from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.core.types import ExtrinsicResponse from bittensor.core.types import UIDs @@ -305,7 +306,7 @@ async def unstake_multiple_extrinsic( if amounts is not None and not all( isinstance(amount, Balance) for amount in amounts ): - raise TypeError("`amounts` must be a list of Balance or None.") + raise BalanceTypeError("`amounts` must be a list of Balance or None.") if amounts is None: amounts = [None] * len(hotkey_ss58s) diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index dd982bcf67..0c9504cb91 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -2,6 +2,7 @@ from async_substrate_interface.errors import SubstrateRequestException +from bittensor.core.errors import BalanceTypeError from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils import format_error_message @@ -251,7 +252,7 @@ def add_stake_multiple_extrinsic( raise TypeError("`hotkey_ss58s` must be a list of str.") if not all(isinstance(a, Balance) for a in amounts): - raise TypeError("Each `amount` must be an instance of Balance.") + raise BalanceTypeError("Each `amount` must be an instance of Balance.") new_amounts: Sequence[Optional[Balance]] = [ amount.set_unit(netuid) for amount, netuid in zip(amounts, netuids) diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 45bda590d0..11c19f1423 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -2,6 +2,7 @@ from async_substrate_interface.errors import SubstrateRequestException +from bittensor.core.errors import BalanceTypeError from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils import format_error_message @@ -300,7 +301,7 @@ def unstake_multiple_extrinsic( if amounts is not None and not all( isinstance(amount, Balance) for amount in amounts ): - raise TypeError("`amounts` must be a list of Balance or None.") + raise BalanceTypeError("`amounts` must be a list of Balance or None.") if amounts is None: amounts = [None] * len(hotkey_ss58s) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 8caa7a9922..0595044182 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1826,6 +1826,7 @@ def get_stake_add_fee( Returns: The calculated stake fee as a Balance object """ + check_balance_amount(amount) return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) def get_mechanism_emission_split( @@ -2016,6 +2017,7 @@ def get_unstake_fee( Returns: The calculated stake fee as a Balance object """ + check_balance_amount(amount) return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) # TODO: update related with fee calculation @@ -2036,6 +2038,7 @@ def get_stake_movement_fee( Returns: The calculated stake fee as a Balance object """ + check_balance_amount(amount) return self.get_stake_operations_fee( amount=amount, netuid=origin_netuid, block=block ) @@ -2139,6 +2142,7 @@ def get_stake_operations_fee( Returns: The calculated stake fee as a Balance object. """ + check_balance_amount(amount) block_hash = self.determine_block_hash(block=block) result = self.substrate.query( module="Swap", @@ -2285,7 +2289,7 @@ def get_transfer_fee( self, wallet: "Wallet", dest: str, - value: Optional[Balance], + amount: Optional[Balance], keep_alive: bool = True, ) -> Balance: """ @@ -2296,7 +2300,7 @@ def get_transfer_fee( Parameters: wallet: The wallet from which the transfer is initiated. dest: The ``SS58`` address of the destination account. - value: The amount of tokens to be transferred, specified as a Balance object, or in Tao or Rao units. + amount: The amount of tokens to be transferred, specified as a Balance object, or in Tao or Rao units. keep_alive: Whether the transfer fee should be calculated based on keeping the wallet alive (existential deposit) or not. @@ -2307,10 +2311,9 @@ def get_transfer_fee( has sufficient funds to cover both the transfer amount and the associated costs. This function provides a crucial tool for managing financial operations within the Bittensor network. """ - if value is not None: - value = check_balance_amount(value) + check_balance_amount(amount) call_params: dict[str, Union[int, str, bool]] - call_function, call_params = get_transfer_fn_params(value, dest, keep_alive) + call_function, call_params = get_transfer_fn_params(amount, dest, keep_alive) call = self.substrate.compose_call( call_module="Balances", @@ -3222,7 +3225,7 @@ def add_stake( 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_balance_amount(amount) + check_balance_amount(amount) return add_stake_extrinsic( subtensor=self, wallet=wallet, @@ -3565,7 +3568,7 @@ def move_stake( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount = check_balance_amount(amount) + check_balance_amount(amount) return move_stake_extrinsic( subtensor=self, wallet=wallet, @@ -4437,7 +4440,7 @@ def swap_stake( - With allow_partial_stake=True: A partial amount will be swapped up to the point where the price ratio would increase by rate_tolerance """ - amount = check_balance_amount(amount) + check_balance_amount(amount) return swap_stake_extrinsic( subtensor=self, wallet=wallet, @@ -4524,8 +4527,7 @@ def transfer( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - if amount is not None: - amount = check_balance_amount(amount) + check_balance_amount(amount) return transfer_extrinsic( subtensor=self, wallet=wallet, @@ -4572,7 +4574,7 @@ def transfer_stake( Returns: ExtrinsicResponse: The result object of the extrinsic execution. """ - amount = check_balance_amount(amount) + check_balance_amount(amount) return transfer_stake_extrinsic( subtensor=self, wallet=wallet, @@ -4631,7 +4633,7 @@ def unstake( 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_balance_amount(amount) + check_balance_amount(amount) return unstake_extrinsic( subtensor=self, wallet=wallet, diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index e68f3a0e1a..996dc35262 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -1,4 +1,4 @@ -from typing import Any, TypedDict, Union +from typing import Optional, TypedDict, Union from scalecodec import ScaleType @@ -851,13 +851,30 @@ def rao(amount: int, netuid: int = 0) -> Balance: return Balance.from_rao(amount).set_unit(netuid) -def check_balance_amount(amount: Any) -> Balance: - """""" +def check_balance_amount(amount: Optional[Balance]) -> Optional[None]: + """ + Validate that the provided value is a Balance instance. + + This function ensures that the `amount` argument is a `Balance` object. If a non-Balance type is passed, it raises + a `BalanceTypeError` to enforce consistent usage of Balance objects across arithmetic operations. + + Args: + amount: The value to validate. + + Returns: + None if amount if None of a Balance instance. + + Raises: + BalanceTypeError: If `amount` is not an instance of Balance. + """ + if amount is None: + return None + if not isinstance(amount, Balance): raise BalanceTypeError( - "Invalid type detected: expected a Balance instance. " + f"Invalid type detected: amount type is {type(amount)}, but expected a Balance instance. " "Passing non-Balance types may lead to incorrect calculations. " "Please update your code to explicitly construct Balance instances " "(e.g., Balance.from_tao(value)) before using this function." ) - return amount + return None diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 1a6ee3e81d..0d5a2bde18 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -662,14 +662,14 @@ async def test_get_balance(subtensor, mocker): assert result == mocked_balance.return_value -@pytest.mark.parametrize("balance", [100, 100.1]) +@pytest.mark.parametrize("balance", [Balance.from_tao(100), Balance.from_tao(100.1)]) @pytest.mark.asyncio async def test_get_transfer_fee(subtensor, fake_wallet, mocker, balance): """Tests get_transfer_fee method.""" # Preps fake_wallet.coldkeypub = "coldkeypub" fake_dest = "fake_dest" - fake_value = Balance(balance) + fake_value = balance mocked_compose_call = mocker.AsyncMock() subtensor.substrate.compose_call = mocked_compose_call @@ -679,7 +679,7 @@ async def test_get_transfer_fee(subtensor, fake_wallet, mocker, balance): # Call result = await subtensor.get_transfer_fee( - wallet=fake_wallet, dest=fake_dest, value=fake_value + wallet=fake_wallet, dest=fake_dest, amount=fake_value ) # Assertions @@ -713,7 +713,7 @@ async def test_get_transfer_with_exception(subtensor, mocker): # Call + Assertions with pytest.raises(BalanceTypeError): await subtensor.get_transfer_fee( - wallet=mocker.Mock(), dest=mocker.Mock(), value=fake_value + wallet=mocker.Mock(), dest=mocker.Mock(), amount=fake_value ) @@ -3807,7 +3807,7 @@ async def test_get_stake_add_fee(subtensor, mocker): """Verify that `get_stake_add_fee` calls proper methods and returns the correct value.""" # Preps netuid = mocker.Mock() - amount = mocker.Mock() + amount = mocker.Mock(spec=Balance) mocked_get_stake_operations_fee = mocker.patch.object( subtensor, "get_stake_operations_fee" ) @@ -3830,7 +3830,7 @@ async def test_get_unstake_fee(subtensor, mocker): """Verify that `get_unstake_fee` calls proper methods and returns the correct value.""" # Preps netuid = mocker.Mock() - amount = mocker.Mock() + amount = mocker.Mock(spec=Balance) mocked_get_stake_operations_fee = mocker.patch.object( subtensor, "get_stake_operations_fee" ) @@ -3853,7 +3853,7 @@ async def test_get_stake_movement_fee(subtensor, mocker): """Verify that `get_stake_movement_fee` calls proper methods and returns the correct value.""" # Preps netuid = mocker.Mock() - amount = mocker.Mock() + amount = mocker.Mock(spec=Balance) mocked_get_stake_operations_fee = mocker.patch.object( subtensor, "get_stake_operations_fee" ) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index f34dabe433..b31235ae29 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1729,7 +1729,7 @@ def test_get_transfer_fee(subtensor, fake_wallet, mocker): subtensor.substrate.get_payment_info.return_value = fake_payment_info # Call - result = subtensor.get_transfer_fee(wallet=fake_wallet, dest=fake_dest, value=value) + result = subtensor.get_transfer_fee(wallet=fake_wallet, dest=fake_dest, amount=value) # Asserts subtensor.substrate.compose_call.assert_called_once_with( @@ -3993,7 +3993,7 @@ def test_get_stake_add_fee(subtensor, mocker): """Verify that `get_stake_add_fee` calls proper methods and returns the correct value.""" # Preps netuid = mocker.Mock() - amount = mocker.Mock() + amount = mocker.Mock(spec=Balance) mocked_get_stake_operations_fee = mocker.patch.object( subtensor, "get_stake_operations_fee" ) @@ -4015,7 +4015,7 @@ def test_get_unstake_fee(subtensor, mocker): """Verify that `get_unstake_fee` calls proper methods and returns the correct value.""" # Preps netuid = mocker.Mock() - amount = mocker.Mock() + amount = mocker.Mock(spec=Balance) mocked_get_stake_operations_fee = mocker.patch.object( subtensor, "get_stake_operations_fee" ) @@ -4037,7 +4037,7 @@ def test_get_stake_movement_fee(subtensor, mocker): """Verify that `get_stake_movement_fee` calls proper methods and returns the correct value.""" # Preps netuid = mocker.Mock() - amount = mocker.Mock() + amount = mocker.Mock(spec=Balance) mocked_get_stake_operations_fee = mocker.patch.object( subtensor, "get_stake_operations_fee" ) From 6c772c41058fd70c0596e2552c4c9630d3175849 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 30 Sep 2025 12:40:55 -0700 Subject: [PATCH 300/416] fix `get_transfer_fee` --- bittensor/core/extrinsics/asyncex/transfer.py | 2 +- bittensor/core/extrinsics/transfer.py | 2 +- tests/e2e_tests/test_transfer.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index f8dc6f8b49..3d4b718dfc 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -75,7 +75,7 @@ async def transfer_extrinsic( ) fee = await subtensor.get_transfer_fee( - wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive + wallet=wallet, dest=destination, amount=amount, keep_alive=keep_alive ) if not keep_alive: diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index 888f9b1ade..56bd44f0e8 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -74,7 +74,7 @@ def transfer_extrinsic( existential_deposit = subtensor.get_existential_deposit(block=block) fee = subtensor.get_transfer_fee( - wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive + wallet=wallet, dest=destination, amount=amount, keep_alive=keep_alive ) # Check if we have enough balance. diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py index d372f4d33d..dc8e69deb3 100644 --- a/tests/e2e_tests/test_transfer.py +++ b/tests/e2e_tests/test_transfer.py @@ -27,7 +27,7 @@ def test_transfer(subtensor, alice_wallet): transfer_fee = subtensor.wallets.get_transfer_fee( wallet=alice_wallet, dest=dest_coldkey, - value=transfer_value, + amount=transfer_value, ) # Account details before transfer @@ -68,7 +68,7 @@ async def test_transfer_async(async_subtensor, alice_wallet): transfer_fee = await async_subtensor.wallets.get_transfer_fee( wallet=alice_wallet, dest=dest_coldkey, - value=transfer_value, + amount=transfer_value, ) # Account details before transfer From a4ec8a4a6f8d18355f305d2f53f39f421b39af8a Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Tue, 30 Sep 2025 14:50:26 -0700 Subject: [PATCH 301/416] set debug for the login when block is older 300 in metagraph --- MIGRATION.md | 15 +++++++++------ bittensor/core/metagraph.py | 4 ++-- tests/unit_tests/test_metagraph.py | 4 +++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index e7c3bc3f3f..dec5e9d0c4 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -32,8 +32,8 @@ 6. ~~Add `hotkey_ss58` parameter to `get_liquidity_list` method. One wallet can have many HKs. Currently, the mentioned method uses default HK only.~~ wrong idea ## 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. +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`. @@ -95,7 +95,6 @@ rename this variable in documentation. ## 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.~~ @@ -111,7 +110,6 @@ It must include: # Migration guide - - [x] `._do_commit_reveal_v3` logic is included in the main code `.commit_timelocked_weights_extrinsic` - [x] `commit_reveal_version` parameter with default value `4` added to `commit_timelocked_weights_extrinsic` - [x] `._do_commit_weights` logic is included in the main code `.commit_weights_extrinsic` @@ -126,8 +124,8 @@ It must include: - [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. -# Standardize parameter order is applied for (extrinsics and related calls): +# Standardize parameter order is applied for (extrinsics and related calls): These parameters will now exist in all extrinsics and related calls (default values could be different depends by extrinsic): ```py @@ -216,6 +214,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - `decrease_take_extrinsic` and `increase_take_extrinsic` have been merged into a single set_take_extrinsic. The API now has a new `action: Literal["increase_take", "decrease_take"]` parameter (DRY rule). + ### Extrinsics has extra data in response's `data` field: - `add_stake_extrinsic` - `add_stake_multiple_extrinsic` @@ -225,6 +224,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - `unstake_extrinsic` - `unstake_multiple_extrinsic` + ### Subtensor changes - method `all_subnets` has renamed parameter from `block_number` to `block` (consistency in the codebase). - The `hotkey` parameter, which meant ss58 key address, was renamed to `hotkey_ss58` in all methods and related extrinsics (consistency in the codebase). @@ -248,6 +248,7 @@ Added sub-package `bittensor.core.addons` to host optional extensions and experi - `bittensor.core.timelock` moved to `bittensor.core.addons.timelock` - local env variable `BT_CHAIN_ENDPOINT` replaced with `BT_SUBTENSOR_CHAIN_ENDPOINT`. + ### Mechid related changes: In the next subtensor methods got updated the parameters order: - `bonds` @@ -262,6 +263,7 @@ In the next subtensor methods got updated the parameters order: Additional: - `bittensor.core.chain_data.metagraph_info.MetagraphInfo` got required attribute `mechid: int`. + ### Renames parameters: - `get_metagraph_info`: `field_indices` -> `selected_indices` (to be consistent) @@ -293,6 +295,7 @@ Currently it contains: - `bittensor.addons.subtensor_api` - `bittensor.addons.timelock` + ### Balance (bittensor/utils/balance.py) and related changes - [x] Added 2 custom errors: - `bittensor.core.errors.BalanceUnitMismatchError` @@ -300,4 +303,4 @@ Currently it contains: - [x] `check_balance` renamed to `check_balance_amount` - [x] `check_and_convert_to_balance` renamed to `check_balance_amount` - [x] `check_balance_amount` raised `BalanceTypeError` error instead of deprecated warning message. -- [x] private function `bittensor.utils.balance._check_currencies` raises `BalanceUnitMismatchError` error instead of deprecated warning message. This function is used inside the Balance class to check if units match during various mathematical and logical operations. \ No newline at end of file +- [x] private function `bittensor.utils.balance._check_currencies` raises `BalanceUnitMismatchError` error instead of deprecated warning message. This function is used inside the Balance class to check if units match during various mathematical and logical operations. diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 494ef24909..15f8080107 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -1412,7 +1412,7 @@ async def sync( ): cur_block = await subtensor.get_current_block() if block and block < (cur_block - 300): - logging.warning( + logging.debug( "Attempting to sync longer than 300 blocks ago on a non-archive node. Please use the 'archive' " "network for subtensor and retry." ) @@ -1730,7 +1730,7 @@ def sync( ): cur_block = subtensor.get_current_block() if block and block < (cur_block - 300): - logging.warning( + logging.debug( "Attempting to sync longer than 300 blocks ago on a non-archive node. Please use the 'archive' " "network for subtensor and retry." ) diff --git a/tests/unit_tests/test_metagraph.py b/tests/unit_tests/test_metagraph.py index cdb735f627..acf62e22d0 100644 --- a/tests/unit_tests/test_metagraph.py +++ b/tests/unit_tests/test_metagraph.py @@ -1,5 +1,6 @@ import asyncio import copy +from bittensor.utils.btlogging import logging from bittensor.utils.balance import Balance from unittest.mock import Mock @@ -159,10 +160,11 @@ def __contains__(self, item): ], ) def test_sync_warning_cases(block, test_id, metagraph_instance, mock_subtensor, caplog): + """Makes sure that the warning message is logged when the block is greater than 300 with debug level.""" + logging.set_debug() mock_subtensor.get_current_block.return_value = 601 mock_subtensor.get_metagraph_info.return_value = [] metagraph_instance.sync(block=block, lite=True, subtensor=mock_subtensor) - expected_message = "Attempting to sync longer than 300 blocks ago on a non-archive node. Please use the 'archive' network for subtensor and retry." assert expected_message in caplog.text, ( f"Test ID: {test_id} - Expected warning message not found in Loguru sink." From dcbe2b63ec4fb5506a8ab6dc847aef13455eb6b4 Mon Sep 17 00:00:00 2001 From: Roman <167799377+basfroman@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:48:33 -0700 Subject: [PATCH 302/416] Update bittensor/utils/balance.py Co-authored-by: BD Himes <37844818+thewhaleking@users.noreply.github.com> --- bittensor/utils/balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 996dc35262..6b006ab9d5 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -851,7 +851,7 @@ def rao(amount: int, netuid: int = 0) -> Balance: return Balance.from_rao(amount).set_unit(netuid) -def check_balance_amount(amount: Optional[Balance]) -> Optional[None]: +def check_balance_amount(amount: Optional[Balance]) -> None: """ Validate that the provided value is a Balance instance. From 923d3497afe2c9d8d29a23608a2bfa7a2edb8bd5 Mon Sep 17 00:00:00 2001 From: Roman <167799377+basfroman@users.noreply.github.com> Date: Thu, 2 Oct 2025 10:48:49 -0700 Subject: [PATCH 303/416] Update bittensor/utils/balance.py Co-authored-by: BD Himes <37844818+thewhaleking@users.noreply.github.com> --- bittensor/utils/balance.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 6b006ab9d5..fca462f3e6 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -862,7 +862,7 @@ def check_balance_amount(amount: Optional[Balance]) -> None: amount: The value to validate. Returns: - None if amount if None of a Balance instance. + None Raises: BalanceTypeError: If `amount` is not an instance of Balance. From 42aa570c62fea5c4f7a4aebaa130eaa75a7a0f25 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 2 Oct 2025 10:50:41 -0700 Subject: [PATCH 304/416] review fixes --- bittensor/core/errors.py | 2 +- bittensor/utils/balance.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bittensor/core/errors.py b/bittensor/core/errors.py index b1d2c510eb..19e748103f 100644 --- a/bittensor/core/errors.py +++ b/bittensor/core/errors.py @@ -218,5 +218,5 @@ class BalanceUnitMismatchError(Exception): """Raised when operations is attempted between Balance objects with different units (netuid).""" -class BalanceTypeError(Exception): +class BalanceTypeError(TypeError): """Raised when an unsupported type is used instead of Balance amount.""" diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index fca462f3e6..7e1f9c0372 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -16,8 +16,8 @@ def _check_currencies(self, other): balance2 = Balance.from_tao(500).set_unit(14) balance1 + balance2 # No error. - balance3 = Balance.from_tao(200).set_unit(14) - balance1 + balance3 # Raises ValueError. + balance3 = Balance.from_tao(200).set_unit(5) + balance1 + balance3 # Raises BalanceUnitMismatchError. In this example: - `from_rao` creates a Balance instance from the amount in rao. @@ -862,7 +862,7 @@ def check_balance_amount(amount: Optional[Balance]) -> None: amount: The value to validate. Returns: - None + None if amount is Balance instance or None, otherwise raise error. Raises: BalanceTypeError: If `amount` is not an instance of Balance. From 2817687100499cf79bd335f5a02fc0d3a9415062 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 2 Oct 2025 11:02:46 -0700 Subject: [PATCH 305/416] `bittensor.addons` -> `bittensor.extras` --- MIGRATION.md | 6 +++--- bittensor/{addons => extras}/__init__.py | 4 ++-- bittensor/{addons => extras}/subtensor_api/__init__.py | 0 bittensor/{addons => extras}/subtensor_api/chain.py | 0 bittensor/{addons => extras}/subtensor_api/commitments.py | 0 bittensor/{addons => extras}/subtensor_api/delegates.py | 0 bittensor/{addons => extras}/subtensor_api/extrinsics.py | 0 bittensor/{addons => extras}/subtensor_api/metagraphs.py | 0 bittensor/{addons => extras}/subtensor_api/neurons.py | 0 bittensor/{addons => extras}/subtensor_api/queries.py | 0 bittensor/{addons => extras}/subtensor_api/staking.py | 0 bittensor/{addons => extras}/subtensor_api/subnets.py | 0 bittensor/{addons => extras}/subtensor_api/utils.py | 2 +- bittensor/{addons => extras}/subtensor_api/wallets.py | 0 bittensor/{addons => extras}/timelock.py | 0 bittensor/utils/easy_imports.py | 2 +- tests/e2e_tests/conftest.py | 2 +- tests/e2e_tests/utils/chain_interactions.py | 2 +- tests/e2e_tests/utils/e2e_test_utils.py | 2 +- tests/integration_tests/test_timelock.py | 2 +- tests/unit_tests/test_subtensor_api.py | 2 +- 21 files changed, 12 insertions(+), 12 deletions(-) rename bittensor/{addons => extras}/__init__.py (87%) rename bittensor/{addons => extras}/subtensor_api/__init__.py (100%) rename bittensor/{addons => extras}/subtensor_api/chain.py (100%) rename bittensor/{addons => extras}/subtensor_api/commitments.py (100%) rename bittensor/{addons => extras}/subtensor_api/delegates.py (100%) rename bittensor/{addons => extras}/subtensor_api/extrinsics.py (100%) rename bittensor/{addons => extras}/subtensor_api/metagraphs.py (100%) rename bittensor/{addons => extras}/subtensor_api/neurons.py (100%) rename bittensor/{addons => extras}/subtensor_api/queries.py (100%) rename bittensor/{addons => extras}/subtensor_api/staking.py (100%) rename bittensor/{addons => extras}/subtensor_api/subnets.py (100%) rename bittensor/{addons => extras}/subtensor_api/utils.py (99%) rename bittensor/{addons => extras}/subtensor_api/wallets.py (100%) rename bittensor/{addons => extras}/timelock.py (100%) diff --git a/MIGRATION.md b/MIGRATION.md index dec5e9d0c4..b2228798c5 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -290,10 +290,10 @@ Links to subpackages removed: - `bittensor.extrinsics` (available in `bittensor.core.extrinsics`) -New subpackage `bittensor.addons` created to host optional extensions and experimental logic enhancing the core functionality. +New subpackage `bittensor.extras` created to host optional extensions and experimental logic enhancing the core functionality. Currently it contains: -- `bittensor.addons.subtensor_api` -- `bittensor.addons.timelock` +- `bittensor.extras.subtensor_api` +- `bittensor.extras.timelock` ### Balance (bittensor/utils/balance.py) and related changes diff --git a/bittensor/addons/__init__.py b/bittensor/extras/__init__.py similarity index 87% rename from bittensor/addons/__init__.py rename to bittensor/extras/__init__.py index 0f7295f24a..27ccdbaeaf 100644 --- a/bittensor/addons/__init__.py +++ b/bittensor/extras/__init__.py @@ -9,8 +9,8 @@ discoverability and structure. """ -from bittensor.addons import timelock -from bittensor.addons.subtensor_api import SubtensorApi +from bittensor.extras import timelock +from bittensor.extras.subtensor_api import SubtensorApi __all__ = [ "timelock", diff --git a/bittensor/addons/subtensor_api/__init__.py b/bittensor/extras/subtensor_api/__init__.py similarity index 100% rename from bittensor/addons/subtensor_api/__init__.py rename to bittensor/extras/subtensor_api/__init__.py diff --git a/bittensor/addons/subtensor_api/chain.py b/bittensor/extras/subtensor_api/chain.py similarity index 100% rename from bittensor/addons/subtensor_api/chain.py rename to bittensor/extras/subtensor_api/chain.py diff --git a/bittensor/addons/subtensor_api/commitments.py b/bittensor/extras/subtensor_api/commitments.py similarity index 100% rename from bittensor/addons/subtensor_api/commitments.py rename to bittensor/extras/subtensor_api/commitments.py diff --git a/bittensor/addons/subtensor_api/delegates.py b/bittensor/extras/subtensor_api/delegates.py similarity index 100% rename from bittensor/addons/subtensor_api/delegates.py rename to bittensor/extras/subtensor_api/delegates.py diff --git a/bittensor/addons/subtensor_api/extrinsics.py b/bittensor/extras/subtensor_api/extrinsics.py similarity index 100% rename from bittensor/addons/subtensor_api/extrinsics.py rename to bittensor/extras/subtensor_api/extrinsics.py diff --git a/bittensor/addons/subtensor_api/metagraphs.py b/bittensor/extras/subtensor_api/metagraphs.py similarity index 100% rename from bittensor/addons/subtensor_api/metagraphs.py rename to bittensor/extras/subtensor_api/metagraphs.py diff --git a/bittensor/addons/subtensor_api/neurons.py b/bittensor/extras/subtensor_api/neurons.py similarity index 100% rename from bittensor/addons/subtensor_api/neurons.py rename to bittensor/extras/subtensor_api/neurons.py diff --git a/bittensor/addons/subtensor_api/queries.py b/bittensor/extras/subtensor_api/queries.py similarity index 100% rename from bittensor/addons/subtensor_api/queries.py rename to bittensor/extras/subtensor_api/queries.py diff --git a/bittensor/addons/subtensor_api/staking.py b/bittensor/extras/subtensor_api/staking.py similarity index 100% rename from bittensor/addons/subtensor_api/staking.py rename to bittensor/extras/subtensor_api/staking.py diff --git a/bittensor/addons/subtensor_api/subnets.py b/bittensor/extras/subtensor_api/subnets.py similarity index 100% rename from bittensor/addons/subtensor_api/subnets.py rename to bittensor/extras/subtensor_api/subnets.py diff --git a/bittensor/addons/subtensor_api/utils.py b/bittensor/extras/subtensor_api/utils.py similarity index 99% rename from bittensor/addons/subtensor_api/utils.py rename to bittensor/extras/subtensor_api/utils.py index 6f7a2b3412..1011ccd88c 100644 --- a/bittensor/addons/subtensor_api/utils.py +++ b/bittensor/extras/subtensor_api/utils.py @@ -1,7 +1,7 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: - from bittensor.addons import SubtensorApi + from bittensor.extras import SubtensorApi def add_legacy_methods(subtensor: "SubtensorApi"): diff --git a/bittensor/addons/subtensor_api/wallets.py b/bittensor/extras/subtensor_api/wallets.py similarity index 100% rename from bittensor/addons/subtensor_api/wallets.py rename to bittensor/extras/subtensor_api/wallets.py diff --git a/bittensor/addons/timelock.py b/bittensor/extras/timelock.py similarity index 100% rename from bittensor/addons/timelock.py rename to bittensor/extras/timelock.py diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index 8d7689bf81..f4dd89902d 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -30,7 +30,6 @@ from bittensor_wallet.keypair import Keypair from bittensor_wallet.wallet import Wallet -from bittensor.addons import timelock, SubtensorApi from bittensor.core import settings, extrinsics from bittensor.core.async_subtensor import AsyncSubtensor, get_async_subtensor from bittensor.core.axon import Axon @@ -108,6 +107,7 @@ from bittensor.core.synapse import TerminalInfo, Synapse from bittensor.core.tensor import Tensor from bittensor.core.threadpool import PriorityThreadPoolExecutor +from bittensor.extras import timelock, SubtensorApi from bittensor.utils import ( mock, ss58_to_vec_u8, diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index f59dda1d9d..8eea338091 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -12,7 +12,7 @@ import pytest import pytest_asyncio -from bittensor.addons import SubtensorApi +from bittensor.extras import SubtensorApi from bittensor.utils.btlogging import logging from tests.e2e_tests.utils.e2e_test_utils import ( Templates, diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py index ee827b2083..87ee70da9d 100644 --- a/tests/e2e_tests/utils/chain_interactions.py +++ b/tests/e2e_tests/utils/chain_interactions.py @@ -15,7 +15,7 @@ # for typing purposes if TYPE_CHECKING: from bittensor import Wallet - from bittensor.addons import SubtensorApi + from bittensor.extras import SubtensorApi from async_substrate_interface import ( AsyncSubstrateInterface, AsyncExtrinsicReceipt, diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index b561bbdd9f..3e97bf2ad5 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -6,7 +6,7 @@ from bittensor_wallet import Keypair, Wallet -from bittensor.addons import SubtensorApi +from bittensor.extras import SubtensorApi from bittensor.utils.btlogging import logging template_path = os.getcwd() + "/neurons/" diff --git a/tests/integration_tests/test_timelock.py b/tests/integration_tests/test_timelock.py index 2b7ddf9823..a1faf44cf6 100644 --- a/tests/integration_tests/test_timelock.py +++ b/tests/integration_tests/test_timelock.py @@ -3,7 +3,7 @@ import pytest -from bittensor.addons import timelock +from bittensor.extras import timelock def test_encrypt_returns_valid_tuple(): diff --git a/tests/unit_tests/test_subtensor_api.py b/tests/unit_tests/test_subtensor_api.py index 39cc918439..7eaac5fc3a 100644 --- a/tests/unit_tests/test_subtensor_api.py +++ b/tests/unit_tests/test_subtensor_api.py @@ -1,6 +1,6 @@ import pytest -from bittensor.addons.subtensor_api import SubtensorApi +from bittensor.extras import SubtensorApi from bittensor.core.subtensor import Subtensor From c9439b5150e597f11a23ea05fa74cb02f79c3ed6 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 2 Oct 2025 12:19:00 -0700 Subject: [PATCH 306/416] important wording --- bittensor/utils/balance.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 7e1f9c0372..f585dd0b11 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -862,10 +862,10 @@ def check_balance_amount(amount: Optional[Balance]) -> None: amount: The value to validate. Returns: - None if amount is Balance instance or None, otherwise raise error. + None: Always returns None if validation passes. Raises: - BalanceTypeError: If `amount` is not an instance of Balance. + BalanceTypeError: If amount is not a Balance instance and not None. """ if amount is None: return None From 3085d005d524acffe3de68a7c997792e7803a317 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 2 Oct 2025 13:25:32 -0700 Subject: [PATCH 307/416] update extrinsics --- bittensor/core/extrinsics/asyncex/staking.py | 32 ++++++++------------ bittensor/core/extrinsics/staking.py | 32 ++++++++------------ 2 files changed, 26 insertions(+), 38 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 7a05e5f504..0aacf21c64 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -421,7 +421,7 @@ async def set_auto_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism. Parameters: @@ -438,15 +438,13 @@ async def set_auto_stake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return False, unlock.message + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked call = await subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -456,7 +454,7 @@ async def set_auto_stake_extrinsic( "hotkey": hotkey_ss58, }, ) - success, message = await subtensor.sign_and_send_extrinsic( + response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, period=period, @@ -465,16 +463,12 @@ async def set_auto_stake_extrinsic( wait_for_finalization=wait_for_finalization, ) - if success: - logging.debug(message) - return True, message + if response.success: + logging.debug(response.message) + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: - if raise_error: - raise error - logging.error(str(error)) - - return False, str(error) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 0c9504cb91..79e26303ee 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -411,7 +411,7 @@ def set_auto_stake_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, -) -> tuple[bool, str]: +) -> ExtrinsicResponse: """Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism. Parameters: @@ -428,15 +428,13 @@ def set_auto_stake_extrinsic( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. """ try: - unlock = unlock_key(wallet, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) - return False, unlock.message + if not ( + unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + ).success: + return unlocked call = subtensor.substrate.compose_call( call_module="SubtensorModule", @@ -446,7 +444,7 @@ def set_auto_stake_extrinsic( "hotkey": hotkey_ss58, }, ) - success, message = subtensor.sign_and_send_extrinsic( + response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, period=period, @@ -455,16 +453,12 @@ def set_auto_stake_extrinsic( wait_for_finalization=wait_for_finalization, ) - if success: - logging.debug(message) - return True, message + if response.success: + logging.debug(response.message) + return response - logging.error(message) - return False, message + logging.error(response.message) + return response except Exception as error: - if raise_error: - raise error - logging.error(str(error)) - - return False, str(error) + return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) From 0ca5bb70f86fba714e45e149423dd050c96e877b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 2 Oct 2025 13:25:49 -0700 Subject: [PATCH 308/416] update related subtensor calls and docstrings --- bittensor/core/async_subtensor.py | 9 +++++---- bittensor/core/subtensor.py | 9 +++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 3358700ce0..dd382e5d8f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5034,7 +5034,7 @@ async def set_auto_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism. Parameters: @@ -5050,9 +5050,10 @@ async def set_auto_stake( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. + + Note: + Use the `get_auto_stakes` method to get the hotkey address of the validator where auto stake is set. """ return await set_auto_stake_extrinsic( subtensor=self, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 0595044182..3aa64c737f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -3877,7 +3877,7 @@ def set_auto_stake( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - ) -> tuple[bool, str]: + ) -> ExtrinsicResponse: """Sets the coldkey to automatically stake to the hotkey within specific subnet mechanism. Parameters: @@ -3893,9 +3893,10 @@ def set_auto_stake( wait_for_finalization: Whether to wait for the finalization of the transaction. Returns: - tuple[bool, str]: - `True` if the extrinsic executed successfully, `False` otherwise. - `message` is a string value describing the success or potential error. + ExtrinsicResponse: The result object of the extrinsic execution. + + Note: + Use the `get_auto_stakes` method to get the hotkey address of the validator where auto stake is set. """ return set_auto_stake_extrinsic( subtensor=self, From 4cc7fe714cded58da19a0ad5f43a5f0626330cca Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 2 Oct 2025 13:25:57 -0700 Subject: [PATCH 309/416] update tests --- tests/e2e_tests/test_staking.py | 48 +++++++++++++++++-- .../extrinsics/asyncex/test_staking.py | 9 ++-- tests/unit_tests/extrinsics/test_staking.py | 6 +-- 3 files changed, 48 insertions(+), 15 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index f40fcdda98..ee7db541e1 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -2088,9 +2088,7 @@ async def test_unstaking_with_limit_async( def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet): """Tests auto staking logic.""" - logging.console.info(f"Testing test_auto_staking.") - - alice_subnet_netuid = subtensor.get_total_subnets() + alice_subnet_netuid = subtensor.subnets.get_total_subnets() assert subtensor.subnets.register_subnet(alice_wallet) assert subtensor.subnets.subnet_exists(alice_subnet_netuid) @@ -2105,7 +2103,8 @@ def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet): wallet=alice_wallet, netuid=alice_subnet_netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, - ) + ).success + # check auto stake assert subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == { alice_subnet_netuid: bob_wallet.hotkey.ss58_address @@ -2125,4 +2124,43 @@ def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet): alice_subnet_netuid: bob_wallet.hotkey.ss58_address } - logging.console.success(f"Test `test_auto_staking` passed.") + +@pytest.mark.asyncio +async def test_auto_staking_async(async_subtensor, alice_wallet, bob_wallet, eve_wallet): + """Tests auto staking logic.""" + + alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() + assert await async_subtensor.subnets.register_subnet(alice_wallet) + assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid) + + assert await async_wait_to_start_call(async_subtensor, alice_wallet, alice_subnet_netuid) + + assert await async_subtensor.extrinsics.burned_register(bob_wallet, alice_subnet_netuid) + + assert await async_subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == {} + + # set auto stake + assert (await async_subtensor.staking.set_auto_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + )).success + + # check auto stake + assert await async_subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == { + alice_subnet_netuid: bob_wallet.hotkey.ss58_address + } + + # set auto stake to nonexistent hotkey + success, message = await async_subtensor.staking.set_auto_stake( + wallet=alice_wallet, + netuid=alice_subnet_netuid, + hotkey_ss58=eve_wallet.hotkey.ss58_address, + ) + assert success is False + assert "HotKeyNotRegisteredInSubNet" in message + + # check auto stake + assert await async_subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == { + alice_subnet_netuid: bob_wallet.hotkey.ss58_address + } diff --git a/tests/unit_tests/extrinsics/asyncex/test_staking.py b/tests/unit_tests/extrinsics/asyncex/test_staking.py index ebed5e6ac0..bd5b77e823 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_staking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_staking.py @@ -1,5 +1,7 @@ import pytest + from bittensor.core.extrinsics.asyncex import staking +from bittensor.core.types import ExtrinsicResponse @pytest.mark.parametrize( @@ -17,14 +19,12 @@ async def test_set_auto_stake_extrinsic( # Preps netuid = mocker.Mock() hotkey_ss58 = mocker.Mock() - mocked_unlock_key = mocker.patch.object( - staking, "unlock_key", return_value=mocker.Mock(success=True, message="True") - ) + 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=(res_success, res_message) + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(res_success, res_message) ) # Call @@ -36,7 +36,6 @@ async def test_set_auto_stake_extrinsic( ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet, raise_error=False) mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", call_function="set_coldkey_auto_stake_hotkey", diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 35d4677cf2..5c1897a188 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -116,14 +116,11 @@ def test_set_auto_stake_extrinsic( # Preps netuid = mocker.Mock() hotkey_ss58 = mocker.Mock() - mocked_unlock_key = mocker.patch.object( - staking, "unlock_key", return_value=mocker.Mock(success=True, message="True") - ) 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=(res_success, res_message) + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(res_success, res_message) ) # Call @@ -135,7 +132,6 @@ def test_set_auto_stake_extrinsic( ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet, raise_error=False) mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", call_function="set_coldkey_auto_stake_hotkey", From 61d0723541da3156cbe62325abd2146c2ae9a35d Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 19:59:01 -0700 Subject: [PATCH 310/416] fix tests after rebase + update structure --- tests/e2e_tests/conftest.py | 27 +- tests/e2e_tests/utils/chain_interactions.py | 561 ------------------ tests/e2e_tests/utils/e2e_test_utils.py | 80 +-- tests/helpers/helpers.py | 2 +- .../extrinsics/asyncex/test_staking.py | 5 +- .../extrinsics/asyncex/test_unstaking.py | 8 +- tests/unit_tests/extrinsics/test_staking.py | 8 +- tests/unit_tests/extrinsics/test_unstaking.py | 4 +- tests/unit_tests/test_subtensor.py | 8 +- tests/unit_tests/utils/test_balance.py | 7 +- tests/unit_tests/utils/test_version.py | 9 - 11 files changed, 61 insertions(+), 658 deletions(-) delete mode 100644 tests/e2e_tests/utils/chain_interactions.py diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index 8eea338091..5256de5aef 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -1,3 +1,5 @@ +import asyncio +import contextlib import os import re import shlex @@ -278,7 +280,8 @@ def templates(): @pytest.fixture def subtensor(local_chain): - return SubtensorApi(network="ws://localhost:9944", legacy_methods=False) + with SubtensorApi(network="ws://localhost:9944", legacy_methods=False) as sub: + yield sub @pytest_asyncio.fixture @@ -286,7 +289,7 @@ 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 + yield a_sub @pytest.fixture @@ -325,3 +328,23 @@ def log_test_start_and_end(request): logging.console.info(f"🏁[green]Testing[/green] [yellow]{test_name}[/yellow]") yield logging.console.success(f"✅ [green]Finished[/green] [yellow]{test_name}[/yellow]") + + +@pytest_asyncio.fixture(scope="session") +def event_loop(): + """Create an instance of the default event loop for each test case and close all alive tasks at the end.""" + loop = asyncio.get_event_loop() + yield loop + + # 1) cance all alive tasks + pending = [t for t in asyncio.all_tasks(loop) if not t.done()] + for t in pending: + t.cancel() + with contextlib.suppress(Exception): + loop.run_until_complete(asyncio.gather(*pending, return_exceptions=True)) + + # 2) cleanup async generators + with contextlib.suppress(Exception): + loop.run_until_complete(loop.shutdown_asyncgens()) + + loop.close() diff --git a/tests/e2e_tests/utils/chain_interactions.py b/tests/e2e_tests/utils/chain_interactions.py deleted file mode 100644 index 87ee70da9d..0000000000 --- a/tests/e2e_tests/utils/chain_interactions.py +++ /dev/null @@ -1,561 +0,0 @@ -""" -This module provides functions interacting with the chain for end-to-end testing; -these are not present in btsdk but are required for e2e tests -""" - -import asyncio -import functools -import time -from symtable import Class -from typing import Union, Optional, TYPE_CHECKING -from dataclasses import dataclass -from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging - -# for typing purposes -if TYPE_CHECKING: - from bittensor import Wallet - from bittensor.extras import SubtensorApi - from async_substrate_interface import ( - AsyncSubstrateInterface, - AsyncExtrinsicReceipt, - SubstrateInterface, - ExtrinsicReceipt, - ) - - -def get_dynamic_balance(rao: int, netuid: int = 0): - """Returns a Balance object with the given rao and netuid for testing purposes with dynamic values.""" - return Balance.from_rao(rao).set_unit(netuid) - - -def sudo_set_hyperparameter_bool( - substrate: "SubstrateInterface", - wallet: "Wallet", - call_function: str, - value: bool, - netuid: int, -) -> bool: - """Sets boolean hyperparameter value through AdminUtils. Mimics setting hyperparams.""" - call = substrate.compose_call( - call_module="AdminUtils", - call_function=call_function, - call_params={"netuid": netuid, "enabled": value}, - ) - extrinsic = substrate.create_signed_extrinsic(call=call, keypair=wallet.coldkey) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - 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", - 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 = substrate.compose_call( - call_module="AdminUtils", - call_function=call_function, - call_params=call_params, - ) - extrinsic = substrate.create_signed_extrinsic(call=call, keypair=wallet.coldkey) - response = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - if return_error_message: - return response.is_success, response.error_message - - 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. - - 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 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 - logging.info(f"tempo = {tempo}") - await wait_interval(tempo, subtensor, netuid, **kwargs) - - -async def async_wait_epoch(async_subtensor: "SubtensorApi", 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.queries.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: The current block number. - tempo: The tempo value for the subnet. - - Returns: - int: The next tempo block number. - """ - return ((current_block // tempo) + 1) * tempo + 1 - - -async def wait_interval( - tempo: int, - subtensor: "SubtensorApi", - netuid: int = 1, - reporting_interval: int = 1, - sleep: float = 0.25, - times: int = 1, -): - """ - Waits until the next tempo interval starts for a specific subnet. - - Calculates the next tempo block start based on the current block number - 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.chain.get_current_block() - next_tempo_block_start = current_block - - for _ in range(times): - next_tempo_block_start = next_tempo(next_tempo_block_start, tempo) - - last_reported = None - - while current_block < next_tempo_block_start: - await asyncio.sleep( - sleep, - ) # Wait before checking the block number again - current_block = subtensor.chain.get_current_block() - if last_reported is None or current_block - last_reported >= reporting_interval: - last_reported = current_block - logging.console.info( - f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" - ) - - -async def async_wait_interval( - tempo: int, - subtensor: "SubtensorApi", - netuid: int = 1, - reporting_interval: int = 1, - sleep: float = 0.25, - times: int = 1, -): - """ - Waits until the next tempo interval starts for a specific subnet. - - Calculates the next tempo block start based on the current block number - 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.chain.get_current_block() - next_tempo_block_start = current_block - - for _ in range(times): - next_tempo_block_start = next_tempo(next_tempo_block_start, tempo) - - last_reported = None - - while current_block < next_tempo_block_start: - await asyncio.sleep( - sleep, - ) # Wait before checking the block number again - current_block = await subtensor.chain.get_current_block() - if last_reported is None or current_block - last_reported >= reporting_interval: - last_reported = current_block - logging.console.info( - f"Current Block: {current_block} Next tempo for netuid {netuid} at: {next_tempo_block_start}" - ) - - -def execute_and_wait_for_next_nonce( - 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.""" - - def decorator(func): - @functools.wraps(func) - def wrapper(*args, **kwargs): - for attempt in range(max_retries): - start_nonce = subtensor.substrate.get_account_next_index( - wallet.hotkey.ss58_address - ) - - result = func(*args, **kwargs) - - start_time = time.time() - - while time.time() - start_time < timeout: - current_nonce = subtensor.substrate.get_account_next_index( - 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) - - logging.warning( - f"⚠️ Attempt {attempt + 1}/{max_retries}: Nonce did not increment." - ) - raise TimeoutError(f"❌ Nonce did not change after {max_retries} attempts.") - - return wrapper - - return decorator - - -# Helper to execute sudo wrapped calls on the chain -def sudo_set_admin_utils( - substrate: "SubstrateInterface", - wallet: "Wallet", - call_function: str, - call_params: dict, - call_module: str = "AdminUtils", -) -> tuple[bool, Optional[dict]]: - """ - Wraps the call in sudo to set hyperparameter values using AdminUtils. - - Args: - 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. - - Returns: - tuple: (success status, error details). - """ - inner_call = substrate.compose_call( - call_module=call_module, - call_function=call_function, - call_params=call_params, - ) - - sudo_call = substrate.compose_call( - call_module="Sudo", - call_function="sudo", - call_params={"call": inner_call}, - ) - extrinsic = substrate.create_signed_extrinsic( - call=sudo_call, keypair=wallet.coldkey - ) - response: "ExtrinsicReceipt" = substrate.submit_extrinsic( - extrinsic, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - return response.is_success, response.error_message - - -async def async_sudo_set_admin_utils( - substrate: "AsyncSubstrateInterface", - wallet: "Wallet", - call_function: str, - call_params: dict, - call_module: str = "AdminUtils", -) -> tuple[bool, Optional[dict]]: - """ - 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. - - 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=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, - call_params=call_params, - ) - extrinsic = substrate.create_signed_extrinsic(call=call, keypair=wallet.coldkey) - - response: "ExtrinsicReceipt" = substrate.submit_extrinsic( - extrinsic=extrinsic, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - return response.is_success, response.error_message - - -def set_identity( - subtensor: "SubtensorApi", - wallet, - name="", - url="", - github_repo="", - image="", - discord="", - description="", - additional="", -): - return subtensor.sign_and_send_extrinsic( - call=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=wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -async def async_set_identity( - subtensor: "SubtensorApi", - wallet: "Wallet", - name="", - url="", - github_repo="", - image="", - discord="", - description="", - additional="", -): - return await subtensor.sign_and_send_extrinsic( - call=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=wallet, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - - -def propose(subtensor, wallet, proposal, duration): - return subtensor.sign_and_send_extrinsic( - call=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, - ) - - -async def async_propose( - subtensor: "SubtensorApi", - 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: "SubtensorApi", - wallet: "Wallet", - hotkey, - proposal, - index, - approve, -): - return subtensor.sign_and_send_extrinsic( - call=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, - ) - - -async def async_vote( - subtensor: "SubtensorApi", - 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, - ) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index 3e97bf2ad5..d004a26ace 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -13,7 +13,11 @@ templates_repo = "templates repository" -def setup_wallet(uri: str) -> tuple[Keypair, Wallet]: +def setup_wallet( + uri: str, + encrypt_hotkey: bool = False, + encrypt_coldkey: bool = False, +) -> tuple[Keypair, Wallet]: """ Sets up a wallet using the provided URI. @@ -26,11 +30,12 @@ def setup_wallet(uri: str) -> tuple[Keypair, Wallet]: - Sets keys in the wallet without encryption and with overwriting enabled. """ keypair = Keypair.create_from_uri(uri) - wallet_path = f"/tmp/btcli-e2e-wallet-{uri.strip('/')}" - wallet = Wallet(path=wallet_path) - wallet.set_coldkey(keypair=keypair, encrypt=False, overwrite=True) + name = uri.strip("/") + wallet_path = f"/tmp/btcli-e2e-wallet-{name}" + wallet = Wallet(name=name, path=wallet_path) + wallet.set_coldkey(keypair=keypair, encrypt=encrypt_coldkey, overwrite=True) wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=True) - wallet.set_hotkey(keypair=keypair, encrypt=False, overwrite=True) + wallet.set_hotkey(keypair=keypair, encrypt=encrypt_hotkey, overwrite=True) return keypair, wallet @@ -227,68 +232,3 @@ def miner(self, wallet, netuid): def validator(self, wallet, netuid): return self.Validator(self.dir, wallet, netuid) - - -def wait_to_start_call( - 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.chain.is_fast_blocks() is False: - in_blocks = 5 - logging.console.info( - f"Waiting for [blue]{in_blocks}[/blue] blocks before [red]start call[/red]. " - f"Current block: [blue]{subtensor.block}[/blue]." - ) - - # make sure subnet isn't active - assert subtensor.subnets.is_subnet_active(netuid) is False, ( - "Subnet is already active." - ) - - # make sure we passed start_call limit - subtensor.wait_for_block(subtensor.block + in_blocks + 1) - status, message = subtensor.extrinsics.start_call( - wallet=subnet_owner_wallet, - netuid=netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert status, message - # make sure subnet is active - assert subtensor.subnets.is_subnet_active(netuid), ( - "Subnet did not activated after start call." - ) - - return True - - -async def async_wait_to_start_call( - 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.chain.is_fast_blocks() is False: - in_blocks = 5 - - current_block = await subtensor.block - - 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.extrinsics.start_call( - wallet=subnet_owner_wallet, - netuid=netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - assert status, message - return True diff --git a/tests/helpers/helpers.py b/tests/helpers/helpers.py index cdf677a2d1..c4fa5b9151 100644 --- a/tests/helpers/helpers.py +++ b/tests/helpers/helpers.py @@ -57,7 +57,7 @@ def __str__(self) -> str: return f"CloseInValue" def __repr__(self) -> str: - return self.__str__() + return repr([self.value - self.tolerance, self.value + self.tolerance]) class ApproxBalance(CloseInValue, Balance): diff --git a/tests/unit_tests/extrinsics/asyncex/test_staking.py b/tests/unit_tests/extrinsics/asyncex/test_staking.py index bd5b77e823..497362094f 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_staking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_staking.py @@ -20,11 +20,12 @@ async def test_set_auto_stake_extrinsic( netuid = mocker.Mock() hotkey_ss58 = 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=ExtrinsicResponse(res_success, res_message) + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(res_success, res_message), ) # Call diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 0721adfca2..4a68883f29 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -15,7 +15,9 @@ async def test_unstake_extrinsic(fake_wallet, mocker): fake_subtensor = mocker.AsyncMock( **{ "get_hotkey_owner.return_value": "hotkey_owner", - "get_stake_for_coldkey_and_hotkey.return_value": Balance.from_tao(10.0, fake_netuid), + "get_stake_for_coldkey_and_hotkey.return_value": Balance.from_tao( + 10.0, fake_netuid + ), "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), "get_stake.return_value": Balance.from_tao(10.0, fake_netuid), "substrate": fake_substrate, @@ -128,7 +130,9 @@ async def test_unstake_multiple_extrinsic_some_unstake_is_happy(fake_wallet, moc unstaking, "unstake_extrinsic", return_value=ExtrinsicResponse(True, "") ) mocker.patch.object( - unstaking, "get_old_stakes", return_value=[Balance.from_tao(1.1, sn_5), Balance.from_tao(0.3, sn_14)] + unstaking, + "get_old_stakes", + return_value=[Balance.from_tao(1.1, sn_5), Balance.from_tao(0.3, sn_14)], ) fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58s = ["hotkey1", "hotkey2"] diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 5c1897a188..d0d9c5276c 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -62,7 +62,9 @@ def test_add_stake_multiple_extrinsic(subtensor, mocker, fake_wallet): """Verify that sync `add_stake_multiple_extrinsic` method calls proper async method.""" # Preps mocked_get_stake_for_coldkey = mocker.patch.object( - subtensor, "get_stake_info_for_coldkey", return_value=[Balance(1.1), Balance(0.3)] + subtensor, + "get_stake_info_for_coldkey", + return_value=[Balance(1.1), Balance(0.3)], ) mocked_get_balance = mocker.patch.object( subtensor, "get_balance", return_value=Balance.from_tao(10) @@ -120,7 +122,9 @@ def test_set_auto_stake_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=ExtrinsicResponse(res_success, res_message) + subtensor, + "sign_and_send_extrinsic", + return_value=ExtrinsicResponse(res_success, res_message), ) # Call diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 4a88a5c8ba..807ee472eb 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -12,7 +12,9 @@ def test_unstake_extrinsic(fake_wallet, mocker): fake_subtensor = mocker.Mock( **{ "get_hotkey_owner.return_value": "hotkey_owner", - "get_stake_for_coldkey_and_hotkey.return_value": Balance.from_tao(10.0, fake_netuid), + "get_stake_for_coldkey_and_hotkey.return_value": Balance.from_tao( + 10.0, fake_netuid + ), "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, ""), "get_stake.return_value": Balance.from_tao(10.0, fake_netuid), "substrate": fake_substrate, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index b31235ae29..ffad802f07 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -368,7 +368,9 @@ def test_blocks_since_last_update_success_calls(subtensor, mocker): # Assertions mocked_get_current_block.assert_called_once() - mocked_get_hyperparameter.assert_called_once_with(param_name="LastUpdate", netuid=7, block=mocked_current_block) + mocked_get_hyperparameter.assert_called_once_with( + param_name="LastUpdate", netuid=7, block=mocked_current_block + ) assert result == 1 # if we change the methods logic in the future we have to be make sure the returned type is correct assert isinstance(result, int) @@ -1729,7 +1731,9 @@ def test_get_transfer_fee(subtensor, fake_wallet, mocker): subtensor.substrate.get_payment_info.return_value = fake_payment_info # Call - result = subtensor.get_transfer_fee(wallet=fake_wallet, dest=fake_dest, amount=value) + result = subtensor.get_transfer_fee( + wallet=fake_wallet, dest=fake_dest, amount=value + ) # Asserts subtensor.substrate.compose_call.assert_called_once_with( diff --git a/tests/unit_tests/utils/test_balance.py b/tests/unit_tests/utils/test_balance.py index fab62002c3..4a5663488f 100644 --- a/tests/unit_tests/utils/test_balance.py +++ b/tests/unit_tests/utils/test_balance.py @@ -535,12 +535,7 @@ def test_balance_raise_errors(first, second): @pytest.mark.parametrize( "amount", - [ - 100, - 100.1, - "100", - "10.2" - ], + [100, 100.1, "100", "10.2"], ) def test_check_balance_amount_raise_error(amount): """Tests Balance.check_rao_value method.""" diff --git a/tests/unit_tests/utils/test_version.py b/tests/unit_tests/utils/test_version.py index 6d8d535d18..5c97ee5936 100644 --- a/tests/unit_tests/utils/test_version.py +++ b/tests/unit_tests/utils/test_version.py @@ -3,14 +3,6 @@ from freezegun import freeze_time from datetime import datetime, timedelta, timezone -# from bittensor.utils.version import ( -# VERSION_CHECK_THRESHOLD, -# VersionCheckError, -# get_and_save_latest_version, -# check_version, -# version_checking, -# __version__ -# ) from bittensor.utils import version from unittest.mock import MagicMock @@ -132,4 +124,3 @@ def test_check_version_up_to_date( captured = capsys.readouterr() assert captured.out == "" - From 5fb05c3279d273404a1ac009f5610b6c745c8758 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 19:59:32 -0700 Subject: [PATCH 311/416] add test framework --- tests/e2e_tests/framework/__init__.py | 8 + tests/e2e_tests/framework/calls/__init__.py | 104 +++ .../framework/calls/non_sudo_calls.py | 830 ++++++++++++++++++ tests/e2e_tests/framework/calls/pallets.py | 24 + tests/e2e_tests/framework/calls/sudo_calls.py | 265 ++++++ tests/e2e_tests/framework/subnet.py | 537 +++++++++++ tests/e2e_tests/framework/utils.py | 51 ++ tests/e2e_tests/utils/__init__.py | 212 +++++ 8 files changed, 2031 insertions(+) create mode 100644 tests/e2e_tests/framework/__init__.py create mode 100644 tests/e2e_tests/framework/calls/__init__.py create mode 100644 tests/e2e_tests/framework/calls/non_sudo_calls.py create mode 100644 tests/e2e_tests/framework/calls/pallets.py create mode 100644 tests/e2e_tests/framework/calls/sudo_calls.py create mode 100644 tests/e2e_tests/framework/subnet.py create mode 100644 tests/e2e_tests/framework/utils.py create mode 100644 tests/e2e_tests/utils/__init__.py diff --git a/tests/e2e_tests/framework/__init__.py b/tests/e2e_tests/framework/__init__.py new file mode 100644 index 0000000000..6aed8910fc --- /dev/null +++ b/tests/e2e_tests/framework/__init__.py @@ -0,0 +1,8 @@ +from .calls import * # noqa: F401 +from .subnet import ( + NETUID, + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + REGISTER_NEURON, +) diff --git a/tests/e2e_tests/framework/calls/__init__.py b/tests/e2e_tests/framework/calls/__init__.py new file mode 100644 index 0000000000..e93243946d --- /dev/null +++ b/tests/e2e_tests/framework/calls/__init__.py @@ -0,0 +1,104 @@ +""" +This module serves primarily as a reference and auxiliary resource for developers. + +Although any command can be constructed directly within a test without relying on the pre-generated call definitions, +the provided command lists (divided into sudo and non-sudo categories), together with the pallet reference, +significantly streamline the creation of accurate, maintainable, and well-structured end-to-end tests. + +In practice, these definitions act as convenient blueprints for composing extrinsic calls and understanding the +structure of available Subtensor operations. +""" + +import os +from bittensor import Subtensor +from tests.e2e_tests.framework.calls.sudo_calls import * # noqa: F401 +from tests.e2e_tests.framework.calls.non_sudo_calls import * # noqa: F401 +from tests.e2e_tests.framework.calls.pallets import * # noqa: F401 + +HEADER = '''""" +This file is auto-generated. Do not edit manually. + +For developers: +- Use the function `recreate_calls_subpackage()` to regenerate this file. +- The command lists are built dynamically from the current Subtensor metadata (`Subtensor.substrate.metadata`). +- Each command is represented as a `namedtuple` with fields: + * System arguments: wallet, pallet (and `sudo` for sudo calls). + * Additional arguments: taken from the extrinsic definition (with type hints for reference). +- These namedtuples are intended as convenient templates for building commands in tests and end-to-end scenarios. + +Note: + Any manual changes will be overwritten the next time the generator is run. +""" + +from collections import namedtuple + + +''' + + +def recreate_calls_subpackage(network="local"): + """Fetch the list of pallets and their call and save them to the corresponding modules.""" + sub = Subtensor(network=network) + + non_sudo_calls = [] + sudo_calls = [] + pallets = [] + meta = sub.substrate.metadata + for pallet in meta.pallets: + calls = getattr(pallet.calls, "value", []) + if calls: + pallets.append(pallet.name) + for call in calls: + name = call.get("name") + fields_lst = call.get("fields", []) + fields_and_annot = [ + f"{f.get('name')}: {f.get('typeName')}" for f in fields_lst + ] + fields = [f'"{f.get("name")}"' for f in fields_lst] + + if name.startswith("sudo_"): + sudo_calls.append( + f'{name.upper()} = namedtuple("{name.upper()}", ["wallet", "pallet", "sudo", {", ".join(fields)}]) ' + f"# args: [{', '.join(fields_and_annot)}] | Pallet: {pallet.name}" + ) + else: + non_sudo_calls.append( + f'{name.upper()} = namedtuple("{name.upper()}", ["wallet", "pallet", {", ".join(fields)}]) ' + f"# args: [{', '.join(fields_and_annot)}] | Pallet: {pallet.name}" + ) + + sudo_text = HEADER + "\n".join(sorted(sudo_calls)) + "\n" + non_sudo_text = HEADER + "\n".join(sorted(non_sudo_calls)) + "\n" + pallets_text = "\n".join([f'{p} = "{p}"' for p in pallets]) + + sudo_calls_file_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "sudo_calls.py" + ) + non_sudo_calls_file_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "non_sudo_calls.py" + ) + pallets_file_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "pallets.py" + ) + + # rewrite sudo_calls.py + with open(sudo_calls_file_path, "w") as f: + f.write(sudo_text) + + print(f"Module {sudo_calls_file_path} has been recreated successfully.") + + # rewrite non_sudo_calls.py + with open(non_sudo_calls_file_path, "w") as f: + f.write(non_sudo_text) + + print(f"Module {non_sudo_calls_file_path} has been recreated successfully.") + + # rewrite pallets.py + with open(pallets_file_path, "w") as f: + f.write(pallets_text) + + print(f"Module {pallets_file_path} has been recreated successfully.") + + +if __name__ == "__main__": + recreate_calls_subpackage() diff --git a/tests/e2e_tests/framework/calls/non_sudo_calls.py b/tests/e2e_tests/framework/calls/non_sudo_calls.py new file mode 100644 index 0000000000..f220d7ee36 --- /dev/null +++ b/tests/e2e_tests/framework/calls/non_sudo_calls.py @@ -0,0 +1,830 @@ +""" +This file is auto-generated. Do not edit manually. + +For developers: +- Use the function `recreate_calls_subpackage()` to regenerate this file. +- The command lists are built dynamically from the current Subtensor metadata (`Subtensor.substrate.metadata`). +- Each command is represented as a `namedtuple` with fields: + * System arguments: wallet, pallet (and `sudo` for sudo calls). + * Additional arguments: taken from the extrinsic definition (with type hints for reference). +- These namedtuples are intended as convenient templates for building commands in tests and end-to-end scenarios. + +Note: + Any manual changes will be overwritten the next time the generator is run. +""" + +from collections import namedtuple + + +ADD_LIQUIDITY = namedtuple( + "ADD_LIQUIDITY", + ["wallet", "pallet", "hotkey", "netuid", "tick_low", "tick_high", "liquidity"], +) # args: [hotkey: T::AccountId, netuid: NetUid, tick_low: TickIndex, tick_high: TickIndex, liquidity: u64] | Pallet: Swap +ADD_MEMBER = namedtuple( + "ADD_MEMBER", ["wallet", "pallet", "who"] +) # args: [who: AccountIdLookupOf] | Pallet: SenateMembers +ADD_MEMBER = namedtuple( + "ADD_MEMBER", ["wallet", "pallet", "who"] +) # args: [who: AccountIdLookupOf] | Pallet: TriumvirateMembers +ADD_PROXY = namedtuple( + "ADD_PROXY", ["wallet", "pallet", "delegate", "proxy_type", "delay"] +) # args: [delegate: AccountIdLookupOf, proxy_type: T::ProxyType, delay: BlockNumberFor] | Pallet: Proxy +ADD_STAKE = namedtuple( + "ADD_STAKE", ["wallet", "pallet", "hotkey", "netuid", "amount_staked"] +) # args: [hotkey: T::AccountId, netuid: NetUid, amount_staked: TaoCurrency] | Pallet: SubtensorModule +ADD_STAKE_LIMIT = namedtuple( + "ADD_STAKE_LIMIT", + [ + "wallet", + "pallet", + "hotkey", + "netuid", + "amount_staked", + "limit_price", + "allow_partial", + ], +) # args: [hotkey: T::AccountId, netuid: NetUid, amount_staked: TaoCurrency, limit_price: TaoCurrency, allow_partial: bool] | Pallet: SubtensorModule +ADJUST_SENATE = namedtuple( + "ADJUST_SENATE", ["wallet", "pallet", "hotkey"] +) # args: [hotkey: T::AccountId] | Pallet: SubtensorModule +ANNOUNCE = namedtuple( + "ANNOUNCE", ["wallet", "pallet", "real", "call_hash"] +) # args: [real: AccountIdLookupOf, call_hash: CallHashOf] | Pallet: Proxy +APPLY_AUTHORIZED_UPGRADE = namedtuple( + "APPLY_AUTHORIZED_UPGRADE", ["wallet", "pallet", "code"] +) # args: [code: Vec] | Pallet: System +APPROVE_AS_MULTI = namedtuple( + "APPROVE_AS_MULTI", + [ + "wallet", + "pallet", + "threshold", + "other_signatories", + "maybe_timepoint", + "call_hash", + "max_weight", + ], +) # args: [threshold: u16, other_signatories: Vec, maybe_timepoint: Option>>, call_hash: [u8; 32], max_weight: Weight] | Pallet: Multisig +ASSOCIATE_EVM_KEY = namedtuple( + "ASSOCIATE_EVM_KEY", + ["wallet", "pallet", "netuid", "evm_key", "block_number", "signature"], +) # args: [netuid: NetUid, evm_key: H160, block_number: u64, signature: Signature] | Pallet: SubtensorModule +AS_DERIVATIVE = namedtuple( + "AS_DERIVATIVE", ["wallet", "pallet", "index", "call"] +) # args: [index: u16, call: Box<::RuntimeCall>] | Pallet: Utility +AS_MULTI = namedtuple( + "AS_MULTI", + [ + "wallet", + "pallet", + "threshold", + "other_signatories", + "maybe_timepoint", + "call", + "max_weight", + ], +) # args: [threshold: u16, other_signatories: Vec, maybe_timepoint: Option>>, call: Box<::RuntimeCall>, max_weight: Weight] | Pallet: Multisig +AS_MULTI_THRESHOLD_1 = namedtuple( + "AS_MULTI_THRESHOLD_1", ["wallet", "pallet", "other_signatories", "call"] +) # args: [other_signatories: Vec, call: Box<::RuntimeCall>] | Pallet: Multisig +AUTHORIZE_UPGRADE = namedtuple( + "AUTHORIZE_UPGRADE", ["wallet", "pallet", "code_hash"] +) # args: [code_hash: T::Hash] | Pallet: System +AUTHORIZE_UPGRADE_WITHOUT_CHECKS = namedtuple( + "AUTHORIZE_UPGRADE_WITHOUT_CHECKS", ["wallet", "pallet", "code_hash"] +) # args: [code_hash: T::Hash] | Pallet: System +BATCH = namedtuple( + "BATCH", ["wallet", "pallet", "calls"] +) # args: [calls: Vec<::RuntimeCall>] | Pallet: Utility +BATCH_ALL = namedtuple( + "BATCH_ALL", ["wallet", "pallet", "calls"] +) # args: [calls: Vec<::RuntimeCall>] | Pallet: Utility +BATCH_COMMIT_WEIGHTS = namedtuple( + "BATCH_COMMIT_WEIGHTS", ["wallet", "pallet", "netuids", "commit_hashes"] +) # args: [netuids: Vec>, commit_hashes: Vec] | Pallet: SubtensorModule +BATCH_REVEAL_WEIGHTS = namedtuple( + "BATCH_REVEAL_WEIGHTS", + [ + "wallet", + "pallet", + "netuid", + "uids_list", + "values_list", + "salts_list", + "version_keys", + ], +) # args: [netuid: NetUid, uids_list: Vec>, values_list: Vec>, salts_list: Vec>, version_keys: Vec] | Pallet: SubtensorModule +BATCH_SET_WEIGHTS = namedtuple( + "BATCH_SET_WEIGHTS", ["wallet", "pallet", "netuids", "weights", "version_keys"] +) # args: [netuids: Vec>, weights: Vec, Compact)>>, version_keys: Vec>] | Pallet: SubtensorModule +BURN = namedtuple( + "BURN", ["wallet", "pallet", "value", "keep_alive"] +) # args: [value: T::Balance, keep_alive: bool] | Pallet: Balances +BURNED_REGISTER = namedtuple( + "BURNED_REGISTER", ["wallet", "pallet", "netuid", "hotkey"] +) # args: [netuid: NetUid, hotkey: T::AccountId] | Pallet: SubtensorModule +BURN_ALPHA = namedtuple( + "BURN_ALPHA", ["wallet", "pallet", "hotkey", "amount", "netuid"] +) # args: [hotkey: T::AccountId, amount: AlphaCurrency, netuid: NetUid] | Pallet: SubtensorModule +CALL = namedtuple( + "CALL", + [ + "wallet", + "pallet", + "source", + "target", + "input", + "value", + "gas_limit", + "max_fee_per_gas", + "max_priority_fee_per_gas", + "nonce", + "access_list", + "authorization_list", + ], +) # args: [source: H160, target: H160, input: Vec, value: U256, gas_limit: u64, max_fee_per_gas: U256, max_priority_fee_per_gas: Option, nonce: Option, access_list: Vec<(H160, Vec)>, authorization_list: AuthorizationList] | Pallet: EVM +CANCEL = namedtuple( + "CANCEL", ["wallet", "pallet", "when", "index"] +) # args: [when: BlockNumberFor, index: u32] | Pallet: Scheduler +CANCEL_AS_MULTI = namedtuple( + "CANCEL_AS_MULTI", + ["wallet", "pallet", "threshold", "other_signatories", "timepoint", "call_hash"], +) # args: [threshold: u16, other_signatories: Vec, timepoint: Timepoint>, call_hash: [u8; 32]] | Pallet: Multisig +CANCEL_NAMED = namedtuple( + "CANCEL_NAMED", ["wallet", "pallet", "id"] +) # args: [id: TaskName] | Pallet: Scheduler +CANCEL_RETRY = namedtuple( + "CANCEL_RETRY", ["wallet", "pallet", "task"] +) # args: [task: TaskAddress>] | Pallet: Scheduler +CANCEL_RETRY_NAMED = namedtuple( + "CANCEL_RETRY_NAMED", ["wallet", "pallet", "id"] +) # args: [id: TaskName] | Pallet: Scheduler +CHANGE_KEY = namedtuple( + "CHANGE_KEY", ["wallet", "pallet", "new"] +) # args: [new: AccountIdLookupOf] | Pallet: SenateMembers +CHANGE_KEY = namedtuple( + "CHANGE_KEY", ["wallet", "pallet", "new"] +) # args: [new: AccountIdLookupOf] | Pallet: TriumvirateMembers +CLEAR_IDENTITY = namedtuple( + "CLEAR_IDENTITY", ["wallet", "pallet", "identified"] +) # args: [identified: T::AccountId] | Pallet: Registry +CLEAR_PRIME = namedtuple( + "CLEAR_PRIME", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: SenateMembers +CLEAR_PRIME = namedtuple( + "CLEAR_PRIME", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: TriumvirateMembers +CLOSE = namedtuple( + "CLOSE", + [ + "wallet", + "pallet", + "proposal_hash", + "index", + "proposal_weight_bound", + "length_bound", + ], +) # args: [proposal_hash: T::Hash, index: ProposalIndex, proposal_weight_bound: Weight, length_bound: u32] | Pallet: Triumvirate +COMMIT_CRV3_MECHANISM_WEIGHTS = namedtuple( + "COMMIT_CRV3_MECHANISM_WEIGHTS", + ["wallet", "pallet", "netuid", "mecid", "commit", "reveal_round"], +) # args: [netuid: NetUid, mecid: MechId, commit: BoundedVec>, reveal_round: u64] | Pallet: SubtensorModule +COMMIT_MECHANISM_WEIGHTS = namedtuple( + "COMMIT_MECHANISM_WEIGHTS", ["wallet", "pallet", "netuid", "mecid", "commit_hash"] +) # args: [netuid: NetUid, mecid: MechId, commit_hash: H256] | Pallet: SubtensorModule +COMMIT_TIMELOCKED_MECHANISM_WEIGHTS = namedtuple( + "COMMIT_TIMELOCKED_MECHANISM_WEIGHTS", + [ + "wallet", + "pallet", + "netuid", + "mecid", + "commit", + "reveal_round", + "commit_reveal_version", + ], +) # args: [netuid: NetUid, mecid: MechId, commit: BoundedVec>, reveal_round: u64, commit_reveal_version: u16] | Pallet: SubtensorModule +COMMIT_TIMELOCKED_WEIGHTS = namedtuple( + "COMMIT_TIMELOCKED_WEIGHTS", + ["wallet", "pallet", "netuid", "commit", "reveal_round", "commit_reveal_version"], +) # args: [netuid: NetUid, commit: BoundedVec>, reveal_round: u64, commit_reveal_version: u16] | Pallet: SubtensorModule +COMMIT_WEIGHTS = namedtuple( + "COMMIT_WEIGHTS", ["wallet", "pallet", "netuid", "commit_hash"] +) # args: [netuid: NetUid, commit_hash: H256] | Pallet: SubtensorModule +CONTRIBUTE = namedtuple( + "CONTRIBUTE", ["wallet", "pallet", "crowdloan_id", "amount"] +) # args: [crowdloan_id: CrowdloanId, amount: BalanceOf] | Pallet: Crowdloan +CREATE = namedtuple( + "CREATE", + [ + "wallet", + "pallet", + "deposit", + "min_contribution", + "cap", + "end", + "call", + "target_address", + ], +) # args: [deposit: BalanceOf, min_contribution: BalanceOf, cap: BalanceOf, end: BlockNumberFor, call: Option::RuntimeCall>>, target_address: Option] | Pallet: Crowdloan +CREATE = namedtuple( + "CREATE", + [ + "wallet", + "pallet", + "source", + "init", + "value", + "gas_limit", + "max_fee_per_gas", + "max_priority_fee_per_gas", + "nonce", + "access_list", + "authorization_list", + ], +) # args: [source: H160, init: Vec, value: U256, gas_limit: u64, max_fee_per_gas: U256, max_priority_fee_per_gas: Option, nonce: Option, access_list: Vec<(H160, Vec)>, authorization_list: AuthorizationList] | Pallet: EVM +CREATE2 = namedtuple( + "CREATE2", + [ + "wallet", + "pallet", + "source", + "init", + "salt", + "value", + "gas_limit", + "max_fee_per_gas", + "max_priority_fee_per_gas", + "nonce", + "access_list", + "authorization_list", + ], +) # args: [source: H160, init: Vec, salt: H256, value: U256, gas_limit: u64, max_fee_per_gas: U256, max_priority_fee_per_gas: Option, nonce: Option, access_list: Vec<(H160, Vec)>, authorization_list: AuthorizationList] | Pallet: EVM +CREATE_PURE = namedtuple( + "CREATE_PURE", ["wallet", "pallet", "proxy_type", "delay", "index"] +) # args: [proxy_type: T::ProxyType, delay: BlockNumberFor, index: u16] | Pallet: Proxy +DECREASE_TAKE = namedtuple( + "DECREASE_TAKE", ["wallet", "pallet", "hotkey", "take"] +) # args: [hotkey: T::AccountId, take: u16] | Pallet: SubtensorModule +DISABLE_WHITELIST = namedtuple( + "DISABLE_WHITELIST", ["wallet", "pallet", "disabled"] +) # args: [disabled: bool] | Pallet: EVM +DISAPPROVE_PROPOSAL = namedtuple( + "DISAPPROVE_PROPOSAL", ["wallet", "pallet", "proposal_hash"] +) # args: [proposal_hash: T::Hash] | Pallet: Triumvirate +DISPATCH_AS = namedtuple( + "DISPATCH_AS", ["wallet", "pallet", "as_origin", "call"] +) # args: [as_origin: Box, call: Box<::RuntimeCall>] | Pallet: Utility +DISPATCH_AS_FALLIBLE = namedtuple( + "DISPATCH_AS_FALLIBLE", ["wallet", "pallet", "as_origin", "call"] +) # args: [as_origin: Box, call: Box<::RuntimeCall>] | Pallet: Utility +DISSOLVE = namedtuple( + "DISSOLVE", ["wallet", "pallet", "crowdloan_id"] +) # args: [crowdloan_id: CrowdloanId] | Pallet: Crowdloan +DISSOLVE_NETWORK = namedtuple( + "DISSOLVE_NETWORK", ["wallet", "pallet", "coldkey", "netuid"] +) # args: [coldkey: T::AccountId, netuid: NetUid] | Pallet: SubtensorModule +ENSURE_UPDATED = namedtuple( + "ENSURE_UPDATED", ["wallet", "pallet", "hashes"] +) # args: [hashes: Vec] | Pallet: Preimage +ENTER = namedtuple( + "ENTER", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: SafeMode +EXECUTE = namedtuple( + "EXECUTE", ["wallet", "pallet", "proposal", "length_bound"] +) # args: [proposal: Box<>::Proposal>, length_bound: u32] | Pallet: Triumvirate +EXTEND = namedtuple( + "EXTEND", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: SafeMode +FAUCET = namedtuple( + "FAUCET", ["wallet", "pallet", "block_number", "nonce", "work"] +) # args: [block_number: u64, nonce: u64, work: Vec] | Pallet: SubtensorModule +FINALIZE = namedtuple( + "FINALIZE", ["wallet", "pallet", "crowdloan_id"] +) # args: [crowdloan_id: CrowdloanId] | Pallet: Crowdloan +FORCE_ADJUST_TOTAL_ISSUANCE = namedtuple( + "FORCE_ADJUST_TOTAL_ISSUANCE", ["wallet", "pallet", "direction", "delta"] +) # args: [direction: AdjustmentDirection, delta: T::Balance] | Pallet: Balances +FORCE_BATCH = namedtuple( + "FORCE_BATCH", ["wallet", "pallet", "calls"] +) # args: [calls: Vec<::RuntimeCall>] | Pallet: Utility +FORCE_ENTER = namedtuple( + "FORCE_ENTER", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: SafeMode +FORCE_EXIT = namedtuple( + "FORCE_EXIT", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: SafeMode +FORCE_EXTEND = namedtuple( + "FORCE_EXTEND", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: SafeMode +FORCE_RELEASE_DEPOSIT = namedtuple( + "FORCE_RELEASE_DEPOSIT", ["wallet", "pallet", "account", "block"] +) # args: [account: T::AccountId, block: BlockNumberFor] | Pallet: SafeMode +FORCE_SET_BALANCE = namedtuple( + "FORCE_SET_BALANCE", ["wallet", "pallet", "who", "new_free"] +) # args: [who: AccountIdLookupOf, new_free: T::Balance] | Pallet: Balances +FORCE_SLASH_DEPOSIT = namedtuple( + "FORCE_SLASH_DEPOSIT", ["wallet", "pallet", "account", "block"] +) # args: [account: T::AccountId, block: BlockNumberFor] | Pallet: SafeMode +FORCE_TRANSFER = namedtuple( + "FORCE_TRANSFER", ["wallet", "pallet", "source", "dest", "value"] +) # args: [source: AccountIdLookupOf, dest: AccountIdLookupOf, value: T::Balance] | Pallet: Balances +FORCE_UNRESERVE = namedtuple( + "FORCE_UNRESERVE", ["wallet", "pallet", "who", "amount"] +) # args: [who: AccountIdLookupOf, amount: T::Balance] | Pallet: Balances +IF_ELSE = namedtuple( + "IF_ELSE", ["wallet", "pallet", "main", "fallback"] +) # args: [main: Box<::RuntimeCall>, fallback: Box<::RuntimeCall>] | Pallet: Utility +INCREASE_TAKE = namedtuple( + "INCREASE_TAKE", ["wallet", "pallet", "hotkey", "take"] +) # args: [hotkey: T::AccountId, take: u16] | Pallet: SubtensorModule +KILL_PREFIX = namedtuple( + "KILL_PREFIX", ["wallet", "pallet", "prefix", "subkeys"] +) # args: [prefix: Key, subkeys: u32] | Pallet: System +KILL_PURE = namedtuple( + "KILL_PURE", + ["wallet", "pallet", "spawner", "proxy_type", "index", "height", "ext_index"], +) # args: [spawner: AccountIdLookupOf, proxy_type: T::ProxyType, index: u16, height: BlockNumberFor, ext_index: u32] | Pallet: Proxy +KILL_STORAGE = namedtuple( + "KILL_STORAGE", ["wallet", "pallet", "keys"] +) # args: [keys: Vec] | Pallet: System +MODIFY_POSITION = namedtuple( + "MODIFY_POSITION", + ["wallet", "pallet", "hotkey", "netuid", "position_id", "liquidity_delta"], +) # args: [hotkey: T::AccountId, netuid: NetUid, position_id: PositionId, liquidity_delta: i64] | Pallet: Swap +MOVE_STAKE = namedtuple( + "MOVE_STAKE", + [ + "wallet", + "pallet", + "origin_hotkey", + "destination_hotkey", + "origin_netuid", + "destination_netuid", + "alpha_amount", + ], +) # args: [origin_hotkey: T::AccountId, destination_hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, alpha_amount: AlphaCurrency] | Pallet: SubtensorModule +NOTE_PREIMAGE = namedtuple( + "NOTE_PREIMAGE", ["wallet", "pallet", "bytes"] +) # args: [bytes: Vec] | Pallet: Preimage +NOTE_STALLED = namedtuple( + "NOTE_STALLED", ["wallet", "pallet", "delay", "best_finalized_block_number"] +) # args: [delay: BlockNumberFor, best_finalized_block_number: BlockNumberFor] | Pallet: Grandpa +POKE_DEPOSIT = namedtuple( + "POKE_DEPOSIT", ["wallet", "pallet", "threshold", "other_signatories", "call_hash"] +) # args: [threshold: u16, other_signatories: Vec, call_hash: [u8; 32]] | Pallet: Multisig +POKE_DEPOSIT = namedtuple( + "POKE_DEPOSIT", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: Proxy +PROPOSE = namedtuple( + "PROPOSE", ["wallet", "pallet", "proposal", "length_bound", "duration"] +) # args: [proposal: Box<>::Proposal>, length_bound: u32, duration: BlockNumberFor] | Pallet: Triumvirate +PROXY = namedtuple( + "PROXY", ["wallet", "pallet", "real", "force_proxy_type", "call"] +) # args: [real: AccountIdLookupOf, force_proxy_type: Option, call: Box<::RuntimeCall>] | Pallet: Proxy +PROXY_ANNOUNCED = namedtuple( + "PROXY_ANNOUNCED", + ["wallet", "pallet", "delegate", "real", "force_proxy_type", "call"], +) # args: [delegate: AccountIdLookupOf, real: AccountIdLookupOf, force_proxy_type: Option, call: Box<::RuntimeCall>] | Pallet: Proxy +RECYCLE_ALPHA = namedtuple( + "RECYCLE_ALPHA", ["wallet", "pallet", "hotkey", "amount", "netuid"] +) # args: [hotkey: T::AccountId, amount: AlphaCurrency, netuid: NetUid] | Pallet: SubtensorModule +REFUND = namedtuple( + "REFUND", ["wallet", "pallet", "crowdloan_id"] +) # args: [crowdloan_id: CrowdloanId] | Pallet: Crowdloan +REGISTER = namedtuple( + "REGISTER", + [ + "wallet", + "pallet", + "netuid", + "block_number", + "nonce", + "work", + "hotkey", + "coldkey", + ], +) # args: [netuid: NetUid, block_number: u64, nonce: u64, work: Vec, hotkey: T::AccountId, coldkey: T::AccountId] | Pallet: SubtensorModule +REGISTER_LEASED_NETWORK = namedtuple( + "REGISTER_LEASED_NETWORK", ["wallet", "pallet", "emissions_share", "end_block"] +) # args: [emissions_share: Percent, end_block: Option>] | Pallet: SubtensorModule +REGISTER_NETWORK = namedtuple( + "REGISTER_NETWORK", ["wallet", "pallet", "hotkey"] +) # args: [hotkey: T::AccountId] | Pallet: SubtensorModule +REGISTER_NETWORK_WITH_IDENTITY = namedtuple( + "REGISTER_NETWORK_WITH_IDENTITY", ["wallet", "pallet", "hotkey", "identity"] +) # args: [hotkey: T::AccountId, identity: Option] | Pallet: SubtensorModule +REJECT_ANNOUNCEMENT = namedtuple( + "REJECT_ANNOUNCEMENT", ["wallet", "pallet", "delegate", "call_hash"] +) # args: [delegate: AccountIdLookupOf, call_hash: CallHashOf] | Pallet: Proxy +RELEASE_DEPOSIT = namedtuple( + "RELEASE_DEPOSIT", ["wallet", "pallet", "account", "block"] +) # args: [account: T::AccountId, block: BlockNumberFor] | Pallet: SafeMode +REMARK = namedtuple( + "REMARK", ["wallet", "pallet", "remark"] +) # args: [remark: Vec] | Pallet: System +REMARK_WITH_EVENT = namedtuple( + "REMARK_WITH_EVENT", ["wallet", "pallet", "remark"] +) # args: [remark: Vec] | Pallet: System +REMOVE_ANNOUNCEMENT = namedtuple( + "REMOVE_ANNOUNCEMENT", ["wallet", "pallet", "real", "call_hash"] +) # args: [real: AccountIdLookupOf, call_hash: CallHashOf] | Pallet: Proxy +REMOVE_KEY = namedtuple( + "REMOVE_KEY", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: Sudo +REMOVE_LIQUIDITY = namedtuple( + "REMOVE_LIQUIDITY", ["wallet", "pallet", "hotkey", "netuid", "position_id"] +) # args: [hotkey: T::AccountId, netuid: NetUid, position_id: PositionId] | Pallet: Swap +REMOVE_MEMBER = namedtuple( + "REMOVE_MEMBER", ["wallet", "pallet", "who"] +) # args: [who: AccountIdLookupOf] | Pallet: SenateMembers +REMOVE_MEMBER = namedtuple( + "REMOVE_MEMBER", ["wallet", "pallet", "who"] +) # args: [who: AccountIdLookupOf] | Pallet: TriumvirateMembers +REMOVE_PROXIES = namedtuple( + "REMOVE_PROXIES", + [ + "wallet", + "pallet", + ], +) # args: [] | Pallet: Proxy +REMOVE_PROXY = namedtuple( + "REMOVE_PROXY", ["wallet", "pallet", "delegate", "proxy_type", "delay"] +) # args: [delegate: AccountIdLookupOf, proxy_type: T::ProxyType, delay: BlockNumberFor] | Pallet: Proxy +REMOVE_STAKE = namedtuple( + "REMOVE_STAKE", ["wallet", "pallet", "hotkey", "netuid", "amount_unstaked"] +) # args: [hotkey: T::AccountId, netuid: NetUid, amount_unstaked: AlphaCurrency] | Pallet: SubtensorModule +REMOVE_STAKE_FULL_LIMIT = namedtuple( + "REMOVE_STAKE_FULL_LIMIT", ["wallet", "pallet", "hotkey", "netuid", "limit_price"] +) # args: [hotkey: T::AccountId, netuid: NetUid, limit_price: Option] | Pallet: SubtensorModule +REMOVE_STAKE_LIMIT = namedtuple( + "REMOVE_STAKE_LIMIT", + [ + "wallet", + "pallet", + "hotkey", + "netuid", + "amount_unstaked", + "limit_price", + "allow_partial", + ], +) # args: [hotkey: T::AccountId, netuid: NetUid, amount_unstaked: AlphaCurrency, limit_price: TaoCurrency, allow_partial: bool] | Pallet: SubtensorModule +REPORT_EQUIVOCATION = namedtuple( + "REPORT_EQUIVOCATION", ["wallet", "pallet", "equivocation_proof", "key_owner_proof"] +) # args: [equivocation_proof: Box>>, key_owner_proof: T::KeyOwnerProof] | Pallet: Grandpa +REPORT_EQUIVOCATION_UNSIGNED = namedtuple( + "REPORT_EQUIVOCATION_UNSIGNED", + ["wallet", "pallet", "equivocation_proof", "key_owner_proof"], +) # args: [equivocation_proof: Box>>, key_owner_proof: T::KeyOwnerProof] | Pallet: Grandpa +REQUEST_PREIMAGE = namedtuple( + "REQUEST_PREIMAGE", ["wallet", "pallet", "hash"] +) # args: [hash: T::Hash] | Pallet: Preimage +RESET_MEMBERS = namedtuple( + "RESET_MEMBERS", ["wallet", "pallet", "members"] +) # args: [members: Vec] | Pallet: SenateMembers +RESET_MEMBERS = namedtuple( + "RESET_MEMBERS", ["wallet", "pallet", "members"] +) # args: [members: Vec] | Pallet: TriumvirateMembers +REVEAL_MECHANISM_WEIGHTS = namedtuple( + "REVEAL_MECHANISM_WEIGHTS", + ["wallet", "pallet", "netuid", "mecid", "uids", "values", "salt", "version_key"], +) # args: [netuid: NetUid, mecid: MechId, uids: Vec, values: Vec, salt: Vec, version_key: u64] | Pallet: SubtensorModule +REVEAL_WEIGHTS = namedtuple( + "REVEAL_WEIGHTS", + ["wallet", "pallet", "netuid", "uids", "values", "salt", "version_key"], +) # args: [netuid: NetUid, uids: Vec, values: Vec, salt: Vec, version_key: u64] | Pallet: SubtensorModule +ROOT_DISSOLVE_NETWORK = namedtuple( + "ROOT_DISSOLVE_NETWORK", ["wallet", "pallet", "netuid"] +) # args: [netuid: NetUid] | Pallet: SubtensorModule +ROOT_REGISTER = namedtuple( + "ROOT_REGISTER", ["wallet", "pallet", "hotkey"] +) # args: [hotkey: T::AccountId] | Pallet: SubtensorModule +SCHEDULE = namedtuple( + "SCHEDULE", ["wallet", "pallet", "when", "maybe_periodic", "priority", "call"] +) # args: [when: BlockNumberFor, maybe_periodic: Option>>, priority: schedule::Priority, call: Box<::RuntimeCall>] | Pallet: Scheduler +SCHEDULE_AFTER = namedtuple( + "SCHEDULE_AFTER", + ["wallet", "pallet", "after", "maybe_periodic", "priority", "call"], +) # args: [after: BlockNumberFor, maybe_periodic: Option>>, priority: schedule::Priority, call: Box<::RuntimeCall>] | Pallet: Scheduler +SCHEDULE_GRANDPA_CHANGE = namedtuple( + "SCHEDULE_GRANDPA_CHANGE", + ["wallet", "pallet", "next_authorities", "in_blocks", "forced"], +) # args: [next_authorities: AuthorityList, in_blocks: BlockNumberFor, forced: Option>] | Pallet: AdminUtils +SCHEDULE_NAMED = namedtuple( + "SCHEDULE_NAMED", + ["wallet", "pallet", "id", "when", "maybe_periodic", "priority", "call"], +) # args: [id: TaskName, when: BlockNumberFor, maybe_periodic: Option>>, priority: schedule::Priority, call: Box<::RuntimeCall>] | Pallet: Scheduler +SCHEDULE_NAMED_AFTER = namedtuple( + "SCHEDULE_NAMED_AFTER", + ["wallet", "pallet", "id", "after", "maybe_periodic", "priority", "call"], +) # args: [id: TaskName, after: BlockNumberFor, maybe_periodic: Option>>, priority: schedule::Priority, call: Box<::RuntimeCall>] | Pallet: Scheduler +SCHEDULE_SWAP_COLDKEY = namedtuple( + "SCHEDULE_SWAP_COLDKEY", ["wallet", "pallet", "new_coldkey"] +) # args: [new_coldkey: T::AccountId] | Pallet: SubtensorModule +SERVE_AXON = namedtuple( + "SERVE_AXON", + [ + "wallet", + "pallet", + "netuid", + "version", + "ip", + "port", + "ip_type", + "protocol", + "placeholder1", + "placeholder2", + ], +) # args: [netuid: NetUid, version: u32, ip: u128, port: u16, ip_type: u8, protocol: u8, placeholder1: u8, placeholder2: u8] | Pallet: SubtensorModule +SERVE_AXON_TLS = namedtuple( + "SERVE_AXON_TLS", + [ + "wallet", + "pallet", + "netuid", + "version", + "ip", + "port", + "ip_type", + "protocol", + "placeholder1", + "placeholder2", + "certificate", + ], +) # args: [netuid: NetUid, version: u32, ip: u128, port: u16, ip_type: u8, protocol: u8, placeholder1: u8, placeholder2: u8, certificate: Vec] | Pallet: SubtensorModule +SERVE_PROMETHEUS = namedtuple( + "SERVE_PROMETHEUS", + ["wallet", "pallet", "netuid", "version", "ip", "port", "ip_type"], +) # args: [netuid: NetUid, version: u32, ip: u128, port: u16, ip_type: u8] | Pallet: SubtensorModule +SET = namedtuple( + "SET", ["wallet", "pallet", "now"] +) # args: [now: T::Moment] | Pallet: Timestamp +SET_BASE_FEE_PER_GAS = namedtuple( + "SET_BASE_FEE_PER_GAS", ["wallet", "pallet", "fee"] +) # args: [fee: U256] | Pallet: BaseFee +SET_BEACON_CONFIG = namedtuple( + "SET_BEACON_CONFIG", ["wallet", "pallet", "config_payload", "signature"] +) # args: [config_payload: BeaconConfigurationPayload>, signature: Option] | Pallet: Drand +SET_CHILDKEY_TAKE = namedtuple( + "SET_CHILDKEY_TAKE", ["wallet", "pallet", "hotkey", "netuid", "take"] +) # args: [hotkey: T::AccountId, netuid: NetUid, take: u16] | Pallet: SubtensorModule +SET_CHILDREN = namedtuple( + "SET_CHILDREN", ["wallet", "pallet", "hotkey", "netuid", "children"] +) # args: [hotkey: T::AccountId, netuid: NetUid, children: Vec<(u64, T::AccountId)>] | Pallet: SubtensorModule +SET_CODE = namedtuple( + "SET_CODE", ["wallet", "pallet", "code"] +) # args: [code: Vec] | Pallet: System +SET_CODE_WITHOUT_CHECKS = namedtuple( + "SET_CODE_WITHOUT_CHECKS", ["wallet", "pallet", "code"] +) # args: [code: Vec] | Pallet: System +SET_COLDKEY_AUTO_STAKE_HOTKEY = namedtuple( + "SET_COLDKEY_AUTO_STAKE_HOTKEY", ["wallet", "pallet", "netuid", "hotkey"] +) # args: [netuid: NetUid, hotkey: T::AccountId] | Pallet: SubtensorModule +SET_COMMITMENT = namedtuple( + "SET_COMMITMENT", ["wallet", "pallet", "netuid", "info"] +) # args: [netuid: NetUid, info: Box>] | Pallet: Commitments +SET_ELASTICITY = namedtuple( + "SET_ELASTICITY", ["wallet", "pallet", "elasticity"] +) # args: [elasticity: Permill] | Pallet: BaseFee +SET_FEE_RATE = namedtuple( + "SET_FEE_RATE", ["wallet", "pallet", "netuid", "rate"] +) # args: [netuid: NetUid, rate: u16] | Pallet: Swap +SET_HEAP_PAGES = namedtuple( + "SET_HEAP_PAGES", ["wallet", "pallet", "pages"] +) # args: [pages: u64] | Pallet: System +SET_IDENTITY = namedtuple( + "SET_IDENTITY", ["wallet", "pallet", "identified", "info"] +) # args: [identified: T::AccountId, info: Box>] | Pallet: Registry +SET_IDENTITY = namedtuple( + "SET_IDENTITY", + [ + "wallet", + "pallet", + "name", + "url", + "github_repo", + "image", + "discord", + "description", + "additional", + ], +) # args: [name: Vec, url: Vec, github_repo: Vec, image: Vec, discord: Vec, description: Vec, additional: Vec] | Pallet: SubtensorModule +SET_KEY = namedtuple( + "SET_KEY", ["wallet", "pallet", "new"] +) # args: [new: AccountIdLookupOf] | Pallet: Sudo +SET_MAX_SPACE = namedtuple( + "SET_MAX_SPACE", ["wallet", "pallet", "new_limit"] +) # args: [new_limit: u32] | Pallet: Commitments +SET_MECHANISM_WEIGHTS = namedtuple( + "SET_MECHANISM_WEIGHTS", + ["wallet", "pallet", "netuid", "mecid", "dests", "weights", "version_key"], +) # args: [netuid: NetUid, mecid: MechId, dests: Vec, weights: Vec, version_key: u64] | Pallet: SubtensorModule +SET_MEMBERS = namedtuple( + "SET_MEMBERS", ["wallet", "pallet", "new_members", "prime", "old_count"] +) # args: [new_members: Vec, prime: Option, old_count: MemberCount] | Pallet: Triumvirate +SET_OLDEST_STORED_ROUND = namedtuple( + "SET_OLDEST_STORED_ROUND", ["wallet", "pallet", "oldest_round"] +) # args: [oldest_round: u64] | Pallet: Drand +SET_PENDING_CHILDKEY_COOLDOWN = namedtuple( + "SET_PENDING_CHILDKEY_COOLDOWN", ["wallet", "pallet", "cooldown"] +) # args: [cooldown: u64] | Pallet: SubtensorModule +SET_PRIME = namedtuple( + "SET_PRIME", ["wallet", "pallet", "who"] +) # args: [who: AccountIdLookupOf] | Pallet: SenateMembers +SET_PRIME = namedtuple( + "SET_PRIME", ["wallet", "pallet", "who"] +) # args: [who: AccountIdLookupOf] | Pallet: TriumvirateMembers +SET_RETRY = namedtuple( + "SET_RETRY", ["wallet", "pallet", "task", "retries", "period"] +) # args: [task: TaskAddress>, retries: u8, period: BlockNumberFor] | Pallet: Scheduler +SET_RETRY_NAMED = namedtuple( + "SET_RETRY_NAMED", ["wallet", "pallet", "id", "retries", "period"] +) # args: [id: TaskName, retries: u8, period: BlockNumberFor] | Pallet: Scheduler +SET_STORAGE = namedtuple( + "SET_STORAGE", ["wallet", "pallet", "items"] +) # args: [items: Vec] | Pallet: System +SET_SUBNET_IDENTITY = namedtuple( + "SET_SUBNET_IDENTITY", + [ + "wallet", + "pallet", + "netuid", + "subnet_name", + "github_repo", + "subnet_contact", + "subnet_url", + "discord", + "description", + "logo_url", + "additional", + ], +) # args: [netuid: NetUid, subnet_name: Vec, github_repo: Vec, subnet_contact: Vec, subnet_url: Vec, discord: Vec, description: Vec, logo_url: Vec, additional: Vec] | Pallet: SubtensorModule +SET_WEIGHTS = namedtuple( + "SET_WEIGHTS", ["wallet", "pallet", "netuid", "dests", "weights", "version_key"] +) # args: [netuid: NetUid, dests: Vec, weights: Vec, version_key: u64] | Pallet: SubtensorModule +SET_WHITELIST = namedtuple( + "SET_WHITELIST", ["wallet", "pallet", "new"] +) # args: [new: Vec] | Pallet: EVM +START_CALL = namedtuple( + "START_CALL", ["wallet", "pallet", "netuid"] +) # args: [netuid: NetUid] | Pallet: SubtensorModule +SUDO = namedtuple( + "SUDO", ["wallet", "pallet", "call"] +) # args: [call: Box<::RuntimeCall>] | Pallet: Sudo +SUDO = namedtuple( + "SUDO", ["wallet", "pallet", "call"] +) # args: [call: Box] | Pallet: SubtensorModule +SWAP_AUTHORITIES = namedtuple( + "SWAP_AUTHORITIES", ["wallet", "pallet", "new_authorities"] +) # args: [new_authorities: BoundedVec<::AuthorityId, T::MaxAuthorities>] | Pallet: AdminUtils +SWAP_COLDKEY = namedtuple( + "SWAP_COLDKEY", ["wallet", "pallet", "old_coldkey", "new_coldkey", "swap_cost"] +) # args: [old_coldkey: T::AccountId, new_coldkey: T::AccountId, swap_cost: TaoCurrency] | Pallet: SubtensorModule +SWAP_HOTKEY = namedtuple( + "SWAP_HOTKEY", ["wallet", "pallet", "hotkey", "new_hotkey", "netuid"] +) # args: [hotkey: T::AccountId, new_hotkey: T::AccountId, netuid: Option] | Pallet: SubtensorModule +SWAP_MEMBER = namedtuple( + "SWAP_MEMBER", ["wallet", "pallet", "remove", "add"] +) # args: [remove: AccountIdLookupOf, add: AccountIdLookupOf] | Pallet: SenateMembers +SWAP_MEMBER = namedtuple( + "SWAP_MEMBER", ["wallet", "pallet", "remove", "add"] +) # args: [remove: AccountIdLookupOf, add: AccountIdLookupOf] | Pallet: TriumvirateMembers +SWAP_STAKE = namedtuple( + "SWAP_STAKE", + [ + "wallet", + "pallet", + "hotkey", + "origin_netuid", + "destination_netuid", + "alpha_amount", + ], +) # args: [hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, alpha_amount: AlphaCurrency] | Pallet: SubtensorModule +SWAP_STAKE_LIMIT = namedtuple( + "SWAP_STAKE_LIMIT", + [ + "wallet", + "pallet", + "hotkey", + "origin_netuid", + "destination_netuid", + "alpha_amount", + "limit_price", + "allow_partial", + ], +) # args: [hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, alpha_amount: AlphaCurrency, limit_price: TaoCurrency, allow_partial: bool] | Pallet: SubtensorModule +TERMINATE_LEASE = namedtuple( + "TERMINATE_LEASE", ["wallet", "pallet", "lease_id", "hotkey"] +) # args: [lease_id: LeaseId, hotkey: T::AccountId] | Pallet: SubtensorModule +TOGGLE_USER_LIQUIDITY = namedtuple( + "TOGGLE_USER_LIQUIDITY", ["wallet", "pallet", "netuid", "enable"] +) # args: [netuid: NetUid, enable: bool] | Pallet: Swap +TRANSACT = namedtuple( + "TRANSACT", ["wallet", "pallet", "transaction"] +) # args: [transaction: Transaction] | Pallet: Ethereum +TRANSFER_ALL = namedtuple( + "TRANSFER_ALL", ["wallet", "pallet", "dest", "keep_alive"] +) # args: [dest: AccountIdLookupOf, keep_alive: bool] | Pallet: Balances +TRANSFER_ALLOW_DEATH = namedtuple( + "TRANSFER_ALLOW_DEATH", ["wallet", "pallet", "dest", "value"] +) # args: [dest: AccountIdLookupOf, value: T::Balance] | Pallet: Balances +TRANSFER_KEEP_ALIVE = namedtuple( + "TRANSFER_KEEP_ALIVE", ["wallet", "pallet", "dest", "value"] +) # args: [dest: AccountIdLookupOf, value: T::Balance] | Pallet: Balances +TRANSFER_STAKE = namedtuple( + "TRANSFER_STAKE", + [ + "wallet", + "pallet", + "destination_coldkey", + "hotkey", + "origin_netuid", + "destination_netuid", + "alpha_amount", + ], +) # args: [destination_coldkey: T::AccountId, hotkey: T::AccountId, origin_netuid: NetUid, destination_netuid: NetUid, alpha_amount: AlphaCurrency] | Pallet: SubtensorModule +TRY_ASSOCIATE_HOTKEY = namedtuple( + "TRY_ASSOCIATE_HOTKEY", ["wallet", "pallet", "hotkey"] +) # args: [hotkey: T::AccountId] | Pallet: SubtensorModule +UNNOTE_PREIMAGE = namedtuple( + "UNNOTE_PREIMAGE", ["wallet", "pallet", "hash"] +) # args: [hash: T::Hash] | Pallet: Preimage +UNREQUEST_PREIMAGE = namedtuple( + "UNREQUEST_PREIMAGE", ["wallet", "pallet", "hash"] +) # args: [hash: T::Hash] | Pallet: Preimage +UNSTAKE_ALL = namedtuple( + "UNSTAKE_ALL", ["wallet", "pallet", "hotkey"] +) # args: [hotkey: T::AccountId] | Pallet: SubtensorModule +UNSTAKE_ALL_ALPHA = namedtuple( + "UNSTAKE_ALL_ALPHA", ["wallet", "pallet", "hotkey"] +) # args: [hotkey: T::AccountId] | Pallet: SubtensorModule +UPDATE_CAP = namedtuple( + "UPDATE_CAP", ["wallet", "pallet", "crowdloan_id", "new_cap"] +) # args: [crowdloan_id: CrowdloanId, new_cap: BalanceOf] | Pallet: Crowdloan +UPDATE_END = namedtuple( + "UPDATE_END", ["wallet", "pallet", "crowdloan_id", "new_end"] +) # args: [crowdloan_id: CrowdloanId, new_end: BlockNumberFor] | Pallet: Crowdloan +UPDATE_MIN_CONTRIBUTION = namedtuple( + "UPDATE_MIN_CONTRIBUTION", + ["wallet", "pallet", "crowdloan_id", "new_min_contribution"], +) # args: [crowdloan_id: CrowdloanId, new_min_contribution: BalanceOf] | Pallet: Crowdloan +UPDATE_SYMBOL = namedtuple( + "UPDATE_SYMBOL", ["wallet", "pallet", "netuid", "symbol"] +) # args: [netuid: NetUid, symbol: Vec] | Pallet: SubtensorModule +UPGRADE_ACCOUNTS = namedtuple( + "UPGRADE_ACCOUNTS", ["wallet", "pallet", "who"] +) # args: [who: Vec] | Pallet: Balances +VOTE = namedtuple( + "VOTE", ["wallet", "pallet", "hotkey", "proposal", "index", "approve"] +) # args: [hotkey: T::AccountId, proposal: T::Hash, index: u32, approve: bool] | Pallet: SubtensorModule +VOTE = namedtuple( + "VOTE", ["wallet", "pallet", "proposal", "index", "approve"] +) # args: [proposal: T::Hash, index: ProposalIndex, approve: bool] | Pallet: Triumvirate +WITHDRAW = namedtuple( + "WITHDRAW", ["wallet", "pallet", "address", "value"] +) # args: [address: H160, value: BalanceOf] | Pallet: EVM +WITHDRAW = namedtuple( + "WITHDRAW", ["wallet", "pallet", "crowdloan_id"] +) # args: [crowdloan_id: CrowdloanId] | Pallet: Crowdloan +WITH_WEIGHT = namedtuple( + "WITH_WEIGHT", ["wallet", "pallet", "call", "weight"] +) # args: [call: Box<::RuntimeCall>, weight: Weight] | Pallet: Utility +WRITE_PULSE = namedtuple( + "WRITE_PULSE", ["wallet", "pallet", "pulses_payload", "signature"] +) # args: [pulses_payload: PulsesPayload>, signature: Option] | Pallet: Drand diff --git a/tests/e2e_tests/framework/calls/pallets.py b/tests/e2e_tests/framework/calls/pallets.py new file mode 100644 index 0000000000..05f8cf3b9b --- /dev/null +++ b/tests/e2e_tests/framework/calls/pallets.py @@ -0,0 +1,24 @@ +System = "System" +Timestamp = "Timestamp" +Grandpa = "Grandpa" +Balances = "Balances" +SubtensorModule = "SubtensorModule" +Triumvirate = "Triumvirate" +TriumvirateMembers = "TriumvirateMembers" +SenateMembers = "SenateMembers" +Utility = "Utility" +Sudo = "Sudo" +Multisig = "Multisig" +Preimage = "Preimage" +Scheduler = "Scheduler" +Proxy = "Proxy" +Registry = "Registry" +Commitments = "Commitments" +AdminUtils = "AdminUtils" +SafeMode = "SafeMode" +Ethereum = "Ethereum" +EVM = "EVM" +BaseFee = "BaseFee" +Drand = "Drand" +Crowdloan = "Crowdloan" +Swap = "Swap" diff --git a/tests/e2e_tests/framework/calls/sudo_calls.py b/tests/e2e_tests/framework/calls/sudo_calls.py new file mode 100644 index 0000000000..dc1710307d --- /dev/null +++ b/tests/e2e_tests/framework/calls/sudo_calls.py @@ -0,0 +1,265 @@ +""" +This file is auto-generated. Do not edit manually. + +For developers: +- Use the function `recreate_calls_subpackage()` to regenerate this file. +- The command lists are built dynamically from the current Subtensor metadata (`Subtensor.substrate.metadata`). +- Each command is represented as a `namedtuple` with fields: + * System arguments: wallet, pallet (and `sudo` for sudo calls). + * Additional arguments: taken from the extrinsic definition (with type hints for reference). +- These namedtuples are intended as convenient templates for building commands in tests and end-to-end scenarios. + +Note: + Any manual changes will be overwritten the next time the generator is run. +""" + +from collections import namedtuple + + +SUDO_AS = namedtuple( + "SUDO_AS", ["wallet", "pallet", "sudo", "who", "call"] +) # args: [who: AccountIdLookupOf, call: Box<::RuntimeCall>] | Pallet: Sudo +SUDO_SET_ACTIVITY_CUTOFF = namedtuple( + "SUDO_SET_ACTIVITY_CUTOFF", + ["wallet", "pallet", "sudo", "netuid", "activity_cutoff"], +) # args: [netuid: NetUid, activity_cutoff: u16] | Pallet: AdminUtils +SUDO_SET_ADJUSTMENT_ALPHA = namedtuple( + "SUDO_SET_ADJUSTMENT_ALPHA", + ["wallet", "pallet", "sudo", "netuid", "adjustment_alpha"], +) # args: [netuid: NetUid, adjustment_alpha: u64] | Pallet: AdminUtils +SUDO_SET_ADJUSTMENT_INTERVAL = namedtuple( + "SUDO_SET_ADJUSTMENT_INTERVAL", + ["wallet", "pallet", "sudo", "netuid", "adjustment_interval"], +) # args: [netuid: NetUid, adjustment_interval: u16] | Pallet: AdminUtils +SUDO_SET_ADMIN_FREEZE_WINDOW = namedtuple( + "SUDO_SET_ADMIN_FREEZE_WINDOW", ["wallet", "pallet", "sudo", "window"] +) # args: [window: u16] | Pallet: AdminUtils +SUDO_SET_ALPHA_SIGMOID_STEEPNESS = namedtuple( + "SUDO_SET_ALPHA_SIGMOID_STEEPNESS", + ["wallet", "pallet", "sudo", "netuid", "steepness"], +) # args: [netuid: NetUid, steepness: i16] | Pallet: AdminUtils +SUDO_SET_ALPHA_VALUES = namedtuple( + "SUDO_SET_ALPHA_VALUES", + ["wallet", "pallet", "sudo", "netuid", "alpha_low", "alpha_high"], +) # args: [netuid: NetUid, alpha_low: u16, alpha_high: u16] | Pallet: AdminUtils +SUDO_SET_BONDS_MOVING_AVERAGE = namedtuple( + "SUDO_SET_BONDS_MOVING_AVERAGE", + ["wallet", "pallet", "sudo", "netuid", "bonds_moving_average"], +) # args: [netuid: NetUid, bonds_moving_average: u64] | Pallet: AdminUtils +SUDO_SET_BONDS_PENALTY = namedtuple( + "SUDO_SET_BONDS_PENALTY", ["wallet", "pallet", "sudo", "netuid", "bonds_penalty"] +) # args: [netuid: NetUid, bonds_penalty: u16] | Pallet: AdminUtils +SUDO_SET_BONDS_RESET_ENABLED = namedtuple( + "SUDO_SET_BONDS_RESET_ENABLED", ["wallet", "pallet", "sudo", "netuid", "enabled"] +) # args: [netuid: NetUid, enabled: bool] | Pallet: AdminUtils +SUDO_SET_CK_BURN = namedtuple( + "SUDO_SET_CK_BURN", ["wallet", "pallet", "sudo", "burn"] +) # args: [burn: u64] | Pallet: AdminUtils +SUDO_SET_COLDKEY_SWAP_SCHEDULE_DURATION = namedtuple( + "SUDO_SET_COLDKEY_SWAP_SCHEDULE_DURATION", ["wallet", "pallet", "sudo", "duration"] +) # args: [duration: BlockNumberFor] | Pallet: AdminUtils +SUDO_SET_COMMIT_REVEAL_VERSION = namedtuple( + "SUDO_SET_COMMIT_REVEAL_VERSION", ["wallet", "pallet", "sudo", "version"] +) # args: [version: u16] | Pallet: AdminUtils +SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED = namedtuple( + "SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED", + ["wallet", "pallet", "sudo", "netuid", "enabled"], +) # args: [netuid: NetUid, enabled: bool] | Pallet: AdminUtils +SUDO_SET_COMMIT_REVEAL_WEIGHTS_INTERVAL = namedtuple( + "SUDO_SET_COMMIT_REVEAL_WEIGHTS_INTERVAL", + ["wallet", "pallet", "sudo", "netuid", "interval"], +) # args: [netuid: NetUid, interval: u64] | Pallet: AdminUtils +SUDO_SET_DEFAULT_TAKE = namedtuple( + "SUDO_SET_DEFAULT_TAKE", ["wallet", "pallet", "sudo", "default_take"] +) # args: [default_take: u16] | Pallet: AdminUtils +SUDO_SET_DIFFICULTY = namedtuple( + "SUDO_SET_DIFFICULTY", ["wallet", "pallet", "sudo", "netuid", "difficulty"] +) # args: [netuid: NetUid, difficulty: u64] | Pallet: AdminUtils +SUDO_SET_DISSOLVE_NETWORK_SCHEDULE_DURATION = namedtuple( + "SUDO_SET_DISSOLVE_NETWORK_SCHEDULE_DURATION", + ["wallet", "pallet", "sudo", "duration"], +) # args: [duration: BlockNumberFor] | Pallet: AdminUtils +SUDO_SET_EMA_PRICE_HALVING_PERIOD = namedtuple( + "SUDO_SET_EMA_PRICE_HALVING_PERIOD", + ["wallet", "pallet", "sudo", "netuid", "ema_halving"], +) # args: [netuid: NetUid, ema_halving: u64] | Pallet: AdminUtils +SUDO_SET_EVM_CHAIN_ID = namedtuple( + "SUDO_SET_EVM_CHAIN_ID", ["wallet", "pallet", "sudo", "chain_id"] +) # args: [chain_id: u64] | Pallet: AdminUtils +SUDO_SET_IMMUNITY_PERIOD = namedtuple( + "SUDO_SET_IMMUNITY_PERIOD", + ["wallet", "pallet", "sudo", "netuid", "immunity_period"], +) # args: [netuid: NetUid, immunity_period: u16] | Pallet: AdminUtils +SUDO_SET_KAPPA = namedtuple( + "SUDO_SET_KAPPA", ["wallet", "pallet", "sudo", "netuid", "kappa"] +) # args: [netuid: NetUid, kappa: u16] | Pallet: AdminUtils +SUDO_SET_LIQUID_ALPHA_ENABLED = namedtuple( + "SUDO_SET_LIQUID_ALPHA_ENABLED", ["wallet", "pallet", "sudo", "netuid", "enabled"] +) # args: [netuid: NetUid, enabled: bool] | Pallet: AdminUtils +SUDO_SET_LOCK_REDUCTION_INTERVAL = namedtuple( + "SUDO_SET_LOCK_REDUCTION_INTERVAL", ["wallet", "pallet", "sudo", "interval"] +) # args: [interval: u64] | Pallet: AdminUtils +SUDO_SET_MAX_ALLOWED_UIDS = namedtuple( + "SUDO_SET_MAX_ALLOWED_UIDS", + ["wallet", "pallet", "sudo", "netuid", "max_allowed_uids"], +) # args: [netuid: NetUid, max_allowed_uids: u16] | Pallet: AdminUtils +SUDO_SET_MAX_ALLOWED_VALIDATORS = namedtuple( + "SUDO_SET_MAX_ALLOWED_VALIDATORS", + ["wallet", "pallet", "sudo", "netuid", "max_allowed_validators"], +) # args: [netuid: NetUid, max_allowed_validators: u16] | Pallet: AdminUtils +SUDO_SET_MAX_BURN = namedtuple( + "SUDO_SET_MAX_BURN", ["wallet", "pallet", "sudo", "netuid", "max_burn"] +) # args: [netuid: NetUid, max_burn: TaoCurrency] | Pallet: AdminUtils +SUDO_SET_MAX_CHILDKEY_TAKE = namedtuple( + "SUDO_SET_MAX_CHILDKEY_TAKE", ["wallet", "pallet", "sudo", "take"] +) # args: [take: u16] | Pallet: SubtensorModule +SUDO_SET_MAX_DIFFICULTY = namedtuple( + "SUDO_SET_MAX_DIFFICULTY", ["wallet", "pallet", "sudo", "netuid", "max_difficulty"] +) # args: [netuid: NetUid, max_difficulty: u64] | Pallet: AdminUtils +SUDO_SET_MAX_REGISTRATIONS_PER_BLOCK = namedtuple( + "SUDO_SET_MAX_REGISTRATIONS_PER_BLOCK", + ["wallet", "pallet", "sudo", "netuid", "max_registrations_per_block"], +) # args: [netuid: NetUid, max_registrations_per_block: u16] | Pallet: AdminUtils +SUDO_SET_MAX_WEIGHT_LIMIT = namedtuple( + "SUDO_SET_MAX_WEIGHT_LIMIT", + ["wallet", "pallet", "sudo", "netuid", "max_weight_limit"], +) # args: [netuid: NetUid, max_weight_limit: u16] | Pallet: AdminUtils +SUDO_SET_MECHANISM_COUNT = namedtuple( + "SUDO_SET_MECHANISM_COUNT", + ["wallet", "pallet", "sudo", "netuid", "mechanism_count"], +) # args: [netuid: NetUid, mechanism_count: MechId] | Pallet: AdminUtils +SUDO_SET_MECHANISM_EMISSION_SPLIT = namedtuple( + "SUDO_SET_MECHANISM_EMISSION_SPLIT", + ["wallet", "pallet", "sudo", "netuid", "maybe_split"], +) # args: [netuid: NetUid, maybe_split: Option>] | Pallet: AdminUtils +SUDO_SET_MIN_ALLOWED_UIDS = namedtuple( + "SUDO_SET_MIN_ALLOWED_UIDS", + ["wallet", "pallet", "sudo", "netuid", "min_allowed_uids"], +) # args: [netuid: NetUid, min_allowed_uids: u16] | Pallet: AdminUtils +SUDO_SET_MIN_ALLOWED_WEIGHTS = namedtuple( + "SUDO_SET_MIN_ALLOWED_WEIGHTS", + ["wallet", "pallet", "sudo", "netuid", "min_allowed_weights"], +) # args: [netuid: NetUid, min_allowed_weights: u16] | Pallet: AdminUtils +SUDO_SET_MIN_BURN = namedtuple( + "SUDO_SET_MIN_BURN", ["wallet", "pallet", "sudo", "netuid", "min_burn"] +) # args: [netuid: NetUid, min_burn: TaoCurrency] | Pallet: AdminUtils +SUDO_SET_MIN_CHILDKEY_TAKE = namedtuple( + "SUDO_SET_MIN_CHILDKEY_TAKE", ["wallet", "pallet", "sudo", "take"] +) # args: [take: u16] | Pallet: SubtensorModule +SUDO_SET_MIN_DELEGATE_TAKE = namedtuple( + "SUDO_SET_MIN_DELEGATE_TAKE", ["wallet", "pallet", "sudo", "take"] +) # args: [take: u16] | Pallet: AdminUtils +SUDO_SET_MIN_DIFFICULTY = namedtuple( + "SUDO_SET_MIN_DIFFICULTY", ["wallet", "pallet", "sudo", "netuid", "min_difficulty"] +) # args: [netuid: NetUid, min_difficulty: u64] | Pallet: AdminUtils +SUDO_SET_NETWORK_IMMUNITY_PERIOD = namedtuple( + "SUDO_SET_NETWORK_IMMUNITY_PERIOD", ["wallet", "pallet", "sudo", "immunity_period"] +) # args: [immunity_period: u64] | Pallet: AdminUtils +SUDO_SET_NETWORK_MIN_LOCK_COST = namedtuple( + "SUDO_SET_NETWORK_MIN_LOCK_COST", ["wallet", "pallet", "sudo", "lock_cost"] +) # args: [lock_cost: TaoCurrency] | Pallet: AdminUtils +SUDO_SET_NETWORK_POW_REGISTRATION_ALLOWED = namedtuple( + "SUDO_SET_NETWORK_POW_REGISTRATION_ALLOWED", + ["wallet", "pallet", "sudo", "netuid", "registration_allowed"], +) # args: [netuid: NetUid, registration_allowed: bool] | Pallet: AdminUtils +SUDO_SET_NETWORK_RATE_LIMIT = namedtuple( + "SUDO_SET_NETWORK_RATE_LIMIT", ["wallet", "pallet", "sudo", "rate_limit"] +) # args: [rate_limit: u64] | Pallet: AdminUtils +SUDO_SET_NETWORK_REGISTRATION_ALLOWED = namedtuple( + "SUDO_SET_NETWORK_REGISTRATION_ALLOWED", + ["wallet", "pallet", "sudo", "netuid", "registration_allowed"], +) # args: [netuid: NetUid, registration_allowed: bool] | Pallet: AdminUtils +SUDO_SET_NOMINATOR_MIN_REQUIRED_STAKE = namedtuple( + "SUDO_SET_NOMINATOR_MIN_REQUIRED_STAKE", ["wallet", "pallet", "sudo", "min_stake"] +) # args: [min_stake: u64] | Pallet: AdminUtils +SUDO_SET_OWNER_HPARAM_RATE_LIMIT = namedtuple( + "SUDO_SET_OWNER_HPARAM_RATE_LIMIT", ["wallet", "pallet", "sudo", "epochs"] +) # args: [epochs: u16] | Pallet: AdminUtils +SUDO_SET_OWNER_IMMUNE_NEURON_LIMIT = namedtuple( + "SUDO_SET_OWNER_IMMUNE_NEURON_LIMIT", + ["wallet", "pallet", "sudo", "netuid", "immune_neurons"], +) # args: [netuid: NetUid, immune_neurons: u16] | Pallet: AdminUtils +SUDO_SET_RAO_RECYCLED = namedtuple( + "SUDO_SET_RAO_RECYCLED", ["wallet", "pallet", "sudo", "netuid", "rao_recycled"] +) # args: [netuid: NetUid, rao_recycled: TaoCurrency] | Pallet: AdminUtils +SUDO_SET_RECYCLE_OR_BURN = namedtuple( + "SUDO_SET_RECYCLE_OR_BURN", + ["wallet", "pallet", "sudo", "netuid", "recycle_or_burn"], +) # args: [netuid: NetUid, recycle_or_burn: pallet_subtensor::RecycleOrBurnEnum] | Pallet: AdminUtils +SUDO_SET_RHO = namedtuple( + "SUDO_SET_RHO", ["wallet", "pallet", "sudo", "netuid", "rho"] +) # args: [netuid: NetUid, rho: u16] | Pallet: AdminUtils +SUDO_SET_SERVING_RATE_LIMIT = namedtuple( + "SUDO_SET_SERVING_RATE_LIMIT", + ["wallet", "pallet", "sudo", "netuid", "serving_rate_limit"], +) # args: [netuid: NetUid, serving_rate_limit: u64] | Pallet: AdminUtils +SUDO_SET_SN_OWNER_HOTKEY = namedtuple( + "SUDO_SET_SN_OWNER_HOTKEY", ["wallet", "pallet", "sudo", "netuid", "hotkey"] +) # args: [netuid: NetUid, hotkey: ::AccountId] | Pallet: AdminUtils +SUDO_SET_STAKE_THRESHOLD = namedtuple( + "SUDO_SET_STAKE_THRESHOLD", ["wallet", "pallet", "sudo", "min_stake"] +) # args: [min_stake: u64] | Pallet: AdminUtils +SUDO_SET_SUBNET_LIMIT = namedtuple( + "SUDO_SET_SUBNET_LIMIT", ["wallet", "pallet", "sudo", "max_subnets"] +) # args: [max_subnets: u16] | Pallet: AdminUtils +SUDO_SET_SUBNET_MOVING_ALPHA = namedtuple( + "SUDO_SET_SUBNET_MOVING_ALPHA", ["wallet", "pallet", "sudo", "alpha"] +) # args: [alpha: I96F32] | Pallet: AdminUtils +SUDO_SET_SUBNET_OWNER_CUT = namedtuple( + "SUDO_SET_SUBNET_OWNER_CUT", ["wallet", "pallet", "sudo", "subnet_owner_cut"] +) # args: [subnet_owner_cut: u16] | Pallet: AdminUtils +SUDO_SET_SUBNET_OWNER_HOTKEY = namedtuple( + "SUDO_SET_SUBNET_OWNER_HOTKEY", ["wallet", "pallet", "sudo", "netuid", "hotkey"] +) # args: [netuid: NetUid, hotkey: ::AccountId] | Pallet: AdminUtils +SUDO_SET_SUBTOKEN_ENABLED = namedtuple( + "SUDO_SET_SUBTOKEN_ENABLED", + ["wallet", "pallet", "sudo", "netuid", "subtoken_enabled"], +) # args: [netuid: NetUid, subtoken_enabled: bool] | Pallet: AdminUtils +SUDO_SET_TARGET_REGISTRATIONS_PER_INTERVAL = namedtuple( + "SUDO_SET_TARGET_REGISTRATIONS_PER_INTERVAL", + ["wallet", "pallet", "sudo", "netuid", "target_registrations_per_interval"], +) # args: [netuid: NetUid, target_registrations_per_interval: u16] | Pallet: AdminUtils +SUDO_SET_TEMPO = namedtuple( + "SUDO_SET_TEMPO", ["wallet", "pallet", "sudo", "netuid", "tempo"] +) # args: [netuid: NetUid, tempo: u16] | Pallet: AdminUtils +SUDO_SET_TOGGLE_TRANSFER = namedtuple( + "SUDO_SET_TOGGLE_TRANSFER", ["wallet", "pallet", "sudo", "netuid", "toggle"] +) # args: [netuid: NetUid, toggle: bool] | Pallet: AdminUtils +SUDO_SET_TOTAL_ISSUANCE = namedtuple( + "SUDO_SET_TOTAL_ISSUANCE", ["wallet", "pallet", "sudo", "total_issuance"] +) # args: [total_issuance: TaoCurrency] | Pallet: AdminUtils +SUDO_SET_TX_CHILDKEY_TAKE_RATE_LIMIT = namedtuple( + "SUDO_SET_TX_CHILDKEY_TAKE_RATE_LIMIT", + ["wallet", "pallet", "sudo", "tx_rate_limit"], +) # args: [tx_rate_limit: u64] | Pallet: SubtensorModule +SUDO_SET_TX_DELEGATE_TAKE_RATE_LIMIT = namedtuple( + "SUDO_SET_TX_DELEGATE_TAKE_RATE_LIMIT", + ["wallet", "pallet", "sudo", "tx_rate_limit"], +) # args: [tx_rate_limit: u64] | Pallet: AdminUtils +SUDO_SET_TX_RATE_LIMIT = namedtuple( + "SUDO_SET_TX_RATE_LIMIT", ["wallet", "pallet", "sudo", "tx_rate_limit"] +) # args: [tx_rate_limit: u64] | Pallet: AdminUtils +SUDO_SET_WEIGHTS_SET_RATE_LIMIT = namedtuple( + "SUDO_SET_WEIGHTS_SET_RATE_LIMIT", + ["wallet", "pallet", "sudo", "netuid", "weights_set_rate_limit"], +) # args: [netuid: NetUid, weights_set_rate_limit: u64] | Pallet: AdminUtils +SUDO_SET_WEIGHTS_VERSION_KEY = namedtuple( + "SUDO_SET_WEIGHTS_VERSION_KEY", + ["wallet", "pallet", "sudo", "netuid", "weights_version_key"], +) # args: [netuid: NetUid, weights_version_key: u64] | Pallet: AdminUtils +SUDO_SET_YUMA3_ENABLED = namedtuple( + "SUDO_SET_YUMA3_ENABLED", ["wallet", "pallet", "sudo", "netuid", "enabled"] +) # args: [netuid: NetUid, enabled: bool] | Pallet: AdminUtils +SUDO_TOGGLE_EVM_PRECOMPILE = namedtuple( + "SUDO_TOGGLE_EVM_PRECOMPILE", + ["wallet", "pallet", "sudo", "precompile_id", "enabled"], +) # args: [precompile_id: PrecompileEnum, enabled: bool] | Pallet: AdminUtils +SUDO_TRIM_TO_MAX_ALLOWED_UIDS = namedtuple( + "SUDO_TRIM_TO_MAX_ALLOWED_UIDS", ["wallet", "pallet", "sudo", "netuid", "max_n"] +) # args: [netuid: NetUid, max_n: u16] | Pallet: AdminUtils +SUDO_UNCHECKED_WEIGHT = namedtuple( + "SUDO_UNCHECKED_WEIGHT", ["wallet", "pallet", "sudo", "call", "weight"] +) # args: [call: Box<::RuntimeCall>, weight: Weight] | Pallet: Sudo +SUDO_UNCHECKED_WEIGHT = namedtuple( + "SUDO_UNCHECKED_WEIGHT", ["wallet", "pallet", "sudo", "call", "weight"] +) # args: [call: Box, weight: Weight] | Pallet: SubtensorModule diff --git a/tests/e2e_tests/framework/subnet.py b/tests/e2e_tests/framework/subnet.py new file mode 100644 index 0000000000..965acdf860 --- /dev/null +++ b/tests/e2e_tests/framework/subnet.py @@ -0,0 +1,537 @@ +from typing import Optional, Union + +from async_substrate_interface.async_substrate import AsyncExtrinsicReceipt +from async_substrate_interface.sync_substrate import ExtrinsicReceipt +from bittensor_wallet import Wallet + +from bittensor.core.extrinsics.asyncex.utils import ( + sudo_call_extrinsic as async_sudo_call_extrinsic, +) +from bittensor.core.extrinsics.utils import sudo_call_extrinsic +from bittensor.core.types import ExtrinsicResponse +from bittensor.extras import SubtensorApi +from bittensor.utils.btlogging import logging +from .calls import * # noqa: F401# +from .utils import ( + is_instance_namedtuple, + split_command, + ACTIVATE_SUBNET, + STEPS, + REGISTER_NEURON, + REGISTER_SUBNET, +) + +CALL_RECORD = namedtuple("CALL_RECORD", ["idx", "operation", "response"]) + +# Use this constant to set netuid in set_hyperparameter using class `self._netuid` +NETUID = "SN_NETUID" + + +class TestSubnet: + """Class for managing test subnet operations.""" + + def __init__( + self, + subtensor: SubtensorApi, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ): + if not isinstance(subtensor, SubtensorApi): + raise TypeError("subtensor must be an instance of `SubtensorApi`.") + self.s: SubtensorApi = subtensor + self.period = period + self.raise_error = raise_error + self.wait_for_inclusion = wait_for_inclusion + self.wait_for_finalization = wait_for_finalization + + self._netuid: Optional[int] = None + self._calls: list[CALL_RECORD] = [] + + @property + def calls(self): + return self._calls + + @property + def netuid(self): + return self._netuid + + def execute_steps(self, steps: list[Union[STEPS, tuple]]): + """Executes a multiple steps synchronously.""" + for step in steps: + self.execute_one(step) + + async def async_execute_steps(self, steps: list[Union[STEPS, tuple]]): + """Executes a multiple steps asynchronously.""" + for step in steps: + await self.async_execute_one(step) + + def execute_one(self, step: Union[STEPS, tuple]) -> ExtrinsicResponse: + """Executes one step synchronously.""" + # subnet registration + response = None + if isinstance(step, REGISTER_SUBNET): + assert isinstance(step.wallet, Wallet), ( + "Bittensor Wallet instance must be provided to register subnet." + ) + response = self._register_subnet(step.wallet) + + # subnet activation + if isinstance(step, ACTIVATE_SUBNET): + assert isinstance(step.wallet, Wallet), ( + "subnet owner wallet must be provided to activate subnet." + ) + owner_wallet = step.wallet + netuid = step.netuid if isinstance(step.netuid, int) else self._netuid + response = self._activate_subnet(owner_wallet, netuid) + + # add neuron to subnet + if isinstance(step, REGISTER_NEURON): + assert isinstance(step.wallet, Wallet), ( + "neuron wallet must be provided to register in subnet." + ) + self._check_netuid() + neuron_wallet = step.wallet + netuid = step.netuid if isinstance(step.netuid, int) else self._netuid + response = self._register_neuron(neuron_wallet, netuid) + + if is_instance_namedtuple(step): + ( + sudo_or_owner_wallet, + call_module, + sudo_call, + call_function, + call_params, + ) = split_command(step) + assert isinstance(sudo_or_owner_wallet, Wallet), ( + "sudo wallet must be provided to the class to set tempo." + ) + + # use `NETUID` for netuid field in namedtuple for using TestSubnet.netuid in setting hyperparameter. + if hasattr(step, "netuid") and getattr(step, "netuid") == NETUID: + call_params.update({"netuid": self._netuid}) + + if call_function.startswith("sudo_") and sudo_call is None: + sudo_call = True + self._check_set_hp( + function_module=call_module, + function_name=call_function, + function_params=call_params, + ) + response = self.set_hyperparameter( + sudo_or_owner_wallet=sudo_or_owner_wallet, + call_function=call_function, + call_module=call_module, + call_params=call_params, + sudo_call=sudo_call, + ) + if not response: + raise NotImplementedError( + f"Execution for step {step} with type {type(step)}." + ) + return response + + async def async_execute_one(self, step: Union[STEPS, tuple]) -> ExtrinsicResponse: + """Executes one step asynchronously.""" + response = None + # subnet registration + if isinstance(step, REGISTER_SUBNET): + assert isinstance(step.wallet, Wallet), ( + "Bittensor Wallet instance must be provided to register subnet." + ) + response = await self._async_register_subnet(step.wallet) + + # subnet activation + if isinstance(step, ACTIVATE_SUBNET): + assert isinstance(step.wallet, Wallet), ( + "subnet owner wallet must be provided to activate subnet." + ) + owner_wallet = step.wallet + netuid = step.netuid if isinstance(step.netuid, int) else self._netuid + response = await self._async_activate_subnet(owner_wallet, netuid) + + # add neuron to subnet + if isinstance(step, REGISTER_NEURON): + assert isinstance(step.wallet, Wallet), ( + "neuron wallet must be provided to register in subnet." + ) + self._check_netuid() + neuron_wallet = step.wallet + netuid = step.netuid if isinstance(step.netuid, int) else self._netuid + response = await self._async_register_neuron(neuron_wallet, netuid) + + if is_instance_namedtuple(step): + ( + sudo_or_owner_wallet, + call_module, + sudo_call, + call_function, + call_params, + ) = split_command(step) + assert isinstance(sudo_or_owner_wallet, Wallet), ( + "sudo wallet must be provided to the class to set tempo." + ) + + # use `NETUID` for netuid field in namedtuple for using TestSubnet.netuid in setting hyperparameter. + if hasattr(step, "netuid") and getattr(step, "netuid") == NETUID: + call_params.update({"netuid": self._netuid}) + + if call_function.startswith("sudo_") and sudo_call is None: + sudo_call = True + self._check_set_hp( + function_module=call_module, + function_name=call_function, + function_params=call_params, + ) + response = await self.async_set_hyperparameter( + sudo_or_owner_wallet=sudo_or_owner_wallet, + call_function=call_function, + call_module=call_module, + call_params=call_params, + sudo_call=sudo_call, + ) + if not response: + raise NotImplementedError( + f"Execution for step {step} with type {type(step)}." + ) + return response + + def _register_subnet( + self, + owner_wallet: Wallet, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> ExtrinsicResponse: + """Register subnet on the chain.""" + self._check_register_subnet() + response = self.s.subnets.register_subnet( + wallet=owner_wallet, + period=period or self.period, + raise_error=raise_error or self.raise_error, + wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, + wait_for_finalization=wait_for_finalization or self.wait_for_finalization, + ) + self._check_response(response) + if netuid := _set_netuid_from_register_response( + response.extrinsic_receipt.triggered_events + ): + self._netuid = netuid + else: + self._netuid = self.s.subnets.get_total_subnets() - 1 + if response.success: + logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was registered.") + self._add_call_record(REGISTER_SUBNET.__name__, response) + return response + + async def _async_register_subnet( + self, + owner_wallet: Wallet, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> ExtrinsicResponse: + """Register subnet on the chain.""" + self._check_register_subnet() + response = await self.s.subnets.register_subnet( + wallet=owner_wallet, + period=period or self.period, + raise_error=raise_error or self.raise_error, + wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, + wait_for_finalization=wait_for_finalization or self.wait_for_finalization, + ) + self._check_response(response) + if netuid := _set_netuid_from_register_response( + await response.extrinsic_receipt.triggered_events + ): + self._netuid = netuid + else: + self._netuid = self.s.subnets.get_total_subnets() - 1 + if response.success: + logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was registered.") + self._add_call_record(REGISTER_SUBNET.__name__, response) + return response + + def _activate_subnet( + self, + owner_wallet: Wallet, + netuid: Optional[int] = None, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> ExtrinsicResponse: + """Activate subnet.""" + self._check_netuid() + current_block = self.s.block + activation_block = self.s.queries.query_constant( + "SubtensorModule", "DurationOfStartCall" + ).value + # added 10 blocks bc 2.5 seconds is not always enough for the chain to update. + self.s.wait_for_block(current_block + activation_block + 10) + response = self.s.subnets.start_call( + wallet=owner_wallet, + netuid=netuid or self._netuid, + period=period or self.period, + raise_error=raise_error or self.raise_error, + wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, + wait_for_finalization=wait_for_finalization or self.wait_for_finalization, + ) + if self._check_response(response): + logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was activated.") + assert self.s.subnets.is_subnet_active(self._netuid), ( + "Subnet was not activated." + ) + self._add_call_record(ACTIVATE_SUBNET.__name__, response) + return response + + async def _async_activate_subnet( + self, + owner_wallet: Wallet, + netuid: Optional[int] = None, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> ExtrinsicResponse: + """Activate subnet.""" + self._check_netuid() + current_block = await self.s.block + activation_block = ( + await self.s.queries.query_constant( + "SubtensorModule", "DurationOfStartCall" + ) + ).value + # added 10 blocks bc 2.5 seconds is not always enough for the chain to update. + await self.s.wait_for_block(current_block + activation_block + 10) + response = await self.s.subnets.start_call( + wallet=owner_wallet, + netuid=netuid or self._netuid, + period=period or self.period, + raise_error=raise_error or self.raise_error, + wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, + wait_for_finalization=wait_for_finalization or self.wait_for_finalization, + ) + if self._check_response(response): + logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was activated.") + assert await self.s.subnets.is_subnet_active(self._netuid), ( + "Subnet was not activated." + ) + self._add_call_record(ACTIVATE_SUBNET.__name__, response) + return response + + def _register_neuron( + self, + neuron_wallet: Wallet, + netuid: Optional[int] = None, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> ExtrinsicResponse: + """Add neuron to the subnet.""" + response = self.s.subnets.burned_register( + wallet=neuron_wallet, + netuid=netuid or self._netuid, + period=period or self.period, + raise_error=raise_error or self.raise_error, + wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, + wait_for_finalization=wait_for_finalization or self.wait_for_finalization, + ) + self._check_response(response) + if response.success: + logging.console.info( + f"Neuron [blue]{neuron_wallet.name.capitalize()} <{neuron_wallet.hotkey.ss58_address}>[/blue] was " + f"registered in subnet [blue]{self._netuid}[/blue]." + ) + self._add_call_record(REGISTER_NEURON.__name__, response) + return response + + async def _async_register_neuron( + self, + neuron_wallet: Wallet, + netuid: Optional[int] = None, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> ExtrinsicResponse: + """Add neuron to the subnet.""" + response = await self.s.subnets.burned_register( + wallet=neuron_wallet, + netuid=netuid or self._netuid, + period=period or self.period, + raise_error=raise_error or self.raise_error, + wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, + wait_for_finalization=wait_for_finalization or self.wait_for_finalization, + ) + self._check_response(response) + if response.success: + logging.console.info( + f"Neuron [blue]{neuron_wallet.name.capitalize()} <{neuron_wallet.hotkey.ss58_address}>[/blue] was " + f"registered in subnet [blue]{self._netuid}[/blue]." + ) + self._add_call_record(REGISTER_NEURON.__name__, response) + return response + + def set_hyperparameter( + self, + sudo_or_owner_wallet: Wallet, + call_function: str, + call_module: str, + call_params: dict, + sudo_call: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> ExtrinsicResponse: + """Set hyperparameter for the chain or subnet.""" + response = sudo_call_extrinsic( + subtensor=self.s.inner_subtensor, + wallet=sudo_or_owner_wallet, + call_function=call_function, + call_module=call_module, + call_params=call_params, + period=period or self.period, + raise_error=raise_error or self.raise_error, + wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, + wait_for_finalization=wait_for_finalization or self.wait_for_finalization, + root=not sudo_call, + ) + + if self._check_response(response): + logging.console.info( + f"Hyperparameter [blue]{call_function}[/blue] was set successfully with params [blue]{call_params}[/blue]." + ) + self._add_call_record("SET_HYPERPARAMETER", response) + return response + + async def async_set_hyperparameter( + self, + sudo_or_owner_wallet: Wallet, + call_function: str, + call_module: str, + call_params: dict, + sudo_call: bool = False, + period: Optional[int] = None, + raise_error: bool = False, + wait_for_inclusion: bool = True, + wait_for_finalization: bool = True, + ) -> ExtrinsicResponse: + """Set hyperparameter for the chain or subnet.""" + response = await async_sudo_call_extrinsic( + subtensor=self.s.inner_subtensor, + wallet=sudo_or_owner_wallet, + call_function=call_function, + call_module=call_module, + call_params=call_params, + period=period or self.period, + raise_error=raise_error or self.raise_error, + wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, + wait_for_finalization=wait_for_finalization or self.wait_for_finalization, + root=not sudo_call, + ) + + if self._check_response(response): + logging.console.info( + f"Hyperparameter [blue]{call_function}[/blue] was set successfully with params [blue]{call_params}[/blue]." + ) + self._add_call_record("SET_HYPERPARAMETER", response) + return response + + def wait_next_epoch(self, netuid: Optional[int] = None): + """Sync wait until the next epoch first block is reached.""" + netuid = netuid or self._netuid + if not netuid: + self._check_netuid() + current_block = self.s.block + next_epoch_block = self.s.subnets.get_next_epoch_start_block(netuid) + logging.console.info( + f"Waiting for next epoch first block: [blue]{next_epoch_block}[/blue]. " + f"Current block: [blue]{current_block}[/blue]." + ) + self.s.wait_for_block(next_epoch_block + 1) + + async def async_wait_next_epoch(self, netuid: Optional[int] = None): + """Async wait until the next epoch first block is reached.""" + netuid = netuid or self._netuid + if not netuid: + self._check_netuid() + current_block = await self.s.block + next_epoch_block = self.s.subnets.get_next_epoch_start_block(netuid) + logging.console.info( + f"Waiting for next epoch first block: [blue]{next_epoch_block}[/blue]. " + f"Current block: [blue]{current_block}[/blue]." + ) + await self.s.wait_for_block(next_epoch_block + 1) + + def _check_netuid(self): + assert self._netuid is not None, ( + "Subnet must be registered before any subnet action is performed." + ) + + def _add_call_record(self, operation: str, response: ExtrinsicResponse): + """Add extrinsic response to the calls list.""" + self._calls.append(CALL_RECORD(len(self._calls), operation, response)) + + def _check_set_hp( + self, + function_module: str, + function_name: str, + function_params: Optional[dict] = None, + ): + """Check if the function exists in the Subtensor node and parameters are provided for the function correctly.""" + pallet = self.s.substrate.metadata.get_metadata_pallet(function_module) + assert pallet is not None, f"Pallet {function_module} is not found." + functions = getattr(pallet.calls, "value", None) + functions_call = [f for f in functions if f.get("name", None) == function_name] + function_call = functions_call[0] if functions_call else None + assert function_call is not None, ( + f"Function {function_name} is not found in pallet {function_module}." + ) + function_call_fields = function_call.get("fields", []) + parameters = [field.get("name") for field in function_call_fields] + assert parameters is not None, ( + f"Parameters are required for function {function_name}." + ) + if "netuid" in parameters and not function_params.get("netuid", None): + function_params.update({"netuid": self._netuid}) + assert len(parameters) == len(function_params) is not False, ( + f"Not all parameters {function_params} where provided for function {function_name}. " + f"All required parameters: {parameters}." + ) + + def _check_response(self, response: ExtrinsicResponse) -> bool: + """Check if the call was successful.""" + if response.success: + return True + + if self.raise_error: + if response.error: + raise response.error + raise RuntimeError(response.message) + + logging.console.warning(response.message) + return False + + def _check_register_subnet(self): + """Avoids multiple subnet registrations within the same class instance.""" + if self._netuid: + raise RuntimeError( + "This instance already has associated netuid. Cannot register again. " + "To register a new subnet, create a new instance of TestSubnet class." + ) + + +def _set_netuid_from_register_response( + events: Union["AsyncExtrinsicReceipt", "ExtrinsicReceipt"], +): + """Get netuid from the register subnet response.""" + for event in events: + if event.get("event_id") == "NetworkAdded": + return event.get("attributes")[0] + return None diff --git a/tests/e2e_tests/framework/utils.py b/tests/e2e_tests/framework/utils.py new file mode 100644 index 0000000000..2da38cf439 --- /dev/null +++ b/tests/e2e_tests/framework/utils.py @@ -0,0 +1,51 @@ +import os +from dataclasses import dataclass +from typing import Optional, Union + +from bittensor_wallet import Wallet + +from bittensor.core.subtensor import Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor +from bittensor.core.types import ExtrinsicResponse + + +@dataclass +class RegisterSubnet: + wallet: Wallet + + +@dataclass +class ActivateSubnet: + wallet: Wallet + netuid: Optional[int] = None + + +@dataclass +class RegisterNeuron: + wallet: Wallet + netuid: Optional[int] = None + + +REGISTER_SUBNET = RegisterSubnet +ACTIVATE_SUBNET = ActivateSubnet +REGISTER_NEURON = RegisterNeuron + +STEPS = Union[REGISTER_SUBNET, ACTIVATE_SUBNET, REGISTER_NEURON] + + +def split_command(command): + """Parse command and return four objects (wallet, pallet, sudo, kwargs).""" + d = command._asdict() + wallet = d.pop("wallet") + pallet = d.pop("pallet") + sudo = d.pop("sudo") + func_name = type(command).__name__.lower() + kwargs = d + return wallet, pallet, sudo, func_name, kwargs + + +def is_instance_namedtuple(obj): + """Check if the object is an instance of a namedtuple.""" + return ( + isinstance(obj, tuple) and hasattr(obj, "_fields") and hasattr(obj, "_asdict") + ) diff --git a/tests/e2e_tests/utils/__init__.py b/tests/e2e_tests/utils/__init__.py new file mode 100644 index 0000000000..d81e647cdb --- /dev/null +++ b/tests/e2e_tests/utils/__init__.py @@ -0,0 +1,212 @@ +import functools +import time +from typing import TYPE_CHECKING + +from bittensor.utils.balance import Balance +from bittensor.utils.btlogging import logging + +from tests.e2e_tests.framework import * # noqa: F401 + + +if TYPE_CHECKING: + from bittensor import Wallet + from bittensor.extras import SubtensorApi + + +def get_dynamic_balance(rao: int, netuid: int = 0): + """Returns a Balance object with the given rao and netuid for testing purposes with dynamic values.""" + return Balance.from_rao(rao).set_unit(netuid) + + +def execute_and_wait_for_next_nonce( + 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.""" + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + for attempt in range(max_retries): + start_nonce = subtensor.substrate.get_account_next_index( + wallet.hotkey.ss58_address + ) + + result = func(*args, **kwargs) + + start_time = time.time() + + while time.time() - start_time < timeout: + current_nonce = subtensor.substrate.get_account_next_index( + 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) + + logging.warning( + f"⚠️ Attempt {attempt + 1}/{max_retries}: Nonce did not increment." + ) + raise TimeoutError(f"❌ Nonce did not change after {max_retries} attempts.") + + return wrapper + + return decorator + + +def set_identity( + subtensor: "SubtensorApi", + wallet, + name="", + url="", + github_repo="", + image="", + discord="", + description="", + additional="", +): + return subtensor.sign_and_send_extrinsic( + call=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=wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + +async def async_set_identity( + subtensor: "SubtensorApi", + wallet: "Wallet", + name="", + url="", + github_repo="", + image="", + discord="", + description="", + additional="", +): + return await subtensor.sign_and_send_extrinsic( + call=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=wallet, + wait_for_inclusion=True, + wait_for_finalization=True, + ) + + +def propose(subtensor, wallet, proposal, duration): + return subtensor.sign_and_send_extrinsic( + call=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, + ) + + +async def async_propose( + subtensor: "SubtensorApi", + 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: "SubtensorApi", + wallet: "Wallet", + hotkey, + proposal, + index, + approve, +): + return subtensor.sign_and_send_extrinsic( + call=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, + ) + + +async def async_vote( + subtensor: "SubtensorApi", + 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 ecf5e48d135fe040e57bc1f80083110cc5969b2c Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 20:00:02 -0700 Subject: [PATCH 312/416] make `sudo_call_extrinsic` super useful --- bittensor/core/extrinsics/asyncex/utils.py | 25 ++++++++++++---------- bittensor/core/extrinsics/utils.py | 25 ++++++++++++---------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/utils.py b/bittensor/core/extrinsics/asyncex/utils.py index 14665807ad..15cc39a934 100644 --- a/bittensor/core/extrinsics/asyncex/utils.py +++ b/bittensor/core/extrinsics/asyncex/utils.py @@ -49,6 +49,7 @@ async def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + root: bool = False, ) -> ExtrinsicResponse: """Execute a sudo call extrinsic. @@ -67,6 +68,7 @@ async def sudo_call_extrinsic( 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. + root: Whether to use the root account for not sudo call in some subtensor pallet. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -79,19 +81,20 @@ async def sudo_call_extrinsic( ).success: return unlocked - sudo_call = await subtensor.substrate.compose_call( - call_module="Sudo", - call_function="sudo", - call_params={ - "call": await subtensor.substrate.compose_call( - call_module=call_module, - call_function=call_function, - call_params=call_params, - ) - }, + call = await subtensor.substrate.compose_call( + call_module=call_module, + call_function=call_function, + call_params=call_params, ) + if not root: + call = await subtensor.substrate.compose_call( + call_module="Sudo", + call_function="sudo", + call_params={"call": call}, + ) + return await subtensor.sign_and_send_extrinsic( - call=sudo_call, + call=call, wallet=wallet, sign_with=sign_with, use_nonce=use_nonce, diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 7849144f8e..097e0d652c 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -84,6 +84,7 @@ def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, + root: Optional[bool] = None, ) -> ExtrinsicResponse: """Execute a sudo call extrinsic. @@ -102,6 +103,7 @@ def sudo_call_extrinsic( 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. + root: Whether to use the root account for not sudo call in some subtensor pallet. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -114,19 +116,20 @@ def sudo_call_extrinsic( ).success: return unlocked - sudo_call = subtensor.substrate.compose_call( - call_module="Sudo", - call_function="sudo", - call_params={ - "call": subtensor.substrate.compose_call( - call_module=call_module, - call_function=call_function, - call_params=call_params, - ) - }, + call = subtensor.substrate.compose_call( + call_module=call_module, + call_function=call_function, + call_params=call_params, ) + if not root: + call = subtensor.substrate.compose_call( + call_module="Sudo", + call_function="sudo", + call_params={"call": call}, + ) + return subtensor.sign_and_send_extrinsic( - call=sudo_call, + call=call, wallet=wallet, sign_with=sign_with, use_nonce=use_nonce, From da6d3a6e80b3f18808cf46bed1bad9bab379df71 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 20:00:45 -0700 Subject: [PATCH 313/416] update SubtensorApi --- bittensor/extras/subtensor_api/subnets.py | 1 + bittensor/extras/subtensor_api/wallets.py | 1 + 2 files changed, 2 insertions(+) diff --git a/bittensor/extras/subtensor_api/subnets.py b/bittensor/extras/subtensor_api/subnets.py index 75b8adbf13..6c0ef8bb59 100644 --- a/bittensor/extras/subtensor_api/subnets.py +++ b/bittensor/extras/subtensor_api/subnets.py @@ -47,6 +47,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.register = subtensor.register self.register_subnet = subtensor.register_subnet self.set_subnet_identity = subtensor.set_subnet_identity + self.start_call = subtensor.start_call self.subnet = subtensor.subnet self.subnet_exists = subtensor.subnet_exists self.subnetwork_n = subtensor.subnetwork_n diff --git a/bittensor/extras/subtensor_api/wallets.py b/bittensor/extras/subtensor_api/wallets.py index 3ad195ae45..12034ed641 100644 --- a/bittensor/extras/subtensor_api/wallets.py +++ b/bittensor/extras/subtensor_api/wallets.py @@ -38,3 +38,4 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_stake_movement_fee = subtensor.get_stake_movement_fee self.get_transfer_fee = subtensor.get_transfer_fee self.get_unstake_fee = subtensor.get_unstake_fee + self.set_children = subtensor.set_children From 4a0d2785dddfe2a8253b7e7f577a5286d5d75829 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 20:01:00 -0700 Subject: [PATCH 314/416] one more unit test --- tests/unit_tests/test_async_subtensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 0d5a2bde18..7fe85689be 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -2507,7 +2507,9 @@ async def test_blocks_since_last_update_success(subtensor, mocker): fake_blocks_since_update = current_block - last_update_block mocker.patch.object( - subtensor.substrate, "get_block_number", return_value=current_block, + subtensor.substrate, + "get_block_number", + return_value=current_block, ) mocked_get_hyperparameter = mocker.patch.object( subtensor, From 7fe01a5ff76d32bcf1b9d9138569063a86a84d29 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 20:01:20 -0700 Subject: [PATCH 315/416] imports optimization --- 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 dd382e5d8f..1729796798 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -88,8 +88,8 @@ ) from bittensor.core.metagraph import AsyncMetagraph from bittensor.core.settings import version_as_int, TYPE_REGISTRY -from bittensor.core.types import ExtrinsicResponse from bittensor.core.types import ( + ExtrinsicResponse, ParamWithTypes, Salt, SubtensorMixin, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 3aa64c737f..31f912e1f2 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -91,8 +91,8 @@ SS58_FORMAT, TYPE_REGISTRY, ) -from bittensor.core.types import ExtrinsicResponse from bittensor.core.types import ( + ExtrinsicResponse, ParamWithTypes, Salt, SubtensorMixin, From 784495653429c66bc4a799acd3a35dc5372ac69a Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 20:01:37 -0700 Subject: [PATCH 316/416] apply framework to all e2e tests --- tests/e2e_tests/test_axon.py | 48 +- tests/e2e_tests/test_commit_reveal.py | 293 ++--- tests/e2e_tests/test_commit_weights.py | 439 +++---- tests/e2e_tests/test_commitment.py | 192 ++- tests/e2e_tests/test_delegate.py | 209 ++-- tests/e2e_tests/test_dendrite.py | 231 +--- tests/e2e_tests/test_hotkeys.py | 431 +++---- tests/e2e_tests/test_incentive.py | 204 +--- tests/e2e_tests/test_liquid_alpha.py | 447 +++---- tests/e2e_tests/test_liquidity.py | 166 ++- tests/e2e_tests/test_metagraph.py | 398 +++---- tests/e2e_tests/test_neuron_certificate.py | 42 +- tests/e2e_tests/test_reveal_commitments.py | 105 +- tests/e2e_tests/test_root_set_weights.py | 164 ++- tests/e2e_tests/test_set_weights.py | 337 ++---- tests/e2e_tests/test_stake_fee.py | 33 +- tests/e2e_tests/test_staking.py | 1159 +++++++------------ tests/e2e_tests/test_subtensor_functions.py | 87 +- tests/e2e_tests/test_transfer.py | 2 +- 19 files changed, 1881 insertions(+), 3106 deletions(-) diff --git a/tests/e2e_tests/test_axon.py b/tests/e2e_tests/test_axon.py index 2723a61261..a2ef032678 100644 --- a/tests/e2e_tests/test_axon.py +++ b/tests/e2e_tests/test_axon.py @@ -2,8 +2,12 @@ import pytest -from bittensor import logging from bittensor.utils import networking +from tests.e2e_tests.utils import ( + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, +) @pytest.mark.asyncio @@ -19,15 +23,14 @@ async def test_axon(subtensor, templates, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - netuid = 2 + alice_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + alice_sn.execute_steps(steps) - # Register a subnet, netuid 2 - assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created" - - # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" - - metagraph = subtensor.metagraphs.metagraph(netuid) + metagraph = subtensor.metagraphs.metagraph(alice_sn.netuid) # Validate current metagraph stats old_axon = metagraph.axons[0] @@ -42,12 +45,12 @@ async def test_axon(subtensor, templates, alice_wallet): 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): + async with templates.miner(alice_wallet, alice_sn.netuid): # Waiting for 5 seconds for metagraph to be updated await asyncio.sleep(5) # Refresh the metagraph - metagraph = subtensor.metagraphs.metagraph(netuid) + metagraph = subtensor.metagraphs.metagraph(alice_sn.netuid) updated_axon = metagraph.axons[0] external_ip = networking.get_external_ip() @@ -92,19 +95,14 @@ async def test_axon_async(async_subtensor, templates, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - 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" - ) + alice_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + await alice_sn.async_execute_steps(steps) - metagraph = await async_subtensor.metagraphs.metagraph(netuid) + metagraph = await async_subtensor.metagraphs.metagraph(alice_sn.netuid) # Validate current metagraph stats old_axon = metagraph.axons[0] @@ -119,12 +117,12 @@ async def test_axon_async(async_subtensor, templates, alice_wallet): 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): + async with templates.miner(alice_wallet, alice_sn.netuid): # Waiting for 5 seconds for metagraph to be updated await asyncio.sleep(5) # Refresh the metagraph - metagraph = await async_subtensor.metagraphs.metagraph(netuid) + metagraph = await async_subtensor.metagraphs.metagraph(alice_sn.netuid) updated_axon = metagraph.axons[0] external_ip = networking.get_external_ip() diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index ee8d10386d..452df32237 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -1,35 +1,28 @@ import asyncio -import re import time import numpy as np import pytest -from bittensor.core.extrinsics.asyncex.sudo import ( - sudo_set_mechanism_count_extrinsic as async_sudo_set_mechanism_count_extrinsic, - sudo_set_admin_freeze_window_extrinsic as async_sudo_set_admin_freeze_window_extrinsic, -) -from bittensor.core.extrinsics.sudo import ( - sudo_set_mechanism_count_extrinsic, - sudo_set_admin_freeze_window_extrinsic, -) 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_wait_interval, - async_sudo_set_admin_utils, - async_sudo_set_hyperparameter_bool, - sudo_set_admin_utils, - sudo_set_hyperparameter_bool, - wait_interval, - next_tempo, +from tests.e2e_tests.utils import ( + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + SUDO_SET_ADMIN_FREEZE_WINDOW, + SUDO_SET_TEMPO, + SUDO_SET_MECHANISM_COUNT, + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED, + SUDO_SET_WEIGHTS_SET_RATE_LIMIT, + NETUID, + AdminUtils, ) -TESTED_SUB_SUBNETS = 2 +TESTED_MECHANISMS = 2 -@pytest.mark.asyncio -async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): +def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): """ Tests the commit/reveal weights mechanism (CRv4) @@ -44,91 +37,43 @@ async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0) - # 12 for non-fast-block, 0.25 for fast block BLOCK_TIME, TEMPO_TO_SET = ( (0.25, 100) if subtensor.chain.is_fast_blocks() else (12.0, 20) ) - logging.console.info(f"Using block time: {BLOCK_TIME}") - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - - # Register root as Alice - assert subtensor.extrinsics.register_subnet(alice_wallet), ( - "Unable to register the subnet" - ) - - # Verify subnet 2 created successfully - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - f"SN #{alice_subnet_netuid} wasn't created successfully" - ) - - assert sudo_set_mechanism_count_extrinsic( - subtensor, alice_wallet, alice_subnet_netuid, TESTED_SUB_SUBNETS - ), "Cannot create sub-subnets." - - logging.console.success(f"SN #{alice_subnet_netuid} is registered.") - - # Enable commit_reveal on the subnet - assert sudo_set_hyperparameter_bool( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_commit_reveal_weights_enabled", - value=True, - netuid=alice_subnet_netuid, - ), f"Unable to enable commit reveal on the SN #{alice_subnet_netuid}" - - # Verify commit_reveal was enabled - assert subtensor.subnets.commit_reveal_enabled(alice_subnet_netuid), ( - "Failed to enable commit/reveal" - ) - logging.console.success("Commit reveal enabled") + # Create and prepare subnet + alice_sn = TestSubnet(subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_MECHANISM_COUNT( + alice_wallet, AdminUtils, True, NETUID, TESTED_MECHANISMS + ), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, True + ), + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0), + ] + alice_sn.execute_steps(steps) cr_version = subtensor.substrate.query( module="SubtensorModule", storage_function="CommitRevealWeightsVersion" ) - assert cr_version == 4, f"Commit reveal version is not 3, got {cr_version}" - - # Change the weights rate limit on the subnet - status, error = sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_weights_set_rate_limit", - call_params={"netuid": alice_subnet_netuid, "weights_set_rate_limit": "0"}, + expected_cr_version = 4 + assert cr_version == expected_cr_version, ( + f"Commit reveal version is not {expected_cr_version}, got {cr_version}" ) - assert status is True - assert error is None - # Verify weights rate limit was changed - assert ( - subtensor.subnets.get_subnet_hyperparameters( - netuid=alice_subnet_netuid - ).weights_rate_limit - == 0 - ), "Failed to set weights_rate_limit" - assert subtensor.subnets.weights_rate_limit(netuid=alice_subnet_netuid) == 0 + assert subtensor.subnets.weights_rate_limit(netuid=alice_sn.netuid) == 0 logging.console.success("sudo_set_weights_set_rate_limit executed: set to 0") - # Change the tempo of the subnet - 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 + tempo = subtensor.subnets.get_subnet_hyperparameters(netuid=alice_sn.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}") # Commit-reveal values - setting weights to self uids = np.array([0], dtype=np.int64) @@ -143,29 +88,24 @@ async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): # Fetch current block and calculate next tempo for the subnet current_block = subtensor.chain.get_current_block() - upcoming_tempo = next_tempo(current_block, tempo) + upcoming_tempo = subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) logging.console.info( f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" ) # Lower than this might mean weights will get revealed before we can check them if upcoming_tempo - current_block < 6: - await wait_interval( - tempo, - subtensor, - netuid=alice_subnet_netuid, - reporting_interval=1, - ) + alice_sn.wait_next_epoch() current_block = subtensor.chain.get_current_block() latest_drand_round = subtensor.chain.last_drand_round() - upcoming_tempo = next_tempo(current_block, tempo) + upcoming_tempo = subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) logging.console.info( f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" ) - for mechid in range(TESTED_SUB_SUBNETS): + for mechid in range(TESTED_MECHANISMS): logging.console.info( - f"[magenta]Testing subnet mechanism: {alice_subnet_netuid}.{mechid}[/magenta]" + f"[magenta]Testing subnet mechanism: {alice_sn.netuid}.{mechid}[/magenta]" ) # commit_block is the block when weights were committed on the chain (transaction block) @@ -173,7 +113,7 @@ async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): # Commit weights response = subtensor.extrinsics.set_weights( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, mechid=mechid, uids=weight_uids, weights=weight_vals, @@ -198,7 +138,7 @@ async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): # Fetch current commits pending on the chain commits_on_chain = subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid, mechid=mechid + netuid=alice_sn.netuid, mechid=mechid ) address, commit_block, commit, reveal_round = commits_on_chain[0] @@ -214,14 +154,12 @@ async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): ] # Ensure no weights are available as of now - assert ( - subtensor.subnets.weights(netuid=alice_subnet_netuid, mechid=mechid) == [] - ) + assert subtensor.subnets.weights(netuid=alice_sn.netuid, mechid=mechid) == [] logging.console.success("No weights are available before next epoch.") # 5 is safety drand offset expected_reveal_block = ( - subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 5 + subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) + 5 ) logging.console.info( @@ -242,7 +180,7 @@ async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): # Fetch weights on the chain as they should be revealed now subnet_weights = subtensor.subnets.weights( - netuid=alice_subnet_netuid, mechid=mechid + netuid=alice_sn.netuid, mechid=mechid ) assert subnet_weights != [], "Weights are not available yet." @@ -260,7 +198,7 @@ async def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): # Now that the commit has been revealed, there shouldn't be any pending commits assert ( subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid, mechid=mechid + netuid=alice_sn.netuid, mechid=mechid ) == [] ) @@ -287,96 +225,45 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert await async_sudo_set_admin_freeze_window_extrinsic( - async_subtensor, alice_wallet, 0 - ) - # 12 for non-fast-block, 0.25 for fast block BLOCK_TIME, TEMPO_TO_SET = ( - (0.25, 100) if (await async_subtensor.chain.is_fast_blocks()) else (12.0, 20) + (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 - - # Register root as Alice - assert await async_subtensor.extrinsics.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"SN #{alice_subnet_netuid} wasn't created successfully" - ) - - assert await async_sudo_set_mechanism_count_extrinsic( - async_subtensor, alice_wallet, alice_subnet_netuid, TESTED_SUB_SUBNETS - ), "Cannot create sub-subnets." - - logging.console.success(f"SN #{alice_subnet_netuid} is registered.") - - # 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=alice_subnet_netuid, - ), f"Unable to enable commit reveal on the SN #{alice_subnet_netuid}" - - # Verify commit_reveal was enabled - assert await async_subtensor.subnets.commit_reveal_enabled(alice_subnet_netuid), ( - "Failed to enable commit/reveal" - ) - logging.console.success("Commit reveal enabled") + # Create and prepare subnet + alice_sn = TestSubnet(async_subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_MECHANISM_COUNT( + alice_wallet, AdminUtils, True, NETUID, TESTED_MECHANISMS + ), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, True + ), + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0), + ] + await alice_sn.async_execute_steps(steps) cr_version = await async_subtensor.substrate.query( module="SubtensorModule", storage_function="CommitRevealWeightsVersion" ) - assert cr_version == 4, f"Commit reveal version is not 3, got {cr_version}" - - # Change the weights rate limit on the subnet - 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": "0"}, + expected_cr_version = 4 + assert cr_version == 4, ( + f"Commit reveal version is not {expected_cr_version}, got {cr_version}" ) - assert status is True - assert error is None - # Verify weights rate limit was changed - assert ( - await async_subtensor.subnets.get_subnet_hyperparameters( - netuid=alice_subnet_netuid - ) - ).weights_rate_limit == 0, "Failed to set weights_rate_limit" - assert ( - await async_subtensor.subnets.weights_rate_limit(netuid=alice_subnet_netuid) - == 0 - ) + assert await async_subtensor.subnets.weights_rate_limit(netuid=alice_sn.netuid) == 0 logging.console.success("sudo_set_weights_set_rate_limit executed: set to 0") - # Change the tempo of the subnet - 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 - ) + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=alice_sn.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}") # Commit-reveal values - setting weights to self uids = np.array([0], dtype=np.int64) @@ -391,35 +278,35 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet # Fetch current block and calculate next tempo for the subnet current_block = await async_subtensor.chain.get_current_block() - upcoming_tempo = next_tempo(current_block, tempo) + upcoming_tempo = await async_subtensor.subnets.get_next_epoch_start_block( + alice_sn.netuid + ) logging.console.info( f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" ) # Lower than this might mean weights will get revealed before we can check them if upcoming_tempo - current_block < 6: - await async_wait_interval( - tempo, - async_subtensor, - netuid=alice_subnet_netuid, - reporting_interval=1, - ) + await alice_sn.async_wait_next_epoch() + current_block = await async_subtensor.chain.get_current_block() latest_drand_round = await async_subtensor.chain.last_drand_round() - upcoming_tempo = next_tempo(current_block, tempo) + upcoming_tempo = await async_subtensor.subnets.get_next_epoch_start_block( + alice_sn.netuid + ) logging.console.info( f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" ) - for mechid in range(TESTED_SUB_SUBNETS): + for mechid in range(TESTED_MECHANISMS): logging.console.info( - f"[magenta]Testing subnet mechanism: {alice_subnet_netuid}.{mechid}[/magenta]" + f"[magenta]Testing subnet mechanism: {alice_sn.netuid}.{mechid}[/magenta]" ) # Commit weights response = await async_subtensor.extrinsics.set_weights( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, mechid=mechid, uids=weight_uids, weights=weight_vals, @@ -440,11 +327,14 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet ) # Fetch current commits pending on the chain - await async_subtensor.wait_for_block(await async_subtensor.block + 12) + await async_subtensor.wait_for_block( + await async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) + + 1 + ) commits_on_chain = ( await async_subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid, mechid=mechid + netuid=alice_sn.netuid, mechid=mechid ) ) address, commit_block, commit, reveal_round = commits_on_chain[0] @@ -453,23 +343,16 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet assert expected_reveal_round == reveal_round assert address == alice_wallet.hotkey.ss58_address - # bc of the drand delay, the commit block can be either the previous block or the current block - # 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.subnets.weights( - netuid=alice_subnet_netuid, mechid=mechid - ) + await async_subtensor.subnets.weights(netuid=alice_sn.netuid, mechid=mechid) == [] ) logging.console.success("No weights are available before next epoch.") # 5 is safety drand offset expected_reveal_block = ( - await async_subtensor.subnets.get_next_epoch_start_block( - alice_subnet_netuid - ) + await async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) + 5 ) @@ -491,7 +374,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet # Fetch weights on the chain as they should be revealed now subnet_weights = await async_subtensor.subnets.weights( - netuid=alice_subnet_netuid, mechid=mechid + netuid=alice_sn.netuid, mechid=mechid ) assert subnet_weights != [], "Weights are not available yet." @@ -509,7 +392,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet # Now that the commit has been revealed, there shouldn't be any pending commits assert ( await async_subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_subnet_netuid, mechid=mechid + netuid=alice_sn.netuid, mechid=mechid ) == [] ) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 692fe29cd6..9cc7fd7d2a 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -1,35 +1,30 @@ +import time + import numpy as np import pytest import retry -import time -import asyncio -from bittensor.core.extrinsics.sudo import ( - sudo_set_mechanism_count_extrinsic, - sudo_set_admin_freeze_window_extrinsic, -) from bittensor.utils import get_mechid_storage_index 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, +from tests.e2e_tests.utils import ( execute_and_wait_for_next_nonce, - wait_epoch, -) -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + NETUID, + SUDO_SET_ADMIN_FREEZE_WINDOW, + SUDO_SET_TEMPO, + SUDO_SET_MECHANISM_COUNT, + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED, + SUDO_SET_WEIGHTS_SET_RATE_LIMIT, + AdminUtils, ) TESTED_SUB_SUBNETS = 2 -@pytest.mark.asyncio -async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): +def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): """ Tests the commit/reveal weights mechanism with subprocess disabled (CR1.0) @@ -42,82 +37,49 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( - "Failed to set admin freeze window to 0" - ) - - netuid = subtensor.subnets.get_total_subnets() # 2 - set_tempo = 50 if subtensor.chain.is_fast_blocks() else 20 - - # Register root as Alice - assert subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet" - ) - - # Verify subnet 2 created successfully - assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" - - assert sudo_set_mechanism_count_extrinsic( - subtensor, alice_wallet, netuid, TESTED_SUB_SUBNETS - ), "Cannot create sub-subnets." - - assert wait_to_start_call(subtensor, alice_wallet, netuid), ( - "Subnet isn't active yet." - ) - - # Enable commit_reveal on the subnet - assert sudo_set_hyperparameter_bool( - 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), ( + TEMPO_TO_SET = 50 if subtensor.chain.is_fast_blocks() else 20 + + # Create and prepare subnet + alice_sn = TestSubnet(subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_MECHANISM_COUNT( + alice_wallet, AdminUtils, True, NETUID, TESTED_SUB_SUBNETS + ), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, True + ), + ] + alice_sn.execute_steps(steps) + + assert subtensor.subnets.commit_reveal_enabled(alice_sn.netuid), ( "Failed to enable commit/reveal" ) assert ( - subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).commit_reveal_period + subtensor.subnets.get_subnet_hyperparameters( + alice_sn.netuid + ).commit_reveal_period == 1 ), "Failed to set commit/reveal periods" - assert subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( + assert subtensor.subnets.weights_rate_limit(alice_sn.netuid) > 0, ( "Weights rate limit is below 0" ) - # Lower the rate limit - status, error = sudo_set_admin_utils( - substrate=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 ( - 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 - - # Increase subnet tempo so we have enough time to commit and reveal weights - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={ - "netuid": netuid, - "tempo": set_tempo, - }, + # set weights rate limit + response = alice_sn.execute_one( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0) ) + assert response.success, response.message + assert subtensor.subnets.weights_rate_limit(netuid=alice_sn.netuid) == 0 for mechid in range(TESTED_SUB_SUBNETS): logging.console.info( - f"[magenta]Testing subnet mechanism {netuid}.{mechid}[/magenta]" + f"[magenta]Testing subnet mechanism {alice_sn.netuid}.{mechid}[/magenta]" ) # Commit-reveal values @@ -131,7 +93,7 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): # Commit weights response = subtensor.extrinsics.commit_weights( wallet=alice_wallet, - netuid=netuid, + netuid=alice_sn.netuid, mechid=mechid, salt=salt, uids=weight_uids, @@ -142,7 +104,7 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): assert response.success, response.message - storage_index = get_mechid_storage_index(netuid, mechid) + storage_index = get_mechid_storage_index(alice_sn.netuid, mechid) weight_commits = subtensor.queries.query_module( module="SubtensorModule", name="WeightCommits", @@ -156,19 +118,19 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): assert commit_block > 0, f"Invalid block number: {commit_block}" # Query the WeightCommitRevealInterval storage map - assert subtensor.subnets.get_subnet_reveal_period_epochs(netuid) > 0, ( + assert subtensor.subnets.get_subnet_reveal_period_epochs(alice_sn.netuid) > 0, ( "Invalid RevealPeriodEpochs" ) # Wait until the reveal block range subtensor.wait_for_block( - subtensor.subnets.get_next_epoch_start_block(netuid) + 1 + subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) + 1 ) # Reveal weights success, message = subtensor.extrinsics.reveal_weights( wallet=alice_wallet, - netuid=netuid, + netuid=alice_sn.netuid, mechid=mechid, uids=weight_uids, weights=weight_vals, @@ -179,7 +141,7 @@ async def test_commit_and_reveal_weights_legacy(subtensor, alice_wallet): assert success is True, message - revealed_weights = subtensor.subnets.weights(netuid, mechid=mechid) + revealed_weights = subtensor.subnets.weights(alice_sn.netuid, mechid=mechid) # Assert that the revealed weights are set correctly assert revealed_weights is not None, "Weight reveal not found in storage" @@ -204,79 +166,42 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert ( - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - ) - )[0] is True, "Failed to set admin freeze window to 0" - - netuid = await async_subtensor.subnets.get_total_subnets() # 2 - set_tempo = 50 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.subnets.subnet_exists(netuid), ( - "Subnet wasn't created successfully" - ) - - assert async_wait_to_start_call(async_subtensor, alice_wallet, netuid), ( - "Subnet isn't active yet." - ) - - # 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), ( + TEMPO_TO_SET = 50 if await async_subtensor.chain.is_fast_blocks() else 20 + + # Create and prepare subnet + alice_sn = TestSubnet(async_subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_MECHANISM_COUNT( + alice_wallet, AdminUtils, True, NETUID, TESTED_SUB_SUBNETS + ), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, True + ), + ] + await alice_sn.async_execute_steps(steps) + + assert await async_subtensor.subnets.commit_reveal_enabled(alice_sn.netuid), ( "Failed to enable commit/reveal" ) assert ( - await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid) + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=alice_sn.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 + await async_subtensor.subnets.weights_rate_limit(netuid=alice_sn.netuid) > 0 + ), "Weights rate limit is below 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, - }, + # set weights rate limit + response = await alice_sn.async_execute_one( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0) ) + assert response.success, response.message + assert await async_subtensor.subnets.weights_rate_limit(netuid=alice_sn.netuid) == 0 # Commit-reveal values uids = np.array([0], dtype=np.int64) @@ -289,7 +214,7 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal # Commit weights success, message = await async_subtensor.extrinsics.commit_weights( wallet=alice_wallet, - netuid=netuid, + netuid=alice_sn.netuid, salt=salt, uids=weight_uids, weights=weight_vals, @@ -301,7 +226,7 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal weight_commits = await async_subtensor.queries.query_module( module="SubtensorModule", name="WeightCommits", - params=[netuid, alice_wallet.hotkey.ss58_address], + params=[alice_sn.netuid, alice_wallet.hotkey.ss58_address], ) logging.console.info(f"weight_commits: {weight_commits}") @@ -311,18 +236,15 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal 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) - # await async_subtensor.wait_for_block(await async_subtensor.subnets.get_next_epoch_start_block(netuid) + 1) + assert ( + await async_subtensor.subnets.get_subnet_reveal_period_epochs(alice_sn.netuid) + > 0 + ), "Invalid RevealPeriodEpochs" # Reveal weights success, message = await async_subtensor.extrinsics.reveal_weights( wallet=alice_wallet, - netuid=netuid, + netuid=alice_sn.netuid, uids=weight_uids, weights=weight_vals, salt=salt, @@ -336,7 +258,7 @@ async def test_commit_and_reveal_weights_legacy_async(async_subtensor, alice_wal revealed_weights = await async_subtensor.queries.query_module( module="SubtensorModule", name="Weights", - params=[netuid, 0], # netuid and uid + params=[alice_sn.netuid, 0], # netuid and uid ) # Assert that the revealed weights are set correctly @@ -362,8 +284,7 @@ def get_weights_and_salt(counter: int): return weight_uids_, weight_vals_, salt_ -@pytest.mark.asyncio -async def test_commit_weights_uses_next_nonce(subtensor, alice_wallet): +def test_commit_weights_uses_next_nonce(subtensor, alice_wallet): """ Tests that committing weights doesn't re-use nonce in the transaction pool. @@ -377,76 +298,48 @@ async def test_commit_weights_uses_next_nonce(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert sudo_set_admin_freeze_window_extrinsic(subtensor, alice_wallet, 0), ( - "Failed to set admin freeze window to 0" - ) - - subnet_tempo = 50 if subtensor.chain.is_fast_blocks() else 20 - netuid = subtensor.subnets.get_total_subnets() # 2 + TEMPO_TO_SET = 50 if subtensor.chain.is_fast_blocks() else 20 + + # Create and prepare subnet + alice_sn = TestSubnet(subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, True + ), + ] + alice_sn.execute_steps(steps) # Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos - subtensor.wait_for_block(subtensor.block + (subnet_tempo * 2) + 1) + subtensor.wait_for_block(subtensor.block + (TEMPO_TO_SET * 2) + 1) - # Register root as Alice - 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" - - # weights sensitive to epoch changes - assert sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={ - "netuid": netuid, - "tempo": subnet_tempo, - }, - ) - - # Enable commit_reveal on the subnet - assert sudo_set_hyperparameter_bool( - 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), ( - "Failed to enable commit/reveal" + assert subtensor.commitments.commit_reveal_enabled(alice_sn.netuid), ( + "Failed to enable commit/reveal." ) assert ( - subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).commit_reveal_period + subtensor.subnets.get_subnet_hyperparameters( + netuid=alice_sn.netuid + ).commit_reveal_period == 1 - ), "Failed to set commit/reveal periods" + ), "Commit reveal period is not 1." - assert subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( + assert subtensor.subnets.weights_rate_limit(netuid=alice_sn.netuid) > 0, ( "Weights rate limit is below 0" ) - # Lower the rate limit - status, error = sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_weights_set_rate_limit", - call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, + # set weights rate limit + response = alice_sn.execute_one( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0) ) - - 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 - ), "Failed to set weights_rate_limit" - assert subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 + assert response.success, response.message + assert subtensor.subnets.weights_rate_limit(netuid=alice_sn.netuid) == 0 # wait while weights_rate_limit changes applied. - subtensor.wait_for_block(subnet_tempo + 1) + subtensor.wait_for_block() logging.console.info( f"[orange]Nonce before first commit_weights: " @@ -459,7 +352,7 @@ async def test_commit_weights_uses_next_nonce(subtensor, alice_wallet): def send_commit(salt_, weight_uids_, weight_vals_): success, message = subtensor.extrinsics.commit_weights( wallet=alice_wallet, - netuid=netuid, + netuid=alice_sn.netuid, salt=salt_, uids=weight_uids_, weights=weight_vals_, @@ -491,7 +384,7 @@ def send_commit(salt_, weight_uids_, weight_vals_): # Wait a few blocks waiting_block = ( - (subtensor.block + subtensor.subnets.tempo(netuid) * 2) + (subtensor.block + subtensor.subnets.tempo(alice_sn.netuid) * 2) if subtensor.chain.is_fast_blocks() else None ) @@ -501,7 +394,7 @@ def send_commit(salt_, weight_uids_, weight_vals_): weight_commits = subtensor.queries.query_module( module="SubtensorModule", name="WeightCommits", - params=[netuid, alice_wallet.hotkey.ss58_address], + params=[alice_sn.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" @@ -529,82 +422,47 @@ async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_walle Raises: AssertionError: If any of the checks or verifications fail """ - assert ( - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - ) - )[0] is True, "Failed to set admin freeze window to 0" - - subnet_tempo = 50 if await async_subtensor.chain.is_fast_blocks() else 10 - netuid = await async_subtensor.subnets.get_total_subnets() # 2 + TEMPO_TO_SET = 50 if await async_subtensor.chain.is_fast_blocks() else 20 + + # Create and prepare subnet + alice_sn = TestSubnet(async_subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, True + ), + ] + await alice_sn.async_execute_steps(steps) # 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 + await async_subtensor.block + (TEMPO_TO_SET * 2) + 1 ) - # Register root as Alice - assert await async_subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet" - ) - - # Verify subnet 1 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, - }, - ) - - # 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.commitments.commit_reveal_enabled(netuid), ( - "Failed to enable commit/reveal" + assert await async_subtensor.commitments.commit_reveal_enabled(alice_sn.netuid), ( + "Failed to enable commit/reveal." ) assert ( - await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid) + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=alice_sn.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" - ) + assert ( + await async_subtensor.subnets.weights_rate_limit(netuid=alice_sn.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"}, + # set weights rate limit + response = await alice_sn.async_execute_one( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0) ) - - assert error is None and status is True, f"Failed to set rate limit: {error}" - - 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 + assert response.success, response.message + assert await async_subtensor.subnets.weights_rate_limit(netuid=alice_sn.netuid) == 0 # wait while weights_rate_limit changes applied. - await async_subtensor.wait_for_block(subnet_tempo + 1) + await async_subtensor.wait_for_block(alice_sn.netuid + 1) logging.console.info( f"[orange]Nonce before first commit_weights: " @@ -622,7 +480,7 @@ async def send_commit(salt_, weight_uids_, weight_vals_): async def send_commit_(): success_, message_ = await async_subtensor.extrinsics.commit_weights( wallet=alice_wallet, - netuid=netuid, + netuid=alice_sn.netuid, salt=salt_, uids=weight_uids_, weights=weight_vals_, @@ -687,8 +545,11 @@ async def send_commit_(): # Wait a few blocks waiting_block = ( - (await async_subtensor.block + await async_subtensor.subnets.tempo(netuid) * 2) - + 12 + ( + await async_subtensor.block + + await async_subtensor.subnets.tempo(alice_sn.netuid) * 2 + ) + + 15 if await async_subtensor.chain.is_fast_blocks() else None ) @@ -698,7 +559,7 @@ async def send_commit_(): weight_commits = await async_subtensor.queries.query_module( module="SubtensorModule", name="WeightCommits", - params=[netuid, alice_wallet.hotkey.ss58_address], + params=[alice_sn.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" diff --git a/tests/e2e_tests/test_commitment.py b/tests/e2e_tests/test_commitment.py index a886ae5bb3..be1f14b5e8 100644 --- a/tests/e2e_tests/test_commitment.py +++ b/tests/e2e_tests/test_commitment.py @@ -1,69 +1,59 @@ +from collections import namedtuple + import pytest from async_substrate_interface.errors import SubstrateRequestException -from bittensor.utils.btlogging import logging -from bittensor.core.extrinsics.utils import sudo_call_extrinsic -from bittensor.core.extrinsics.asyncex.utils import ( - sudo_call_extrinsic as async_sudo_call_extrinsic, -) -from tests.e2e_tests.utils.e2e_test_utils import ( - wait_to_start_call, - async_wait_to_start_call, +from tests.e2e_tests.utils import ( + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + REGISTER_NEURON, ) +SET_MAX_SPACE = namedtuple("SET_MAX_SPACE", ["wallet", "pallet", "sudo", "new_limit"]) +COMMITMENT_MESSAGE = "Hello World!" -def test_commitment(subtensor, alice_wallet, dave_wallet): - dave_subnet_netuid = 2 - assert subtensor.subnets.register_subnet(dave_wallet) - assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) +def test_commitment(subtensor, alice_wallet, dave_wallet): + """Tests commitment extrinsic.""" + # Create and prepare subnet + dave_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(dave_wallet), + ACTIVATE_SUBNET(dave_wallet), + ] + dave_sn.execute_steps(steps) with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"): subtensor.commitments.set_commitment( wallet=alice_wallet, - netuid=dave_subnet_netuid, - data="Hello World!", + netuid=dave_sn.netuid, + data=COMMITMENT_MESSAGE, raise_error=True, ) - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - ).success + dave_sn.execute_steps([REGISTER_NEURON(alice_wallet)]) uid = subtensor.subnets.get_uid_for_hotkey_on_subnet( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) - assert uid is not None - assert "" == subtensor.commitments.get_commitment( - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, uid=uid, ) assert subtensor.commitments.set_commitment( wallet=alice_wallet, - netuid=dave_subnet_netuid, - data="Hello World!", + netuid=dave_sn.netuid, + data=COMMITMENT_MESSAGE, ) - status, error = sudo_call_extrinsic( - subtensor=subtensor, - wallet=alice_wallet, - call_module="Commitments", - call_function="set_max_space", - call_params={ - "netuid": dave_subnet_netuid, - "new_limit": len("Hello World!"), - }, + response = dave_sn.execute_one( + SET_MAX_SPACE(alice_wallet, "Commitments", True, len(COMMITMENT_MESSAGE)) ) - - assert status is True, error + assert response.success, response.message with pytest.raises( SubstrateRequestException, @@ -71,99 +61,81 @@ def test_commitment(subtensor, alice_wallet, dave_wallet): ): subtensor.commitments.set_commitment( wallet=alice_wallet, - netuid=dave_subnet_netuid, - data="Hello World!1", + netuid=dave_sn.netuid, + data=COMMITMENT_MESSAGE + "longer", raise_error=True, ) - assert "Hello World!" == subtensor.commitments.get_commitment( - netuid=dave_subnet_netuid, + assert COMMITMENT_MESSAGE == subtensor.commitments.get_commitment( + netuid=dave_sn.netuid, uid=uid, ) assert ( - subtensor.commitments.get_all_commitments(netuid=dave_subnet_netuid)[ + subtensor.commitments.get_all_commitments(netuid=dave_sn.netuid)[ alice_wallet.hotkey.ss58_address ] - == "Hello World!" + == COMMITMENT_MESSAGE ) @pytest.mark.asyncio async def test_commitment_async(async_subtensor, alice_wallet, dave_wallet): - dave_subnet_netuid = 2 - assert await async_subtensor.subnets.register_subnet(dave_wallet) - assert await async_subtensor.subnets.subnet_exists(dave_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert await async_wait_to_start_call( - async_subtensor, dave_wallet, dave_subnet_netuid - ) + # Create and prepare subnet + dave_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(dave_wallet), + ACTIVATE_SUBNET(dave_wallet), + ] + await dave_sn.async_execute_steps(steps) - async with async_subtensor as sub: - with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"): - await sub.commitments.set_commitment( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - data="Hello World!", - raise_error=True, - ) - - assert ( - await sub.subnets.burned_register( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - ) - ).success - - uid = await sub.subnets.get_uid_for_hotkey_on_subnet( - alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + with pytest.raises(SubstrateRequestException, match="AccountNotAllowedCommit"): + await async_subtensor.commitments.set_commitment( + wallet=alice_wallet, + netuid=dave_sn.netuid, + data=COMMITMENT_MESSAGE, + raise_error=True, ) - assert uid is not None + await dave_sn.async_execute_steps([REGISTER_NEURON(alice_wallet)]) - assert "" == await sub.commitments.get_commitment( - netuid=dave_subnet_netuid, - uid=uid, - ) + uid = await async_subtensor.subnets.get_uid_for_hotkey_on_subnet( + hotkey_ss58=alice_wallet.hotkey.ss58_address, + netuid=dave_sn.netuid, + ) + assert uid is not None + assert "" == await async_subtensor.commitments.get_commitment( + netuid=dave_sn.netuid, + uid=uid, + ) - assert await sub.commitments.set_commitment( - alice_wallet, - netuid=dave_subnet_netuid, - data="Hello World!", - ) + assert await async_subtensor.commitments.set_commitment( + wallet=alice_wallet, + netuid=dave_sn.netuid, + data=COMMITMENT_MESSAGE, + ) + + response = await dave_sn.async_execute_one( + SET_MAX_SPACE(alice_wallet, "Commitments", True, len(COMMITMENT_MESSAGE)) + ) + assert response.success, response.message - status, error = await async_sudo_call_extrinsic( - subtensor=async_subtensor, + with pytest.raises( + SubstrateRequestException, + match="SpaceLimitExceeded", + ): + await async_subtensor.commitments.set_commitment( wallet=alice_wallet, - call_module="Commitments", - call_function="set_max_space", - call_params={ - "netuid": dave_subnet_netuid, - "new_limit": len("Hello World!"), - }, + netuid=dave_sn.netuid, + data=COMMITMENT_MESSAGE + "longer", + raise_error=True, ) - assert status is True, error - - with pytest.raises( - SubstrateRequestException, - match="SpaceLimitExceeded", - ): - await sub.commitments.set_commitment( - alice_wallet, - netuid=dave_subnet_netuid, - data="Hello World!1", - raise_error=True, - ) - - assert "Hello World!" == await sub.commitments.get_commitment( - netuid=dave_subnet_netuid, - uid=uid, - ) + assert COMMITMENT_MESSAGE == await async_subtensor.commitments.get_commitment( + netuid=dave_sn.netuid, + uid=uid, + ) - assert (await sub.commitments.get_all_commitments(netuid=dave_subnet_netuid))[ - alice_wallet.hotkey.ss58_address - ] == "Hello World!" + assert ( + await async_subtensor.commitments.get_all_commitments(netuid=dave_sn.netuid) + )[alice_wallet.hotkey.ss58_address] == COMMITMENT_MESSAGE diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 3ebb405653..8c1f849ea1 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -10,21 +10,21 @@ NonAssociatedColdKey, ) from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils.chain_interactions import ( +from tests.e2e_tests.utils 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 ( - async_wait_to_start_call, - wait_to_start_call, + TestSubnet, + AdminUtils, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + REGISTER_NEURON, + SUDO_SET_NOMINATOR_MIN_REQUIRED_STAKE, + SUDO_SET_TX_DELEGATE_TAKE_RATE_LIMIT, ) from tests.helpers.helpers import CloseInValue @@ -217,13 +217,8 @@ def test_change_take(subtensor, alice_wallet, bob_wallet): take = subtensor.delegates.get_delegate_take(alice_wallet.hotkey.ss58_address) assert take == 0.09999237048905166 - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tx_delegate_take_rate_limit", - call_params={ - "tx_rate_limit": 0, - }, + TestSubnet(subtensor).execute_one( + SUDO_SET_TX_DELEGATE_TAKE_RATE_LIMIT(alice_wallet, AdminUtils, True, 0) ) assert subtensor.delegates.set_delegate_take( @@ -309,13 +304,8 @@ async def test_change_take_async(async_subtensor, alice_wallet, bob_wallet): ) 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 TestSubnet(async_subtensor).async_execute_one( + SUDO_SET_TX_DELEGATE_TAKE_RATE_LIMIT(alice_wallet, AdminUtils, True, 0) ) assert ( @@ -417,38 +407,23 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): assert subtensor.delegates.get_delegated(bob_wallet.coldkey.ss58_address) == [] - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - set_tempo = 10 - # Register a subnet, netuid 2 - assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created" - - # Verify subnet 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) - - # set the same tempo for both type of nodes (fast and non-fast blocks) - assert ( - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={"netuid": alice_subnet_netuid, "tempo": set_tempo}, - )[0] - is True - ) + TEMPO_TO_SET = 10 + alice_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + alice_sn.execute_steps(steps) assert subtensor.staking.add_stake( wallet=bob_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), ).success # let chain update validator_permits - subtensor.wait_for_block(subtensor.block + set_tempo + 1) + subtensor.wait_for_block(subtensor.block + TEMPO_TO_SET + 1) bob_delegated = subtensor.delegates.get_delegated(bob_wallet.coldkey.ss58_address) assert bob_delegated == [ @@ -456,11 +431,11 @@ def test_delegates(subtensor, alice_wallet, bob_wallet): 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], + validator_permits=[alice_sn.netuid], + registrations=[0, alice_sn.netuid], return_per_1000=Balance(0), - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_subnet_netuid), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_sn.netuid), ), ] @@ -575,43 +550,25 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): == [] ) - 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 + TEMPO_TO_SET = 10 + alice_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + await alice_sn.async_execute_steps(steps) assert ( await async_subtensor.staking.add_stake( wallet=bob_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), ) ).success # let chain update validator_permits - await async_subtensor.wait_for_block(await async_subtensor.block + set_tempo + 1) + await async_subtensor.wait_for_block(await async_subtensor.block + TEMPO_TO_SET + 1) bob_delegated = await async_subtensor.delegates.get_delegated( bob_wallet.coldkey.ss58_address @@ -621,11 +578,11 @@ async def test_delegates_async(async_subtensor, alice_wallet, bob_wallet): 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], + validator_permits=[alice_sn.netuid], + registrations=[0, alice_sn.netuid], return_per_1000=Balance(0), - netuid=alice_subnet_netuid, - stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_subnet_netuid), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(bob_delegated[0].stake.rao, alice_sn.netuid), ), ] @@ -638,33 +595,22 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ - Update NominatorMinRequiredStake - Check Nominator is removed """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - - # Register a subnet, netuid 2 - assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created" - - # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" + alice_sn = TestSubnet(subtensor) + alice_sn.execute_steps( + [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + REGISTER_NEURON(dave_wallet), + ] ) - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) minimum_required_stake = subtensor.staking.get_minimum_required_stake() assert minimum_required_stake == Balance(0) - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success - - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - ).success - assert subtensor.staking.add_stake( wallet=dave_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1000), ).success @@ -672,18 +618,15 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ stake = subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) assert stake > 0 # this will trigger clear_small_nominations - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_nominator_min_required_stake", - call_params={ - "min_stake": "100000000000000", - }, + alice_sn.execute_one( + SUDO_SET_NOMINATOR_MIN_REQUIRED_STAKE( + alice_wallet, AdminUtils, True, 100000000000000 + ) ) minimum_required_stake = subtensor.staking.get_minimum_required_stake() @@ -692,9 +635,9 @@ def test_nominator_min_required_stake(subtensor, alice_wallet, bob_wallet, dave_ stake = subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) - assert stake == Balance.from_tao(0, alice_subnet_netuid) + assert stake == Balance.from_tao(0, alice_sn.netuid) @pytest.mark.asyncio @@ -708,19 +651,14 @@ async def test_nominator_min_required_stake_async( - Update NominatorMinRequiredStake - Check Nominator is removed """ - 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), ( - "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 + alice_sn = TestSubnet(async_subtensor) + await alice_sn.async_execute_steps( + [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + REGISTER_NEURON(dave_wallet), + ] ) minimum_required_stake = await async_subtensor.staking.get_minimum_required_stake() @@ -729,21 +667,21 @@ async def test_nominator_min_required_stake_async( assert ( await async_subtensor.subnets.burned_register( wallet=bob_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) ).success assert ( await async_subtensor.subnets.burned_register( wallet=dave_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) ).success assert ( await async_subtensor.staking.add_stake( wallet=dave_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1000), ) @@ -752,18 +690,15 @@ async def test_nominator_min_required_stake_async( 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, + netuid=alice_sn.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", - }, + await alice_sn.async_execute_one( + SUDO_SET_NOMINATOR_MIN_REQUIRED_STAKE( + alice_wallet, AdminUtils, True, 100000000000000 + ) ) minimum_required_stake = await async_subtensor.staking.get_minimum_required_stake() @@ -772,9 +707,9 @@ async def test_nominator_min_required_stake_async( 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, + netuid=alice_sn.netuid, ) - assert stake == Balance.from_tao(0, alice_subnet_netuid) + assert stake == Balance.from_tao(0, alice_sn.netuid) def test_get_vote_data(subtensor, alice_wallet): diff --git a/tests/e2e_tests/test_dendrite.py b/tests/e2e_tests/test_dendrite.py index 748c1fc6f8..48f822bd61 100644 --- a/tests/e2e_tests/test_dendrite.py +++ b/tests/e2e_tests/test_dendrite.py @@ -4,21 +4,17 @@ 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 bittensor.core.extrinsics.utils import sudo_call_extrinsic -from bittensor.core.extrinsics import sudo -from bittensor.core.extrinsics.asyncex import sudo as async_sudo -from bittensor.core.extrinsics.asyncex.utils import ( - sudo_call_extrinsic as async_sudo_call_extrinsic, -) -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, +from tests.e2e_tests.utils import ( + TestSubnet, + AdminUtils, + NETUID, + ACTIVATE_SUBNET, + REGISTER_NEURON, + REGISTER_SUBNET, + SUDO_SET_ADMIN_FREEZE_WINDOW, + SUDO_SET_TEMPO, + SUDO_SET_MAX_ALLOWED_VALIDATORS, + SUDO_SET_WEIGHTS_SET_RATE_LIMIT, ) FAST_RUNTIME_TEMPO = 100 @@ -39,82 +35,33 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - SET_TEMPO = ( + TEMPO_TO_SET = ( FAST_RUNTIME_TEMPO if subtensor.chain.is_fast_blocks() else NON_FAST_RUNTIME_TEMPO ) - - assert sudo.sudo_set_admin_freeze_window_extrinsic( - subtensor, alice_wallet, 0 - ).success - - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - - # Register a subnet, netuid 2 - assert subtensor.subnets.register_subnet(alice_wallet).success, ( - "Subnet wasn't created." - ) - - # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully." - ) - - assert sudo_call_extrinsic( - subtensor=subtensor, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={ - "netuid": alice_subnet_netuid, - "tempo": SET_TEMPO, - }, - ).success, "Unable to set tempo." - - 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." - ) + alice_sn = TestSubnet(subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_MAX_ALLOWED_VALIDATORS(alice_wallet, AdminUtils, True, NETUID, 1), + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 10), + REGISTER_NEURON(bob_wallet), + ] + alice_sn.execute_steps(steps) 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, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), ).success - # update max_allowed_validators so only one neuron can get validator_permit - assert sudo_call_extrinsic( - subtensor=subtensor, - wallet=alice_wallet, - call_function="sudo_set_max_allowed_validators", - call_params={ - "netuid": alice_subnet_netuid, - "max_allowed_validators": 1, - }, - ).success, "Unable to set max_allowed_validators." - - # update weights_set_rate_limit for fast-blocks - assert sudo_call_extrinsic( - subtensor=subtensor, - wallet=alice_wallet, - call_function="sudo_set_weights_set_rate_limit", - call_params={ - "netuid": alice_subnet_netuid, - "weights_set_rate_limit": 10, - }, - ).success, "Unable to set weights_set_rate_limit." - - # Register Bob to the network - assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid).success, ( - "Unable to register Bob as a neuron." - ) - - metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) + metagraph = subtensor.metagraphs.metagraph(alice_sn.netuid) # Assert neurons are Alice and Bob assert len(metagraph.neurons) == 2 @@ -136,13 +83,11 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): # Stake to become to top neuron after the first epoch tao = Balance.from_tao(10_000) - alpha, _ = subtensor.subnets.subnet(alice_subnet_netuid).tao_to_alpha_with_slippage( - tao - ) + alpha, _ = subtensor.subnets.subnet(alice_sn.netuid).tao_to_alpha_with_slippage(tao) assert subtensor.staking.add_stake( wallet=bob_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=tao, ).success, "Unable to stake to Bob." @@ -151,13 +96,14 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): subtensor.wait_for_block() # Refresh metagraph - metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) + metagraph = subtensor.metagraphs.metagraph(alice_sn.netuid) bob_neuron = next( n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address ) logging.console.info( - f"block: {subtensor.block}, bob_neuron.stake.rao: {bob_neuron.stake.rao}, alpha.rao: {alpha.rao}, division: {bob_neuron.stake.rao / alpha.rao}" + f"block: {subtensor.block}, bob_neuron.stake.rao: {bob_neuron.stake.rao}, " + f"alpha.rao: {alpha.rao}, division: {bob_neuron.stake.rao / alpha.rao}" ) # Assert alpha is close to stake equivalent assert 0.95 < bob_neuron.stake.rao / alpha.rao < 1.05 @@ -168,15 +114,15 @@ async def test_dendrite(subtensor, templates, alice_wallet, bob_wallet): assert bob_neuron.validator_trust == 0.0 assert bob_neuron.pruning_score == 0 - async with templates.validator(bob_wallet, alice_subnet_netuid): + async with templates.validator(bob_wallet, alice_sn.netuid): await asyncio.sleep(5) # wait for 5 seconds for the Validator to process subtensor.wait_for_block( - subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 1 + subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) + 1 ) # Refresh metagraph - metagraph = subtensor.metagraphs.metagraph(alice_subnet_netuid) + metagraph = subtensor.metagraphs.metagraph(alice_sn.netuid) # Refresh validator neuron updated_neuron = next( @@ -205,95 +151,24 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall Raises: AssertionError: If any of the checks or verifications fail """ - SET_TEMPO = ( + TEMPO_TO_SET = ( FAST_RUNTIME_TEMPO if await async_subtensor.chain.is_fast_blocks() else NON_FAST_RUNTIME_TEMPO ) + alice_sn = TestSubnet(async_subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_MAX_ALLOWED_VALIDATORS(alice_wallet, AdminUtils, True, NETUID, 1), + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 10), + REGISTER_NEURON(bob_wallet), + ] + await alice_sn.async_execute_steps(steps) - assert ( - await async_sudo.sudo_set_admin_freeze_window_extrinsic( - async_subtensor, alice_wallet, 0 - ) - ).success - - 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)).success, ( - "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_sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={ - "netuid": alice_subnet_netuid, - "tempo": SET_TEMPO, - }, - ) - ).success, "Unable to set tempo." - - 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." - ) - - 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, - hotkey_ss58=alice_wallet.hotkey.ss58_address, - amount=Balance.from_tao(5), - wait_for_inclusion=False, - wait_for_finalization=False, - ) - ).success - - # update max_allowed_validators so only one neuron can get validator_permit - assert ( - await async_sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_max_allowed_validators", - call_params={ - "netuid": alice_subnet_netuid, - "max_allowed_validators": 1, - }, - ) - ).success, "Unable to set max_allowed_validators." - - # update weights_set_rate_limit for fast-blocks - assert ( - await async_sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_weights_set_rate_limit", - call_params={ - "netuid": alice_subnet_netuid, - "weights_set_rate_limit": 10, - }, - ) - ).success, "Unable to set weights_set_rate_limit." - - # Register Bob to the network - assert ( - await async_subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid) - ).success, "Unable to register Bob as a neuron." - - metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) + metagraph = await async_subtensor.metagraphs.metagraph(alice_sn.netuid) # Assert neurons are Alice and Bob assert len(metagraph.neurons) == 2 @@ -316,13 +191,13 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall # 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) + await async_subtensor.subnets.subnet(alice_sn.netuid) ).tao_to_alpha_with_slippage(tao) assert ( await async_subtensor.staking.add_stake( wallet=bob_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=tao, wait_for_inclusion=False, @@ -334,7 +209,7 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall await async_subtensor.wait_for_block() # Refresh metagraph - metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) + metagraph = await async_subtensor.metagraphs.metagraph(alice_sn.netuid) bob_neuron = next( n for n in metagraph.neurons if n.hotkey == bob_wallet.hotkey.ss58_address ) @@ -352,18 +227,16 @@ async def test_dendrite_async(async_subtensor, templates, alice_wallet, bob_wall assert bob_neuron.validator_trust == 0.0 assert bob_neuron.pruning_score == 0 - async with templates.validator(bob_wallet, alice_subnet_netuid): + async with templates.validator(bob_wallet, alice_sn.netuid): await asyncio.sleep(5) # wait for 5 seconds for the Validator to process await async_subtensor.wait_for_block( - await async_subtensor.subnets.get_next_epoch_start_block( - alice_subnet_netuid - ) + await async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) + 1 ) # Refresh metagraph - metagraph = await async_subtensor.metagraphs.metagraph(alice_subnet_netuid) + metagraph = await async_subtensor.metagraphs.metagraph(alice_sn.netuid) # Refresh validator neuron updated_neuron = next( diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 261703f83b..ab599a2c65 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -1,5 +1,3 @@ -import asyncio - import pytest from bittensor.core.errors import ( @@ -13,16 +11,23 @@ TxRateLimitExceeded, NonAssociatedColdKey, ) -from bittensor.core.extrinsics import sudo -from bittensor.core.extrinsics.asyncex import sudo as async_sudo from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, +from tests.e2e_tests.utils import ( + TestSubnet, + AdminUtils, + NETUID, + ACTIVATE_SUBNET, + REGISTER_NEURON, + REGISTER_SUBNET, + SUDO_SET_ADMIN_FREEZE_WINDOW, + SUDO_SET_TEMPO, + SUDO_SET_TX_RATE_LIMIT, + SUDO_SET_STAKE_THRESHOLD, ) -SET_CHILDREN_RATE_LIMIT = 30 -ROOT_COOLDOWN = 50 # blocks +# all values are in blocks +SET_CHILDREN_RATE_LIMIT = 50 +ROOT_COOLDOWN = 30 FAST_RUNTIME_TEMPO = 100 NON_FAST_RUNTIME_TEMPO = 10 @@ -33,14 +38,14 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): - Check if Hotkey exists - Check if Hotkey is registered """ - dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(dave_wallet) - assert subtensor.subnets.subnet_exists(dave_subnet_netuid), ( - f"Subnet #{dave_subnet_netuid} does not exist." + dave_sn = TestSubnet(subtensor) + dave_sn.execute_steps( + [ + REGISTER_SUBNET(dave_wallet), + ACTIVATE_SUBNET(dave_wallet), + ] ) - assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) - coldkey = alice_wallet.coldkeypub.ss58_address hotkey = alice_wallet.hotkey.ss58_address @@ -55,14 +60,14 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): assert ( subtensor.wallets.is_hotkey_registered_on_subnet( hotkey_ss58=hotkey, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) is False ) assert subtensor.subnets.burned_register( wallet=alice_wallet, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ).success assert subtensor.wallets.does_hotkey_exist(hotkey) is True @@ -73,7 +78,7 @@ def test_hotkeys(subtensor, alice_wallet, dave_wallet): assert ( subtensor.wallets.is_hotkey_registered_on_subnet( hotkey_ss58=hotkey, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) is True ) @@ -86,14 +91,12 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): - Check if Hotkey exists - Check if Hotkey is registered """ - dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - 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." - ) - - assert await async_wait_to_start_call( - async_subtensor, dave_wallet, dave_subnet_netuid + dave_sn = TestSubnet(async_subtensor) + await dave_sn.async_execute_steps( + [ + REGISTER_SUBNET(dave_wallet), + ACTIVATE_SUBNET(dave_wallet), + ] ) coldkey = alice_wallet.coldkeypub.ss58_address @@ -110,7 +113,7 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): assert ( await async_subtensor.wallets.is_hotkey_registered_on_subnet( hotkey_ss58=hotkey, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) is False ) @@ -118,7 +121,7 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): assert ( await async_subtensor.subnets.burned_register( wallet=alice_wallet, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) ).success @@ -130,7 +133,7 @@ async def test_hotkeys_async(async_subtensor, alice_wallet, dave_wallet): assert ( await async_subtensor.wallets.is_hotkey_registered_on_subnet( hotkey_ss58=hotkey, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) is True ) @@ -147,48 +150,24 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): - Trigger rate limit - Clear children list """ - # turn off admin freeze window limit for testing - assert sudo.sudo_set_admin_freeze_window_extrinsic( - subtensor, alice_wallet, 0 - ).success - - SET_TEMPO = ( + TEMPO_TO_SET = ( FAST_RUNTIME_TEMPO if subtensor.chain.is_fast_blocks() else NON_FAST_RUNTIME_TEMPO ) - dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + # set PendingChildKeyCooldown to SET_CHILDREN_RATE_LIMIT before everything + subtensor.extrinsics.root_set_pending_childkey_cooldown(alice_wallet, ROOT_COOLDOWN) - # Set cooldown - success, message = subtensor.extrinsics.root_set_pending_childkey_cooldown( - wallet=alice_wallet, cooldown=ROOT_COOLDOWN - ) - assert success is True, message - assert message == "Success" - - assert subtensor.subnets.register_subnet(dave_wallet) - 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) - assert sudo.sudo_call_extrinsic( - subtensor=subtensor, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, - ).success - assert subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO - - assert sudo.sudo_call_extrinsic( - subtensor=subtensor, - wallet=alice_wallet, - call_function="sudo_set_tx_rate_limit", - call_params={"tx_rate_limit": 0}, - ).success + dave_sn = TestSubnet(subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(dave_wallet), + ACTIVATE_SUBNET(dave_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + SUDO_SET_TX_RATE_LIMIT(alice_wallet, AdminUtils, True, 0), + ] + dave_sn.execute_steps(steps) with pytest.raises(RegistrationNotPermittedOnRootSubnet): subtensor.extrinsics.set_children( @@ -217,21 +196,16 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): raise_error=True, ) - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - ).success - logging.console.success(f"Alice registered on subnet {dave_subnet_netuid}") - - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ).success - logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") + dave_sn.execute_steps( + [ + REGISTER_NEURON(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + ) success, children, error = subtensor.wallets.get_children( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) assert error == "" @@ -242,7 +216,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 1.0, @@ -256,7 +230,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 0.1, @@ -271,7 +245,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 1.0, @@ -289,7 +263,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 0.5, @@ -306,7 +280,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): success, message = subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 1.0, @@ -320,13 +294,10 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): assert success is True, message assert message == "Success" - set_children_block = subtensor.block - # children not set yet (have to wait cool-down period) success, children, error = subtensor.wallets.get_children( hotkey_ss58=alice_wallet.hotkey.ss58_address, - block=set_children_block, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) assert success is True assert children == [] @@ -335,28 +306,36 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): # children are in pending state pending, cooldown = subtensor.wallets.get_children_pending( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) + assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] + # Wait for first block of the next tempo after the cooldown's tempo + block = subtensor.block + extra_blocks = block // TEMPO_TO_SET * 3 + wait_to_block = ( + cooldown + - subtensor.subnets.blocks_since_last_step(cooldown) + + TEMPO_TO_SET + + extra_blocks + ) logging.console.info( - f"[orange]block: {subtensor.block}, cooldown: {cooldown}[/orange]" + f"[orange]block: {block}, cooldown: {cooldown} wait_to_block: {wait_to_block}[/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 + 1) + subtensor.wait_for_block(wait_to_block) success, children, error = subtensor.wallets.get_children( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) + logging.console.info(f"[orange]block get_children: {subtensor.block}") + assert error == "" assert success is True assert children == [(1.0, bob_wallet.hotkey.ss58_address)] parent_ = subtensor.wallets.get_parents( - bob_wallet.hotkey.ss58_address, dave_subnet_netuid + bob_wallet.hotkey.ss58_address, dave_sn.netuid ) assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] @@ -364,92 +343,96 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): # pending queue is empty pending, cooldown = subtensor.wallets.get_children_pending( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) assert pending == [] logging.console.info( - f"[orange]block: {subtensor.block}, cooldown: {cooldown}[/orange]" + f"[orange]block get_children_pending: {subtensor.block}, cooldown: {cooldown}[/orange]" ) with pytest.raises(TxRateLimitExceeded): set_children_block = subtensor.block - subtensor.extrinsics.set_children( + # first passed + assert subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[], raise_error=True, - ) + ).success + + # second raise the error subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[], raise_error=True, ) # wait for rate limit to expire + 1 block to ensure that the rate limit is expired - subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT + 5) + subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT + 1) - success, message = subtensor.extrinsics.set_children( + response = subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[], raise_error=True, wait_for_inclusion=True, wait_for_finalization=True, ) - assert success is True, message - assert message == "Success" - - set_children_block = subtensor.block + assert response.success, response.message pending, cooldown = subtensor.wallets.get_children_pending( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) - assert pending == [] + + # sometimes we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait. But no longer than one tempo. + # Actually this is additional protection for fast runtime note test. + block = subtensor.block + extra_blocks = block // TEMPO_TO_SET * 3 + wait_to_block = ( + cooldown + - subtensor.subnets.blocks_since_last_step(cooldown) + + TEMPO_TO_SET + + extra_blocks + ) logging.console.info( - f"[orange]block: {subtensor.block}, cooldown: {cooldown}[/orange]" + f"[orange]block: {block}, cooldown: {cooldown} wait_to_block: {wait_to_block}[/orange]" ) + subtensor.wait_for_block(wait_to_block) - subtensor.wait_for_block(cooldown) - - # we need to wait some amount of blocks to ensure that children are posted on chain - # than slower the machine then longer need to wait. But no longer than one tempo. - children = [] + start_block = subtensor.block while not children: success, children, error = subtensor.wallets.get_children( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) - logging.console.info(f"block: {subtensor.block}") - if subtensor.block > cooldown + SET_TEMPO: + block = subtensor.block + if block - start_block > TEMPO_TO_SET: break + logging.console.info(f"block get_children: {subtensor.block}") subtensor.wait_for_block() assert error == "" assert success is True assert children == [(1.0, bob_wallet.hotkey.ss58_address)] - subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT) + subtensor.wait_for_block(set_children_block + SET_CHILDREN_RATE_LIMIT + 1) - sudo.sudo_call_extrinsic( - subtensor=subtensor, - wallet=alice_wallet, - call_function="sudo_set_stake_threshold", - call_params={ - "min_stake": 1_000_000_000_000, - }, + dave_sn.execute_one( + SUDO_SET_STAKE_THRESHOLD(alice_wallet, AdminUtils, True, 1_000_000_000_000) ) with pytest.raises(NotEnoughStakeToSetChildkeys): subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 1.0, @@ -472,59 +455,26 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa - Trigger rate limit - Clear children list """ - # turn off admin freeze window limit for testing - assert ( - await async_sudo.sudo_set_admin_freeze_window_extrinsic( - async_subtensor, alice_wallet, 0 - ) - ).success - - SET_TEMPO = ( + TEMPO_TO_SET = ( FAST_RUNTIME_TEMPO if await async_subtensor.chain.is_fast_blocks() else NON_FAST_RUNTIME_TEMPO ) - dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - - # Set cooldown - ( - success, - message, - ) = await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( - wallet=alice_wallet, cooldown=ROOT_COOLDOWN + # set PendingChildKeyCooldown to SET_CHILDREN_RATE_LIMIT before everything + await async_subtensor.extrinsics.root_set_pending_childkey_cooldown( + alice_wallet, ROOT_COOLDOWN ) - assert success is True, message - assert message == "Success" - 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." - ) - - assert await async_wait_to_start_call( - async_subtensor, dave_wallet, dave_subnet_netuid - ) - - # set the same tempo for both type of nodes (to avoid tests timeout) - assert ( - await async_sudo.sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={"netuid": dave_subnet_netuid, "tempo": SET_TEMPO}, - ) - ).success - assert await async_subtensor.subnets.tempo(dave_subnet_netuid) == SET_TEMPO - - assert ( - await async_sudo.sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_tx_rate_limit", - call_params={"tx_rate_limit": 0}, - ) - ).success + dave_sn = TestSubnet(async_subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(dave_wallet), + ACTIVATE_SUBNET(dave_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + SUDO_SET_TX_RATE_LIMIT(alice_wallet, AdminUtils, True, 0), + ] + await dave_sn.async_execute_steps(steps) with pytest.raises(RegistrationNotPermittedOnRootSubnet): await async_subtensor.extrinsics.set_children( @@ -553,27 +503,17 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa raise_error=True, ) - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dave_subnet_netuid, - ) - ).success - 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, - ) - ).success - logging.console.success(f"Bob registered on subnet {dave_subnet_netuid}") + await dave_sn.async_execute_steps( + [ + REGISTER_NEURON(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + ) success, children, error = await async_subtensor.wallets.get_children( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) - assert error == "" assert success is True assert children == [] @@ -582,7 +522,7 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 1.0, @@ -596,7 +536,7 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 0.1, @@ -611,7 +551,7 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 1.0, @@ -629,7 +569,7 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 0.5, @@ -646,7 +586,7 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa success, message = await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 1.0, @@ -660,13 +600,10 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa assert success is True, message assert message == "Success" - set_children_block = await async_subtensor.block - # children not set yet (have to wait cool-down period) success, children, error = await async_subtensor.wallets.get_children( hotkey_ss58=alice_wallet.hotkey.ss58_address, - block=set_children_block, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) assert success is True assert children == [] @@ -675,28 +612,36 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa # children are in pending state pending, cooldown = await async_subtensor.wallets.get_children_pending( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) + assert pending == [(1.0, bob_wallet.hotkey.ss58_address)] + # Wait for first block of the next tempo after the cooldown's tempo + block = await async_subtensor.block + extra_blocks = block // TEMPO_TO_SET * 3 + wait_to_block = ( + cooldown + - await async_subtensor.subnets.blocks_since_last_step(cooldown) + + TEMPO_TO_SET + + extra_blocks + ) logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + f"[orange]block: {block}, cooldown: {cooldown} wait_to_block: {wait_to_block}[/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 + 1) + await async_subtensor.wait_for_block(wait_to_block) success, children, error = await async_subtensor.wallets.get_children( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) + logging.console.info(f"[orange]block get_children: {await async_subtensor.block}") + 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 + bob_wallet.hotkey.ss58_address, dave_sn.netuid ) assert parent_ == [(1.0, alice_wallet.hotkey.ss58_address)] @@ -704,95 +649,101 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa # pending queue is empty pending, cooldown = await async_subtensor.wallets.get_children_pending( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) assert pending == [] logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + f"[orange]block get_children_pending: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" ) with pytest.raises(TxRateLimitExceeded): - set_children_block = await async_subtensor.chain.get_current_block() + set_children_block = await async_subtensor.block + # first passed await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[], raise_error=True, wait_for_finalization=False, ) + # second raise the error await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[], raise_error=True, wait_for_finalization=False, ) + # wait for rate limit to expire + 1 block to ensure that the rate limit is expired await async_subtensor.wait_for_block( set_children_block + SET_CHILDREN_RATE_LIMIT + 1 ) - success, message = await async_subtensor.extrinsics.set_children( + response = await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[], raise_error=True, wait_for_inclusion=True, wait_for_finalization=True, ) - assert success is True, message - assert message == "Success" - - set_children_block = await async_subtensor.block + assert response.success, response.message pending, cooldown = await async_subtensor.wallets.get_children_pending( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) - assert pending == [] + + # sometimes we need to wait some amount of blocks to ensure that children are posted on chain + # than slower the machine then longer need to wait. But no longer than one tempo. + # Actually this is additional protection for fast runtime note test. + block = await async_subtensor.block + extra_blocks = block // TEMPO_TO_SET * 3 + wait_to_block = ( + cooldown + - await async_subtensor.subnets.blocks_since_last_step(cooldown) + + TEMPO_TO_SET + + extra_blocks + ) logging.console.info( - f"[orange]block: {await async_subtensor.block}, cooldown: {cooldown}[/orange]" + f"[orange]block: {block}, cooldown: {cooldown} wait_to_block: {wait_to_block}[/orange]" ) + await async_subtensor.wait_for_block(wait_to_block) - await async_subtensor.wait_for_block(cooldown) - - # we need to wait some amount of blocks to ensure that children are posted on chain - # than slower the machine then longer need to wait. But no longer than one tempo. - children = [] + start_block = await async_subtensor.block while not children: success, children, error = await async_subtensor.wallets.get_children( hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, ) - logging.console.info(f"block: {await async_subtensor.block}") - if await async_subtensor.block > cooldown + SET_TEMPO: + block = await async_subtensor.block + if block - start_block > TEMPO_TO_SET: break + logging.console.info(f"block get_children: {block}") await async_subtensor.wait_for_block() 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_subtensor.wait_for_block( + set_children_block + SET_CHILDREN_RATE_LIMIT + 1 + ) - await async_sudo.sudo_call_extrinsic( - subtensor=async_subtensor, - wallet=alice_wallet, - call_function="sudo_set_stake_threshold", - call_params={ - "min_stake": 1_000_000_000_000, - }, + await dave_sn.async_execute_one( + SUDO_SET_STAKE_THRESHOLD(alice_wallet, AdminUtils, True, 1_000_000_000_000) ) with pytest.raises(NotEnoughStakeToSetChildkeys): await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=dave_subnet_netuid, + netuid=dave_sn.netuid, children=[ ( 1.0, diff --git a/tests/e2e_tests/test_incentive.py b/tests/e2e_tests/test_incentive.py index e0e0ce7329..49cfa60098 100644 --- a/tests/e2e_tests/test_incentive.py +++ b/tests/e2e_tests/test_incentive.py @@ -3,14 +3,16 @@ import pytest 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, -) -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, +from tests.e2e_tests.utils import ( + TestSubnet, + AdminUtils, + NETUID, + ACTIVATE_SUBNET, + REGISTER_NEURON, + REGISTER_SUBNET, + SUDO_SET_ADMIN_FREEZE_WINDOW, + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED, + SUDO_SET_WEIGHTS_SET_RATE_LIMIT, ) @@ -27,57 +29,30 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - # turn off admin freeze window limit for testing - assert ( - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - )[0] - is True - ), "Failed to set admin freeze window to 0" - - # Register root as Alice - the subnet owner and validator - assert subtensor.subnets.register_subnet(alice_wallet), "Subnet wasn't created" - - # Verify subnet created successfully - 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( - substrate=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 wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - - # Register Bob as a neuron on the subnet - assert subtensor.subnets.burned_register(bob_wallet, alice_subnet_netuid).success, ( - "Unable to register Bob as a neuron" - ) + alice_sn = TestSubnet(subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, False + ), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + alice_sn.execute_steps(steps) # Assert two neurons are in network - assert len(subtensor.neurons.neurons(netuid=alice_subnet_netuid)) == 2, ( + assert len(subtensor.neurons.neurons(netuid=alice_sn.netuid)) == 2, ( "Alice & Bob not registered in the subnet" ) # Wait for the first epoch to pass subtensor.wait_for_block( - subtensor.subnets.get_next_epoch_start_block(alice_subnet_netuid) + 5 + subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) + 5 ) # Get current miner/validator stats - alice_neuron = subtensor.neurons.neurons(netuid=alice_subnet_netuid)[0] + alice_neuron = subtensor.neurons.neurons(netuid=alice_sn.netuid)[0] assert alice_neuron.validator_permit is True assert alice_neuron.dividends == 0 @@ -86,7 +61,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): assert alice_neuron.consensus == 0 assert alice_neuron.rank == 0 - bob_neuron = subtensor.neurons.neurons(netuid=alice_subnet_netuid)[1] + bob_neuron = subtensor.neurons.neurons(netuid=alice_sn.netuid)[1] assert bob_neuron.incentive == 0 assert bob_neuron.consensus == 0 @@ -94,29 +69,20 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): assert bob_neuron.trust == 0 # update weights_set_rate_limit for fast-blocks - tempo = subtensor.subnets.tempo(alice_subnet_netuid) - status, error = sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_weights_set_rate_limit", - call_params={ - "netuid": alice_subnet_netuid, - "weights_set_rate_limit": tempo, - }, + tempo = subtensor.subnets.tempo(alice_sn.netuid) + alice_sn.execute_one( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 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: + async with templates.miner(bob_wallet, alice_sn.netuid) as miner: await asyncio.wait_for(miner.started.wait(), 60) async with templates.validator( - alice_wallet, alice_subnet_netuid + alice_wallet, alice_sn.netuid ) as validator: # wait for the Validator to process and set_weights await asyncio.wait_for(validator.set_weights.wait(), 60) @@ -129,27 +95,27 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): # wait one tempo (fast block) next_epoch_start_block = subtensor.subnets.get_next_epoch_start_block( - alice_subnet_netuid + alice_sn.netuid ) subtensor.wait_for_block(next_epoch_start_block + tempo + 1) validators = subtensor.metagraphs.get_metagraph_info( - alice_subnet_netuid, selected_indices=[72] + alice_sn.netuid, selected_indices=[72] ).validators alice_uid = subtensor.subnets.get_uid_for_hotkey_on_subnet( - hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_sn.netuid ) assert validators[alice_uid] == 1 bob_uid = subtensor.subnets.get_uid_for_hotkey_on_subnet( - hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid + hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_sn.netuid ) assert validators[bob_uid] == 0 while True: try: - neurons = subtensor.neurons.neurons(netuid=alice_subnet_netuid) + neurons = subtensor.neurons.neurons(netuid=alice_sn.netuid) logging.info(f"neurons: {neurons}") # Get current emissions and validate that Alice has gotten tao @@ -170,7 +136,7 @@ async def test_incentive(subtensor, templates, alice_wallet, bob_wallet): assert bob_neuron.rank > 0.5 assert bob_neuron.trust == 1 - bonds = subtensor.subnets.bonds(alice_subnet_netuid) + bonds = subtensor.subnets.bonds(alice_sn.netuid) assert bonds == [ ( @@ -205,64 +171,31 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal Raises: AssertionError: If any of the checks or verifications fail """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - - # turn off admin freeze window limit for testing - assert ( - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - ) - )[0] is True, "Failed to set admin freeze window to 0" - - # Register root as Alice - the subnet owner and validator - 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" - ) - - # 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) - ).success, "Unable to register Bob as a neuron" + alice_sn = TestSubnet(async_subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, False + ), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + await alice_sn.async_execute_steps(steps) # 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_sn.netuid)) == 2, ( + "Alice & Bob not registered in the subnet" + ) # Wait for the first epoch to pass next_epoch_start_block = await async_subtensor.subnets.get_next_epoch_start_block( - netuid=alice_subnet_netuid + netuid=alice_sn.netuid ) await async_subtensor.wait_for_block(next_epoch_start_block + 5) # 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_sn.netuid))[0] assert alice_neuron.validator_permit is True assert alice_neuron.dividends == 0 @@ -271,7 +204,7 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal assert alice_neuron.consensus == 0 assert alice_neuron.rank == 0 - bob_neuron = (await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid))[1] + bob_neuron = (await async_subtensor.neurons.neurons(netuid=alice_sn.netuid))[1] assert bob_neuron.incentive == 0 assert bob_neuron.consensus == 0 @@ -279,29 +212,20 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal 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, - }, + tempo = await async_subtensor.subnets.tempo(alice_sn.netuid) + await alice_sn.async_execute_one( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 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: + async with templates.miner(bob_wallet, alice_sn.netuid) as miner: await asyncio.wait_for(miner.started.wait(), 60) async with templates.validator( - alice_wallet, alice_subnet_netuid + alice_wallet, alice_sn.netuid ) as validator: # wait for the Validator to process and set_weights await asyncio.wait_for(validator.set_weights.wait(), 60) @@ -314,29 +238,29 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal # wait one tempo (fast block) next_epoch_start_block = await async_subtensor.subnets.get_next_epoch_start_block( - alice_subnet_netuid + alice_sn.netuid ) await async_subtensor.wait_for_block(next_epoch_start_block + tempo + 1) validators = ( await async_subtensor.metagraphs.get_metagraph_info( - alice_subnet_netuid, selected_indices=[72] + alice_sn.netuid, selected_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 + hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_sn.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 + hotkey_ss58=bob_wallet.hotkey.ss58_address, netuid=alice_sn.netuid ) assert validators[bob_uid] == 0 while True: try: - neurons = await async_subtensor.neurons.neurons(netuid=alice_subnet_netuid) + neurons = await async_subtensor.neurons.neurons(netuid=alice_sn.netuid) logging.info(f"neurons: {neurons}") # Get current emissions and validate that Alice has gotten tao @@ -357,7 +281,7 @@ async def test_incentive_async(async_subtensor, templates, alice_wallet, bob_wal assert bob_neuron.rank > 0.5 assert bob_neuron.trust == 1 - bonds = await async_subtensor.subnets.bonds(alice_subnet_netuid) + bonds = await async_subtensor.subnets.bonds(alice_sn.netuid) assert bonds == [ ( diff --git a/tests/e2e_tests/test_liquid_alpha.py b/tests/e2e_tests/test_liquid_alpha.py index 8d5fd2b6b9..708bd4b2ea 100644 --- a/tests/e2e_tests/test_liquid_alpha.py +++ b/tests/e2e_tests/test_liquid_alpha.py @@ -1,18 +1,18 @@ import pytest +from bittensor.utils import U16_MAX 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, - async_sudo_set_admin_utils, - sudo_set_hyperparameter_bool, - sudo_set_hyperparameter_values, - sudo_set_admin_utils, -) -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, +from tests.e2e_tests.utils import ( + TestSubnet, + AdminUtils, + NETUID, + ACTIVATE_SUBNET, + REGISTER_NEURON, + REGISTER_SUBNET, + SUDO_SET_ADMIN_FREEZE_WINDOW, + SUDO_SET_ALPHA_VALUES, + SUDO_SET_LIQUID_ALPHA_ENABLED, ) @@ -38,92 +38,63 @@ def test_liquid_alpha(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert ( - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - )[0] - is True - ), "Failed to set admin freeze window to 0" - - u16_max = 65535 - netuid = 2 - - # Register root as Alice - assert subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet." - ) - - # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(netuid) - - assert wait_to_start_call(subtensor, alice_wallet, netuid), ( - "Subnet failed to start." - ) - - # Register a neuron (Alice) to the subnet - assert subtensor.subnets.burned_register(alice_wallet, netuid).success, ( - "Unable to register Alice as a neuron." - ) + alice_sn = TestSubnet(subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(alice_wallet), + ] + alice_sn.execute_steps(steps) # Stake to become to top neuron after the first epoch assert subtensor.staking.add_stake( wallet=alice_wallet, - netuid=netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), ).success, "Unable to stake to Alice neuron." # Assert liquid alpha is disabled assert ( - subtensor.subnets.get_subnet_hyperparameters(netuid=netuid).liquid_alpha_enabled + subtensor.subnets.get_subnet_hyperparameters( + netuid=alice_sn.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 = sudo_set_hyperparameter_values( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_alpha_values", - call_params=call_params, - return_error_message=True, + call_params = liquid_alpha_call_params(alice_sn.netuid, alpha_values) + + response = alice_sn.execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert response.success is False and "LiquidAlphaDisabled" in response.message, ( + "Alpha values set while being disabled." ) - assert result is False, "Alpha values set while being disabled." - assert error_message["name"] == "LiquidAlphaDisabled" - # Enabled liquid alpha on the subnet - assert sudo_set_hyperparameter_bool( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_liquid_alpha_enabled", - value=True, - netuid=netuid, - ), "Unable to enable liquid alpha" + alice_sn.execute_one( + SUDO_SET_LIQUID_ALPHA_ENABLED(alice_wallet, AdminUtils, True, NETUID, True) + ) assert subtensor.subnets.get_subnet_hyperparameters( - netuid, + alice_sn.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 sudo_set_hyperparameter_values( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_alpha_values", - call_params=call_params, - ), "Unable to set alpha_values." - assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_high == 54099, ( - "Failed to set alpha high" - ) - assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_low == 26001, ( - "Failed to set alpha low." + call_params = liquid_alpha_call_params(alice_sn.netuid, alpha_values) + alice_sn.execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) ) + assert ( + subtensor.subnets.get_subnet_hyperparameters(alice_sn.netuid).alpha_high + == 54099 + ), "Failed to set alpha high" + assert ( + subtensor.subnets.get_subnet_hyperparameters(alice_sn.netuid).alpha_low == 26001 + ), "Failed to set alpha low." # Testing alpha high upper and lower bounds @@ -138,94 +109,85 @@ def test_liquid_alpha(subtensor, alice_wallet): f"OwnerHyperparamRateLimit is {owner_hyperparam_ratelimit} tempo(s)." ) subtensor.wait_for_block( - subtensor.block + subtensor.subnets.tempo(netuid) * owner_hyperparam_ratelimit + subtensor.block + + subtensor.subnets.tempo(alice_sn.netuid) * owner_hyperparam_ratelimit ) - call_params = liquid_alpha_call_params(netuid, f"6553, {alpha_high_too_low}") - result, error_message = sudo_set_hyperparameter_values( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_alpha_values", - call_params=call_params, - return_error_message=True, + call_params = liquid_alpha_call_params( + alice_sn.netuid, f"6553, {alpha_high_too_low}" ) - assert result is False, "Able to set incorrect alpha_high value." - assert error_message["name"] == "AlphaHighTooLow" + response = alice_sn.execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert response.success is False and "AlphaHighTooLow" in response.message, ( + "Able to set incorrect alpha_high value." + ) # 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: - sudo_set_hyperparameter_values( - substrate=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}" + alpha_high_too_high = U16_MAX + 1 # One more than the max acceptable value + call_params = liquid_alpha_call_params( + alice_sn.netuid, f"6553, {alpha_high_too_high}" + ) - # Testing alpha low upper and lower bounds + response = alice_sn.execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert ( + response.success is False and "65536 out of range for u16" in response.message + ), f"Unexpected error: {response}" # 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 = sudo_set_hyperparameter_values( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_alpha_values", - call_params=call_params, - return_error_message=True, + call_params = liquid_alpha_call_params( + alice_sn.netuid, f"{alpha_low_too_low}, 53083" + ) + + response = alice_sn.execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert response.success is False and "AlphaLowOutOfRange" in response.message, ( + "Able to set incorrect alpha_low value." ) - 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 = sudo_set_hyperparameter_values( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_alpha_values", - call_params=call_params, - return_error_message=True, + call_params = liquid_alpha_call_params( + alice_sn.netuid, f"{alpha_low_too_high}, 53083" + ) + response = alice_sn.execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert response.success is False and "AlphaLowOutOfRange" in response.message, ( + "Able to set incorrect alpha_low value." ) - 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 sudo_set_hyperparameter_values( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_alpha_values", - call_params=call_params, - ), "Unable to set liquid alpha values." - - assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_high == 53083, ( - "Failed to set alpha high." - ) - assert subtensor.subnets.get_subnet_hyperparameters(netuid).alpha_low == 6553, ( - "Failed to set alpha low." + call_params = liquid_alpha_call_params(alice_sn.netuid, alpha_values) + alice_sn.execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) ) + assert ( + subtensor.subnets.get_subnet_hyperparameters(alice_sn.netuid).alpha_high + == 53083 + ), "Failed to set alpha high." + assert ( + subtensor.subnets.get_subnet_hyperparameters(alice_sn.netuid).alpha_low == 6553 + ), "Failed to set alpha low." + # Disable Liquid Alpha - assert sudo_set_hyperparameter_bool( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_liquid_alpha_enabled", - value=False, - netuid=netuid, - ), "Unable to disable liquid alpha." + alice_sn.execute_one( + SUDO_SET_LIQUID_ALPHA_ENABLED(alice_wallet, AdminUtils, True, NETUID, False) + ) assert ( - subtensor.subnets.get_subnet_hyperparameters(netuid).liquid_alpha_enabled + subtensor.subnets.get_subnet_hyperparameters( + alice_sn.netuid + ).liquid_alpha_enabled is False ), "Failed to disable liquid alpha." - logging.console.info("✅ Passed [blue]test_liquid_alpha[/blue]") @pytest.mark.asyncio @@ -242,94 +204,61 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert ( - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - ) - )[0] is True, "Failed to set admin freeze window to 0" - - 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), "Subnet does not exist." - - assert await async_wait_to_start_call(async_subtensor, alice_wallet, netuid), ( - "Subnet failed to start." - ) - - # Register a neuron (Alice) to the subnet - assert ( - await async_subtensor.subnets.burned_register(alice_wallet, netuid) - ).success, "Unable to register Alice as a neuron." + alice_sn = TestSubnet(async_subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(alice_wallet), + ] + await alice_sn.async_execute_steps(steps) # Stake to become to top neuron after the first epoch assert ( await async_subtensor.staking.add_stake( wallet=alice_wallet, - netuid=netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), ) - ).success, "Unable to stake to Alice neuron" + ).success, "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" + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=alice_sn.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, + call_params = liquid_alpha_call_params(alice_sn.netuid, alpha_values) + + response = await alice_sn.async_execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert response.success is False and "LiquidAlphaDisabled" in response.message, ( + "Alpha values set while being disabled." ) - 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" + await alice_sn.async_execute_one( + SUDO_SET_LIQUID_ALPHA_ENABLED(alice_wallet, AdminUtils, True, NETUID, True) + ) assert ( - await async_subtensor.subnets.get_subnet_hyperparameters( - netuid, - ) - ).liquid_alpha_enabled, "Failed to enable liquid alpha" + await async_subtensor.subnets.get_subnet_hyperparameters(alice_sn.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 + call_params = liquid_alpha_call_params(alice_sn.netuid, alpha_values) + await alice_sn.async_execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters(alice_sn.netuid) + ).alpha_high == 54099, "Failed to set alpha high" + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters(alice_sn.netuid) + ).alpha_low == 26001, "Failed to set alpha low." # 1. Test setting Alpha_high too low alpha_high_too_low = 87 @@ -343,88 +272,78 @@ async def test_liquid_alpha_async(async_subtensor, alice_wallet): ) await async_subtensor.wait_for_block( await async_subtensor.block - + await async_subtensor.subnets.tempo(netuid) * owner_hyperparam_ratelimit + + await async_subtensor.subnets.tempo(alice_sn.netuid) + * owner_hyperparam_ratelimit ) - 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, + call_params = liquid_alpha_call_params( + alice_sn.netuid, f"6553, {alpha_high_too_low}" ) - assert result is False, "Able to set incorrect alpha_high value" - assert error_message["name"] == "AlphaHighTooLow" + response = await alice_sn.async_execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert response.success is False and "AlphaHighTooLow" in response.message, ( + "Able to set incorrect alpha_high value." + ) # 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}" + alpha_high_too_high = U16_MAX + 1 # One more than the max acceptable value + call_params = liquid_alpha_call_params( + alice_sn.netuid, f"6553, {alpha_high_too_high}" + ) - # Testing alpha low upper and lower bounds + response = await alice_sn.async_execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert ( + response.success is False and "65536 out of range for u16" in response.message + ), f"Unexpected error: {response}" # 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, + call_params = liquid_alpha_call_params( + alice_sn.netuid, f"{alpha_low_too_low}, 53083" + ) + + response = await alice_sn.async_execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert response.success is False and "AlphaLowOutOfRange" in response.message, ( + "Able to set incorrect alpha_low value." ) - 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, + call_params = liquid_alpha_call_params( + alice_sn.netuid, f"{alpha_low_too_high}, 53083" + ) + response = await alice_sn.async_execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) + assert response.success is False and "AlphaLowOutOfRange" in response.message, ( + "Able to set incorrect alpha_low value." ) - 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." + call_params = liquid_alpha_call_params(alice_sn.netuid, alpha_values) + await alice_sn.async_execute_one( + SUDO_SET_ALPHA_VALUES(alice_wallet, AdminUtils, False, **call_params) + ) - 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" + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters(alice_sn.netuid) + ).alpha_high == 53083, "Failed to set alpha high." + assert ( + await async_subtensor.subnets.get_subnet_hyperparameters(alice_sn.netuid) + ).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." + await alice_sn.async_execute_one( + SUDO_SET_LIQUID_ALPHA_ENABLED(alice_wallet, AdminUtils, True, NETUID, False) + ) assert ( - await async_subtensor.subnets.get_subnet_hyperparameters(netuid) + await async_subtensor.subnets.get_subnet_hyperparameters(alice_sn.netuid) ).liquid_alpha_enabled is False, "Failed to disable liquid alpha." - - logging.console.info("✅ Passed [blue]test_liquid_alpha_async[/blue]") diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index 62e0dfe55d..0abd694069 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -1,11 +1,12 @@ import pytest from bittensor import Balance, logging -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, -) from bittensor.utils.liquidity import LiquidityPosition +from tests.e2e_tests.utils import ( + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, +) @pytest.mark.asyncio @@ -25,41 +26,29 @@ async def test_liquidity(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. """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 + alice_sn = TestSubnet(subtensor) + alice_sn.execute_one(REGISTER_SUBNET(alice_wallet)) # Make sure `get_liquidity_list` return None if SN doesn't exist assert ( - subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid - ) - is None + subtensor.subnets.get_liquidity_list(wallet=alice_wallet, netuid=14) is None ), "❌ `get_liquidity_list` is not None for unexisting subnet." - # Register root as Alice - 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), ( - f"❌ Subnet {alice_subnet_netuid} wasn't created successfully" - ) - # Make sure `get_liquidity_list` return None without activ SN assert ( subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) is None ), "❌ `get_liquidity_list` is not None when no activ subnet." # Wait until start call availability and do this call - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + alice_sn.execute_one(ACTIVATE_SUBNET(alice_wallet)) # Make sure `get_liquidity_list` return None without activ SN assert ( subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) == [] ), "❌ `get_liquidity_list` is not empty list before fist liquidity add." @@ -67,7 +56,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): # enable user liquidity in SN success, message = subtensor.extrinsics.toggle_user_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, enable=True, ) assert success, message @@ -77,7 +66,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): assert subtensor.staking.add_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, amount=Balance.from_tao(1), ).success, "❌ Cannot cannot add stake to Alice from Alice." @@ -95,7 +84,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): # Add liquidity success, message = subtensor.extrinsics.add_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, liquidity=Balance.from_tao(1), price_low=Balance.from_tao(1.7), price_high=Balance.from_tao(1.8), @@ -107,7 +96,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): # Get liquidity liquidity_positions = subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) assert len(liquidity_positions) == 1, ( @@ -122,14 +111,14 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.netuid, ), "❌ `get_liquidity_list` still empty list after liquidity add." # Modify liquidity position with positive value success, message = subtensor.extrinsics.modify_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, position_id=liquidity_position.id, liquidity_delta=Balance.from_tao(20), wait_for_inclusion=True, @@ -139,7 +128,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): assert message == "Success", "❌ cannot modify liquidity position." liquidity_positions = subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) assert len(liquidity_positions) == 1, ( @@ -153,14 +142,14 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.netuid, ) # Modify liquidity position with negative value success, message = subtensor.extrinsics.modify_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, position_id=liquidity_position.id, liquidity_delta=-Balance.from_tao(11), wait_for_inclusion=True, @@ -170,7 +159,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): assert message == "Success", "❌ cannot modify liquidity position." liquidity_positions = subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) assert len(liquidity_positions) == 1, ( @@ -184,15 +173,15 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.netuid, ) # Add stake from Bob to Alice assert subtensor.staking.add_stake( wallet=bob_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, amount=Balance.from_tao(1000), ).success, "❌ Cannot add stake from Bob to Alice." @@ -210,7 +199,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): # Add second liquidity position success, message = subtensor.extrinsics.add_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, liquidity=Balance.from_tao(150), price_low=Balance.from_tao(0.8), price_high=Balance.from_tao(1.2), @@ -221,7 +210,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): assert message == "Success", "❌ Cannot add liquidity." liquidity_positions = subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) assert len(liquidity_positions) == 2, ( @@ -236,8 +225,8 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.netuid, ) liquidity_position_first = liquidity_positions[1] @@ -247,8 +236,8 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.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) @@ -256,7 +245,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): # Bob remove all stake from alice assert subtensor.extrinsics.unstake_all( wallet=bob_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, rate_tolerance=0.9, # keep high rate tolerance to avoid flaky behavior wait_for_inclusion=True, @@ -265,18 +254,16 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): # Check that fees_alpha comes too after all unstake liquidity_position_first = subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.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 - ) + assert liquidity_position_first.fees_alpha > Balance.from_tao(0, alice_sn.netuid) # Remove all liquidity positions for p in liquidity_positions: success, message = subtensor.extrinsics.remove_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, position_id=p.id, wait_for_inclusion=True, wait_for_finalization=True, @@ -287,7 +274,7 @@ async def test_liquidity(subtensor, alice_wallet, bob_wallet): # Make sure all liquidity positions removed assert ( subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) == [] ), "❌ Not all liquidity positions removed." @@ -312,43 +299,30 @@ async def test_liquidity_async(async_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. """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 + alice_sn = TestSubnet(async_subtensor) + await alice_sn.async_execute_one(REGISTER_SUBNET(alice_wallet)) # 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 - ) + await async_subtensor.subnets.get_liquidity_list(wallet=alice_wallet, netuid=14) 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 + wallet=alice_wallet, netuid=alice_sn.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 - ) + await alice_sn.async_execute_one(ACTIVATE_SUBNET(alice_wallet)) # 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 + wallet=alice_wallet, netuid=alice_sn.netuid ) == [] ), "❌ `get_liquidity_list` is not empty list before fist liquidity add." @@ -356,7 +330,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): # enable user liquidity in SN success, message = await async_subtensor.extrinsics.toggle_user_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, enable=True, ) assert success, message @@ -367,7 +341,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): await async_subtensor.staking.add_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, amount=Balance.from_tao(1), ) ).success, "❌ Cannot cannot add stake to Alice from Alice." @@ -388,7 +362,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): # Add liquidity success, message = await async_subtensor.extrinsics.add_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, liquidity=Balance.from_tao(1), price_low=Balance.from_tao(1.7), price_high=Balance.from_tao(1.8), @@ -400,7 +374,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): # Get liquidity liquidity_positions = await async_subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) assert len(liquidity_positions) == 1, ( @@ -415,14 +389,14 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.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, + netuid=alice_sn.netuid, position_id=liquidity_position.id, liquidity_delta=Balance.from_tao(20), wait_for_inclusion=True, @@ -432,7 +406,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): assert message == "Success", "❌ cannot modify liquidity position." liquidity_positions = await async_subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) assert len(liquidity_positions) == 1, ( @@ -446,14 +420,14 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.netuid, ) # Modify liquidity position with negative value success, message = await async_subtensor.extrinsics.modify_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, position_id=liquidity_position.id, liquidity_delta=-Balance.from_tao(11), wait_for_inclusion=True, @@ -463,7 +437,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): assert message == "Success", "❌ cannot modify liquidity position." liquidity_positions = await async_subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) assert len(liquidity_positions) == 1, ( @@ -477,8 +451,8 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.netuid, ) # Add stake from Bob to Alice @@ -486,7 +460,7 @@ async def test_liquidity_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, + netuid=alice_sn.netuid, amount=Balance.from_tao(1000), ) ).success, "❌ Cannot add stake from Bob to Alice." @@ -507,7 +481,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): # Add second liquidity position success, message = await async_subtensor.extrinsics.add_liquidity( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, liquidity=Balance.from_tao(150), price_low=Balance.from_tao(0.8), price_high=Balance.from_tao(1.2), @@ -518,7 +492,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): assert message == "Success", "❌ Cannot add liquidity." liquidity_positions = await async_subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) assert len(liquidity_positions) == 2, ( @@ -533,8 +507,8 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.netuid, ) liquidity_position_first = liquidity_positions[1] @@ -544,8 +518,8 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): 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, + fees_alpha=Balance.from_tao(0, netuid=alice_sn.netuid), + netuid=alice_sn.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) @@ -554,7 +528,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): assert ( await async_subtensor.extrinsics.unstake_all( wallet=bob_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, rate_tolerance=0.9, # keep high rate tolerance to avoid flaky behavior wait_for_inclusion=True, @@ -565,19 +539,17 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): # 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 + wallet=alice_wallet, netuid=alice_sn.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 - ) + assert liquidity_position_first.fees_alpha > Balance.from_tao(0, alice_sn.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, + netuid=alice_sn.netuid, position_id=p.id, wait_for_inclusion=True, wait_for_finalization=True, @@ -588,7 +560,7 @@ async def test_liquidity_async(async_subtensor, alice_wallet, bob_wallet): # Make sure all liquidity positions removed assert ( await async_subtensor.subnets.get_liquidity_list( - wallet=alice_wallet, netuid=alice_subnet_netuid + wallet=alice_wallet, netuid=alice_sn.netuid ) == [] ), "❌ Not all liquidity positions removed." diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 404c7d91f9..133eebd23f 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -9,9 +9,11 @@ from bittensor.core.chain_data.metagraph_info import MetagraphInfo from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, +from tests.e2e_tests.utils import ( + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + REGISTER_NEURON, ) NULL_KEY = tuple(bytearray(32)) @@ -52,29 +54,22 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - - logging.console.info("Register the subnet through Alice") - assert subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet" - ) - - logging.console.info("Verify subnet was created successfully") - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" + alice_sn = TestSubnet(subtensor) + alice_sn.execute_steps( + [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] ) - logging.console.info("Make sure we passed start_call limit (10 blocks)") - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - logging.console.info("Initialize metagraph") - metagraph = subtensor.metagraphs.metagraph(netuid=alice_subnet_netuid) + metagraph = subtensor.metagraphs.metagraph(netuid=alice_sn.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.subnets.burned_register(bob_wallet, alice_subnet_netuid).success, ( + assert subtensor.subnets.burned_register(bob_wallet, alice_sn.netuid).success, ( "Unable to register Bob as a neuron" ) @@ -96,11 +91,11 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): logging.console.info("Fetch UID of Bob") uid = subtensor.subnets.get_uid_for_hotkey_on_subnet( - bob_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid + bob_wallet.hotkey.ss58_address, netuid=alice_sn.netuid ) logging.console.info("Fetch neuron info of Bob through subtensor and metagraph") - neuron_info_bob = subtensor.neurons.neuron_for_uid(uid, netuid=alice_subnet_netuid) + neuron_info_bob = subtensor.neurons.neuron_for_uid(uid, netuid=alice_sn.netuid) metagraph_dict = neuron_to_dict(metagraph.neurons[uid]) subtensor_dict = neuron_to_dict(neuron_info_bob) @@ -111,12 +106,12 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): ) logging.console.info("Create pre_dave metagraph for future verifications") - metagraph_pre_dave = subtensor.metagraphs.metagraph(netuid=alice_subnet_netuid) + metagraph_pre_dave = subtensor.metagraphs.metagraph(netuid=alice_sn.netuid) logging.console.info("Register Dave as a neuron") - assert subtensor.subnets.burned_register( - dave_wallet, alice_subnet_netuid - ).success, "Unable to register Dave as a neuron" + assert subtensor.subnets.burned_register(dave_wallet, alice_sn.netuid).success, ( + "Unable to register Dave as a neuron" + ) metagraph.sync(subtensor=subtensor.inner_subtensor) @@ -136,12 +131,10 @@ 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.subnets.subnet(alice_subnet_netuid).tao_to_alpha_with_slippage( - tao - ) + alpha, _ = subtensor.subnets.subnet(alice_sn.netuid).tao_to_alpha_with_slippage(tao) assert subtensor.staking.add_stake( wallet=bob_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=tao, ).success, "Failed to add stake for Bob" @@ -208,161 +201,144 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w Raises: AssertionError: If any of the checks or verifications fail """ - 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), ( - "Unable to register the subnet" - ) + alice_sn = TestSubnet(async_subtensor) + await alice_sn.async_execute_steps( + [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + ) - 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("Initialize metagraph") + metagraph = await async_subtensor.metagraphs.metagraph(netuid=alice_sn.netuid) - 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("Assert metagraph has only Alice (owner)") + assert len(metagraph.uids) == 1, "Metagraph doesn't have exactly 1 neuron" - logging.console.info("Initialize metagraph") - metagraph = await async_subtensor.metagraphs.metagraph( - netuid=alice_subnet_netuid - ) + logging.console.info("Register Bob to the subnet") + assert ( + await async_subtensor.subnets.burned_register(bob_wallet, alice_sn.netuid) + ).success, "Unable to register Bob as a neuron" - logging.console.info("Assert metagraph has only Alice (owner)") - assert len(metagraph.uids) == 1, "Metagraph doesn't have exactly 1 neuron" + logging.console.info("Refresh the metagraph") + await metagraph.sync(subtensor=async_subtensor.inner_subtensor) - logging.console.info("Register Bob to the subnet") - assert ( - await async_subtensor.subnets.burned_register( - bob_wallet, alice_subnet_netuid - ) - ).success, "Unable to register Bob as a neuron" + 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("Refresh the metagraph") - await metagraph.sync(subtensor=async_subtensor.inner_subtensor) + 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_sn.netuid + ) - 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_sn.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) - 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("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_sn.netuid + ) - 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_sn.netuid) + ).success, "Unable to register Dave as a neuron" - logging.console.info("Register Dave as a neuron") - assert ( - await async_subtensor.subnets.burned_register( - dave_wallet, alice_subnet_netuid - ) - ).success, "Unable to register Dave as a neuron" + await metagraph.sync(subtensor=async_subtensor.inner_subtensor) - await metagraph.sync(subtensor=async_subtensor.inner_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("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_sn.netuid) + ).tao_to_alpha_with_slippage(tao) + assert ( + await async_subtensor.staking.add_stake( + wallet=bob_wallet, + netuid=alice_sn.netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + amount=tao, ) + ).success, "Failed to add stake for Bob" - 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, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - amount=tao, - ) - ).success, "Failed to add stake for Bob" - - logging.console.info("Assert stake is added after updating metagraph") - await metagraph.sync(subtensor=async_subtensor.inner_subtensor) - assert 0.95 < metagraph.neurons[1].stake.rao / alpha.rao < 1.05, ( - "Bob's stake not updated in metagraph" - ) + logging.console.info("Assert stake is added after updating metagraph") + await metagraph.sync(subtensor=async_subtensor.inner_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" - ) + 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)) - 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" - ) + 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.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" - ) + 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]") + logging.console.info("✅ Passed [blue]test_metagraph_async[/blue]") def test_metagraph_info(subtensor, alice_wallet, bob_wallet): @@ -373,8 +349,8 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): - Register Subnet - Check MetagraphInfo is updated """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet) + alice_sn = TestSubnet(subtensor) + alice_sn.execute_one(REGISTER_SUBNET(alice_wallet)) metagraph_info = subtensor.metagraphs.get_metagraph_info(netuid=1, block=1) @@ -558,14 +534,9 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): assert metagraph_infos == expected_metagraph_infos - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + alice_sn.execute_steps([ACTIVATE_SUBNET(alice_wallet), REGISTER_NEURON(bob_wallet)]) - assert subtensor.subnets.burned_register( - bob_wallet, - netuid=alice_subnet_netuid, - ).success - - metagraph_info = subtensor.metagraphs.get_metagraph_info(netuid=alice_subnet_netuid) + metagraph_info = subtensor.metagraphs.get_metagraph_info(netuid=alice_sn.netuid) assert metagraph_info.num_uids == 2 assert metagraph_info.hotkeys == [ @@ -594,16 +565,16 @@ 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) + bob_sn = TestSubnet(subtensor) + bob_sn.execute_one(REGISTER_SUBNET(bob_wallet)) block = subtensor.chain.get_current_block() metagraph_info = subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, block=block + netuid=bob_sn.netuid, block=block ) - assert metagraph_info.owner_coldkey == alice_wallet.hotkey.ss58_address - assert metagraph_info.owner_hotkey == alice_wallet.coldkey.ss58_address + assert metagraph_info.owner_coldkey == bob_wallet.hotkey.ss58_address + assert metagraph_info.owner_hotkey == bob_wallet.coldkey.ss58_address metagraph_infos = subtensor.metagraphs.get_all_metagraphs_info(block) @@ -611,9 +582,7 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): assert metagraph_infos[-1] == metagraph_info # non-existed subnet - metagraph_info = subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid + 1 - ) + metagraph_info = subtensor.metagraphs.get_metagraph_info(netuid=bob_sn.netuid + 1) assert metagraph_info is None @@ -629,8 +598,8 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): - Register Subnet - Check MetagraphInfo is updated """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - assert await async_subtensor.subnets.register_subnet(alice_wallet) + alice_sn = TestSubnet(async_subtensor) + await alice_sn.async_execute_one(REGISTER_SUBNET(alice_wallet)) metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( netuid=1, block=1 @@ -816,19 +785,12 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): assert metagraph_infos == expected_metagraph_infos - assert await async_wait_to_start_call( - async_subtensor, alice_wallet, alice_subnet_netuid + await alice_sn.async_execute_steps( + [ACTIVATE_SUBNET(alice_wallet), REGISTER_NEURON(bob_wallet)] ) - assert ( - await async_subtensor.subnets.burned_register( - bob_wallet, - netuid=alice_subnet_netuid, - ) - ).success - metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid + netuid=alice_sn.netuid ) assert metagraph_info.num_uids == 2 @@ -858,16 +820,16 @@ 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) + bob_sn = TestSubnet(async_subtensor) + await bob_sn.async_execute_one(REGISTER_SUBNET(bob_wallet)) block = await async_subtensor.chain.get_current_block() metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, block=block + netuid=bob_sn.netuid, block=block ) - assert metagraph_info.owner_coldkey == alice_wallet.hotkey.ss58_address - assert metagraph_info.owner_hotkey == alice_wallet.coldkey.ss58_address + assert metagraph_info.owner_coldkey == bob_wallet.hotkey.ss58_address + assert metagraph_info.owner_hotkey == bob_wallet.coldkey.ss58_address metagraph_infos = await async_subtensor.metagraphs.get_all_metagraphs_info(block) @@ -876,7 +838,7 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): # non-existed subnet metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid + 1 + netuid=bob_sn.netuid + 1 ) assert metagraph_info is None @@ -892,8 +854,8 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): - Register Subnet - Check MetagraphInfo is updated """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet) + alice_sn = TestSubnet(subtensor) + alice_sn.execute_one(REGISTER_SUBNET(alice_wallet)) selected_indices = [ SelectiveMetagraphIndex.Name, @@ -904,11 +866,11 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): ] metagraph_info = subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, selected_indices=selected_indices + netuid=alice_sn.netuid, selected_indices=selected_indices ) assert metagraph_info == MetagraphInfo( - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, mechid=0, name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, @@ -996,12 +958,7 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): commitments=None, ) - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success + alice_sn.execute_steps([ACTIVATE_SUBNET(alice_wallet), REGISTER_NEURON(bob_wallet)]) fields = [ SelectiveMetagraphIndex.Name, @@ -1012,11 +969,11 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): ] metagraph_info = subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, selected_indices=fields + netuid=alice_sn.netuid, selected_indices=fields ) assert metagraph_info == MetagraphInfo( - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, mechid=0, name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, @@ -1128,8 +1085,8 @@ async def test_metagraph_info_with_indexes_async( - Register Subnet - Check MetagraphInfo is updated """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - assert await async_subtensor.subnets.register_subnet(alice_wallet) + alice_sn = TestSubnet(async_subtensor) + await alice_sn.async_execute_one(REGISTER_SUBNET(alice_wallet)) selected_indices = [ SelectiveMetagraphIndex.Name, @@ -1140,11 +1097,11 @@ async def test_metagraph_info_with_indexes_async( ] metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, selected_indices=selected_indices + netuid=alice_sn.netuid, selected_indices=selected_indices ) assert metagraph_info == MetagraphInfo( - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, mechid=0, name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, @@ -1232,17 +1189,10 @@ async def test_metagraph_info_with_indexes_async( commitments=None, ) - assert await async_wait_to_start_call( - async_subtensor, alice_wallet, alice_subnet_netuid + await alice_sn.async_execute_steps( + [ACTIVATE_SUBNET(alice_wallet), REGISTER_NEURON(bob_wallet)] ) - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ) - ).success - fields = [ SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.Active, @@ -1252,11 +1202,11 @@ async def test_metagraph_info_with_indexes_async( ] metagraph_info = await async_subtensor.metagraphs.get_metagraph_info( - netuid=alice_subnet_netuid, selected_indices=fields + netuid=alice_sn.netuid, selected_indices=fields ) assert metagraph_info == MetagraphInfo( - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, mechid=0, name="omron", owner_hotkey=alice_wallet.hotkey.ss58_address, diff --git a/tests/e2e_tests/test_neuron_certificate.py b/tests/e2e_tests/test_neuron_certificate.py index 198e581ad0..cdaba7a94e 100644 --- a/tests/e2e_tests/test_neuron_certificate.py +++ b/tests/e2e_tests/test_neuron_certificate.py @@ -1,6 +1,10 @@ import pytest + from bittensor.core.axon import Axon -from bittensor.utils.btlogging import logging +from tests.e2e_tests.utils import ( + TestSubnet, + REGISTER_SUBNET, +) @pytest.mark.asyncio @@ -15,17 +19,19 @@ async def test_neuron_certificate(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.info("Testing [blue]neuron_certificate[/blue]") - netuid = 2 + alice_sn = TestSubnet(subtensor) + alice_sn.execute_one(REGISTER_SUBNET(alice_wallet)) # Register root as Alice - the subnet owner and validator assert subtensor.subnets.register_subnet(alice_wallet) # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" + assert subtensor.subnets.subnet_exists(alice_sn.netuid), ( + "Subnet wasn't created successfully" + ) # Register Alice as a neuron on the subnet - assert subtensor.subnets.burned_register(alice_wallet, netuid).success, ( + assert subtensor.subnets.burned_register(alice_wallet, alice_sn.netuid).success, ( "Unable to register Alice as a neuron" ) @@ -33,8 +39,8 @@ async def test_neuron_certificate(subtensor, alice_wallet): axon = Axon(wallet=alice_wallet) encoded_certificate = "?FAKE_ALICE_CERT" subtensor.extrinsics.serve_axon( - netuid, - axon, + netuid=alice_sn.netuid, + axon=axon, certificate=encoded_certificate, wait_for_inclusion=True, wait_for_finalization=True, @@ -43,12 +49,14 @@ async def test_neuron_certificate(subtensor, alice_wallet): # Verify we are getting the correct certificate assert ( subtensor.neurons.get_neuron_certificate( - netuid=netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, ) == encoded_certificate ) - all_certs_query = subtensor.neurons.get_all_neuron_certificates(netuid=netuid) + all_certs_query = subtensor.neurons.get_all_neuron_certificates( + netuid=alice_sn.netuid + ) assert alice_wallet.hotkey.ss58_address in all_certs_query.keys() assert all_certs_query[alice_wallet.hotkey.ss58_address] == encoded_certificate @@ -65,28 +73,28 @@ async def test_neuron_certificate_async(async_subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - logging.info("Testing [blue]neuron_certificate[/blue]") - netuid = 2 + alice_sn = TestSubnet(async_subtensor) + await alice_sn.async_execute_one(REGISTER_SUBNET(alice_wallet)) # 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), ( + assert await async_subtensor.subnets.subnet_exists(alice_sn.netuid), ( "Subnet wasn't created successfully" ) # Register Alice as a neuron on the subnet assert ( - await async_subtensor.subnets.burned_register(alice_wallet, netuid) + await async_subtensor.subnets.burned_register(alice_wallet, alice_sn.netuid) ).success, "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, + netuid=alice_sn.netuid, + axon=axon, certificate=encoded_certificate, wait_for_inclusion=True, wait_for_finalization=True, @@ -95,13 +103,13 @@ async def test_neuron_certificate_async(async_subtensor, alice_wallet): # Verify we are getting the correct certificate assert ( await async_subtensor.neurons.get_neuron_certificate( - netuid=netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, ) == encoded_certificate ) all_certs_query = await async_subtensor.neurons.get_all_neuron_certificates( - netuid=netuid + netuid=alice_sn.netuid ) assert alice_wallet.hotkey.ss58_address in all_certs_query.keys() assert all_certs_query[alice_wallet.hotkey.ss58_address] == encoded_certificate diff --git a/tests/e2e_tests/test_reveal_commitments.py b/tests/e2e_tests/test_reveal_commitments.py index 865e7777d5..db9c4a940e 100644 --- a/tests/e2e_tests/test_reveal_commitments.py +++ b/tests/e2e_tests/test_reveal_commitments.py @@ -3,9 +3,11 @@ import pytest from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, +from tests.e2e_tests.utils import ( + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + REGISTER_NEURON, ) @@ -31,24 +33,13 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): (0.25, 10) if subtensor.chain.is_fast_blocks() else (12.0, 5) ) - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - - # Register subnet as Alice - assert subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet" - ) - - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - - # Register Bob's neuron - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success, "Bob's neuron was not register." - - # Verify subnet 2 created successfully - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" + alice_sn = TestSubnet(subtensor) + alice_sn.execute_steps( + [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] ) # Set commitment from Alice hotkey @@ -56,24 +47,24 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): response = subtensor.commitments.set_reveal_commitment( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, data=message_alice, blocks_until_reveal=BLOCKS_UNTIL_REVEAL, block_time=BLOCK_TIME, ) - assert response.success is True + assert response.success, response.message # 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( wallet=bob_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, data=message_bob, blocks_until_reveal=BLOCKS_UNTIL_REVEAL, block_time=BLOCK_TIME, ) - assert response.success is True + assert response.success, response.message target_reveal_round = response.data.get("reveal_round") @@ -89,7 +80,7 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): logging.console.info(f"Current last reveled drand round {last_drand_round}") time.sleep(3) - actual_all = subtensor.commitments.get_all_revealed_commitments(alice_subnet_netuid) + actual_all = subtensor.commitments.get_all_revealed_commitments(alice_sn.netuid) alice_result = actual_all.get(alice_wallet.hotkey.ss58_address) assert alice_result is not None, "Alice's commitment was not received." @@ -107,10 +98,10 @@ def test_set_reveal_commitment(subtensor, alice_wallet, bob_wallet): # Assertions for get_revealed_commitment (based of hotkey) actual_alice_block, actual_alice_message = ( - subtensor.commitments.get_revealed_commitment(alice_subnet_netuid, 0)[0] + subtensor.commitments.get_revealed_commitment(alice_sn.netuid, 0)[0] ) actual_bob_block, actual_bob_message = ( - subtensor.commitments.get_revealed_commitment(alice_subnet_netuid, 1)[0] + subtensor.commitments.get_revealed_commitment(alice_sn.netuid, 1)[0] ) assert message_alice == actual_alice_message @@ -140,50 +131,38 @@ async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wa (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 - - # Register subnet as Alice - assert await async_subtensor.subnets.register_subnet(alice_wallet), ( - "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) - ).success, "Bob's neuron was not register." - - # Verify subnet 2 created successfully - assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" + alice_sn = TestSubnet(async_subtensor) + await alice_sn.async_execute_steps( + [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] ) # Set commitment from Alice hotkey message_alice = f"This is test message with time {time.time()} from Alice." response = await async_subtensor.commitments.set_reveal_commitment( - alice_wallet, - alice_subnet_netuid, - message_alice, - BLOCKS_UNTIL_REVEAL, - BLOCK_TIME, + wallet=alice_wallet, + netuid=alice_sn.netuid, + data=message_alice, + blocks_until_reveal=BLOCKS_UNTIL_REVEAL, + block_time=BLOCK_TIME, ) - assert response.success is True + assert response.success, response.message # Set commitment from Bob's hotkey message_bob = f"This is test message with time {time.time()} from Bob." response = await async_subtensor.commitments.set_reveal_commitment( - bob_wallet, - alice_subnet_netuid, - message_bob, - BLOCKS_UNTIL_REVEAL, + wallet=bob_wallet, + netuid=alice_sn.netuid, + data=message_bob, + blocks_until_reveal=BLOCKS_UNTIL_REVEAL, block_time=BLOCK_TIME, ) - assert response.success is True + assert response.success, response.message target_reveal_round = response.data.get("reveal_round") @@ -200,7 +179,7 @@ async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wa time.sleep(3) actual_all = await async_subtensor.commitments.get_all_revealed_commitments( - alice_subnet_netuid + alice_sn.netuid ) alice_result = actual_all.get(alice_wallet.hotkey.ss58_address) @@ -219,14 +198,10 @@ async def test_set_reveal_commitment_async(async_subtensor, alice_wallet, bob_wa # Assertions for get_revealed_commitment (based of hotkey) actual_alice_block, actual_alice_message = ( - await async_subtensor.commitments.get_revealed_commitment( - alice_subnet_netuid, 0 - ) + await async_subtensor.commitments.get_revealed_commitment(alice_sn.netuid, 0) )[0] actual_bob_block, actual_bob_message = ( - await async_subtensor.commitments.get_revealed_commitment( - alice_subnet_netuid, 1 - ) + await async_subtensor.commitments.get_revealed_commitment(alice_sn.netuid, 1) )[0] assert message_alice == actual_alice_message diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index 5b402f0d5f..d33d3c5a81 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -3,16 +3,14 @@ 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 ( - async_wait_to_start_call, - wait_to_start_call, +from tests.e2e_tests.utils import ( + TestSubnet, + AdminUtils, + NETUID, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + SUDO_SET_DIFFICULTY, + SUDO_SET_NETWORK_POW_REGISTRATION_ALLOWED, ) FAST_BLOCKS_SPEEDUP_FACTOR = 5 @@ -59,8 +57,6 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall Raises: AssertionError: If any of the checks or verifications fail. """ - 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.chain.is_fast_blocks() else 360 @@ -74,76 +70,68 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall assert alice_root_neuron.hotkey == alice_wallet.hotkey.ss58_address # Create netuid = 2 - assert subtensor.subnets.register_subnet(alice_wallet) - - assert wait_to_start_call( - subtensor=subtensor, subnet_owner_wallet=alice_wallet, netuid=netuid - ) + alice_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + alice_sn.execute_steps(steps) # Ensure correct immunity period and tempo is being fetched - assert subtensor.subnets.immunity_period(netuid=netuid) == default_immunity_period - assert subtensor.subnets.tempo(netuid=netuid) == default_tempo + assert ( + subtensor.subnets.immunity_period(netuid=alice_sn.netuid) + == default_immunity_period + ) + assert subtensor.subnets.tempo(netuid=alice_sn.netuid) == default_tempo assert subtensor.staking.add_stake( wallet=bob_wallet, - netuid=netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), period=16, ).success, "Unable to stake from Bob to Alice" - async with templates.validator(alice_wallet, netuid): + async with templates.validator(alice_wallet, alice_sn.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 = subtensor.subnets.get_uid_for_hotkey_on_subnet( - alice_wallet.hotkey.ss58_address, netuid + alice_wallet.hotkey.ss58_address, alice_sn.netuid ) # Fetch the block since last update for the neuron block_since_update = subtensor.subnets.blocks_since_last_update( - netuid=netuid, uid=alice_uid_sn_2 + netuid=alice_sn.netuid, uid=alice_uid_sn_2 ) assert block_since_update is not None - # Verify subnet created successfully - assert subtensor.subnets.subnet_exists(netuid), "Subnet wasn't created successfully" - # Use subnetwork_n hyperparam to check sn creation - assert subtensor.subnets.subnetwork_n(netuid) == 1 # TODO? - assert subtensor.subnets.subnetwork_n(netuid + 1) is None + assert subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 # TODO? + assert subtensor.subnets.subnetwork_n(alice_sn.netuid + 1) is None # Ensure correct hyperparams are being fetched regarding weights - 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 + assert subtensor.subnets.min_allowed_weights(alice_sn.netuid) is not None + assert subtensor.subnets.max_weight_limit(alice_sn.netuid) is not None + assert subtensor.subnets.weights_rate_limit(alice_sn.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( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_difficulty", - call_params={"netuid": netuid, "difficulty": "1_000_000"}, - return_error_message=True, + subtensor.wait_for_block( + subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) + 1 ) - assert sudo_set_hyperparameter_values( - 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, + # Change hyperparams so we can execute pow_register + alice_sn.execute_steps( + [ + SUDO_SET_DIFFICULTY(alice_wallet, AdminUtils, True, NETUID, 1_000_000), + SUDO_SET_NETWORK_POW_REGISTRATION_ALLOWED( + alice_wallet, AdminUtils, True, NETUID, 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) + sn_one_neurons = subtensor.neurons.neurons_lite(netuid=alice_sn.netuid) assert ( sn_one_neurons[alice_uid_sn_2].coldkey == alice_wallet.coldkeypub.ss58_address ) @@ -175,8 +163,6 @@ async def test_root_reg_hyperparams_async( Raises: AssertionError: If any of the checks or verifications fail. """ - 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 @@ -190,83 +176,73 @@ async def test_root_reg_hyperparams_async( 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 - ) + alice_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + await alice_sn.async_execute_steps(steps) # Ensure correct immunity period and tempo is being fetched assert ( - await async_subtensor.subnets.immunity_period(netuid=netuid) + await async_subtensor.subnets.immunity_period(netuid=alice_sn.netuid) == default_immunity_period ) - assert await async_subtensor.subnets.tempo(netuid=netuid) == default_tempo + assert await async_subtensor.subnets.tempo(netuid=alice_sn.netuid) == default_tempo assert ( await async_subtensor.staking.add_stake( wallet=bob_wallet, - netuid=netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), period=16, ) ).success, "Unable to stake from Bob to Alice" - async with templates.validator(alice_wallet, netuid): + async with templates.validator(alice_wallet, alice_sn.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 + alice_wallet.hotkey.ss58_address, alice_sn.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 + netuid=alice_sn.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 + assert await async_subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 # TODO? + assert await async_subtensor.subnets.subnetwork_n(alice_sn.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 + assert ( + await async_subtensor.subnets.min_allowed_weights(alice_sn.netuid) is not None + ) + assert await async_subtensor.subnets.max_weight_limit(alice_sn.netuid) is not None + assert await async_subtensor.subnets.weights_rate_limit(alice_sn.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, + next_epoch_start_block = await async_subtensor.subnets.get_next_epoch_start_block( + alice_sn.netuid ) + await async_subtensor.wait_for_block(next_epoch_start_block + 1) - 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, + # Change hyperparams so we can execute pow_register + await alice_sn.async_execute_steps( + [ + SUDO_SET_DIFFICULTY(alice_wallet, AdminUtils, True, NETUID, 1_000_000), + SUDO_SET_NETWORK_POW_REGISTRATION_ALLOWED( + alice_wallet, AdminUtils, True, NETUID, 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 = await async_subtensor.neurons.neurons_lite(netuid=netuid) + sn_one_neurons = await async_subtensor.neurons.neurons_lite(netuid=alice_sn.netuid) assert ( sn_one_neurons[alice_uid_sn_2].coldkey == alice_wallet.coldkeypub.ss58_address ) diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 582ff52a3c..466c278fab 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -4,26 +4,27 @@ import pytest import retry - -from bittensor.core.extrinsics.sudo import ( - sudo_set_mechanism_count_extrinsic, - sudo_set_admin_freeze_window_extrinsic, -) -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, +from tests.e2e_tests.utils import ( execute_and_wait_for_next_nonce, + AdminUtils, + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + NETUID, + SUDO_SET_ADMIN_FREEZE_WINDOW, + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED, + SUDO_SET_LOCK_REDUCTION_INTERVAL, + SUDO_SET_MECHANISM_COUNT, + SUDO_SET_NETWORK_RATE_LIMIT, + SUDO_SET_TEMPO, + SUDO_SET_WEIGHTS_SET_RATE_LIMIT, ) -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, -) + +TESTED_MECHANISMS = 2 +TESTED_NETUIDS = [2, 3] def test_set_weights_uses_next_nonce(subtensor, alice_wallet): @@ -40,117 +41,75 @@ def test_set_weights_uses_next_nonce(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert sudo_set_admin_freeze_window_extrinsic( - subtensor=subtensor, - wallet=alice_wallet, - window=0, - ) - - netuids = [2, 3] - TESTED_SUB_SUBNETS = 2 - - # 12 for non-fast-block, 0.25 for fast block block_time, subnet_tempo = ( (0.25, 50) if subtensor.chain.is_fast_blocks() else (12.0, 20) ) - subnet_tempo = 50 + sns = [TestSubnet(subtensor) for _ in TESTED_NETUIDS] - # Lower the network registration rate limit and cost - sudo_set_admin_utils( - 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( - substrate=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 subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the subnet" - ) + hps_set_steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + SUDO_SET_NETWORK_RATE_LIMIT(alice_wallet, AdminUtils, True, 0), + SUDO_SET_LOCK_REDUCTION_INTERVAL(alice_wallet, AdminUtils, True, 1), + ] - # Verify all subnets created successfully - assert subtensor.subnets.subnet_exists(netuid), ( - "Subnet wasn't created successfully" - ) + sns[0].execute_steps(hps_set_steps) - # Weights sensitive to epoch changes - assert sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_tempo", - call_params={ - "netuid": netuid, - "tempo": subnet_tempo, - }, - ) - assert sudo_set_mechanism_count_extrinsic( - subtensor=subtensor, - wallet=alice_wallet, - netuid=netuid, - mech_count=2, - ) - - assert wait_to_start_call(subtensor, alice_wallet, netuid) + sns_steps = [ + REGISTER_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, subnet_tempo), + SUDO_SET_MECHANISM_COUNT( + alice_wallet, AdminUtils, True, NETUID, TESTED_MECHANISMS + ), + ACTIVATE_SUBNET(alice_wallet), + ] + for sn in sns: + sn.execute_steps(sns_steps) # 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: + for sn in sns: assert subtensor.staking.add_stake( wallet=alice_wallet, - netuid=netuid, + netuid=sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), ).success # Set weight hyperparameters per subnet - for netuid in netuids: - assert sudo_set_hyperparameter_bool( - 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" + for sn in sns: + sn.execute_one( + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, False + ) + ) assert not subtensor.subnets.commit_reveal_enabled( - netuid, + netuid=sn.netuid, ), "Failed to enable commit/reveal" - assert subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( + assert subtensor.subnets.weights_rate_limit(netuid=sn.netuid) > 0, ( "Weights rate limit is below 0" ) # Lower set weights rate limit - status, error = sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_weights_set_rate_limit", - call_params={"netuid": netuid, "weights_set_rate_limit": "0"}, + response = sn.execute_one( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0) ) - - assert error is None - assert status is True + assert response.success, response.message assert ( subtensor.subnets.get_subnet_hyperparameters( - netuid=netuid + netuid=sn.netuid ).weights_rate_limit == 0 ), "Failed to set weights_rate_limit" - assert subtensor.subnets.get_hyperparameter("WeightsSetRateLimit", netuid) == 0 - assert subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 + assert ( + subtensor.subnets.get_hyperparameter("WeightsSetRateLimit", sn.netuid) == 0 + ) + assert subtensor.subnets.weights_rate_limit(netuid=sn.netuid) == 0 # Weights values uids = np.array([0], dtype=np.int64) @@ -186,31 +145,29 @@ def set_weights(netuid_, mechid_): f"{subtensor.substrate.get_account_next_index(alice_wallet.hotkey.ss58_address)}[/orange]" ) - for mechid in range(TESTED_SUB_SUBNETS): + for mechid in range(TESTED_MECHANISMS): # Set weights for each subnet - for netuid in netuids: - set_weights(netuid, mechid) + for sn in sns: + set_weights(sn.netuid, mechid) - for netuid in netuids: + for sn in sns: # Query the Weights storage map for all three subnets weights = subtensor.subnets.weights( - netuid=netuid, + netuid=sn.netuid, mechid=mechid, ) alice_weights = weights[0][1] logging.console.info( - f"Weights for subnet mechanism {netuid}.{mechid}: {alice_weights}" + f"Weights for subnet mechanism {sn.netuid}.{mechid}: {alice_weights}" ) assert alice_weights is not None, ( - f"Weights not found for subnet mechanism {netuid}.{mechid}" + f"Weights not found for subnet mechanism {sn.netuid}.{mechid}" ) assert alice_weights == list(zip(weight_uids, weight_vals)), ( - f"Weights do not match for subnet {netuid}" + f"Weights do not match for subnet {sn.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): @@ -227,111 +184,75 @@ async def test_set_weights_uses_next_nonce_async(async_subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - # turn off admin freeze window limit for testing - assert ( - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - ) - )[0] is True, "Failed to set admin freeze window to 0" - - 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 + block_time, subnet_tempo = ( + (0.25, 50) if await async_subtensor.chain.is_fast_blocks() else (12.0, 20) ) - for netuid in netuids: - # Register the subnets - 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), ( - "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) + sns = [TestSubnet(async_subtensor) for _ in TESTED_NETUIDS] + + hps_set_steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + SUDO_SET_NETWORK_RATE_LIMIT(alice_wallet, AdminUtils, True, 0), + SUDO_SET_LOCK_REDUCTION_INTERVAL(alice_wallet, AdminUtils, True, 1), + ] + await sns[0].async_execute_steps(hps_set_steps) + + sns_steps = [ + REGISTER_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, subnet_tempo), + SUDO_SET_MECHANISM_COUNT( + alice_wallet, AdminUtils, True, NETUID, TESTED_MECHANISMS + ), + ACTIVATE_SUBNET(alice_wallet), + ] + for sn in sns: + await sn.async_execute_steps(sns_steps) # 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: + for sn in sns: assert ( await async_subtensor.staking.add_stake( wallet=alice_wallet, - netuid=netuid, + netuid=sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(10_000), ) ).success # 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" - + for sn in sns: + await sn.async_execute_one( + SUDO_SET_COMMIT_REVEAL_WEIGHTS_ENABLED( + alice_wallet, AdminUtils, True, NETUID, False + ) + ) assert not await async_subtensor.subnets.commit_reveal_enabled( - netuid, + sn.netuid, ), "Failed to enable commit/reveal" - assert await async_subtensor.subnets.weights_rate_limit(netuid=netuid) > 0, ( + assert await async_subtensor.subnets.weights_rate_limit(netuid=sn.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"}, + response = await sn.async_execute_one( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0) ) - - assert error is None - assert status is True + assert response.success, response.message assert ( - await async_subtensor.subnets.get_subnet_hyperparameters(netuid=netuid) + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=sn.netuid) ).weights_rate_limit == 0, "Failed to set weights_rate_limit" assert ( await async_subtensor.subnets.get_hyperparameter( - "WeightsSetRateLimit", netuid + "WeightsSetRateLimit", sn.netuid ) == 0 ) - assert await async_subtensor.subnets.weights_rate_limit(netuid=netuid) == 0 + assert await async_subtensor.subnets.weights_rate_limit(netuid=sn.netuid) == 0 # Weights values uids = np.array([0], dtype=np.int64) @@ -345,22 +266,7 @@ async def test_set_weights_uses_next_nonce_async(async_subtensor, alice_wallet): 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_): + async def set_weights(netuid_, mechid_): """ To avoid adding asynchronous retrieval to dependencies, we implement a retrieval behavior with asynchronous behavior. @@ -370,6 +276,7 @@ async def set_weights_(): success_, message_ = await async_subtensor.extrinsics.set_weights( wallet=alice_wallet, netuid=netuid_, + mechid=mechid_, uids=weight_uids, weights=weight_vals, wait_for_inclusion=True, @@ -409,31 +316,29 @@ async def set_weights_(): 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 - ) - - weights = query.value - logging.console.info(f"Weights for subnet {netuid}: {weights}") + for mechid in range(TESTED_MECHANISMS): + # Set weights for each subnet + for sn in sns: + await set_weights(sn.netuid, mechid) - 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( + f"[orange]Nonce after second set_weights: " + f"{await async_subtensor.substrate.get_account_nonce(alice_wallet.hotkey.ss58_address)}[/orange]" ) - logging.console.info( - "✅ Passed [blue]test_set_weights_uses_next_nonce_async[/blue]" - ) + for sn in sns: + # Query the Weights storage map for all three subnets + weights = await async_subtensor.subnets.weights( + netuid=sn.netuid, + mechid=mechid, + ) + alice_weights = weights[0][1] + logging.console.info( + f"Weights for subnet mechanism {sn.netuid}.{mechid}: {alice_weights}" + ) + assert alice_weights is not None, ( + f"Weights not found for subnet mechanism {sn.netuid}.{mechid}" + ) + assert alice_weights == list(zip(weight_uids, weight_vals)), ( + f"Weights do not match for subnet {sn.netuid}" + ) diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index 4a7882e332..39b2fb522e 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -1,6 +1,7 @@ import pytest + from bittensor.utils.balance import Balance -from bittensor.utils.btlogging import logging +from tests.e2e_tests.utils import TestSubnet, REGISTER_SUBNET def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): @@ -14,21 +15,17 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): - Removing stake - Moving stake between hotkeys/subnets/coldkeys """ - 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" + sn = TestSubnet(subtensor) + sn.execute_one(REGISTER_SUBNET(alice_wallet)) # Test add_stake fee stake_fee_0 = subtensor.staking.get_stake_add_fee( amount=stake_amount, - netuid=netuid, + netuid=sn.netuid, ) assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object." assert stake_fee_0 == min_stake_fee, ( @@ -66,7 +63,7 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): }, # Move between coldkeys on non-root { - "origin_netuid": netuid, + "origin_netuid": sn.netuid, "stake_fee": min_stake_fee, }, ] @@ -92,7 +89,7 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): stake_fee = subtensor.staking.get_stake_movement_fee( amount=stake_amount, - origin_netuid=netuid, + origin_netuid=sn.netuid, ) assert isinstance(stake_fee, Balance), "Stake fee should be a Balance object" assert stake_fee >= min_stake_fee, ( @@ -112,23 +109,17 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): - Removing stake - Moving stake between hotkeys/subnets/coldkeys """ - 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 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" - ) + sn = TestSubnet(async_subtensor) + await sn.async_execute_one(REGISTER_SUBNET(alice_wallet)) # Test add_stake fee stake_fee_0 = await async_subtensor.staking.get_stake_add_fee( amount=stake_amount, - netuid=netuid, + netuid=sn.netuid, ) assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object." assert stake_fee_0 == min_stake_fee, ( @@ -166,7 +157,7 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): }, # Move between coldkeys on non-root { - "origin_netuid": netuid, + "origin_netuid": sn.netuid, "stake_fee": min_stake_fee, }, ] @@ -192,7 +183,7 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): stake_fee = await async_subtensor.staking.get_stake_movement_fee( amount=stake_amount, - origin_netuid=netuid, + origin_netuid=sn.netuid, ) assert isinstance(stake_fee, Balance), "Stake fee should be a Balance object" assert stake_fee >= min_stake_fee, ( diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index ee7db541e1..692bfe2704 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -6,14 +6,16 @@ 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, +from tests.e2e_tests.utils import ( get_dynamic_balance, - sudo_set_admin_utils, -) -from tests.e2e_tests.utils.e2e_test_utils import ( - async_wait_to_start_call, - wait_to_start_call, + AdminUtils, + TestSubnet, + ACTIVATE_SUBNET, + REGISTER_SUBNET, + REGISTER_NEURON, + NETUID, + SUDO_SET_ADMIN_FREEZE_WINDOW, + SUDO_SET_TEMPO, ) from tests.helpers.helpers import CloseInValue @@ -25,40 +27,25 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): - Unstaking using `unstake` - Checks StakeInfo """ - 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).success - - # Verify subnet 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 subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success - logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") + alice_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + alice_sn.execute_steps(steps) stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) - assert stake == Balance(0).set_unit(alice_subnet_netuid) + assert stake == Balance(0).set_unit(alice_sn.netuid) assert subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), period=16, @@ -67,29 +54,31 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): stake_alice = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) logging.console.info(f"Alice stake: {stake_alice}") stake_bob = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) logging.console.info(f"Bob stake: {stake_bob}") - assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) + assert stake_bob > Balance(0).set_unit(alice_sn.netuid) - stakes = subtensor.staking.get_stake_info_for_coldkey(alice_wallet.coldkey.ss58_address) + stakes = subtensor.staking.get_stake_info_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), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ), @@ -100,12 +89,10 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): 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 - ), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(stakes[1].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ) @@ -147,10 +134,10 @@ def test_single_operation(subtensor, alice_wallet, bob_wallet): 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), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(stakes[2].stake.rao, alice_sn.netuid), + locked=Balance.from_tao(0, netuid=alice_sn.netuid), + emission=get_dynamic_balance(stakes[2].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ), @@ -159,14 +146,14 @@ 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, + netuid=alice_sn.netuid, ) logging.console.info(f"Alice stake before unstake: {stake}") # unstale all to check in later response = subtensor.staking.unstake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=stake, period=16, @@ -177,11 +164,11 @@ 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, + netuid=alice_sn.netuid, ) # all balances have been unstaked - assert stake == Balance(0).set_unit(alice_subnet_netuid) + assert stake == Balance(0).set_unit(alice_sn.netuid) @pytest.mark.asyncio @@ -192,47 +179,26 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) - Unstaking using `unstake` - Checks StakeInfo """ - 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)).success - - # 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 - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ) - ).success - logging.console.success(f"Alice is registered in subnet {alice_subnet_netuid}") - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ) - ).success - logging.console.success(f"Bob is registered in subnet {alice_subnet_netuid}") + alice_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + await alice_sn.async_execute_steps(steps) 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, + netuid=alice_sn.netuid, ) - assert stake == Balance(0).set_unit(alice_subnet_netuid) + assert stake == Balance(0).set_unit(alice_sn.netuid) assert ( await async_subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=Balance.from_tao(1), period=16, @@ -242,18 +208,18 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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, + netuid=alice_sn.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, + netuid=alice_sn.netuid, ) logging.console.info(f"Bob stake: {stake_bob}") - assert stake_bob > Balance(0).set_unit(alice_subnet_netuid) + assert stake_bob > Balance(0).set_unit(alice_sn.netuid) stakes = await async_subtensor.staking.get_stake_info_for_coldkey( alice_wallet.coldkey.ss58_address @@ -263,10 +229,10 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ), @@ -277,12 +243,10 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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 - ), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(stakes[1].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ) @@ -324,10 +288,10 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(stakes[2].stake.rao, alice_sn.netuid), + locked=Balance.from_tao(0, netuid=alice_sn.netuid), + emission=get_dynamic_balance(stakes[2].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ), @@ -336,14 +300,14 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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, + netuid=alice_sn.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, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, period=16, ) @@ -352,12 +316,12 @@ async def test_single_operation_async(async_subtensor, alice_wallet, bob_wallet) 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, + netuid=alice_sn.netuid, ) logging.console.info(f"Alice stake after unstake: {stake}") # all balances have been unstaked - assert stake == Balance(0).set_unit(alice_subnet_netuid) + assert stake == Balance(0).set_unit(alice_sn.netuid) def test_batch_operations(subtensor, alice_wallet, bob_wallet): @@ -368,32 +332,30 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): - Checks StakeInfo - Checks Accounts Balance """ - netuids = [ - 2, - 3, - ] + subnets_tested = 2 - for _ in netuids: - assert subtensor.subnets.register_subnet(alice_wallet).success + sns = [TestSubnet(subtensor) for _ in range(subnets_tested)] - # make sure we passed start_call limit for both subnets - for netuid in netuids: - assert wait_to_start_call(subtensor, alice_wallet, netuid) + sn_steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] - for netuid in netuids: - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=netuid, - ).success + for sn in sns: + sn.execute_steps(sn_steps) - for netuid in netuids: stake = subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, bob_wallet.hotkey.ss58_address, - netuid=netuid, + netuid=sn.netuid, ) - assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" + assert stake == Balance(0).set_unit(sn.netuid), ( + f"netuid={sn.netuid} stake={stake}" + ) + + netuids = [sn.netuid for sn in sns] balances = subtensor.wallets.get_balances( alice_wallet.coldkey.ss58_address, @@ -424,9 +386,9 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=netuid, + netuid=sn.netuid, ) - for netuid in netuids + for sn in sns ] for netuid, stake in zip(netuids, stakes): @@ -447,37 +409,24 @@ def test_batch_operations(subtensor, alice_wallet, bob_wallet): balances[bob_wallet.coldkey.ss58_address].rao ), } - assert balances == expected_balances - expected_fee_paid = Balance(0) - for netuid in netuids: - call = 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 = subtensor.substrate.get_payment_info( - call, alice_wallet.coldkeypub - ) - fee_alpha = Balance.from_rao(payment_info["partial_fee"]).set_unit(netuid) - dynamic_info = subtensor.subnets.subnet(netuid) - fee_tao = dynamic_info.alpha_to_tao(fee_alpha) - expected_fee_paid += fee_tao - response = subtensor.staking.unstake_multiple( wallet=alice_wallet, - netuids=netuids, + netuids=[sn.netuid for sn in sns], hotkey_ss58s=[bob_wallet.hotkey.ss58_address for _ in netuids], amounts=[Balance.from_tao(100) for _ in netuids], raise_error=True, ) - logging.console.info(f">>> res {response}") assert response.success, response.message + total_fee = sum( + [ + v.extrinsic_fee + for _, v in response.data.items() + if hasattr(v, "extrinsic_fee") + ] + ) + logging.console.info(f"Total fee: [blue]{total_fee}[/blue]") for netuid, old_stake in zip(netuids, stakes): stake = subtensor.staking.get_stake( @@ -509,34 +458,30 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) - Checks StakeInfo - Checks Accounts Balance """ - netuids = [ - 2, - 3, - ] + subnets_tested = 2 - for _ in netuids: - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success + sns = [TestSubnet(async_subtensor) for _ in range(subnets_tested)] - # 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) + sn_steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] - for netuid in netuids: - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=netuid, - ) - ).success + for sn in sns: + await sn.async_execute_steps(sn_steps) - 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, + alice_wallet.coldkey.ss58_address, + bob_wallet.hotkey.ss58_address, + netuid=sn.netuid, ) - assert stake == Balance(0).set_unit(netuid), f"netuid={netuid} stake={stake}" + assert stake == Balance(0).set_unit(sn.netuid), ( + f"netuid={sn.netuid} stake={stake}" + ) + + netuids = [sn.netuid for sn in sns] balances = await async_subtensor.wallets.get_balances( alice_wallet.coldkey.ss58_address, @@ -568,9 +513,9 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) await async_subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=netuid, + netuid=sn.netuid, ) - for netuid in netuids + for sn in sns ] for netuid, stake in zip(netuids, stakes): @@ -591,28 +536,8 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) 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 - response = await async_subtensor.staking.unstake_multiple( wallet=alice_wallet, netuids=netuids, @@ -620,6 +545,14 @@ async def test_batch_operations_async(async_subtensor, alice_wallet, bob_wallet) amounts=[Balance.from_tao(100) for _ in netuids], ) assert response.success, response.message + total_fee = sum( + [ + v.extrinsic_fee + for _, v in response.data.items() + if hasattr(v, "extrinsic_fee") + ] + ) + logging.console.info(f"Total fee: [blue]{total_fee}[/blue]") for netuid, old_stake in zip(netuids, stakes): stake = await async_subtensor.staking.get_stake( @@ -651,58 +584,28 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) 2. Succeeds with strict threshold (0.5%) and partial staking allowed 3. Succeeds with lenient threshold (10% and 30%) and no partial staking """ - # turn off admin freeze window limit for testing - assert ( - sudo_set_admin_utils( - substrate=subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - )[0] - is True - ), "Failed to set admin freeze window to 0" - - 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) - - # 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 = 50 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) + alice_sn = TestSubnet(subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + alice_sn.execute_steps(steps) - assert subtensor.extrinsics.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - ).success + tempo = subtensor.subnets.get_subnet_hyperparameters(netuid=alice_sn.netuid).tempo + assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." + logging.console.success(f"SN #{alice_sn.netuid} tempo set to {TEMPO_TO_SET}") initial_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) - assert initial_stake == Balance(0).set_unit(alice_subnet_netuid) + assert initial_stake == Balance(0).set_unit(alice_sn.netuid) logging.console.info(f"[orange]Initial stake: {initial_stake}[orange]") # Test Staking Scenarios @@ -712,7 +615,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) assert ( subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=stake_amount, safe_staking=True, @@ -725,9 +628,9 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) current_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) - assert current_stake == Balance(0).set_unit(alice_subnet_netuid), ( + assert current_stake == Balance(0).set_unit(alice_sn.netuid), ( "Stake should not change after failed attempt" ) logging.console.info(f"[orange]Current stake: {current_stake}[orange]") @@ -735,7 +638,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) # 2. Partial allowed - should succeed partially assert subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=stake_amount, safe_staking=True, @@ -746,20 +649,20 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) partial_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) - assert partial_stake > Balance(0).set_unit(alice_subnet_netuid), ( + assert partial_stake > Balance(0).set_unit(alice_sn.netuid), ( "Partial stake should be added" ) - assert partial_stake < Balance.from_tao(stake_amount.tao).set_unit(alice_subnet_netuid), ( - "Partial stake should be less than requested amount" - ) + assert partial_stake < Balance.from_tao(stake_amount.tao).set_unit( + alice_sn.netuid + ), "Partial stake should be less than requested amount" # 3. Higher threshold - should succeed fully amount = Balance.from_tao(100) assert subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=amount, safe_staking=True, @@ -770,14 +673,14 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) full_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) # Test Unstaking Scenarios # 1. Strict params - should fail response = subtensor.staking.unstake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=full_stake, safe_unstaking=True, @@ -789,7 +692,7 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) current_stake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) logging.console.info(f"[orange]Current stake: {current_stake}[orange]") @@ -803,29 +706,29 @@ def test_safe_staking_scenarios(subtensor, alice_wallet, bob_wallet, eve_wallet) # 2. Partial allowed - should succeed partially response = subtensor.staking.unstake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=current_stake, safe_unstaking=True, rate_tolerance=0.005, # 0.5% allow_partial_stake=True, ) - assert response.success is True + assert response.success, response.message partial_unstake = subtensor.staking.get_stake( coldkey_ss58=alice_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, ) logging.console.info(f"[orange]Partial unstake: {partial_unstake}[orange]") - assert partial_unstake > Balance(0).set_unit(alice_subnet_netuid), ( + assert partial_unstake > Balance(0).set_unit(alice_sn.netuid), ( "Some stake should remain" ) # 3. Higher threshold - should succeed fully response = subtensor.staking.unstake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=partial_unstake, safe_unstaking=True, @@ -847,62 +750,30 @@ async def test_safe_staking_scenarios_async( 2. Succeeds with strict threshold (0.5%) and partial staking allowed 3. Succeeds with lenient threshold (10% and 30%) and no partial staking """ - # turn off admin freeze window limit for testing - assert ( - await async_sudo_set_admin_utils( - substrate=async_subtensor.substrate, - wallet=alice_wallet, - call_function="sudo_set_admin_freeze_window", - call_params={"window": 0}, - ) - )[0] is True, "Failed to set admin freeze window to 0" - - 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) + TEMPO_TO_SET = 50 if await async_subtensor.chain.is_fast_blocks() else 20 - # Verify subnet created successfully - assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) + alice_sn = TestSubnet(async_subtensor) + steps = [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + await alice_sn.async_execute_steps(steps) - # Change the tempo of the subnet - TEMPO_TO_SET = 50 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 - ) + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=alice_sn.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 - ) - - assert ( - await async_subtensor.extrinsics.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - wait_for_inclusion=True, - wait_for_finalization=True, - ) - ).success + logging.console.success(f"SN #{alice_sn.netuid} tempo set to {TEMPO_TO_SET}") 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, + netuid=alice_sn.netuid, ) - assert initial_stake == Balance(0).set_unit(alice_subnet_netuid) + assert initial_stake == Balance(0).set_unit(alice_sn.netuid) logging.console.info(f"[orange]Initial stake: {initial_stake}[orange]") # Test Staking Scenarios @@ -912,7 +783,7 @@ async def test_safe_staking_scenarios_async( assert ( await async_subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=stake_amount, safe_staking=True, @@ -924,9 +795,9 @@ async def test_safe_staking_scenarios_async( 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, + netuid=alice_sn.netuid, ) - assert current_stake == Balance(0).set_unit(alice_subnet_netuid), ( + assert current_stake == Balance(0).set_unit(alice_sn.netuid), ( "Stake should not change after failed attempt" ) logging.console.info(f"[orange]Current stake: {current_stake}[orange]") @@ -935,7 +806,7 @@ async def test_safe_staking_scenarios_async( assert ( await async_subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=stake_amount, safe_staking=True, @@ -947,21 +818,21 @@ async def test_safe_staking_scenarios_async( 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, + netuid=alice_sn.netuid, ) - assert partial_stake > Balance(0).set_unit(alice_subnet_netuid), ( + assert partial_stake > Balance(0).set_unit(alice_sn.netuid), ( "Partial stake should be added" ) - assert partial_stake < Balance.from_tao(stake_amount.tao).set_unit(alice_subnet_netuid), ( - "Partial stake should be less than requested amount" - ) + assert partial_stake < Balance.from_tao(stake_amount.tao).set_unit( + alice_sn.netuid + ), "Partial stake should be less than requested amount" # 3. Higher threshold - should succeed fully amount = Balance.from_tao(100) assert ( await async_subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=amount, safe_staking=True, @@ -973,14 +844,14 @@ async def test_safe_staking_scenarios_async( 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, + netuid=alice_sn.netuid, ) # Test Unstaking Scenarios # 1. Strict params - should fail response = await async_subtensor.staking.unstake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=full_stake, safe_unstaking=True, @@ -992,7 +863,7 @@ async def test_safe_staking_scenarios_async( 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, + netuid=alice_sn.netuid, ) logging.console.info(f"[orange]Current stake: {current_stake}[orange]") @@ -1006,7 +877,7 @@ async def test_safe_staking_scenarios_async( # 2. Partial allowed - should succeed partially response = await async_subtensor.staking.unstake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=current_stake, safe_unstaking=True, @@ -1018,17 +889,17 @@ async def test_safe_staking_scenarios_async( 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, + netuid=alice_sn.netuid, ) logging.console.info(f"[orange]Partial unstake: {partial_unstake}[orange]") - assert partial_unstake > Balance(0).set_unit(alice_subnet_netuid), ( + assert partial_unstake > Balance(0).set_unit(alice_sn.netuid), ( "Some stake should remain" ) # 3. Higher threshold - should succeed fully response = await async_subtensor.staking.unstake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, amount=partial_unstake, safe_unstaking=True, @@ -1046,37 +917,22 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): 1. Fails with strict threshold (0.5%) 2. Succeeds with lenient threshold (10%) """ - # Create new subnet (netuid 2) and register Alice - origin_netuid = 2 - assert subtensor.subnets.register_subnet(bob_wallet).success - assert subtensor.subnets.subnet_exists(origin_netuid), ( - "Subnet wasn't created successfully" - ) - dest_netuid = 3 - assert subtensor.subnets.register_subnet(bob_wallet).success - 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 - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=origin_netuid, - ).success - assert subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dest_netuid, - ).success + origin_sn = TestSubnet(subtensor) + dest_sn = TestSubnet(subtensor) + + sns = [origin_sn, dest_sn] + steps = [ + REGISTER_SUBNET(bob_wallet), + ACTIVATE_SUBNET(bob_wallet), + REGISTER_NEURON(alice_wallet), + ] + [sn.execute_steps(steps) for sn in sns] # Add initial stake to swap from initial_stake_amount = Balance.from_tao(10_000) assert subtensor.staking.add_stake( wallet=alice_wallet, - netuid=origin_netuid, + netuid=origin_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=initial_stake_amount, ).success @@ -1084,9 +940,9 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): origin_stake = subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, alice_wallet.hotkey.ss58_address, - netuid=origin_netuid, + netuid=origin_sn.netuid, ) - assert origin_stake > Balance(0).set_unit(origin_netuid), ( + assert origin_stake > Balance(0).set_unit(origin_sn.netuid), ( "Origin stake should be non-zero" ) @@ -1095,8 +951,8 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): response = subtensor.staking.swap_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=dest_netuid, + origin_netuid=origin_sn.netuid, + destination_netuid=dest_sn.netuid, amount=stake_swap_amount, wait_for_inclusion=True, wait_for_finalization=True, @@ -1110,9 +966,9 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): dest_stake = subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, alice_wallet.hotkey.ss58_address, - netuid=dest_netuid, + netuid=dest_sn.netuid, ) - assert dest_stake == Balance(0).set_unit(dest_netuid), ( + assert dest_stake == Balance(0).set_unit(dest_sn.netuid), ( "Destination stake should remain 0 after failed swap" ) @@ -1121,8 +977,8 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): response = subtensor.staking.swap_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=dest_netuid, + origin_netuid=origin_sn.netuid, + destination_netuid=dest_sn.netuid, amount=stake_swap_amount, wait_for_inclusion=True, wait_for_finalization=True, @@ -1136,9 +992,9 @@ def test_safe_swap_stake_scenarios(subtensor, alice_wallet, bob_wallet): dest_stake = subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, alice_wallet.hotkey.ss58_address, - netuid=dest_netuid, + netuid=dest_sn.netuid, ) - assert dest_stake > Balance(0).set_unit(dest_netuid), ( + assert dest_stake > Balance(0).set_unit(dest_sn.netuid), ( "Destination stake should be non-zero after successful swap" ) @@ -1154,42 +1010,23 @@ async def test_safe_swap_stake_scenarios_async( 1. Fails with strict threshold (0.5%) 2. Succeeds with lenient threshold (10%) """ - # Create new subnet (netuid 2) and register Alice - origin_netuid = 2 - assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success - 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)).success - 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 - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=origin_netuid, - ) - ).success - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=dest_netuid, - ) - ).success + origin_sn = TestSubnet(async_subtensor) + dest_sn = TestSubnet(async_subtensor) + + sns = [origin_sn, dest_sn] + steps = [ + REGISTER_SUBNET(bob_wallet), + ACTIVATE_SUBNET(bob_wallet), + REGISTER_NEURON(alice_wallet), + ] + [await sn.async_execute_steps(steps) for sn in sns] # Add initial stake to swap from initial_stake_amount = Balance.from_tao(10_000) assert ( await async_subtensor.staking.add_stake( wallet=alice_wallet, - netuid=origin_netuid, + netuid=origin_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=initial_stake_amount, ) @@ -1198,9 +1035,9 @@ async def test_safe_swap_stake_scenarios_async( origin_stake = await async_subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, alice_wallet.hotkey.ss58_address, - netuid=origin_netuid, + netuid=origin_sn.netuid, ) - assert origin_stake > Balance(0).set_unit(origin_netuid), ( + assert origin_stake > Balance(0).set_unit(origin_sn.netuid), ( "Origin stake should be non-zero" ) @@ -1209,8 +1046,8 @@ async def test_safe_swap_stake_scenarios_async( response = await async_subtensor.staking.swap_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=dest_netuid, + origin_netuid=origin_sn.netuid, + destination_netuid=dest_sn.netuid, amount=stake_swap_amount, wait_for_inclusion=True, wait_for_finalization=True, @@ -1224,9 +1061,9 @@ async def test_safe_swap_stake_scenarios_async( dest_stake = await async_subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, alice_wallet.hotkey.ss58_address, - netuid=dest_netuid, + netuid=dest_sn.netuid, ) - assert dest_stake == Balance(0).set_unit(dest_netuid), ( + assert dest_stake == Balance(0).set_unit(dest_sn.netuid), ( "Destination stake should remain 0 after failed swap" ) @@ -1235,8 +1072,8 @@ async def test_safe_swap_stake_scenarios_async( response = await async_subtensor.staking.swap_stake( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=origin_netuid, - destination_netuid=dest_netuid, + origin_netuid=origin_sn.netuid, + destination_netuid=dest_sn.netuid, amount=stake_swap_amount, wait_for_inclusion=True, wait_for_finalization=True, @@ -1250,9 +1087,9 @@ async def test_safe_swap_stake_scenarios_async( dest_stake = await async_subtensor.staking.get_stake( alice_wallet.coldkey.ss58_address, alice_wallet.hotkey.ss58_address, - netuid=dest_netuid, + netuid=dest_sn.netuid, ) - assert dest_stake > Balance(0).set_unit(dest_netuid), ( + assert dest_stake > Balance(0).set_unit(dest_sn.netuid), ( "Destination stake should be non-zero after successful swap" ) @@ -1264,78 +1101,72 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): - Moving stake from one hotkey-subnet pair to another - Testing `move_stake` method with `move_all_stake=True` flag. """ - alice_subnet_netuid = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet).success - assert subtensor.subnets.subnet_exists(alice_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) + alice_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + alice_sn.execute_steps(steps) assert subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1_000), ).success - stakes = subtensor.staking.get_stake_info_for_coldkey(alice_wallet.coldkey.ss58_address) + stakes = subtensor.staking.get_stake_info_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), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ), ] - bob_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet(bob_wallet).success - assert subtensor.subnets.subnet_exists(bob_subnet_netuid), ( - "Subnet wasn't created successfully" - ) - - assert wait_to_start_call(subtensor, bob_wallet, bob_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ).success + bob_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(bob_wallet), + ACTIVATE_SUBNET(bob_wallet), + ] + bob_sn.execute_steps(steps) - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - ).success + alice_sn.execute_steps([REGISTER_NEURON(bob_wallet), REGISTER_NEURON(dave_wallet)]) response = subtensor.staking.move_stake( wallet=alice_wallet, origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=alice_subnet_netuid, + origin_netuid=alice_sn.netuid, destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, + destination_netuid=bob_sn.netuid, amount=stakes[0].stake, wait_for_finalization=True, wait_for_inclusion=True, ) assert response.success is True - stakes = subtensor.staking.get_stake_info_for_coldkey(alice_wallet.coldkey.ss58_address) + stakes = subtensor.staking.get_stake_info_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 + netuid=alice_sn.netuid if subtensor.chain.is_fast_blocks() - else bob_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), + else bob_sn.netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ) @@ -1346,10 +1177,10 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): 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), + netuid=bob_sn.netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, bob_sn.netuid), + locked=Balance(0).set_unit(bob_sn.netuid), + emission=get_dynamic_balance(stakes[1].emission.rao, bob_sn.netuid), drain=0, is_registered=True, ), @@ -1368,13 +1199,13 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): dave_stake = subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=bob_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, + netuid=bob_sn.netuid, ) logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") assert subtensor.staking.add_stake( wallet=dave_wallet, - netuid=bob_subnet_netuid, + netuid=bob_sn.netuid, hotkey_ss58=dave_wallet.hotkey.ss58_address, amount=Balance.from_tao(1000), allow_partial_stake=True, @@ -1383,21 +1214,21 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): dave_stake = subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, + netuid=bob_sn.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) + subtensor.block + subtensor.subnets.tempo(netuid=bob_sn.netuid) ) response = subtensor.staking.move_stake( wallet=dave_wallet, origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, - origin_netuid=bob_subnet_netuid, + origin_netuid=bob_sn.netuid, destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, + destination_netuid=bob_sn.netuid, wait_for_inclusion=True, wait_for_finalization=True, move_all_stake=True, @@ -1407,7 +1238,7 @@ def test_move_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): dave_stake = subtensor.staking.get_stake( coldkey_ss58=dave_wallet.coldkey.ss58_address, hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, + netuid=bob_sn.netuid, ) logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") @@ -1422,20 +1253,17 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ - Moving stake from one hotkey-subnet pair to another - Testing `move_stake` method with `move_all_stake=True` flag. """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - 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 - ) + alice_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + await alice_sn.async_execute_steps(steps) assert ( await async_subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1_000), ) @@ -1449,45 +1277,32 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ 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), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ), ] - bob_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 - assert (await async_subtensor.subnets.register_subnet(bob_wallet)).success - assert await async_subtensor.subnets.subnet_exists(bob_subnet_netuid), ( - "Subnet wasn't created successfully" - ) + bob_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(bob_wallet), + ACTIVATE_SUBNET(bob_wallet), + ] + await bob_sn.async_execute_steps(steps) - assert await async_wait_to_start_call( - async_subtensor, bob_wallet, bob_subnet_netuid + await alice_sn.async_execute_steps( + [REGISTER_NEURON(bob_wallet), REGISTER_NEURON(dave_wallet)] ) - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid, - ) - ).success - - assert ( - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid, - ) - ).success - response = await async_subtensor.staking.move_stake( wallet=alice_wallet, origin_hotkey_ss58=alice_wallet.hotkey.ss58_address, - origin_netuid=alice_subnet_netuid, + origin_netuid=alice_sn.netuid, destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, + destination_netuid=bob_sn.netuid, amount=stakes[0].stake, wait_for_finalization=True, wait_for_inclusion=True, @@ -1502,12 +1317,12 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ StakeInfo( hotkey_ss58=stakes[0].hotkey_ss58, coldkey_ss58=alice_wallet.coldkey.ss58_address, - netuid=alice_subnet_netuid + netuid=alice_sn.netuid if await async_subtensor.chain.is_fast_blocks() - else bob_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), + else bob_sn.netuid, + stake=get_dynamic_balance(stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(stakes[0].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ) @@ -1518,10 +1333,10 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ 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), + netuid=bob_sn.netuid, + stake=get_dynamic_balance(stakes[1].stake.rao, bob_sn.netuid), + locked=Balance(0).set_unit(bob_sn.netuid), + emission=get_dynamic_balance(stakes[1].emission.rao, bob_sn.netuid), drain=0, is_registered=True, ), @@ -1537,7 +1352,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ 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, + netuid=bob_sn.netuid, ) logging.console.info(f"[orange]Dave stake before adding: {dave_stake}[orange]") @@ -1545,7 +1360,7 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ await async_subtensor.staking.add_stake( wallet=dave_wallet, hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=bob_subnet_netuid, + netuid=bob_sn.netuid, amount=Balance.from_tao(1000), allow_partial_stake=True, ) @@ -1554,12 +1369,12 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ 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, + netuid=bob_sn.netuid, ) logging.console.info(f"[orange]Dave stake after adding: {dave_stake}[orange]") block_, tampo_ = await asyncio.gather( - async_subtensor.block, async_subtensor.subnets.tempo(netuid=bob_subnet_netuid) + async_subtensor.block, async_subtensor.subnets.tempo(netuid=bob_sn.netuid) ) # let chain to process the transaction await async_subtensor.wait_for_block(block_ + tampo_) @@ -1567,23 +1382,25 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ response = await async_subtensor.staking.move_stake( wallet=dave_wallet, origin_hotkey_ss58=dave_wallet.hotkey.ss58_address, - origin_netuid=bob_subnet_netuid, + origin_netuid=bob_sn.netuid, destination_hotkey_ss58=bob_wallet.hotkey.ss58_address, - destination_netuid=bob_subnet_netuid, + destination_netuid=bob_sn.netuid, wait_for_inclusion=True, wait_for_finalization=True, move_all_stake=True, ) - assert response.success is True + assert response.success, response.error_message 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, + netuid=bob_sn.netuid, ) logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") assert dave_stake.rao == CloseInValue(0, 0.00001) + print(">>> alice", alice_sn.calls) + print(">>> alice", alice_sn.calls) def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -1592,23 +1409,16 @@ 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.subnets.get_total_subnets() # 2 - - assert subtensor.subnets.register_subnet(alice_wallet).success - 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.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ).success + alice_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + alice_sn.execute_steps(steps) assert subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1_000), ).success @@ -1621,12 +1431,10 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): 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 - ), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(alice_stakes[0].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ), @@ -1635,25 +1443,22 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): bob_stakes = subtensor.staking.get_stake_info_for_coldkey( bob_wallet.coldkey.ss58_address ) - assert bob_stakes == [] - dave_subnet_netuid = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet(dave_wallet).success - - assert wait_to_start_call(subtensor, dave_wallet, dave_subnet_netuid) - - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ).success + dave_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(dave_wallet), + ACTIVATE_SUBNET(dave_wallet), + REGISTER_NEURON(bob_wallet), + ] + dave_sn.execute_steps(steps) response = 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, + origin_netuid=alice_sn.netuid, + destination_netuid=dave_sn.netuid, amount=alice_stakes[0].stake, wait_for_inclusion=True, wait_for_finalization=True, @@ -1669,13 +1474,11 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): 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), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), emission=get_dynamic_balance( - alice_stakes[0].emission.rao, alice_subnet_netuid + alice_stakes[0].emission.rao, alice_sn.netuid ), drain=0, is_registered=True, @@ -1695,12 +1498,10 @@ def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): 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 - ), + netuid=dave_sn.netuid, + stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_sn.netuid), + locked=Balance(0).set_unit(dave_sn.netuid), + emission=get_dynamic_balance(bob_stakes[0].emission.rao, dave_sn.netuid), drain=0, is_registered=False, ), @@ -1717,28 +1518,17 @@ async def test_transfer_stake_async( - Adding stake - Transferring stake from one coldkey-subnet pair to another """ - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 2 - - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - 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 - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - ) - ).success + alice_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + ] + await alice_sn.async_execute_steps(steps) assert ( await async_subtensor.staking.add_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(1_000), ) @@ -1752,12 +1542,10 @@ async def test_transfer_stake_async( 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 - ), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), + emission=get_dynamic_balance(alice_stakes[0].emission.rao, alice_sn.netuid), drain=0, is_registered=True, ), @@ -1766,29 +1554,22 @@ async def test_transfer_stake_async( bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( bob_wallet.coldkey.ss58_address ) - assert bob_stakes == [] - dave_subnet_netuid = await async_subtensor.subnets.get_total_subnets() # 3 - assert (await async_subtensor.subnets.register_subnet(dave_wallet)).success - - assert await async_wait_to_start_call( - async_subtensor, dave_wallet, dave_subnet_netuid - ) - - assert ( - await async_subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=dave_subnet_netuid, - ) - ).success + dave_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(dave_wallet), + ACTIVATE_SUBNET(dave_wallet), + REGISTER_NEURON(bob_wallet), + ] + await dave_sn.async_execute_steps(steps) response = 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, + origin_netuid=alice_sn.netuid, + destination_netuid=dave_sn.netuid, amount=alice_stakes[0].stake, wait_for_inclusion=True, wait_for_finalization=True, @@ -1804,13 +1585,11 @@ async def test_transfer_stake_async( 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), + netuid=alice_sn.netuid, + stake=get_dynamic_balance(alice_stakes[0].stake.rao, alice_sn.netuid), + locked=Balance(0).set_unit(alice_sn.netuid), emission=get_dynamic_balance( - alice_stakes[0].emission.rao, alice_subnet_netuid + alice_stakes[0].emission.rao, alice_sn.netuid ), drain=0, is_registered=True, @@ -1830,12 +1609,10 @@ async def test_transfer_stake_async( 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 - ), + netuid=dave_sn.netuid, + stake=get_dynamic_balance(bob_stakes[0].stake.rao, dave_sn.netuid), + locked=Balance(0).set_unit(dave_sn.netuid), + emission=get_dynamic_balance(bob_stakes[0].emission.rao, dave_sn.netuid), drain=0, is_registered=False, ), @@ -1859,44 +1636,24 @@ def test_unstaking_with_limit( ): """Test unstaking with limits goes well for all subnets with and without price limit.""" # Register first SN - alice_subnet_netuid_2 = subtensor.subnets.get_total_subnets() # 2 - assert subtensor.subnets.register_subnet(alice_wallet).success - 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) - - # Register Bob and Dave in SN2 - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid_2, - ).success - - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_2, - ).success + alice_sn_2 = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + REGISTER_NEURON(dave_wallet), + ] + alice_sn_2.execute_steps(steps) # Register second SN - alice_subnet_netuid_3 = subtensor.subnets.get_total_subnets() # 3 - assert subtensor.subnets.register_subnet(alice_wallet).success - 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) - - # Register Bob and Dave in SN3 - assert subtensor.subnets.burned_register( - wallet=bob_wallet, - netuid=alice_subnet_netuid_3, - ).success - - assert subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_3, - ).success + alice_sn_3 = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + REGISTER_NEURON(dave_wallet), + ] + alice_sn_3.execute_steps(steps) # Check Bob's stakes are empty. assert ( @@ -1909,18 +1666,18 @@ def test_unstaking_with_limit( assert subtensor.staking.add_stake( wallet=bob_wallet, hotkey_ss58=dave_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid_2, + netuid=alice_sn_2.netuid, amount=Balance.from_tao(10000), period=16, - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + ).success, f"Cant add stake to dave in SN {alice_sn_2.netuid}" assert subtensor.staking.add_stake( wallet=bob_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, - netuid=alice_subnet_netuid_3, + netuid=alice_sn_3.netuid, amount=Balance.from_tao(15000), period=16, - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" + ).success, f"Cant add stake to dave in SN {alice_sn_3.netuid}" # Check that both stakes are presented in result bob_stakes = subtensor.staking.get_stake_info_for_coldkey( @@ -1970,55 +1727,24 @@ async def test_unstaking_with_limit_async( ): """Test unstaking with limits goes well for all subnets with and without price limit.""" # Register first SN - alice_subnet_netuid_2 = await async_subtensor.subnets.get_total_subnets() # 2 - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - 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, - ) - ).success - - assert ( - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_2, - ) - ).success + alice_sn_2 = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + REGISTER_NEURON(dave_wallet), + ] + await alice_sn_2.async_execute_steps(steps) # Register second SN - alice_subnet_netuid_3 = await async_subtensor.subnets.get_total_subnets() # 3 - assert (await async_subtensor.subnets.register_subnet(alice_wallet)).success - 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, - ) - ).success - - assert ( - await async_subtensor.subnets.burned_register( - wallet=dave_wallet, - netuid=alice_subnet_netuid_3, - ) - ).success - + alice_sn_3 = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + REGISTER_NEURON(dave_wallet), + ] + await alice_sn_3.async_execute_steps(steps) # Check Bob's stakes are empty. assert ( await async_subtensor.staking.get_stake_info_for_coldkey( @@ -2032,22 +1758,22 @@ async def test_unstaking_with_limit_async( assert ( await async_subtensor.staking.add_stake( wallet=bob_wallet, - netuid=alice_subnet_netuid_2, + netuid=alice_sn_2.netuid, hotkey_ss58=dave_wallet.hotkey.ss58_address, amount=Balance.from_tao(10000), period=16, ) - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_2}" + ).success, f"Cant add stake to dave in SN {alice_sn_2.netuid}" assert ( await async_subtensor.staking.add_stake( wallet=bob_wallet, - netuid=alice_subnet_netuid_3, + netuid=alice_sn_3.netuid, hotkey_ss58=alice_wallet.hotkey.ss58_address, amount=Balance.from_tao(15000), period=16, ) - ).success, f"Cant add stake to dave in SN {alice_subnet_netuid_3}" + ).success, f"Cant add stake to dave in SN {alice_sn_3.netuid}" # Check that both stakes are presented in result bob_stakes = await async_subtensor.staking.get_stake_info_for_coldkey( @@ -2087,33 +1813,32 @@ async def test_unstaking_with_limit_async( def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet): """Tests auto staking logic.""" - - alice_subnet_netuid = subtensor.subnets.get_total_subnets() - assert subtensor.subnets.register_subnet(alice_wallet) - assert subtensor.subnets.subnet_exists(alice_subnet_netuid) - - assert wait_to_start_call(subtensor, alice_wallet, alice_subnet_netuid) - - assert subtensor.extrinsics.burned_register(bob_wallet, alice_subnet_netuid) + alice_sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + alice_sn.execute_steps(steps) assert subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == {} # set auto stake assert subtensor.staking.set_auto_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=bob_wallet.hotkey.ss58_address, ).success # check auto stake assert subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == { - alice_subnet_netuid: bob_wallet.hotkey.ss58_address + alice_sn.netuid: bob_wallet.hotkey.ss58_address } # set auto stake to nonexistent hotkey success, message = subtensor.staking.set_auto_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=eve_wallet.hotkey.ss58_address, ) assert success is False @@ -2121,46 +1846,52 @@ def test_auto_staking(subtensor, alice_wallet, bob_wallet, eve_wallet): # check auto stake assert subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == { - alice_subnet_netuid: bob_wallet.hotkey.ss58_address + alice_sn.netuid: bob_wallet.hotkey.ss58_address } @pytest.mark.asyncio -async def test_auto_staking_async(async_subtensor, alice_wallet, bob_wallet, eve_wallet): +async def test_auto_staking_async( + async_subtensor, alice_wallet, bob_wallet, eve_wallet +): """Tests auto staking logic.""" + alice_sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + await alice_sn.async_execute_steps(steps) - alice_subnet_netuid = await async_subtensor.subnets.get_total_subnets() - assert await async_subtensor.subnets.register_subnet(alice_wallet) - assert await async_subtensor.subnets.subnet_exists(alice_subnet_netuid) - - assert await async_wait_to_start_call(async_subtensor, alice_wallet, alice_subnet_netuid) - - assert await async_subtensor.extrinsics.burned_register(bob_wallet, alice_subnet_netuid) - - assert await async_subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == {} + assert ( + await async_subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) + == {} + ) # set auto stake - assert (await async_subtensor.staking.set_auto_stake( - wallet=alice_wallet, - netuid=alice_subnet_netuid, - hotkey_ss58=bob_wallet.hotkey.ss58_address, - )).success + assert ( + await async_subtensor.staking.set_auto_stake( + wallet=alice_wallet, + netuid=alice_sn.netuid, + hotkey_ss58=bob_wallet.hotkey.ss58_address, + ) + ).success # check auto stake - assert await async_subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == { - alice_subnet_netuid: bob_wallet.hotkey.ss58_address - } + assert await async_subtensor.staking.get_auto_stakes( + alice_wallet.coldkey.ss58_address + ) == {alice_sn.netuid: bob_wallet.hotkey.ss58_address} # set auto stake to nonexistent hotkey success, message = await async_subtensor.staking.set_auto_stake( wallet=alice_wallet, - netuid=alice_subnet_netuid, + netuid=alice_sn.netuid, hotkey_ss58=eve_wallet.hotkey.ss58_address, ) assert success is False assert "HotKeyNotRegisteredInSubNet" in message # check auto stake - assert await async_subtensor.staking.get_auto_stakes(alice_wallet.coldkey.ss58_address) == { - alice_subnet_netuid: bob_wallet.hotkey.ss58_address - } + assert await async_subtensor.staking.get_auto_stakes( + alice_wallet.coldkey.ss58_address + ) == {alice_sn.netuid: bob_wallet.hotkey.ss58_address} diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 570171da8c..bc03a5de49 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -8,14 +8,7 @@ from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils.chain_interactions import ( - async_wait_epoch, - 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 import TestSubnet, REGISTER_SUBNET, ACTIVATE_SUBNET, REGISTER_NEURON """ Verifies: @@ -72,8 +65,9 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall pre_subnet_creation_cost = subtensor.subnets.get_subnet_burn_cost() # Register subnet - response = subtensor.subnets.register_subnet(alice_wallet) - assert response.success, "Unable to register the subnet." + alice_sn = TestSubnet(subtensor) + response = alice_sn.execute_one(REGISTER_SUBNET(alice_wallet)) + netuid = alice_sn.netuid # Subnet burn cost is increased immediately after a subnet is registered post_subnet_creation_cost = subtensor.subnets.get_subnet_burn_cost() @@ -143,12 +137,10 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall 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.subnets.burned_register(bob_wallet, netuid).success, ( - "Unable to register Bob as a neuron" - ) + alice_sn.execute_steps([ + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ]) # Verify Bob's UID on netuid 2 is 1 assert ( @@ -160,16 +152,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall # Fetch recycle_amount to register to the subnet recycle_amount = subtensor.subnets.recycle(netuid) - call = subtensor.substrate.compose_call( - call_module="SubtensorModule", - call_function="burned_register", - call_params={ - "netuid": netuid, - "hotkey": bob_wallet.hotkey.ss58_address, - }, - ) - payment_info = subtensor.substrate.get_payment_info(call, bob_wallet.coldkeypub) - fee = Balance.from_rao(payment_info["partial_fee"]) + fee = alice_sn.calls[-1].extrinsic_fee bob_balance_post_reg = subtensor.wallets.get_balance( bob_wallet.coldkeypub.ss58_address ) @@ -179,24 +162,12 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall "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 wait_epoch(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" + alice_sn.wait_next_epoch() # Fetch and assert existential deposit for an account in the network assert subtensor.chain.get_existential_deposit() == existential_deposit, ( @@ -260,8 +231,9 @@ async def test_subtensor_extrinsics_async( pre_subnet_creation_cost = await async_subtensor.subnets.get_subnet_burn_cost() # Register subnet - response = await async_subtensor.subnets.register_subnet(alice_wallet) - assert response.success, "Unable to register the subnet." + alice_sn = TestSubnet(async_subtensor) + response = await alice_sn.async_execute_one(REGISTER_SUBNET(alice_wallet)) + netuid = alice_sn.netuid # Subnet burn cost is increased immediately after a subnet is registered post_subnet_creation_cost = await async_subtensor.subnets.get_subnet_burn_cost() @@ -333,12 +305,10 @@ async def test_subtensor_extrinsics_async( 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) - ).success, "Unable to register Bob as a neuron." + alice_sn.execute_steps([ + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ]) # Verify Bob's UID on netuid 2 is 1 assert ( @@ -350,18 +320,7 @@ async def test_subtensor_extrinsics_async( # 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"]) + fee = alice_sn.calls[-1].extrinsic_fee bob_balance_post_reg = await async_subtensor.wallets.get_balance( bob_wallet.coldkeypub.ss58_address ) @@ -380,15 +339,7 @@ async def test_subtensor_extrinsics_async( 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" + await alice_sn.async_wait_next_epoch() # Fetch and assert existential deposit for an account in the network assert ( diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py index dc8e69deb3..dcc87ff9a2 100644 --- a/tests/e2e_tests/test_transfer.py +++ b/tests/e2e_tests/test_transfer.py @@ -7,7 +7,7 @@ from bittensor import logging if typing.TYPE_CHECKING: - from bittensor.core.addons import SubtensorApi + from bittensor.extras import SubtensorApi def test_transfer(subtensor, alice_wallet): From 88b2b6280d1d1422080a1949aa6a73b4ab156243 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 20:03:04 -0700 Subject: [PATCH 317/416] ruff --- tests/e2e_tests/test_subtensor_functions.py | 27 ++++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index bc03a5de49..fa13c49bd2 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -8,7 +8,12 @@ from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from tests.e2e_tests.utils import TestSubnet, REGISTER_SUBNET, ACTIVATE_SUBNET, REGISTER_NEURON +from tests.e2e_tests.utils import ( + TestSubnet, + REGISTER_SUBNET, + ACTIVATE_SUBNET, + REGISTER_NEURON, +) """ Verifies: @@ -137,10 +142,12 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall bob_balance = subtensor.wallets.get_balance(bob_wallet.coldkeypub.ss58_address) - alice_sn.execute_steps([ - ACTIVATE_SUBNET(alice_wallet), - REGISTER_NEURON(bob_wallet), - ]) + alice_sn.execute_steps( + [ + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + ) # Verify Bob's UID on netuid 2 is 1 assert ( @@ -305,10 +312,12 @@ async def test_subtensor_extrinsics_async( bob_wallet.coldkeypub.ss58_address ) - alice_sn.execute_steps([ - ACTIVATE_SUBNET(alice_wallet), - REGISTER_NEURON(bob_wallet), - ]) + alice_sn.execute_steps( + [ + ACTIVATE_SUBNET(alice_wallet), + REGISTER_NEURON(bob_wallet), + ] + ) # Verify Bob's UID on netuid 2 is 1 assert ( From b9f3e7d13740efff7a41a5ecc39d51111777faf1 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 20:29:43 -0700 Subject: [PATCH 318/416] update MIGRATION.md --- MIGRATION.md | 63 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 26 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index b2228798c5..046d28089f 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -3,25 +3,18 @@ ## 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) -2. ✅ Unify extrinsic return values by introducing an ExtrinsicResponse class. Extrinsics currently return either a boolean or a tuple. +2. ✅ 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. +3. ✅ Make the internal logic of extrinsics the same. There are extrinsics that are slightly different in implementation. - Purpose: - - Ease of processing. - - This class should contain success, message, and optionally data. - - Opportunity to expand the content of the extrinsic's response at any time upon community request or based on new technical requirements any time. - -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.~~ Just improved regarding usage. +4. ✅ ~~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.~~ Just improved regarding usage. -6. ✅ Remove `unstake_all` parameter from `unstake_extrinsic` since we have `unstake_all_extrinsic`which is calles another subtensor function. +5. ✅ 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`. +6. ✅ `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. +7. ✅ 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`.~~ `get_transfer_fee` isn't `get_extrinsic_fee` +8. ✅ ~~`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`.~~ (`get_transfer_fee` isn't `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. @@ -30,6 +23,10 @@ 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.~~ wrong idea +7. Apply `SimSwap` logic to calculate any stake operation fees (this is not an extrinsic fee) +8. Should the next functions move to `subtensor` as methods? They have exactly the same behavior as subtensor methods. + - `get_metadata` + - `get_last_bonds_reset` ## Metagraph 1. ✅ Remove verbose archival node warnings for blocks older than 300. Some users complained about many messages for them. @@ -69,30 +66,44 @@ rename this variable in documentation. 11. ✅ Remove deprecated `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. -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) +12. ✅ The SDK is dropping support for `Python 3.9` starting with this release. +13. ✅ Remove `Default is` and `Default to` in docstrings bc parameters enough. +14. `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) +15. 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 Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 -3. Implement `Crowdloan` logic. Issue: https://github.com/opentensor/bittensor/issues/3017 +1. ✅ 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. + - Opportunity to expand the content of the extrinsic's response at any time upon community request or based on new technical requirements any time. +2. ✅ Add `bittensor.utils.hex_to_ss58` function. SDK still doesn't have it. (Probably inner import `from scalecodec import ss58_encode, ss58_decode`) +3. ✅ Implement Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 +4. Implement classes for `Block` and `BlockHash` objects that will contain the following fields: + - number + - hash + - timestamp + - block_explorer (with the link to tao.app) + + This implementation has been repeatedly requested by the community in the past. The instances of this classes should behave the same as `block` (int) and `block_hash` (str) currently do (to safe backward compatibility). +5. Implement `Crowdloan` logic. Issue: https://github.com/opentensor/bittensor/issues/3017 ## 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) +3. ✅ Write a configurable test harness class for tests that will accept arguments and immediately: + - [x] create a subnet + - [x] activate a subnet (if the argument is passed as True) + - [x] register neurons (use wallets as arguments) + - [x] 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: From 4d869957d7b5ca924ddd3a2818c2755a7c6a505f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 20:36:37 -0700 Subject: [PATCH 319/416] oops top much wait. fixed --- tests/e2e_tests/test_commit_reveal.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 452df32237..f8877d0082 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -20,6 +20,7 @@ ) TESTED_MECHANISMS = 2 +EXPECTED_CR_VERSION = 4 def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): @@ -63,9 +64,8 @@ def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): cr_version = subtensor.substrate.query( module="SubtensorModule", storage_function="CommitRevealWeightsVersion" ) - expected_cr_version = 4 - assert cr_version == expected_cr_version, ( - f"Commit reveal version is not {expected_cr_version}, got {cr_version}" + assert cr_version == EXPECTED_CR_VERSION, ( + f"Commit reveal version is not {EXPECTED_CR_VERSION}, got {cr_version}" ) # Verify weights rate limit was changed @@ -251,9 +251,8 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet cr_version = await async_subtensor.substrate.query( module="SubtensorModule", storage_function="CommitRevealWeightsVersion" ) - expected_cr_version = 4 - assert cr_version == 4, ( - f"Commit reveal version is not {expected_cr_version}, got {cr_version}" + assert cr_version == EXPECTED_CR_VERSION, ( + f"Commit reveal version is not {EXPECTED_CR_VERSION}, got {cr_version}" ) # Verify weights rate limit was changed @@ -327,10 +326,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet ) # Fetch current commits pending on the chain - await async_subtensor.wait_for_block( - await async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) - + 1 - ) + await async_subtensor.wait_for_block(await async_subtensor.block + 1) commits_on_chain = ( await async_subtensor.commitments.get_timelocked_weight_commits( From 09ee46ef01cd5ea01b4a236fd52933ee1c133a0d Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 21:23:26 -0700 Subject: [PATCH 320/416] add `tests/e2e_tests/framework/README.md` --- tests/e2e_tests/framework/README.md | 148 ++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) create mode 100644 tests/e2e_tests/framework/README.md diff --git a/tests/e2e_tests/framework/README.md b/tests/e2e_tests/framework/README.md new file mode 100644 index 0000000000..80f07e76b0 --- /dev/null +++ b/tests/e2e_tests/framework/README.md @@ -0,0 +1,148 @@ +# E2E Test Framework + +## Overview +The E2E test framework provides a unified orchestration layer for subnet operations testing in Bittensor. +It simplifies the creation of end-to-end test scenarios by abstracting low-level Subtensor API calls into declarative steps. + +--- + +## Structure +``` +tests/e2e_tests/framework/ +├── subnet.py # Main orchestration class (TestSubnet) +├── utils.py # Common helpers and validators +├── __init__.py +└── calls/ # Auto-generated extrinsic definitions + ├── sudo_calls.py + ├── non_sudo_calls.py + ├── pallets.py + └── __init__.py +``` + +--- + +## The `TestSubnet` Class + +`TestSubnet` provides a high-level API for registering, activating, and configuring subnets, adding neurons, and +modifying hyperparameters. + +### Key Features +- Supports both single-step and multi-step execution for full flexibility in test composition. +- Supports synchronous and asynchronous execution (`execute_steps`, `async_execute_steps`). +- Maintains a history of all calls through `CALL_RECORD` for detailed introspection. +- Returns a standardized `ExtrinsicResponse` object for each operation — including status, message, receipt, and fees — + greatly enhancing debugging capabilities. +- Automatically injects the subnet `netuid` for all commands that use the `NETUID` constant. + This ensures all steps executed within a single `TestSubnet` instance are scoped to the subnet registered during that + test session. +- Validates pallet and parameter correctness based on live Subtensor metadata. +- Fully compatible with SDK extrinsics such as `sudo_call_extrinsic` and `async_sudo_call_extrinsic`. +- Designed not only for SDK developers but also for the broader Bittensor community — enabling subnet operators, + researchers, and contributors to deploy, configure, and validate subnet behavior in reproducible on-chain or simulated + environments. + +--- + +## Example Usage + +### Synchronous Example +```python +def test_subnet_setup(subtensor, alice_wallet): + from tests.e2e_tests.utils import ( + TestSubnet, + NETUID, + REGISTER_SUBNET, + ACTIVATE_SUBNET, + SUDO_SET_TEMPO, + AdminUtils + ) + + sn = TestSubnet(subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, 100), + ] + sn.execute_steps(steps) + + assert subtensor.subnets.is_subnet_active(sn.netuid) +``` + +### Asynchronous Example +```python +import pytest + +@pytest.mark.asyncio +async def test_subnet_async(async_subtensor, alice_wallet): + from tests.e2e_tests.utils import ( + TestSubnet, + NETUID, + REGISTER_SUBNET, + ACTIVATE_SUBNET, + SUDO_SET_TEMPO, + AdminUtils + ) + + sn = TestSubnet(async_subtensor) + steps = [ + REGISTER_SUBNET(alice_wallet), + ACTIVATE_SUBNET(alice_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, 100), + ] + await sn.async_execute_steps(steps) + + assert await async_subtensor.subnets.is_subnet_active(sn.netuid) +``` + +> **Note:** +> The `NETUID` constant is a dynamic placeholder automatically replaced with the actual `netuid` +> obtained during subnet registration. +> This ensures all subsequent operations (e.g., `sudo_set_tempo`, `set_weights_set_rate_limit`) apply +> to the correct subnet within the same test instance. + +--- + +## Community and Ecosystem Use + +While this framework was originally designed to streamline internal SDK development and testing, it is intentionally +built as a reusable and transparent orchestration layer. +Community developers, subnet operators, and researchers can leverage it to deploy, configure, and validate subnet +behavior under real or simulated network conditions. +By exposing the same primitives that the SDK itself uses, the framework enables reproducible experiments, automated +scenario testing, and regression validation without needing deep familiarity with Subtensor internals. + +In other words, it serves both as a developer tool and a community-facing harness for controlled, verifiable subnet +testing across environments. + +## Logging and Debugging +All operations performed through `TestSubnet` are logged with colorized output: +- Subnet registration and activation +- Neuron registration +- Hyperparameter updates +- Extrinsic success or failure + +Example console output: +``` +Subnet [blue]1[/blue] was registered. +Subnet [blue]1[/blue] was activated. +Hyperparameter [blue]sudo_set_tempo[/blue] was set successfully with params [blue]{'netuid': 1, 'tempo': 20}[/blue]. +``` + +The **`ExtrinsicResponse`** type provides: +- Direct access to `extrinsic_receipt` +- Status and message details +- Fee and inclusion/finalization data +- A unified return contract for all extrinsics + +This combination allows tests to become more accurate, expressive, and sensitive to on-chain state. + +--- + +## Extensibility +The framework can be easily extended with new orchestrators. +All of them can reuse the same interface and underlying mechanisms as `TestSubnet`. + +--- + +## Migration Note +All E2E tests now use this framework. The codebase is cleaner, more maintainable, and logging/debugging has been significantly improved. From 52fa878c845e727c781f171c130f4d90574b32a0 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 21:23:38 -0700 Subject: [PATCH 321/416] add import --- tests/e2e_tests/framework/subnet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/framework/subnet.py b/tests/e2e_tests/framework/subnet.py index 965acdf860..730d7d9f42 100644 --- a/tests/e2e_tests/framework/subnet.py +++ b/tests/e2e_tests/framework/subnet.py @@ -1,5 +1,5 @@ from typing import Optional, Union - +from collections import namedtuple from async_substrate_interface.async_substrate import AsyncExtrinsicReceipt from async_substrate_interface.sync_substrate import ExtrinsicReceipt from bittensor_wallet import Wallet @@ -462,7 +462,7 @@ async def async_wait_next_epoch(self, netuid: Optional[int] = None): if not netuid: self._check_netuid() current_block = await self.s.block - next_epoch_block = self.s.subnets.get_next_epoch_start_block(netuid) + next_epoch_block = await self.s.subnets.get_next_epoch_start_block(netuid) logging.console.info( f"Waiting for next epoch first block: [blue]{next_epoch_block}[/blue]. " f"Current block: [blue]{current_block}[/blue]." From a4f8e1b6db4f24fb029dbd25a7ac3ed1bbb02e7b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Fri, 3 Oct 2025 21:23:44 -0700 Subject: [PATCH 322/416] API --- tests/e2e_tests/test_subtensor_functions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index fa13c49bd2..85f77848d0 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -159,7 +159,7 @@ async def test_subtensor_extrinsics(subtensor, templates, alice_wallet, bob_wall # Fetch recycle_amount to register to the subnet recycle_amount = subtensor.subnets.recycle(netuid) - fee = alice_sn.calls[-1].extrinsic_fee + fee = alice_sn.calls[-1].response.extrinsic_fee bob_balance_post_reg = subtensor.wallets.get_balance( bob_wallet.coldkeypub.ss58_address ) @@ -312,7 +312,7 @@ async def test_subtensor_extrinsics_async( bob_wallet.coldkeypub.ss58_address ) - alice_sn.execute_steps( + await alice_sn.async_execute_steps( [ ACTIVATE_SUBNET(alice_wallet), REGISTER_NEURON(bob_wallet), @@ -329,7 +329,7 @@ async def test_subtensor_extrinsics_async( # Fetch recycle_amount to register to the subnet recycle_amount = await async_subtensor.subnets.recycle(netuid) - fee = alice_sn.calls[-1].extrinsic_fee + fee = alice_sn.calls[-1].response.extrinsic_fee bob_balance_post_reg = await async_subtensor.wallets.get_balance( bob_wallet.coldkeypub.ss58_address ) From 3334af2f0ec4e5df4f0315c47bbf434b575ca93a Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 02:14:37 -0700 Subject: [PATCH 323/416] add class BlockInfo --- bittensor/core/types.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 74e5d54787..e26f3a0c33 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -1,6 +1,7 @@ import argparse from abc import ABC from dataclasses import dataclass +from datetime import datetime from typing import Any, Literal, Optional, TypedDict, Union, TYPE_CHECKING import numpy as np @@ -518,3 +519,12 @@ def with_log( if self.message: getattr(logging, level)(self.message) return self + + +@dataclass +class BlockInfo: + number: int + hash: str + timestamp: datetime + header: Optional[dict] = None + extrinsics: Optional[dict] = None From e6969c97b28ae2156b3be67fb77cf6a3f53ccc34 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 02:15:00 -0700 Subject: [PATCH 324/416] add `subtensor.get_block_info` method --- bittensor/core/async_subtensor.py | 44 +++++++++++++++++++++++++++++++ bittensor/core/subtensor.py | 44 +++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 1729796798..a70c7e2aae 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -89,6 +89,7 @@ from bittensor.core.metagraph import AsyncMetagraph from bittensor.core.settings import version_as_int, TYPE_REGISTRY from bittensor.core.types import ( + BlockInfo, ExtrinsicResponse, ParamWithTypes, Salt, @@ -1342,6 +1343,49 @@ async def get_block_hash(self, block: Optional[int] = None) -> str: else: return await self.substrate.get_chain_head() + async def get_block_info( + self, + block: Optional[int] = None, + block_hash: Optional[str] = None, + ) -> Optional[BlockInfo]: + """ + Retrieve complete information about a specific block from the Subtensor chain. + + This method aggregates multiple low-level RPC calls into a single structured response, returning both the raw + on-chain data and high-level decoded metadata for the given block. + + Args: + block: The block number for which the hash is to be retrieved. + block_hash: The hash of the block to retrieve the block from. + + Returns: + BlockInfo instance: + A dataclass containing all available information about the specified block, including: + - number: The block number. + - hash: The corresponding block hash. + - timestamp: The timestamp of the block (based on the `Timestamp.Now` extrinsic). + - header: The raw block header returned by the node RPC. + - extrinsics: The list of decoded extrinsics included in the block. + """ + block_info = self.substrate.get_block( + block_number=block, + block_hash=block_hash, + ignore_decoding_errors=True + ) + if isinstance(block_info, dict) and (header := block_info.get("header")): + block = block or header.get("number", None) + block_hash = block_hash or header.get("hash", None) + extrinsics = block_info.get("extrinsics") + timestamp = await self.get_timestamp(block=block) + return BlockInfo( + number=block, + hash=block_hash, + timestamp=timestamp, + header=header, + extrinsics=extrinsics, + ) + return None + async def get_parents( self, hotkey_ss58: str, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 31f912e1f2..15ebca2c5a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -92,6 +92,7 @@ TYPE_REGISTRY, ) from bittensor.core.types import ( + BlockInfo, ExtrinsicResponse, ParamWithTypes, Salt, @@ -781,6 +782,49 @@ def get_block_hash(self, block: Optional[int] = None) -> str: else: return self.substrate.get_chain_head() + def get_block_info( + self, + block: Optional[int] = None, + block_hash: Optional[str] = None, + ) -> Optional[BlockInfo]: + """ + Retrieve complete information about a specific block from the Subtensor chain. + + This method aggregates multiple low-level RPC calls into a single structured response, returning both the raw + on-chain data and high-level decoded metadata for the given block. + + Args: + block: The block number for which the hash is to be retrieved. + block_hash: The hash of the block to retrieve the block from. + + Returns: + BlockInfo instance: + A dataclass containing all available information about the specified block, including: + - number: The block number. + - hash: The corresponding block hash. + - timestamp: The timestamp of the block (based on the `Timestamp.Now` extrinsic). + - header: The raw block header returned by the node RPC. + - extrinsics: The list of decoded extrinsics included in the block. + """ + block_info = self.substrate.get_block( + block_number=block, + block_hash=block_hash, + ignore_decoding_errors=True + ) + if isinstance(block_info, dict) and (header := block_info.get("header")): + block = block or header.get("number", None) + block_hash = block_hash or header.get("hash", None) + extrinsics = block_info.get("extrinsics") + timestamp = self.get_timestamp(block=block) + return BlockInfo( + number=block, + hash=block_hash, + timestamp=timestamp, + header=header, + extrinsics=extrinsics, + ) + return None + def determine_block_hash(self, block: Optional[int]) -> Optional[str]: if block is None: return None From 81874fed996b20702cfa95a3fd6134cbc25254e1 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 02:15:09 -0700 Subject: [PATCH 325/416] update SubtensorApi --- bittensor/extras/subtensor_api/chain.py | 1 + bittensor/extras/subtensor_api/utils.py | 1 + 2 files changed, 2 insertions(+) diff --git a/bittensor/extras/subtensor_api/chain.py b/bittensor/extras/subtensor_api/chain.py index 8a45169169..dfebb034a6 100644 --- a/bittensor/extras/subtensor_api/chain.py +++ b/bittensor/extras/subtensor_api/chain.py @@ -9,6 +9,7 @@ class Chain: def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_admin_freeze_window = subtensor.get_admin_freeze_window self.get_block_hash = subtensor.get_block_hash + self.get_block_info = subtensor.get_block_info self.get_current_block = subtensor.get_current_block self.get_delegate_identities = subtensor.get_delegate_identities self.get_existential_deposit = subtensor.get_existential_deposit diff --git a/bittensor/extras/subtensor_api/utils.py b/bittensor/extras/subtensor_api/utils.py index 1011ccd88c..e0d9066ad2 100644 --- a/bittensor/extras/subtensor_api/utils.py +++ b/bittensor/extras/subtensor_api/utils.py @@ -44,6 +44,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_balance = subtensor.inner_subtensor.get_balance subtensor.get_balances = subtensor.inner_subtensor.get_balances subtensor.get_block_hash = subtensor.inner_subtensor.get_block_hash + subtensor.get_block_info = subtensor.inner_subtensor.get_block_info subtensor.get_children = subtensor.inner_subtensor.get_children subtensor.get_children_pending = subtensor.inner_subtensor.get_children_pending subtensor.get_commitment = subtensor.inner_subtensor.get_commitment From a2128fc9567c876fd9d59628040b4db1526e7f93 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 03:13:46 -0700 Subject: [PATCH 326/416] fix format + add tao app link to BlockInfo --- bittensor/core/async_subtensor.py | 6 +++++- bittensor/core/chain_data/scheduled_coldkey_swap_info.py | 2 +- bittensor/core/chain_data/utils.py | 4 ++-- bittensor/core/settings.py | 5 ++--- bittensor/core/subtensor.py | 4 +++- bittensor/core/types.py | 5 +++-- bittensor/utils/__init__.py | 2 +- 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index a70c7e2aae..088cff24ef 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -87,7 +87,11 @@ set_weights_extrinsic, ) from bittensor.core.metagraph import AsyncMetagraph -from bittensor.core.settings import version_as_int, TYPE_REGISTRY +from bittensor.core.settings import ( + version_as_int, + TYPE_REGISTRY, + TAO_APP_BLOCK_EXPLORER, +) from bittensor.core.types import ( BlockInfo, ExtrinsicResponse, diff --git a/bittensor/core/chain_data/scheduled_coldkey_swap_info.py b/bittensor/core/chain_data/scheduled_coldkey_swap_info.py index 991665a77e..361a366c37 100644 --- a/bittensor/core/chain_data/scheduled_coldkey_swap_info.py +++ b/bittensor/core/chain_data/scheduled_coldkey_swap_info.py @@ -1,11 +1,11 @@ from dataclasses import dataclass from typing import Optional +from bittensor_wallet.utils import SS58_FORMAT from scalecodec.utils.ss58 import ss58_encode from bittensor.core.chain_data.info_base import InfoBase from bittensor.core.chain_data.utils import from_scale_encoding, ChainDataType -from bittensor.core.settings import SS58_FORMAT @dataclass diff --git a/bittensor/core/chain_data/utils.py b/bittensor/core/chain_data/utils.py index 621477ad21..5374652d8c 100644 --- a/bittensor/core/chain_data/utils.py +++ b/bittensor/core/chain_data/utils.py @@ -3,12 +3,12 @@ from enum import Enum from typing import Optional, Union, TYPE_CHECKING -from scalecodec.base import RuntimeConfiguration, ScaleBytes from async_substrate_interface.types import ScaleObj +from bittensor_wallet.utils import SS58_FORMAT +from scalecodec.base import RuntimeConfiguration, ScaleBytes from scalecodec.type_registry import load_type_registry_preset from scalecodec.utils.ss58 import ss58_encode -from bittensor.core.settings import SS58_FORMAT from bittensor.utils.balance import Balance if TYPE_CHECKING: diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index 0407e80001..29a744f33e 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -14,6 +14,8 @@ WALLETS_DIR = USER_BITTENSOR_DIR / "wallets" MINERS_DIR = USER_BITTENSOR_DIR / "miners" +TAO_APP_BLOCK_EXPLORER = "https://www.tao.app/block/" + __version__ = importlib.metadata.version("bittensor") @@ -64,9 +66,6 @@ # Substrate chain block time (seconds). BLOCKTIME = 12 -# Substrate ss58_format -SS58_FORMAT = 42 - # Wallet ss58 address length SS58_ADDRESS_LENGTH = 48 diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 15ebca2c5a..35ba331c56 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -86,9 +86,10 @@ set_weights_extrinsic, ) from bittensor.core.metagraph import Metagraph +from bittensor_wallet.utils import SS58_FORMAT from bittensor.core.settings import ( version_as_int, - SS58_FORMAT, + TAO_APP_BLOCK_EXPLORER, TYPE_REGISTRY, ) from bittensor.core.types import ( @@ -822,6 +823,7 @@ def get_block_info( timestamp=timestamp, header=header, extrinsics=extrinsics, + explorer=f"{TAO_APP_BLOCK_EXPLORER}/" ) return None diff --git a/bittensor/core/types.py b/bittensor/core/types.py index e26f3a0c33..a8166734c3 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -526,5 +526,6 @@ class BlockInfo: number: int hash: str timestamp: datetime - header: Optional[dict] = None - extrinsics: Optional[dict] = None + header: dict + extrinsics: dict + explorer: str diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 7d58d94de3..f821928842 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -20,7 +20,7 @@ ) from bittensor.core import settings -from bittensor.core.settings import SS58_FORMAT +from bittensor_wallet.utils import SS58_FORMAT from bittensor.utils.btlogging import logging from .registration import torch, use_torch from .version import check_version, VersionCheckError From 40a93c99b9d55f23661c38417723c38f107de4ec Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 03:15:29 -0700 Subject: [PATCH 327/416] add block --- bittensor/core/async_subtensor.py | 1 + bittensor/core/subtensor.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 088cff24ef..5d3749df35 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1387,6 +1387,7 @@ async def get_block_info( timestamp=timestamp, header=header, extrinsics=extrinsics, + explorer=f"{TAO_APP_BLOCK_EXPLORER}{block}" ) return None diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 35ba331c56..e48cce28a7 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -823,7 +823,7 @@ def get_block_info( timestamp=timestamp, header=header, extrinsics=extrinsics, - explorer=f"{TAO_APP_BLOCK_EXPLORER}/" + explorer=f"{TAO_APP_BLOCK_EXPLORER}{block}" ) return None From 265e7b68be44dadfae3cdfe47143f497d3df7d6d Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 02:38:55 -0700 Subject: [PATCH 328/416] add `set_trace` for `log_verbose=True` --- MIGRATION.md | 37 +++++++++++++++++-------------- bittensor/core/async_subtensor.py | 1 + bittensor/core/subtensor.py | 1 + 3 files changed, 22 insertions(+), 17 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 046d28089f..f0216ca9d6 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -23,8 +23,9 @@ 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.~~ wrong idea -7. Apply `SimSwap` logic to calculate any stake operation fees (this is not an extrinsic fee) -8. Should the next functions move to `subtensor` as methods? They have exactly the same behavior as subtensor methods. +7. ✅ apply correct logic if subtensor got `lov_verbose=True` -> `set_trace` level for btloggin +8. Apply `SimSwap` logic to calculate any stake operation fees (this is not an extrinsic fee) +9. Should the next functions move to `subtensor` as methods? They have exactly the same behavior as subtensor methods. - `get_metadata` - `get_last_bonds_reset` @@ -80,13 +81,15 @@ rename this variable in documentation. - Opportunity to expand the content of the extrinsic's response at any time upon community request or based on new technical requirements any time. 2. ✅ Add `bittensor.utils.hex_to_ss58` function. SDK still doesn't have it. (Probably inner import `from scalecodec import ss58_encode, ss58_decode`) 3. ✅ Implement Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 -4. Implement classes for `Block` and `BlockHash` objects that will contain the following fields: - - number - - hash - - timestamp - - block_explorer (with the link to tao.app) +4. Implement classes for `BlockInfo` objects that will contain the following fields: + - number (int) + - hash (str) + - timestamp (datetime) + - header (dict) + - extrinsics (list) + - block_explorer (link to tao.app) - This implementation has been repeatedly requested by the community in the past. The instances of this classes should behave the same as `block` (int) and `block_hash` (str) currently do (to safe backward compatibility). + This implementation has been repeatedly requested by the community in the past. 5. Implement `Crowdloan` logic. Issue: https://github.com/opentensor/bittensor/issues/3017 ## Testing @@ -286,15 +289,15 @@ Added: - `from bittenosor import get_async_subtensor` Next variables removed: -- `async_subtensor` not -> `AsyncSubtensor` -- `axon` not -> `Axon` -- `config` not -> `Config` -- `dendrite` not -> `Dendrite` -- `keyfile` not -> `Keyfile` -- `metagraph` not -> `Metagraph` -- `wallet` not -> `Wallet` -- `subtensor` not -> `Subtensor` -- `synapse` not -> `Synapse` +- `async_subtensor` now only -> `AsyncSubtensor` +- `axon` now only -> `Axon` +- `config` now only -> `Config` +- `dendrite` now only -> `Dendrite` +- `keyfile` now only -> `Keyfile` +- `metagraph` now only -> `Metagraph` +- `wallet` now only -> `Wallet` +- `subtensor` now only -> `Subtensor` +- `synapse` now only -> `Synapse` Links to subpackages removed: - `bittensor.mock` (available in `bittensor.core.mock`) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 5d3749df35..b544725fb3 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -209,6 +209,7 @@ async def main(): ws_shutdown_timer=websocket_shutdown_timer, ) if self.log_verbose: + logging.set_trace() logging.info( f"Connected to {self.network} network and {self.chain_endpoint}." ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e48cce28a7..d527411899 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -184,6 +184,7 @@ def __init__( archive_endpoints=archive_endpoints, ) if self.log_verbose: + logging.set_trace() logging.info( f"Connected to {self.network} network and {self.chain_endpoint}." ) From 62bbdbf5b2e50aaeab27f464fcf5e29333561177 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 03:53:15 -0700 Subject: [PATCH 329/416] add docstring --- bittensor/core/types.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index a8166734c3..2b58a81245 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -523,9 +523,23 @@ def with_log( @dataclass class BlockInfo: + """ + Class that holds information about a blockchain block. + + This class encapsulates all relevant information about a block in the blockchain, including its number, hash, + timestamp, and contents. + + Attributes: + number: The block number. + hash: The corresponding block hash. + timestamp: The timestamp of the block (based on the `Timestamp.Now` extrinsic). + header: The raw block header returned by the node RPC. + extrinsics: The list of decoded extrinsics included in the block. + explorer: The link to block explorer service. + """ number: int hash: str timestamp: datetime header: dict - extrinsics: dict + extrinsics: list explorer: str From f824df95c4e78432ca1efdc7643729e44012ccee Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 03:53:41 -0700 Subject: [PATCH 330/416] add cast for checking pass --- bittensor/core/async_subtensor.py | 11 +++++------ bittensor/core/subtensor.py | 9 ++++----- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index b544725fb3..d3d2e831a2 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1371,16 +1371,15 @@ async def get_block_info( - timestamp: The timestamp of the block (based on the `Timestamp.Now` extrinsic). - header: The raw block header returned by the node RPC. - extrinsics: The list of decoded extrinsics included in the block. + - explorer: The link to block explorer service. """ - block_info = self.substrate.get_block( - block_number=block, - block_hash=block_hash, - ignore_decoding_errors=True + block_info = await self.substrate.get_block( + block_number=block, block_hash=block_hash, ignore_decoding_errors=True ) if isinstance(block_info, dict) and (header := block_info.get("header")): block = block or header.get("number", None) block_hash = block_hash or header.get("hash", None) - extrinsics = block_info.get("extrinsics") + extrinsics = cast(list, block_info.get("extrinsics")) timestamp = await self.get_timestamp(block=block) return BlockInfo( number=block, @@ -1388,7 +1387,7 @@ async def get_block_info( timestamp=timestamp, header=header, extrinsics=extrinsics, - explorer=f"{TAO_APP_BLOCK_EXPLORER}{block}" + explorer=f"{TAO_APP_BLOCK_EXPLORER}{block}", ) return None diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index d527411899..c26a37dbdc 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -807,16 +807,15 @@ def get_block_info( - timestamp: The timestamp of the block (based on the `Timestamp.Now` extrinsic). - header: The raw block header returned by the node RPC. - extrinsics: The list of decoded extrinsics included in the block. + - explorer: The link to block explorer service. """ block_info = self.substrate.get_block( - block_number=block, - block_hash=block_hash, - ignore_decoding_errors=True + block_number=block, block_hash=block_hash, ignore_decoding_errors=True ) if isinstance(block_info, dict) and (header := block_info.get("header")): block = block or header.get("number", None) block_hash = block_hash or header.get("hash", None) - extrinsics = block_info.get("extrinsics") + extrinsics = cast(list, block_info.get("extrinsics")) timestamp = self.get_timestamp(block=block) return BlockInfo( number=block, @@ -824,7 +823,7 @@ def get_block_info( timestamp=timestamp, header=header, extrinsics=extrinsics, - explorer=f"{TAO_APP_BLOCK_EXPLORER}{block}" + explorer=f"{TAO_APP_BLOCK_EXPLORER}{block}", ) return None From 421455ec11e331a4b264effb1aba055dd8eb989f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 03:53:49 -0700 Subject: [PATCH 331/416] add unit tests --- tests/unit_tests/test_async_subtensor.py | 46 ++++++++++++++++++++++-- tests/unit_tests/test_subtensor.py | 40 +++++++++++++++++++++ 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 7fe85689be..7a55b334f8 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -1,12 +1,12 @@ import datetime import unittest.mock as mock -from bittensor.core.errors import BalanceTypeError + import pytest from async_substrate_interface.types import ScaleObj from bittensor_wallet import Wallet from bittensor import u64_normalized_float -from bittensor.core import async_subtensor +from bittensor.core import async_subtensor, settings from bittensor.core.async_subtensor import AsyncSubtensor from bittensor.core.chain_data import ( proposal_vote_data, @@ -15,6 +15,7 @@ StakeInfo, SelectiveMetagraphIndex, ) +from bittensor.core.errors import BalanceTypeError from bittensor.core.types import ExtrinsicResponse from bittensor.utils import U64_MAX, get_function_name from bittensor.utils.balance import Balance @@ -4171,3 +4172,44 @@ async def fake_get_block_hash(block: int) -> str: with pytest.raises(ValueError): await subtensor.determine_block_hash(block=2, block_hash=mocked_hash) + + +@pytest.mark.asyncio +async def test_get_block_info(subtensor, mocker): + """Tests that `get_block_info` calls proper methods and returns the correct value.""" + # Preps + fake_block = mocker.Mock(spec=int) + fake_hash = mocker.Mock(spec=str) + fake_substrate_block = { + "header": { + "number": fake_block, + "hash": fake_hash, + }, + "extrinsics": [] + + } + mocked_get_block = mocker.patch.object(subtensor.substrate, "get_block", return_value=fake_substrate_block) + mocked_get_timestamp = mocker.patch.object(subtensor, "get_timestamp") + mocked_BlockInfo = mocker.patch.object(async_subtensor, "BlockInfo") + + # Call + result = await subtensor.get_block_info() + + # Asserts + mocked_get_block.assert_awaited_once_with( + block_hash=None, + block_number=None, + ignore_decoding_errors=True, + ) + mocked_get_timestamp.assert_awaited_once_with( + block=fake_block + ) + mocked_BlockInfo.assert_called_once_with( + number=fake_block, + hash=fake_hash, + timestamp=mocked_get_timestamp.return_value, + header=fake_substrate_block.get("header"), + extrinsics=fake_substrate_block.get("extrinsics"), + explorer=f"{settings.TAO_APP_BLOCK_EXPLORER}{fake_block}" + ) + assert result == mocked_BlockInfo.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index ffad802f07..84607e782a 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4305,3 +4305,43 @@ def test_set_auto_stake(subtensor, mocker): ) assert result == mocked_extrinsic.return_value + + +def test_get_block_info(subtensor, mocker): + """Tests that `get_block_info` calls proper methods and returns the correct value.""" + # Preps + fake_block = mocker.Mock(spec=int) + fake_hash = mocker.Mock(spec=str) + fake_substrate_block = { + "header": { + "number": fake_block, + "hash": fake_hash, + }, + "extrinsics": [] + + } + mocked_get_block = mocker.patch.object(subtensor.substrate, "get_block", return_value=fake_substrate_block) + mocked_get_timestamp = mocker.patch.object(subtensor, "get_timestamp") + mocked_BlockInfo = mocker.patch.object(subtensor_module, "BlockInfo") + + # Call + result = subtensor.get_block_info() + + # Asserts + mocked_get_block.assert_called_once_with( + block_hash=None, + block_number=None, + ignore_decoding_errors=True, + ) + mocked_get_timestamp.assert_called_once_with( + block=fake_block + ) + mocked_BlockInfo.assert_called_once_with( + number=fake_block, + hash=fake_hash, + timestamp=mocked_get_timestamp.return_value, + header=fake_substrate_block.get("header"), + extrinsics=fake_substrate_block.get("extrinsics"), + explorer=f"{settings.TAO_APP_BLOCK_EXPLORER}{fake_block}" + ) + assert result == mocked_BlockInfo.return_value From add72c612b3e5b1ec364695d51747e0af1b6a424 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 03:55:54 -0700 Subject: [PATCH 332/416] move tests test_blocks and ruff --- bittensor/core/types.py | 1 + tests/e2e_tests/test_metagraph.py | 41 -------------------- tests/e2e_tests/test_subtensor_functions.py | 43 ++++++++++++++++++++- 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 2b58a81245..e7df956b2e 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -537,6 +537,7 @@ class BlockInfo: extrinsics: The list of decoded extrinsics included in the block. explorer: The link to block explorer service. """ + number: int hash: str timestamp: datetime diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 133eebd23f..4a82aa4b4c 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -1307,44 +1307,3 @@ async def test_metagraph_info_with_indexes_async( logging.console.info( "✅ Passed [blue]test_metagraph_info_with_indexes_async[/blue]" ) - - -def test_blocks(subtensor): - """ - Tests: - - Get current block - - Get block hash - - Wait for block - """ - get_current_block = subtensor.chain.get_current_block() - block = subtensor.block - - # Several random tests fell during the block finalization period. Fast blocks of 0.25 seconds (very fast) - assert get_current_block in [block, block + 1] - - 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]") - - -@pytest.mark.asyncio -async def test_blocks_async(subtensor): - """ - Async tests: - - Get current block - - Get block hash - - Wait for block - """ - 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() in [block + 10, block + 11] - logging.console.info("✅ Passed [blue]test_blocks_async[/blue]") diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 85f77848d0..777212bd6e 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -1,5 +1,5 @@ import asyncio - +import re import pytest from bittensor.core.extrinsics.asyncex.utils import ( @@ -372,3 +372,44 @@ async def test_subtensor_extrinsics_async( assert actual_owner == expected_owner, ( f"Expected owner {expected_owner}, but found {actual_owner}" ) + + +def test_blocks(subtensor): + """ + Tests: + - Get current block + - Get block hash + - Wait for block + """ + get_current_block = subtensor.chain.get_current_block() + block = subtensor.block + + # Several random tests fell during the block finalization period. Fast blocks of 0.25 seconds (very fast) + assert get_current_block in [block, block + 1] + + 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]") + + +@pytest.mark.asyncio +async def test_blocks_async(subtensor): + """ + Async tests: + - Get current block + - Get block hash + - Wait for block + """ + 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() in [block + 10, block + 11] + logging.console.info("✅ Passed [blue]test_blocks_async[/blue]") From fba0a0b21f9a852e74396d78daaaf41619ec3b12 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sun, 5 Oct 2025 04:17:26 -0700 Subject: [PATCH 333/416] add simple e2e tests for `get_block_info` + update MIGRATION.md --- MIGRATION.md | 7 +-- tests/e2e_tests/test_subtensor_functions.py | 47 +++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index f0216ca9d6..cf724435fe 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -69,8 +69,9 @@ rename this variable in documentation. 12. ✅ The SDK is dropping support for `Python 3.9` starting with this release. 13. ✅ Remove `Default is` and `Default to` in docstrings bc parameters enough. -14. `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) -15. Find and process all `TODOs` across the entire code base. If in doubt, discuss each one with the team separately. SDK has 29 TODOs. +14. Solve the issue when a script using SDK receives the `--config` cli parameter. Disable `argparse` processing by default and enable it only when using SOME? a local environment variable. +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) +16. 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. ✅ Unify extrinsic return values by introducing an ExtrinsicResponse class. Extrinsics currently return either a boolean or a tuple. @@ -81,7 +82,7 @@ rename this variable in documentation. - Opportunity to expand the content of the extrinsic's response at any time upon community request or based on new technical requirements any time. 2. ✅ Add `bittensor.utils.hex_to_ss58` function. SDK still doesn't have it. (Probably inner import `from scalecodec import ss58_encode, ss58_decode`) 3. ✅ Implement Sub-subnets logic. Subtensor PR https://github.com/opentensor/subtensor/pull/1984 -4. Implement classes for `BlockInfo` objects that will contain the following fields: +4. ✅ Implement classes for `BlockInfo` objects that will contain the following fields: - number (int) - hash (str) - timestamp (datetime) diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 777212bd6e..ce4c3e412c 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -413,3 +413,50 @@ async def test_blocks_async(subtensor): subtensor.wait_for_block(block + 10) assert subtensor.chain.get_current_block() in [block + 10, block + 11] logging.console.info("✅ Passed [blue]test_blocks_async[/blue]") + + +@pytest.mark.parametrize( + "block, block_hash, result", + [ + (None, None, True), + (1, None, True), + (None, "SOME_HASH", True), + (1, "SOME_HASH", False) + ] +) +def test_block_info(subtensor, block, block_hash, result): + """Tests sync get_block_info.""" + if block_hash: + block_hash = subtensor.chain.get_block_hash() + + subtensor.wait_for_block(2) + + try: + res = subtensor.chain.get_block_info(block=block, block_hash=block_hash) + assert (res is not None) == result + except Exception as e: + assert "Either block_hash or block_number should be set" in str(e) + + +@pytest.mark.parametrize( + "block, block_hash, result", + [ + (None, None, True), + (1, None, True), + (None, "SOME_HASH", True), + (1, "SOME_HASH", False) + ] +) +@pytest.mark.asyncio +async def test_block_info(async_subtensor, block, block_hash, result): + """Tests async get_block_info.""" + if block_hash: + block_hash = await async_subtensor.chain.get_block_hash() + + await async_subtensor.wait_for_block(2) + + try: + res = await async_subtensor.chain.get_block_info(block=block, block_hash=block_hash) + assert (res is not None) == result + except Exception as e: + assert "Either block_hash or block_number should be set" in str(e) From 488c634e160297d9cd5d10bef5679c26231a1a54 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sat, 4 Oct 2025 23:03:07 -0700 Subject: [PATCH 334/416] move methods to subtensor --- bittensor/core/async_subtensor.py | 59 ++++++++++++++++++-- bittensor/core/extrinsics/asyncex/serving.py | 55 ------------------ bittensor/core/extrinsics/serving.py | 36 ------------ bittensor/core/subtensor.py | 39 +++++++++++-- 4 files changed, 89 insertions(+), 100 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index d3d2e831a2..98e5a1a72f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -61,9 +61,7 @@ ) from bittensor.core.extrinsics.asyncex.root import root_register_extrinsic from bittensor.core.extrinsics.asyncex.serving import ( - get_last_bonds_reset, publish_metadata_extrinsic, - get_metadata, ) from bittensor.core.extrinsics.asyncex.serving import serve_axon_extrinsic from bittensor.core.extrinsics.asyncex.staking import ( @@ -1582,8 +1580,9 @@ async def get_commitment( ) return "" - metadata = await get_metadata( - self, netuid, hotkey, block, block_hash, reuse_block + metadata = cast( + dict, + await self.get_metadata(netuid, hotkey, block, block_hash, reuse_block), ) try: return decode_metadata(metadata) @@ -1591,6 +1590,36 @@ async def get_commitment( logging.error(error) return "" + async def get_last_bonds_reset( + self, + netuid: int, + hotkey_ss58: str, + block: Optional[int] = None, + 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. + + Parameters: + netuid: The network uid to fetch from. + hotkey_ss58: 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 self.determine_block_hash(block, block_hash, reuse_block) + block = await self.substrate.query( + module="Commitments", + storage_function="LastBondsReset", + params=[netuid, hotkey_ss58], + block_hash=block_hash, + ) + return block + async def get_last_commitment_bonds_reset_block( self, netuid: int, uid: int ) -> Optional[int]: @@ -1613,7 +1642,7 @@ async def get_last_commitment_bonds_reset_block( "Your uid is not in the hotkeys. Please double-check your UID." ) return None - block = await get_last_bonds_reset(self, netuid, hotkey) + block = await self.get_last_bonds_reset(netuid, hotkey) try: return decode_block(block) except TypeError: @@ -2035,6 +2064,26 @@ async def get_minimum_required_stake(self): return Balance.from_rao(getattr(result, "value", 0)) + async def get_metadata( + self, + netuid: int, + hotkey_ss58: str, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Union[str, dict]: + """Fetches metadata from the blockchain for a given hotkey and netuid.""" + async with self.substrate: + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + commit_data = await self.substrate.query( + module="Commitments", + storage_function="CommitmentOf", + params=[netuid, hotkey_ss58], + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) + return commit_data + async def get_metagraph_info( self, netuid: int, diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 88c05e63ea..98c9938db6 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -286,58 +286,3 @@ async def publish_metadata_extrinsic( except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) - - -async def get_metadata( - subtensor: "AsyncSubtensor", - netuid: int, - hotkey_ss58: str, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, -) -> Union[str, dict]: - """Fetches metadata from the blockchain for a given hotkey and netuid.""" - async with subtensor.substrate: - block_hash = await subtensor.determine_block_hash( - block, block_hash, reuse_block - ) - commit_data = await subtensor.substrate.query( - module="Commitments", - storage_function="CommitmentOf", - params=[netuid, hotkey_ss58], - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) - return commit_data - - -async def get_last_bonds_reset( - subtensor: "AsyncSubtensor", - netuid: int, - hotkey_ss58: str, - block: Optional[int] = None, - 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. - - Parameters: - subtensor: Subtensor instance object. - netuid: The network uid to fetch from. - hotkey_ss58: 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", - storage_function="LastBondsReset", - params=[netuid, hotkey_ss58], - block_hash=block_hash, - ) - return block diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index c1efae01f1..a9b1184dee 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -283,39 +283,3 @@ def publish_metadata_extrinsic( except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) - - -def get_metadata( - subtensor: "Subtensor", netuid: int, hotkey_ss58: str, block: Optional[int] = None -) -> Union[str, dict]: - """Fetches metadata from the blockchain for a given hotkey and netuid.""" - commit_data = subtensor.substrate.query( - module="Commitments", - storage_function="CommitmentOf", - params=[netuid, hotkey_ss58], - block_hash=subtensor.determine_block_hash(block), - ) - return commit_data - - -def get_last_bonds_reset( - subtensor: "Subtensor", netuid: int, hotkey_ss58: str, block: Optional[int] = None -) -> bytes: - """ - 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_ss58: 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. - """ - return subtensor.substrate.query( - module="Commitments", - storage_function="LastBondsReset", - params=[netuid, hotkey_ss58], - block_hash=subtensor.determine_block_hash(block), - ) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index c26a37dbdc..97331ec5af 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -60,9 +60,7 @@ ) from bittensor.core.extrinsics.root import root_register_extrinsic from bittensor.core.extrinsics.serving import ( - get_last_bonds_reset, publish_metadata_extrinsic, - get_metadata, serve_axon_extrinsic, ) from bittensor.core.extrinsics.staking import ( @@ -1011,13 +1009,34 @@ def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) -> ) return "" - metadata = cast(dict, get_metadata(self, netuid, hotkey, block)) + metadata = cast(dict, self.get_metadata(netuid, hotkey, block)) try: return decode_metadata(metadata) except Exception as error: logging.error(error) return "" + def get_last_bonds_reset( + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None + ) -> bytes: + """ + Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid. + + Parameters: + netuid: The network uid to fetch from. + hotkey_ss58: 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. + """ + return self.substrate.query( + module="self", + storage_function="LastBondsReset", + params=[netuid, hotkey_ss58], + block_hash=self.determine_block_hash(block), + ) + def get_last_commitment_bonds_reset_block( self, netuid: int, uid: int ) -> Optional[int]: @@ -1040,7 +1059,7 @@ def get_last_commitment_bonds_reset_block( "Your uid is not in the hotkeys. Please double-check your UID." ) return None - block = get_last_bonds_reset(self, netuid, hotkey) + block = self.get_last_bonds_reset(netuid, hotkey) if block is None: return None return decode_block(block) @@ -1355,6 +1374,18 @@ def get_minimum_required_stake(self) -> Balance: return Balance.from_rao(getattr(result, "value", 0)) + def get_metadata( + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None + ) -> Union[str, dict]: + """Fetches metadata from the blockchain for a given hotkey and netuid.""" + commit_data = self.substrate.query( + module="Commitments", + storage_function="CommitmentOf", + params=[netuid, hotkey_ss58], + block_hash=self.determine_block_hash(block), + ) + return commit_data + def get_metagraph_info( self, netuid: int, From 15db0987fd909f3365b1b71102335e73b7a01ddc Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sat, 4 Oct 2025 23:03:22 -0700 Subject: [PATCH 335/416] update SubtensorApi --- bittensor/extras/subtensor_api/commitments.py | 2 ++ bittensor/extras/subtensor_api/utils.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/bittensor/extras/subtensor_api/commitments.py b/bittensor/extras/subtensor_api/commitments.py index 9c3c989c47..7e848585dc 100644 --- a/bittensor/extras/subtensor_api/commitments.py +++ b/bittensor/extras/subtensor_api/commitments.py @@ -11,9 +11,11 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_all_commitments = subtensor.get_all_commitments self.get_all_revealed_commitments = subtensor.get_all_revealed_commitments self.get_commitment = subtensor.get_commitment + self.get_last_bonds_reset = subtensor.get_last_bonds_reset self.get_last_commitment_bonds_reset_block = ( subtensor.get_last_commitment_bonds_reset_block ) + self.get_metadata = subtensor.get_metadata self.get_revealed_commitment = subtensor.get_revealed_commitment self.get_revealed_commitment_by_hotkey = ( subtensor.get_revealed_commitment_by_hotkey diff --git a/bittensor/extras/subtensor_api/utils.py b/bittensor/extras/subtensor_api/utils.py index e0d9066ad2..d5d71ad0e1 100644 --- a/bittensor/extras/subtensor_api/utils.py +++ b/bittensor/extras/subtensor_api/utils.py @@ -62,6 +62,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_hotkey_owner = subtensor.inner_subtensor.get_hotkey_owner subtensor.get_hotkey_stake = subtensor.inner_subtensor.get_hotkey_stake subtensor.get_hyperparameter = subtensor.inner_subtensor.get_hyperparameter + subtensor.get_last_bonds_reset = subtensor.inner_subtensor.get_last_bonds_reset subtensor.get_last_commitment_bonds_reset_block = ( subtensor.inner_subtensor.get_last_commitment_bonds_reset_block ) @@ -70,6 +71,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_mechanism_emission_split = ( subtensor.inner_subtensor.get_mechanism_emission_split ) + subtensor.get_metadata = subtensor.inner_subtensor.get_metadata subtensor.get_metagraph_info = subtensor.inner_subtensor.get_metagraph_info subtensor.get_minimum_required_stake = ( subtensor.inner_subtensor.get_minimum_required_stake From f599d3954fbaf71314330320d11085bcb4de8e13 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sat, 4 Oct 2025 23:03:28 -0700 Subject: [PATCH 336/416] fix tests --- tests/unit_tests/test_subtensor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 84607e782a..e850899e49 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1500,7 +1500,7 @@ def test_get_commitment(subtensor, mocker): subtensor.metagraph = mocked_metagraph mocked_metagraph.return_value.hotkeys = {fake_uid: fake_hotkey} - mocked_get_metadata = mocker.patch.object(subtensor_module, "get_metadata") + mocked_get_metadata = mocker.patch.object(subtensor, "get_metadata") mocked_get_metadata.return_value = { "deposit": 0, "block": 3843930, @@ -1654,7 +1654,7 @@ def test_get_last_commitment_bonds_reset_block(subtensor, mocker): expected_result = 3 mocked_get_last_bonds_reset = mocker.patch.object( - subtensor_module, "get_last_bonds_reset" + subtensor, "get_last_bonds_reset" ) mocked_get_last_bonds_reset.return_value = expected_result From 3b794afa04d7d189873d5d971de368bf9d1ea26c Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Sat, 4 Oct 2025 23:12:47 -0700 Subject: [PATCH 337/416] update MIGRATION.md --- MIGRATION.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index cf724435fe..9be3e57d05 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -24,10 +24,10 @@ 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.~~ wrong idea 7. ✅ apply correct logic if subtensor got `lov_verbose=True` -> `set_trace` level for btloggin -8. Apply `SimSwap` logic to calculate any stake operation fees (this is not an extrinsic fee) -9. Should the next functions move to `subtensor` as methods? They have exactly the same behavior as subtensor methods. +8. ✅ Should the next functions move to `subtensor` as methods? They have exactly the same behavior as subtensor methods. - `get_metadata` - `get_last_bonds_reset` +9. Apply `SimSwap` logic to calculate any stake operation fees (this is not an extrinsic fee) ## Metagraph 1. ✅ Remove verbose archival node warnings for blocks older than 300. Some users complained about many messages for them. @@ -257,6 +257,8 @@ Removing deprecated extrinsics and replacing them with consistent ones: - attribute `DelegateInfo/lite.total_daily_return` has been deleted (Vune confirmed that we shouldn't use it) - `Async/Subtensor` parameter `_mock` renamed to `mock`, also moved to last one in order. Community can use mocked `Async/Subtensor` in their tests in the same way as in we use it in the codebase. - method `get_traansfer_fee` has renamed parameter `value` to `amount` +- `bittensor.core.extrinsic.serving.get_metadata` functions moved to `subtensor.get_metadata` method +- `bittensor.core.extrinsic.serving.get_last_bonds_reset` function moved to `subtensor.get_last_bonds_reset` method Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` From f81e9b39b8ad3ad5e0d798a048542bc6977eb242 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 10:08:41 -0700 Subject: [PATCH 338/416] proper naming from review --- bittensor/core/async_subtensor.py | 98 +++++++++++++++++++------------ bittensor/core/subtensor.py | 87 +++++++++++++++++---------- 2 files changed, 117 insertions(+), 68 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 98e5a1a72f..ae8a3df1cc 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1287,6 +1287,37 @@ async def get_balances( results.update({item[0].params[0]: Balance(value["data"]["free"])}) return results + async def get_commitment_metadata( + self, + netuid: int, + hotkey_ss58: str, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Union[str, dict]: + """Fetches raw commitment metadata from specific subnet for given hotkey. + + Parameters: + netuid: The unique subnet identifier. + hotkey_ss58: The hotkey ss58 address. + block: The blockchain block number for the query. + block_hash: The hash of the block at which to check the hotkey ownership. + reuse_block: Whether to reuse the last-used blockchain hash. + + Returns: + The raw commitment metadata from specific subnet for given hotkey. + """ + async with self.substrate: + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + commit_data = await self.substrate.query( + module="Commitments", + storage_function="CommitmentOf", + params=[netuid, hotkey_ss58], + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) + return commit_data + async def get_current_block(self) -> int: """Returns the current block number on the Bittensor blockchain. @@ -1582,7 +1613,9 @@ async def get_commitment( metadata = cast( dict, - await self.get_metadata(netuid, hotkey, block, block_hash, reuse_block), + await self.get_commitment_metadata( + netuid, hotkey, block, block_hash, reuse_block + ), ) try: return decode_metadata(metadata) @@ -1599,12 +1632,12 @@ async def get_last_bonds_reset( reuse_block: bool = False, ) -> bytes: """ - Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid. + Retrieves the last bonds reset triggered at commitment from given subnet for a specific hotkey. Parameters: netuid: The network uid to fetch from. hotkey_ss58: 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: The block number to query. 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. @@ -1617,11 +1650,17 @@ async def get_last_bonds_reset( storage_function="LastBondsReset", params=[netuid, hotkey_ss58], block_hash=block_hash, + reuse_block_hash=reuse_block, ) return block async def get_last_commitment_bonds_reset_block( - self, netuid: int, uid: int + self, + netuid: int, + uid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Optional[int]: """ Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. @@ -1629,12 +1668,15 @@ async def get_last_commitment_bonds_reset_block( Parameters: netuid: The unique identifier of the subnetwork. uid: The unique identifier of the neuron. + block: The block number to query. + 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: The block number when the bonds were last reset, or None if not found. """ - metagraph = await self.metagraph(netuid) + metagraph = await self.metagraph(netuid, block=block) try: hotkey = metagraph.hotkeys[uid] except IndexError: @@ -1642,9 +1684,11 @@ async def get_last_commitment_bonds_reset_block( "Your uid is not in the hotkeys. Please double-check your UID." ) return None - block = await self.get_last_bonds_reset(netuid, hotkey) + block_data = await self.get_last_bonds_reset( + netuid, hotkey, block, block_hash, reuse_block + ) try: - return decode_block(block) + return decode_block(block_data) except TypeError: return None @@ -1655,7 +1699,7 @@ async def get_all_commitments( block_hash: Optional[str] = None, reuse_block: bool = False, ) -> dict[str, str]: - """Retrieves the on-chain commitments for a specific subnet in the Bittensor network. + """Retrieves raw commitment metadata from a given subnet. This method retrieves all commitment data for all neurons in a specific subnet. This is useful for analyzing the commit-reveal patterns across an entire subnet. @@ -1698,16 +1742,16 @@ async def get_all_commitments( async def get_revealed_commitment_by_hotkey( self, netuid: int, - hotkey_ss58_address: Optional[str] = None, + hotkey_ss58: Optional[str] = None, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, ) -> Optional[tuple[tuple[int, str], ...]]: - """Returns hotkey related revealed commitment for a given netuid. + """Retrieves hotkey related revealed commitment for a given subnet. Parameters: netuid: The unique identifier of the subnetwork. - hotkey_ss58_address: The ss58 address of the committee member. + hotkey_ss58: The ss58 address of the committee member. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. @@ -1715,13 +1759,13 @@ async def get_revealed_commitment_by_hotkey( Returns: A tuple of reveal block and commitment message. """ - if not is_valid_ss58_address(address=hotkey_ss58_address): - raise ValueError(f"Invalid ss58 address {hotkey_ss58_address} provided.") + if not is_valid_ss58_address(address=hotkey_ss58): + raise ValueError(f"Invalid ss58 address {hotkey_ss58} provided.") query = await self.query_module( module="Commitments", name="RevealedCommitments", - params=[netuid, hotkey_ss58_address], + params=[netuid, hotkey_ss58], block=block, block_hash=block_hash, reuse_block=reuse_block, @@ -1753,14 +1797,14 @@ async def get_revealed_commitment( try: meta_info = await self.get_metagraph_info(netuid, block=block) if meta_info: - hotkey_ss58_address = meta_info.hotkeys[uid] + hotkey_ss58 = meta_info.hotkeys[uid] else: raise ValueError(f"Subnet with netuid {netuid} does not exist.") except IndexError: raise ValueError(f"Subnet {netuid} does not have a neuron with uid {uid}.") return await self.get_revealed_commitment_by_hotkey( - netuid=netuid, hotkey_ss58_address=hotkey_ss58_address, block=block + netuid=netuid, hotkey_ss58=hotkey_ss58, block=block ) async def get_all_revealed_commitments( @@ -1770,7 +1814,7 @@ async def get_all_revealed_commitments( block_hash: Optional[str] = None, reuse_block: bool = False, ) -> dict[str, tuple[tuple[int, str], ...]]: - """Returns all revealed commitments for a given netuid. + """Retrieves all revealed commitments for a given subnet. Parameters: netuid: The unique identifier of the subnetwork. @@ -2064,26 +2108,6 @@ async def get_minimum_required_stake(self): return Balance.from_rao(getattr(result, "value", 0)) - async def get_metadata( - self, - netuid: int, - hotkey_ss58: str, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> Union[str, dict]: - """Fetches metadata from the blockchain for a given hotkey and netuid.""" - async with self.substrate: - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - commit_data = await self.substrate.query( - module="Commitments", - storage_function="CommitmentOf", - params=[netuid, hotkey_ss58], - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) - return commit_data - async def get_metagraph_info( self, netuid: int, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 97331ec5af..f37e172603 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -670,7 +670,7 @@ def get_auto_stakes( Parameters: coldkey_ss58: Coldkey ss58 address. - block: Subnet unique identifier. + block: The block number for the query. Returns: dict[int, str]: @@ -744,6 +744,27 @@ def get_balances( results.update({item[0].params[0]: Balance(value["data"]["free"])}) return results + def get_commitment_metadata( + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None + ) -> Union[str, dict]: + """Fetches raw commitment metadata from specific subnet for given hotkey. + + Parameters: + netuid: The unique subnet identifier. + hotkey_ss58: The hotkey ss58 address. + block: The blockchain block number for the query. + + Returns: + The raw commitment metadata from specific subnet for given hotkey. + """ + commit_data = self.substrate.query( + module="Commitments", + storage_function="CommitmentOf", + params=[netuid, hotkey_ss58], + block_hash=self.determine_block_hash(block), + ) + return commit_data + def get_current_block(self) -> int: """ Returns the current block number on the Bittensor blockchain. This function provides the latest block number, @@ -1009,7 +1030,7 @@ def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) -> ) return "" - metadata = cast(dict, self.get_metadata(netuid, hotkey, block)) + metadata = cast(dict, self.get_commitment_metadata(netuid, hotkey, block)) try: return decode_metadata(metadata) except Exception as error: @@ -1020,25 +1041,28 @@ def get_last_bonds_reset( self, netuid: int, hotkey_ss58: str, block: Optional[int] = None ) -> bytes: """ - Fetches the last bonds reset triggered at commitment from the blockchain for a given hotkey and netuid. + Retrieves the last bonds reset triggered at commitment from given subnet for a specific hotkey. Parameters: netuid: The network uid to fetch from. hotkey_ss58: 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: The block number to query. Returns: - bytes: The last bonds reset data for the specified hotkey and netuid. + bytes: The last bonds reset data from given subnet for the specified hotkey. """ return self.substrate.query( - module="self", + module="Commitments", storage_function="LastBondsReset", params=[netuid, hotkey_ss58], block_hash=self.determine_block_hash(block), ) def get_last_commitment_bonds_reset_block( - self, netuid: int, uid: int + self, + netuid: int, + uid: int, + block: Optional[int] = None, ) -> Optional[int]: """ Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. @@ -1046,6 +1070,7 @@ def get_last_commitment_bonds_reset_block( Parameters: netuid: The unique identifier of the subnetwork. uid: The unique identifier of the neuron. + block: The block number to query. Returns: The block number when the bonds were last reset, or None if not found. @@ -1053,20 +1078,32 @@ def get_last_commitment_bonds_reset_block( metagraph = self.metagraph(netuid) try: - hotkey = metagraph.hotkeys[uid] + hotkey_ss58 = metagraph.hotkeys[uid] except IndexError: logging.error( "Your uid is not in the hotkeys. Please double-check your UID." ) return None - block = self.get_last_bonds_reset(netuid, hotkey) + block_data = self.get_last_bonds_reset(netuid, hotkey_ss58, block) if block is None: return None - return decode_block(block) + return decode_block(block_data) def get_all_commitments( self, netuid: int, block: Optional[int] = None ) -> dict[str, str]: + """Retrieves raw commitment metadata from a given subnet. + + This method retrieves all commitment data for all neurons in a specific subnet. This is useful for analyzing the + commit-reveal patterns across an entire subnet. + + Parameters: + netuid: The unique subnet identifier. + block: The blockchain block number for the query. + + Returns: + The raw on-chain commitment metadata (as SCALE-decoded object or raw bytes) from specific subnet. + """ query = self.query_map( module="Commitments", name="CommitmentOf", @@ -1086,26 +1123,26 @@ def get_all_commitments( def get_revealed_commitment_by_hotkey( self, netuid: int, - hotkey_ss58_address: str, + hotkey_ss58: str, block: Optional[int] = None, ) -> Optional[tuple[tuple[int, str], ...]]: - """Returns hotkey related revealed commitment for a given netuid. + """Retrieves hotkey related revealed commitment for a given subnet. Parameters: netuid: The unique identifier of the subnetwork. - hotkey_ss58_address: The ss58 address of the committee member. + hotkey_ss58: The ss58 address of the committee member. block: The block number to retrieve the commitment from. Returns: A tuple of reveal block and commitment message. """ - if not is_valid_ss58_address(address=hotkey_ss58_address): - raise ValueError(f"Invalid ss58 address {hotkey_ss58_address} provided.") + if not is_valid_ss58_address(address=hotkey_ss58): + raise ValueError(f"Invalid ss58 address {hotkey_ss58} provided.") query = self.query_module( module="Commitments", name="RevealedCommitments", - params=[netuid, hotkey_ss58_address], + params=[netuid, hotkey_ss58], block=block, ) if query is None: @@ -1135,20 +1172,20 @@ def get_revealed_commitment( try: meta_info = self.get_metagraph_info(netuid, block=block) if meta_info: - hotkey_ss58_address = meta_info.hotkeys[uid] + hotkey_ss58 = meta_info.hotkeys[uid] else: raise ValueError(f"Subnet with netuid {netuid} does not exist.") except IndexError: raise ValueError(f"Subnet {netuid} does not have a neuron with uid {uid}.") return self.get_revealed_commitment_by_hotkey( - netuid=netuid, hotkey_ss58_address=hotkey_ss58_address, block=block + netuid=netuid, hotkey_ss58=hotkey_ss58, block=block ) def get_all_revealed_commitments( self, netuid: int, block: Optional[int] = None ) -> dict[str, tuple[tuple[int, str], ...]]: - """Returns all revealed commitments for a given netuid. + """Retrieves all revealed commitments for a given subnet. Parameters: netuid: The unique identifier of the subnetwork. @@ -1374,18 +1411,6 @@ def get_minimum_required_stake(self) -> Balance: return Balance.from_rao(getattr(result, "value", 0)) - def get_metadata( - self, netuid: int, hotkey_ss58: str, block: Optional[int] = None - ) -> Union[str, dict]: - """Fetches metadata from the blockchain for a given hotkey and netuid.""" - commit_data = self.substrate.query( - module="Commitments", - storage_function="CommitmentOf", - params=[netuid, hotkey_ss58], - block_hash=self.determine_block_hash(block), - ) - return commit_data - def get_metagraph_info( self, netuid: int, From 311e154a0f71cb19cf5d9eac3ca258d8935eab8a Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 10:08:56 -0700 Subject: [PATCH 339/416] update SubtensorApi --- bittensor/extras/subtensor_api/commitments.py | 2 +- bittensor/extras/subtensor_api/utils.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bittensor/extras/subtensor_api/commitments.py b/bittensor/extras/subtensor_api/commitments.py index 7e848585dc..3bd3b8ad1a 100644 --- a/bittensor/extras/subtensor_api/commitments.py +++ b/bittensor/extras/subtensor_api/commitments.py @@ -11,11 +11,11 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_all_commitments = subtensor.get_all_commitments self.get_all_revealed_commitments = subtensor.get_all_revealed_commitments self.get_commitment = subtensor.get_commitment + self.get_commitment_metadata = subtensor.get_commitment_metadata self.get_last_bonds_reset = subtensor.get_last_bonds_reset self.get_last_commitment_bonds_reset_block = ( subtensor.get_last_commitment_bonds_reset_block ) - self.get_metadata = subtensor.get_metadata self.get_revealed_commitment = subtensor.get_revealed_commitment self.get_revealed_commitment_by_hotkey = ( subtensor.get_revealed_commitment_by_hotkey diff --git a/bittensor/extras/subtensor_api/utils.py b/bittensor/extras/subtensor_api/utils.py index d5d71ad0e1..8f4c500801 100644 --- a/bittensor/extras/subtensor_api/utils.py +++ b/bittensor/extras/subtensor_api/utils.py @@ -48,6 +48,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_children = subtensor.inner_subtensor.get_children subtensor.get_children_pending = subtensor.inner_subtensor.get_children_pending subtensor.get_commitment = subtensor.inner_subtensor.get_commitment + subtensor.get_commitment_metadata = ( + subtensor.inner_subtensor.get_commitment_metadata + ) subtensor.get_current_block = subtensor.inner_subtensor.get_current_block subtensor.get_delegate_by_hotkey = subtensor.inner_subtensor.get_delegate_by_hotkey subtensor.get_delegate_identities = ( @@ -71,7 +74,6 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_mechanism_emission_split = ( subtensor.inner_subtensor.get_mechanism_emission_split ) - subtensor.get_metadata = subtensor.inner_subtensor.get_metadata subtensor.get_metagraph_info = subtensor.inner_subtensor.get_metagraph_info subtensor.get_minimum_required_stake = ( subtensor.inner_subtensor.get_minimum_required_stake From 7b07eeb2f899a016e937bbadb530225631c04579 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 10:09:02 -0700 Subject: [PATCH 340/416] fix tests --- tests/unit_tests/test_subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index e850899e49..627c2421e2 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1500,7 +1500,7 @@ def test_get_commitment(subtensor, mocker): subtensor.metagraph = mocked_metagraph mocked_metagraph.return_value.hotkeys = {fake_uid: fake_hotkey} - mocked_get_metadata = mocker.patch.object(subtensor, "get_metadata") + mocked_get_metadata = mocker.patch.object(subtensor, "get_commitment_metadata") mocked_get_metadata.return_value = { "deposit": 0, "block": 3843930, From bee04ce65c2622f97df6d27ba702cfc00a772f0b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 10:09:09 -0700 Subject: [PATCH 341/416] update MIGRATION.md --- MIGRATION.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 9be3e57d05..699bd33f3e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -69,8 +69,8 @@ rename this variable in documentation. 12. ✅ The SDK is dropping support for `Python 3.9` starting with this release. 13. ✅ Remove `Default is` and `Default to` in docstrings bc parameters enough. -14. Solve the issue when a script using SDK receives the `--config` cli parameter. Disable `argparse` processing by default and enable it only when using SOME? a local environment variable. -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) +14. ✅ `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). `get_commitment_metadata` added. +15. Solve the issue when a script using SDK receives the `--config` cli parameter. Disable `argparse` processing by default and enable it only when using SOME? a local environment variable. 16. 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 @@ -242,8 +242,8 @@ Removing deprecated extrinsics and replacing them with consistent ones: ### Subtensor changes - method `all_subnets` has renamed parameter from `block_number` to `block` (consistency in the codebase). -- The `hotkey` parameter, which meant ss58 key address, was renamed to `hotkey_ss58` in all methods and related extrinsics (consistency in the codebase). -- The `coldkey` parameter, which meant ss58 key address, was renamed to `coldkey_ss58` in all methods (consistency in the codebase). +- The `hotkey`,`hotkey_ss58_address` parameter, which meant ss58 key address, was renamed to `hotkey_ss58` in all methods and related extrinsics (consistency in the codebase). +- The `coldkey`, `coldkey_ss58_address` parameter, which meant ss58 key address, was renamed to `coldkey_ss58` in all methods (consistency in the codebase). - method `query_subtensor` has updated parameters order. - method `query_module` has updated parameters order. - method `query_map_subtensor` has updated parameters order. @@ -257,7 +257,7 @@ Removing deprecated extrinsics and replacing them with consistent ones: - attribute `DelegateInfo/lite.total_daily_return` has been deleted (Vune confirmed that we shouldn't use it) - `Async/Subtensor` parameter `_mock` renamed to `mock`, also moved to last one in order. Community can use mocked `Async/Subtensor` in their tests in the same way as in we use it in the codebase. - method `get_traansfer_fee` has renamed parameter `value` to `amount` -- `bittensor.core.extrinsic.serving.get_metadata` functions moved to `subtensor.get_metadata` method +- `bittensor.core.extrinsic.serving.get_metadata` functions moved to `subtensor.get_commitment_metadata` method - `bittensor.core.extrinsic.serving.get_last_bonds_reset` function moved to `subtensor.get_last_bonds_reset` method Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. From c72b2138acbc7fabc9a70573c138c62ffeeb01f1 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 11:06:31 -0700 Subject: [PATCH 342/416] fix get_last_commitment_bonds_reset_block --- bittensor/core/subtensor.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index f37e172603..833bba4a91 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1076,7 +1076,7 @@ def get_last_commitment_bonds_reset_block( The block number when the bonds were last reset, or None if not found. """ - metagraph = self.metagraph(netuid) + metagraph = self.metagraph(netuid, block=block) try: hotkey_ss58 = metagraph.hotkeys[uid] except IndexError: @@ -1085,9 +1085,10 @@ def get_last_commitment_bonds_reset_block( ) return None block_data = self.get_last_bonds_reset(netuid, hotkey_ss58, block) - if block is None: + try: + return decode_block(block_data) + except TypeError: return None - return decode_block(block_data) def get_all_commitments( self, netuid: int, block: Optional[int] = None From 6e05e92a118916259e447eb89e6c0d48dfd00039 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 11:06:37 -0700 Subject: [PATCH 343/416] fix unit test --- tests/unit_tests/test_subtensor.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 627c2421e2..6a83bafa1a 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1651,12 +1651,11 @@ def test_get_last_commitment_bonds_reset_block(subtensor, mocker): fake_netuid = 1 fake_uid = 2 fake_hotkey = "hotkey" - expected_result = 3 mocked_get_last_bonds_reset = mocker.patch.object( subtensor, "get_last_bonds_reset" ) - mocked_get_last_bonds_reset.return_value = expected_result + mocked_decode_block = mocker.patch.object(subtensor_module, "decode_block") mocked_metagraph = mocker.MagicMock() subtensor.metagraph = mocked_metagraph @@ -1668,8 +1667,12 @@ def test_get_last_commitment_bonds_reset_block(subtensor, mocker): ) # Assertions - mocked_get_last_bonds_reset.assert_called_once() - assert result == expected_result + mocked_metagraph.assert_called_once_with(fake_netuid, None) + mocked_get_last_bonds_reset.assert_called_once_with( + fake_netuid, fake_hotkey, None + ) + mocked_decode_block.assert_called_once_with(mocked_get_last_bonds_reset.return_value) + assert result == mocked_decode_block.return_value def test_min_allowed_weights(subtensor, mocker): From 8a92175c539389d6ed515f4b5196eafcf2d21ea9 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 11:30:12 -0700 Subject: [PATCH 344/416] move `tests.e2e_tests.framework` to `bittensor.extras.dev_framework` --- MIGRATION.md | 1 + .../extras/dev_framework}/README.md | 16 +++++++---- .../extras/dev_framework}/__init__.py | 0 .../extras/dev_framework}/calls/__init__.py | 27 ++++++++++++++----- .../dev_framework}/calls/non_sudo_calls.py | 1 + .../extras/dev_framework}/calls/pallets.py | 4 +++ .../extras/dev_framework}/calls/sudo_calls.py | 1 + .../extras/dev_framework}/subnet.py | 0 .../extras/dev_framework}/utils.py | 0 tests/e2e_tests/utils/__init__.py | 2 +- 10 files changed, 40 insertions(+), 12 deletions(-) rename {tests/e2e_tests/framework => bittensor/extras/dev_framework}/README.md (89%) rename {tests/e2e_tests/framework => bittensor/extras/dev_framework}/__init__.py (100%) rename {tests/e2e_tests/framework => bittensor/extras/dev_framework}/calls/__init__.py (82%) rename {tests/e2e_tests/framework => bittensor/extras/dev_framework}/calls/non_sudo_calls.py (99%) rename {tests/e2e_tests/framework => bittensor/extras/dev_framework}/calls/pallets.py (93%) rename {tests/e2e_tests/framework => bittensor/extras/dev_framework}/calls/sudo_calls.py (99%) rename {tests/e2e_tests/framework => bittensor/extras/dev_framework}/subnet.py (100%) rename {tests/e2e_tests/framework => bittensor/extras/dev_framework}/utils.py (100%) diff --git a/MIGRATION.md b/MIGRATION.md index 699bd33f3e..eae73d0174 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -311,6 +311,7 @@ New subpackage `bittensor.extras` created to host optional extensions and experi Currently it contains: - `bittensor.extras.subtensor_api` - `bittensor.extras.timelock` +- `bittensor.extras.dev_framework` (read `bittensor/extras/dev_framework/README.md`) ### Balance (bittensor/utils/balance.py) and related changes diff --git a/tests/e2e_tests/framework/README.md b/bittensor/extras/dev_framework/README.md similarity index 89% rename from tests/e2e_tests/framework/README.md rename to bittensor/extras/dev_framework/README.md index 80f07e76b0..35a46b4245 100644 --- a/tests/e2e_tests/framework/README.md +++ b/bittensor/extras/dev_framework/README.md @@ -1,18 +1,24 @@ # E2E Test Framework ## Overview -The E2E test framework provides a unified orchestration layer for subnet operations testing in Bittensor. -It simplifies the creation of end-to-end test scenarios by abstracting low-level Subtensor API calls into declarative steps. - +The Bittensor Test Framework provides a unified orchestration layer for subnet operations and on-chain behavior testing. +Originally built to support SDK end-to-end (E2E) testing, it now serves as a general-purpose testing framework that can +be used both internally and by the Bittensor community. + +It abstracts low-level Subtensor RPC and extrinsic interactions into reproducible, declarative test steps, making it +suitable for: +- Local development and simulation. +- Testnet experimentation. +- Other suitable cases. --- ## Structure ``` -tests/e2e_tests/framework/ +bittensor/extras/dev_framework/ ├── subnet.py # Main orchestration class (TestSubnet) ├── utils.py # Common helpers and validators ├── __init__.py -└── calls/ # Auto-generated extrinsic definitions +└── calls/ # Auto-generated extrinsic definitions (based on current Subtensor spec version) ├── sudo_calls.py ├── non_sudo_calls.py ├── pallets.py diff --git a/tests/e2e_tests/framework/__init__.py b/bittensor/extras/dev_framework/__init__.py similarity index 100% rename from tests/e2e_tests/framework/__init__.py rename to bittensor/extras/dev_framework/__init__.py diff --git a/tests/e2e_tests/framework/calls/__init__.py b/bittensor/extras/dev_framework/calls/__init__.py similarity index 82% rename from tests/e2e_tests/framework/calls/__init__.py rename to bittensor/extras/dev_framework/calls/__init__.py index e93243946d..0455d723f9 100644 --- a/tests/e2e_tests/framework/calls/__init__.py +++ b/bittensor/extras/dev_framework/calls/__init__.py @@ -11,9 +11,9 @@ import os from bittensor import Subtensor -from tests.e2e_tests.framework.calls.sudo_calls import * # noqa: F401 -from tests.e2e_tests.framework.calls.non_sudo_calls import * # noqa: F401 -from tests.e2e_tests.framework.calls.pallets import * # noqa: F401 +from bittensor.extras.dev_framework.calls.sudo_calls import * # noqa: F401 +from bittensor.extras.dev_framework.calls.non_sudo_calls import * # noqa: F401 +from bittensor.extras.dev_framework.calls.pallets import * # noqa: F401 HEADER = '''""" This file is auto-generated. Do not edit manually. @@ -28,6 +28,9 @@ Note: Any manual changes will be overwritten the next time the generator is run. +''' + +IMPORT_TEXT = ''' """ from collections import namedtuple @@ -40,6 +43,8 @@ def recreate_calls_subpackage(network="local"): """Fetch the list of pallets and their call and save them to the corresponding modules.""" sub = Subtensor(network=network) + spec_version = sub.query_constant("System", "Version").value["spec_version"] + spec_version_text = f" Subtensor spec version: {spec_version}" non_sudo_calls = [] sudo_calls = [] pallets = [] @@ -67,9 +72,19 @@ def recreate_calls_subpackage(network="local"): f"# args: [{', '.join(fields_and_annot)}] | Pallet: {pallet.name}" ) - sudo_text = HEADER + "\n".join(sorted(sudo_calls)) + "\n" - non_sudo_text = HEADER + "\n".join(sorted(non_sudo_calls)) + "\n" - pallets_text = "\n".join([f'{p} = "{p}"' for p in pallets]) + sudo_text = ( + HEADER + spec_version_text + IMPORT_TEXT + "\n".join(sorted(sudo_calls)) + "\n" + ) + non_sudo_text = ( + HEADER + + spec_version_text + + IMPORT_TEXT + + "\n".join(sorted(non_sudo_calls)) + + "\n" + ) + pallets_text = f'""""\n{spec_version_text} \n"""\n' + "\n".join( + [f'{p} = "{p}"' for p in pallets] + ) sudo_calls_file_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "sudo_calls.py" diff --git a/tests/e2e_tests/framework/calls/non_sudo_calls.py b/bittensor/extras/dev_framework/calls/non_sudo_calls.py similarity index 99% rename from tests/e2e_tests/framework/calls/non_sudo_calls.py rename to bittensor/extras/dev_framework/calls/non_sudo_calls.py index f220d7ee36..c3aac6e842 100644 --- a/tests/e2e_tests/framework/calls/non_sudo_calls.py +++ b/bittensor/extras/dev_framework/calls/non_sudo_calls.py @@ -11,6 +11,7 @@ Note: Any manual changes will be overwritten the next time the generator is run. + Subtensor spec version: 325 """ from collections import namedtuple diff --git a/tests/e2e_tests/framework/calls/pallets.py b/bittensor/extras/dev_framework/calls/pallets.py similarity index 93% rename from tests/e2e_tests/framework/calls/pallets.py rename to bittensor/extras/dev_framework/calls/pallets.py index 05f8cf3b9b..fd49851ee7 100644 --- a/tests/e2e_tests/framework/calls/pallets.py +++ b/bittensor/extras/dev_framework/calls/pallets.py @@ -1,3 +1,7 @@ +""" " +Subtensor spec version: 325 +""" + System = "System" Timestamp = "Timestamp" Grandpa = "Grandpa" diff --git a/tests/e2e_tests/framework/calls/sudo_calls.py b/bittensor/extras/dev_framework/calls/sudo_calls.py similarity index 99% rename from tests/e2e_tests/framework/calls/sudo_calls.py rename to bittensor/extras/dev_framework/calls/sudo_calls.py index dc1710307d..5702247234 100644 --- a/tests/e2e_tests/framework/calls/sudo_calls.py +++ b/bittensor/extras/dev_framework/calls/sudo_calls.py @@ -11,6 +11,7 @@ Note: Any manual changes will be overwritten the next time the generator is run. + Subtensor spec version: 325 """ from collections import namedtuple diff --git a/tests/e2e_tests/framework/subnet.py b/bittensor/extras/dev_framework/subnet.py similarity index 100% rename from tests/e2e_tests/framework/subnet.py rename to bittensor/extras/dev_framework/subnet.py diff --git a/tests/e2e_tests/framework/utils.py b/bittensor/extras/dev_framework/utils.py similarity index 100% rename from tests/e2e_tests/framework/utils.py rename to bittensor/extras/dev_framework/utils.py diff --git a/tests/e2e_tests/utils/__init__.py b/tests/e2e_tests/utils/__init__.py index d81e647cdb..fbfe8b4cdc 100644 --- a/tests/e2e_tests/utils/__init__.py +++ b/tests/e2e_tests/utils/__init__.py @@ -5,7 +5,7 @@ from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging -from tests.e2e_tests.framework import * # noqa: F401 +from bittensor.extras.dev_framework import * # noqa: F401 if TYPE_CHECKING: From daed40cc9eecd1767148bc0893bec67d9edc9723 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 11:33:39 -0700 Subject: [PATCH 345/416] change name in README.md --- bittensor/extras/dev_framework/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/extras/dev_framework/README.md b/bittensor/extras/dev_framework/README.md index 35a46b4245..be40df587d 100644 --- a/bittensor/extras/dev_framework/README.md +++ b/bittensor/extras/dev_framework/README.md @@ -1,4 +1,4 @@ -# E2E Test Framework +# Development Test Framework ## Overview The Bittensor Test Framework provides a unified orchestration layer for subnet operations and on-chain behavior testing. From 9c83f8aca9ec43e099db42027f16098e706c81f6 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 11:36:38 -0700 Subject: [PATCH 346/416] remove unused imports --- bittensor/extras/dev_framework/utils.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bittensor/extras/dev_framework/utils.py b/bittensor/extras/dev_framework/utils.py index 2da38cf439..c574933d82 100644 --- a/bittensor/extras/dev_framework/utils.py +++ b/bittensor/extras/dev_framework/utils.py @@ -1,13 +1,8 @@ -import os from dataclasses import dataclass from typing import Optional, Union from bittensor_wallet import Wallet -from bittensor.core.subtensor import Subtensor -from bittensor.core.async_subtensor import AsyncSubtensor -from bittensor.core.types import ExtrinsicResponse - @dataclass class RegisterSubnet: From b660723c6540aac7259d833c8885f50ad6cc36a0 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 11:38:46 -0700 Subject: [PATCH 347/416] fix test --- tests/unit_tests/test_subtensor.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 6a83bafa1a..7573f1247b 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1667,7 +1667,9 @@ def test_get_last_commitment_bonds_reset_block(subtensor, mocker): ) # Assertions - mocked_metagraph.assert_called_once_with(fake_netuid, None) + mocked_metagraph.assert_called_once_with( + fake_netuid, + block=None) mocked_get_last_bonds_reset.assert_called_once_with( fake_netuid, fake_hotkey, None ) From c25b85940f43aa5f5dbe54a2dd08bc0127064712 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 11:54:20 -0700 Subject: [PATCH 348/416] fix import --- tests/unit_tests/utils/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit_tests/utils/test_utils.py b/tests/unit_tests/utils/test_utils.py index 9a6527d643..d7e25d6c93 100644 --- a/tests/unit_tests/utils/test_utils.py +++ b/tests/unit_tests/utils/test_utils.py @@ -1,7 +1,7 @@ import pytest from bittensor import warnings, __getattr__, version_split, logging, trace, debug, utils -from bittensor.core.settings import SS58_FORMAT +from bittensor_wallet.utils import SS58_FORMAT def test_getattr_version_split(): From 9b6ab1ffe57fe20aa558e01d81dab5cd45f2a23b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 12:42:43 -0700 Subject: [PATCH 349/416] typo --- tests/e2e_tests/test_subtensor_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index ce4c3e412c..b236f9bd53 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -384,7 +384,7 @@ def test_blocks(subtensor): get_current_block = subtensor.chain.get_current_block() block = subtensor.block - # Several random tests fell during the block finalization period. Fast blocks of 0.25 seconds (very fast) + # Several random tests fail during the block finalization period. Fast blocks of 0.25 seconds (very fast) assert get_current_block in [block, block + 1] block_hash = subtensor.chain.get_block_hash(block) From a7358431b7fcd2143f726f461c174be68882cc7b Mon Sep 17 00:00:00 2001 From: Roman <167799377+basfroman@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:46:30 -0700 Subject: [PATCH 350/416] Update bittensor/core/async_subtensor.py Co-authored-by: BD Himes <37844818+thewhaleking@users.noreply.github.com> --- bittensor/core/async_subtensor.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ae8a3df1cc..96aabd45cd 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1409,7 +1409,12 @@ async def get_block_info( block = block or header.get("number", None) block_hash = block_hash or header.get("hash", None) extrinsics = cast(list, block_info.get("extrinsics")) - timestamp = await self.get_timestamp(block=block) + timestamp = None + for ext in extrinsics: + decoded = ext.decode() + if decoded["call"]["call_module"] == "Timestamp": + timestamp = decoded["call"]["call_args"][0]["value"] + break return BlockInfo( number=block, hash=block_hash, From d81d1024a833c5e6a0599d9f6f81e0a4fc212c7b Mon Sep 17 00:00:00 2001 From: Roman <167799377+basfroman@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:46:38 -0700 Subject: [PATCH 351/416] Update bittensor/core/subtensor.py Co-authored-by: BD Himes <37844818+thewhaleking@users.noreply.github.com> --- bittensor/core/subtensor.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 833bba4a91..36a470f6c2 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -835,7 +835,12 @@ def get_block_info( block = block or header.get("number", None) block_hash = block_hash or header.get("hash", None) extrinsics = cast(list, block_info.get("extrinsics")) - timestamp = self.get_timestamp(block=block) + timestamp = None + for ext in extrinsics: + decoded = ext.decode() + if decoded["call"]["call_module"] == "Timestamp": + timestamp = decoded["call"]["call_args"][0]["value"] + break return BlockInfo( number=block, hash=block_hash, From f6643b2e0dbdb117d7ba8f944cb9cde6c8e52d55 Mon Sep 17 00:00:00 2001 From: Roman <167799377+basfroman@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:46:52 -0700 Subject: [PATCH 352/416] Update bittensor/core/types.py Co-authored-by: BD Himes <37844818+thewhaleking@users.noreply.github.com> --- bittensor/core/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index e7df956b2e..e229642541 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -534,7 +534,7 @@ class BlockInfo: hash: The corresponding block hash. timestamp: The timestamp of the block (based on the `Timestamp.Now` extrinsic). header: The raw block header returned by the node RPC. - extrinsics: The list of decoded extrinsics included in the block. + extrinsics: The list of extrinsics included in the block. explorer: The link to block explorer service. """ From 9907762bda1f3e0402ac5053e218b0057107b432 Mon Sep 17 00:00:00 2001 From: Roman <167799377+basfroman@users.noreply.github.com> Date: Mon, 6 Oct 2025 12:47:13 -0700 Subject: [PATCH 353/416] Update tests/unit_tests/test_subtensor.py Co-authored-by: BD Himes <37844818+thewhaleking@users.noreply.github.com> --- tests/unit_tests/test_subtensor.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 7573f1247b..76f5e94e22 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4338,9 +4338,6 @@ def test_get_block_info(subtensor, mocker): block_number=None, ignore_decoding_errors=True, ) - mocked_get_timestamp.assert_called_once_with( - block=fake_block - ) mocked_BlockInfo.assert_called_once_with( number=fake_block, hash=fake_hash, From 7f6e88311abfff3ca3e3d8f1700b3245bd1a08f9 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 13:13:40 -0700 Subject: [PATCH 354/416] update method --- bittensor/core/async_subtensor.py | 7 +++---- bittensor/core/subtensor.py | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 96aabd45cd..b7bdc8cb4f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1400,7 +1400,7 @@ async def get_block_info( - timestamp: The timestamp of the block (based on the `Timestamp.Now` extrinsic). - header: The raw block header returned by the node RPC. - extrinsics: The list of decoded extrinsics included in the block. - - explorer: The link to block explorer service. + - explorer: The link to block explorer service. Always related with finney block data. """ block_info = await self.substrate.get_block( block_number=block, block_hash=block_hash, ignore_decoding_errors=True @@ -1411,9 +1411,8 @@ async def get_block_info( extrinsics = cast(list, block_info.get("extrinsics")) timestamp = None for ext in extrinsics: - decoded = ext.decode() - if decoded["call"]["call_module"] == "Timestamp": - timestamp = decoded["call"]["call_args"][0]["value"] + if ext.value_serialized["call"]["call_module"] == "Timestamp": + timestamp = ext.value_serialized["call"]["call_args"][0]["value"] break return BlockInfo( number=block, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 36a470f6c2..cfb2da2cdb 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -826,7 +826,7 @@ def get_block_info( - timestamp: The timestamp of the block (based on the `Timestamp.Now` extrinsic). - header: The raw block header returned by the node RPC. - extrinsics: The list of decoded extrinsics included in the block. - - explorer: The link to block explorer service. + - explorer: The link to block explorer service. Always related with finney block data. """ block_info = self.substrate.get_block( block_number=block, block_hash=block_hash, ignore_decoding_errors=True @@ -837,9 +837,8 @@ def get_block_info( extrinsics = cast(list, block_info.get("extrinsics")) timestamp = None for ext in extrinsics: - decoded = ext.decode() - if decoded["call"]["call_module"] == "Timestamp": - timestamp = decoded["call"]["call_args"][0]["value"] + if ext.value_serialized["call"]["call_module"] == "Timestamp": + timestamp = ext.value_serialized["call"]["call_args"][0]["value"] break return BlockInfo( number=block, From 7bcd790700905757302ceff27057162156a2ddd4 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 13:13:48 -0700 Subject: [PATCH 355/416] update class --- bittensor/core/types.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index e229642541..08280c6d2a 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -1,7 +1,6 @@ import argparse from abc import ABC from dataclasses import dataclass -from datetime import datetime from typing import Any, Literal, Optional, TypedDict, Union, TYPE_CHECKING import numpy as np @@ -540,7 +539,7 @@ class BlockInfo: number: int hash: str - timestamp: datetime + timestamp: Optional[int] header: dict extrinsics: list explorer: str From 4a6b5c5e0c9119461a4ea88153e83c84e6f4074c Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 13:13:54 -0700 Subject: [PATCH 356/416] fix tests --- tests/unit_tests/test_async_subtensor.py | 19 +++++++++++++------ tests/unit_tests/test_subtensor.py | 16 +++++++++++++--- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 7a55b334f8..37d02cf381 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -4180,16 +4180,26 @@ async def test_get_block_info(subtensor, mocker): # Preps fake_block = mocker.Mock(spec=int) fake_hash = mocker.Mock(spec=str) + fake_timestamp = mocker.Mock(spec=int) + fake_decoded = mocker.Mock( + value_serialized={ + "call": { + "call_module": "Timestamp", + "call_args": [{"value": fake_timestamp}], + } + } + ) fake_substrate_block = { "header": { "number": fake_block, "hash": fake_hash, }, - "extrinsics": [] + "extrinsics": [ + fake_decoded, + ] } mocked_get_block = mocker.patch.object(subtensor.substrate, "get_block", return_value=fake_substrate_block) - mocked_get_timestamp = mocker.patch.object(subtensor, "get_timestamp") mocked_BlockInfo = mocker.patch.object(async_subtensor, "BlockInfo") # Call @@ -4201,13 +4211,10 @@ async def test_get_block_info(subtensor, mocker): block_number=None, ignore_decoding_errors=True, ) - mocked_get_timestamp.assert_awaited_once_with( - block=fake_block - ) mocked_BlockInfo.assert_called_once_with( number=fake_block, hash=fake_hash, - timestamp=mocked_get_timestamp.return_value, + timestamp=fake_timestamp, header=fake_substrate_block.get("header"), extrinsics=fake_substrate_block.get("extrinsics"), explorer=f"{settings.TAO_APP_BLOCK_EXPLORER}{fake_block}" diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 76f5e94e22..ab35d422bb 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4317,16 +4317,26 @@ def test_get_block_info(subtensor, mocker): # Preps fake_block = mocker.Mock(spec=int) fake_hash = mocker.Mock(spec=str) + fake_timestamp = mocker.Mock(spec=int) + fake_decoded = mocker.Mock( + value_serialized={ + "call": { + "call_module": "Timestamp", + "call_args": [{"value": fake_timestamp}], + } + } + ) fake_substrate_block = { "header": { "number": fake_block, "hash": fake_hash, }, - "extrinsics": [] + "extrinsics": [ + fake_decoded, + ] } mocked_get_block = mocker.patch.object(subtensor.substrate, "get_block", return_value=fake_substrate_block) - mocked_get_timestamp = mocker.patch.object(subtensor, "get_timestamp") mocked_BlockInfo = mocker.patch.object(subtensor_module, "BlockInfo") # Call @@ -4341,7 +4351,7 @@ def test_get_block_info(subtensor, mocker): mocked_BlockInfo.assert_called_once_with( number=fake_block, hash=fake_hash, - timestamp=mocked_get_timestamp.return_value, + timestamp=fake_timestamp, header=fake_substrate_block.get("header"), extrinsics=fake_substrate_block.get("extrinsics"), explorer=f"{settings.TAO_APP_BLOCK_EXPLORER}{fake_block}" From 93ae00a4b73cb7ecc2d0c17db992dfe93b6d9e4a Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 13:20:28 -0700 Subject: [PATCH 357/416] avoid close substrate after this call --- bittensor/core/async_subtensor.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index b7bdc8cb4f..3ee86ea1bb 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1307,15 +1307,14 @@ async def get_commitment_metadata( Returns: The raw commitment metadata from specific subnet for given hotkey. """ - async with self.substrate: - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - commit_data = await self.substrate.query( - module="Commitments", - storage_function="CommitmentOf", - params=[netuid, hotkey_ss58], - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + commit_data = await self.substrate.query( + module="Commitments", + storage_function="CommitmentOf", + params=[netuid, hotkey_ss58], + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) return commit_data async def get_current_block(self) -> int: From 7cd5e3256e6c27a38063cb6bdbaac397fbf27573 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 13:54:51 -0700 Subject: [PATCH 358/416] update `sudo_call_extrinsic` --- bittensor/core/extrinsics/asyncex/utils.py | 6 +++--- bittensor/core/extrinsics/utils.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/utils.py b/bittensor/core/extrinsics/asyncex/utils.py index 15cc39a934..8de92284d9 100644 --- a/bittensor/core/extrinsics/asyncex/utils.py +++ b/bittensor/core/extrinsics/asyncex/utils.py @@ -49,7 +49,7 @@ async def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - root: bool = False, + sn_owner_call: bool = False, ) -> ExtrinsicResponse: """Execute a sudo call extrinsic. @@ -68,7 +68,7 @@ async def sudo_call_extrinsic( 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. - root: Whether to use the root account for not sudo call in some subtensor pallet. + : True, if the subnet owner makes a call. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -86,7 +86,7 @@ async def sudo_call_extrinsic( call_function=call_function, call_params=call_params, ) - if not root: + if not sn_owner_call: call = await subtensor.substrate.compose_call( call_module="Sudo", call_function="sudo", diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 097e0d652c..09da25bf2a 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -84,7 +84,7 @@ def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - root: Optional[bool] = None, + sn_owner_call: bool = False, ) -> ExtrinsicResponse: """Execute a sudo call extrinsic. @@ -103,7 +103,7 @@ def sudo_call_extrinsic( 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. - root: Whether to use the root account for not sudo call in some subtensor pallet. + sn_owner_call: True, if the subnet owner makes a call. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -121,7 +121,7 @@ def sudo_call_extrinsic( call_function=call_function, call_params=call_params, ) - if not root: + if not sn_owner_call: call = subtensor.substrate.compose_call( call_module="Sudo", call_function="sudo", From f24f8295c9ea39fff7dbd21efb9c9338f0a272aa Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 13:55:12 -0700 Subject: [PATCH 359/416] update `async_set_hyperparameter` and `set_hyperparameter` --- bittensor/extras/dev_framework/subnet.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bittensor/extras/dev_framework/subnet.py b/bittensor/extras/dev_framework/subnet.py index 730d7d9f42..b6378e0600 100644 --- a/bittensor/extras/dev_framework/subnet.py +++ b/bittensor/extras/dev_framework/subnet.py @@ -400,7 +400,7 @@ def set_hyperparameter( raise_error=raise_error or self.raise_error, wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, wait_for_finalization=wait_for_finalization or self.wait_for_finalization, - root=not sudo_call, + sn_owner_call=not sudo_call, ) if self._check_response(response): @@ -433,7 +433,7 @@ async def async_set_hyperparameter( raise_error=raise_error or self.raise_error, wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, wait_for_finalization=wait_for_finalization or self.wait_for_finalization, - root=not sudo_call, + sn_owner_call=not sudo_call, ) if self._check_response(response): From bf7a132851c4ec88972780d325052d46b605a21f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 14:08:00 -0700 Subject: [PATCH 360/416] more updates --- bittensor/core/extrinsics/asyncex/utils.py | 6 +++--- bittensor/core/extrinsics/utils.py | 6 +++--- bittensor/extras/dev_framework/subnet.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/utils.py b/bittensor/core/extrinsics/asyncex/utils.py index 8de92284d9..f8fa39e794 100644 --- a/bittensor/core/extrinsics/asyncex/utils.py +++ b/bittensor/core/extrinsics/asyncex/utils.py @@ -49,7 +49,7 @@ async def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - sn_owner_call: bool = False, + root_call: bool = False, ) -> ExtrinsicResponse: """Execute a sudo call extrinsic. @@ -68,7 +68,7 @@ async def sudo_call_extrinsic( 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. - : True, if the subnet owner makes a call. + root_call: False, if the subnet owner makes a call. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -86,7 +86,7 @@ async def sudo_call_extrinsic( call_function=call_function, call_params=call_params, ) - if not sn_owner_call: + if not root_call: call = await subtensor.substrate.compose_call( call_module="Sudo", call_function="sudo", diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 09da25bf2a..6327e98e74 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -84,7 +84,7 @@ def sudo_call_extrinsic( raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, - sn_owner_call: bool = False, + root_call: bool = False, ) -> ExtrinsicResponse: """Execute a sudo call extrinsic. @@ -103,7 +103,7 @@ def sudo_call_extrinsic( 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. - sn_owner_call: True, if the subnet owner makes a call. + root_call: False, if the subnet owner makes a call. Returns: ExtrinsicResponse: The result object of the extrinsic execution. @@ -121,7 +121,7 @@ def sudo_call_extrinsic( call_function=call_function, call_params=call_params, ) - if not sn_owner_call: + if not root_call: call = subtensor.substrate.compose_call( call_module="Sudo", call_function="sudo", diff --git a/bittensor/extras/dev_framework/subnet.py b/bittensor/extras/dev_framework/subnet.py index b6378e0600..af6f5f03df 100644 --- a/bittensor/extras/dev_framework/subnet.py +++ b/bittensor/extras/dev_framework/subnet.py @@ -400,7 +400,7 @@ def set_hyperparameter( raise_error=raise_error or self.raise_error, wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, wait_for_finalization=wait_for_finalization or self.wait_for_finalization, - sn_owner_call=not sudo_call, + root_call=not sudo_call, ) if self._check_response(response): @@ -433,7 +433,7 @@ async def async_set_hyperparameter( raise_error=raise_error or self.raise_error, wait_for_inclusion=wait_for_inclusion or self.wait_for_inclusion, wait_for_finalization=wait_for_finalization or self.wait_for_finalization, - sn_owner_call=not sudo_call, + root_call=not sudo_call, ) if self._check_response(response): From 2f4b5b6eac684f7c99c0908f47e43d152dbdba92 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 18:41:05 -0700 Subject: [PATCH 361/416] update settings.py (`BT_NETWORK` -> `BT_SUBTENSOR_NETWORK`) --- bittensor/core/settings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index 29a744f33e..6ff48febbd 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -122,7 +122,7 @@ "subtensor": { "chain_endpoint": os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT") or DEFAULT_ENDPOINT, - "network": os.getenv("BT_NETWORK") or DEFAULT_NETWORK, + "network": os.getenv("BT_SUBTENSOR_NETWORK") or DEFAULT_NETWORK, "_mock": False, }, "wallet": { @@ -130,6 +130,9 @@ "hotkey": os.getenv("BT_WALLET_HOTKEY") or "default", "path": os.getenv("BT_WALLET_PATH") or str(WALLETS_DIR), }, + "config": False, + "strict": False, + "no_version_checking": False, } ) From 42997a0b6ae154d836e6d77c5855cf5b13cbb029 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 18:41:38 -0700 Subject: [PATCH 362/416] update config.py (check for `BT_PARSE_CLI_ARGS` added) --- bittensor/core/config.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/bittensor/core/config.py b/bittensor/core/config.py index 2fb3d8495b..0d132c369d 100644 --- a/bittensor/core/config.py +++ b/bittensor/core/config.py @@ -20,8 +20,8 @@ import os import sys from copy import deepcopy -from typing import Any, TypeVar, Type, Optional - +from typing import Any, Optional +from bittensor.core.settings import DEFAULTS import yaml from munch import DefaultMunch @@ -51,12 +51,29 @@ def __init__( parser: argparse.ArgumentParser = None, args: Optional[list[str]] = None, strict: bool = False, - default: Any = None, + default: Any = DEFAULTS, ) -> None: - super().__init__(default) + parse_cli = os.getenv("BT_PARSE_CLI_ARGS", "").lower() in ( + "1", + "true", + "yes", + "on", + ) + + # Fallback to defaults if not provided + default = deepcopy(default or DEFAULTS) + + if isinstance(default, DefaultMunch): + # Initialize Munch with defaults (dict-safe) + super().__init__(None, default.toDict()) + else: + # if defaults passed as dict + super().__init__(None, default) + self.__is_set = {} - if parser is None: + # If CLI parsing disabled, stop here + if not parse_cli or parser is None: return self._add_default_arguments(parser) @@ -234,15 +251,3 @@ def _add_default_arguments(self, parser: argparse.ArgumentParser) -> None: except argparse.ArgumentError: # this can fail if argument has already been added. pass - - -T = TypeVar("T", bound="DefaultConfig") - - -class DefaultConfig(Config): - """A Config with a set of default values.""" - - @classmethod - def default(cls: Type[T]) -> T: - """Get default config.""" - raise NotImplementedError("Function default is not implemented.") From e7ad1ea682037b2174733a83bf51f0cd5170b44b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 18:41:48 -0700 Subject: [PATCH 363/416] update MIGRATION.md --- MIGRATION.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index eae73d0174..fa720f6950 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -2,18 +2,12 @@ ## 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) - 2. ✅ 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. 3. ✅ Make the internal logic of extrinsics the same. There are extrinsics that are slightly different in implementation. - 4. ✅ ~~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.~~ Just improved regarding usage. - 5. ✅ Remove `unstake_all` parameter from `unstake_extrinsic` since we have `unstake_all_extrinsic`which is calles another subtensor function. - 6. ✅ `unstake` and `unstake_multiple` extrinsics should have `safe_unstaking` parameters instead of `safe_staking`. - 7. ✅ Remove `_do*` extrinsic calls and combine them with extrinsic logic. - 8. ✅ ~~`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`.~~ (`get_transfer_fee` isn't `get_extrinsic_fee`) ## Subtensor @@ -35,15 +29,18 @@ ## 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. +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. + - remove `BT_CHAIN_ENDPOINT` (settings.py :line 124) and use `BT_SUBTENSOR_CHAIN_ENDPOINT` instead of that. + rename this variable in documentation. + - rename local env variable`BT_NETWORK` to `BT_SUBTENSOR_NETWORK` 3. ✅ ~~Move `bittensor.utils.get_transfer_fn_params` to `bittensor.core.extrinsics.utils`.~~ it's on the right place. @@ -70,7 +67,7 @@ rename this variable in documentation. 12. ✅ The SDK is dropping support for `Python 3.9` starting with this release. 13. ✅ Remove `Default is` and `Default to` in docstrings bc parameters enough. 14. ✅ `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). `get_commitment_metadata` added. -15. Solve the issue when a script using SDK receives the `--config` cli parameter. Disable `argparse` processing by default and enable it only when using SOME? a local environment variable. +15. ✅ Resolve an issue where a script using the SDK receives the `--config` or any other CLI parameters used in the SDK. Disable configuration processing. Use default values ​​instead. 16. 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 @@ -322,3 +319,7 @@ Currently it contains: - [x] `check_and_convert_to_balance` renamed to `check_balance_amount` - [x] `check_balance_amount` raised `BalanceTypeError` error instead of deprecated warning message. - [x] private function `bittensor.utils.balance._check_currencies` raises `BalanceUnitMismatchError` error instead of deprecated warning message. This function is used inside the Balance class to check if units match during various mathematical and logical operations. + + +### ArgParser issue +- to turn on args parser across SDK, the local env variable `BT_PARSE_CLI_ARGS` should be set to on of the values: `1`, `true`, `yes`, `on`. From 75666162528cb39c693c54b35850b2a76750bc97 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 18:41:54 -0700 Subject: [PATCH 364/416] add tests --- .../test_config_does_not_process_cli_args.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 tests/integration_tests/test_config_does_not_process_cli_args.py diff --git a/tests/integration_tests/test_config_does_not_process_cli_args.py b/tests/integration_tests/test_config_does_not_process_cli_args.py new file mode 100644 index 0000000000..235e96901c --- /dev/null +++ b/tests/integration_tests/test_config_does_not_process_cli_args.py @@ -0,0 +1,51 @@ +import argparse +import sys + +import pytest + +import bittensor as bt +from bittensor.core.config import InvalidConfigFile + + +def _config_call(): + """Create a config object from the bt cli args.""" + parser = argparse.ArgumentParser() + bt.Axon.add_args(parser) + bt.Subtensor.add_args(parser) + bt.AsyncSubtensor.add_args(parser) + bt.Wallet.add_args(parser) + bt.logging.add_args(parser) + bt.PriorityThreadPoolExecutor.add_args(parser) + config = bt.Config(parser) + return config + + +TEST_ARGS = [ + "bittensor", + "--config", "path/to/config.yaml", + "--strict", + "--no_version_checking" +] + + +def test_bittensor_cli_parser_enabled(monkeypatch): + """Tests that the bt cli args are processed.""" + monkeypatch.setenv("BT_PARSE_CLI_ARGS", "true") + + monkeypatch.setattr(sys, "argv", TEST_ARGS) + + with pytest.raises(InvalidConfigFile) as error: + _config_call() + + assert "No such file or directory" in str(error) + + +def test_bittensor_cli_parser_disabled(monkeypatch): + """Tests that the bt cli args are not processed.""" + monkeypatch.setattr(sys, "argv", TEST_ARGS) + + config = _config_call() + + assert config.config is False + assert config.strict is False + assert config.no_version_checking is False From f3778d3a373b9ae9f18a32fe36137160fe62ef72 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 18:54:39 -0700 Subject: [PATCH 365/416] update MIGRATION.md --- MIGRATION.md | 1 + 1 file changed, 1 insertion(+) diff --git a/MIGRATION.md b/MIGRATION.md index fa720f6950..d248ac331b 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -323,3 +323,4 @@ Currently it contains: ### ArgParser issue - to turn on args parser across SDK, the local env variable `BT_PARSE_CLI_ARGS` should be set to on of the values: `1`, `true`, `yes`, `on`. +- `bittensor.core.config.DefaultConfig` class has been deleted (unused). \ No newline at end of file From 5bb795a125c68a3bdea22e6f549a9ead6f9cfce6 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 19:42:21 -0700 Subject: [PATCH 366/416] revers logic --- bittensor/core/config.py | 4 ++-- .../test_config_does_not_process_cli_args.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor/core/config.py b/bittensor/core/config.py index 0d132c369d..2f979e2b65 100644 --- a/bittensor/core/config.py +++ b/bittensor/core/config.py @@ -53,7 +53,7 @@ def __init__( strict: bool = False, default: Any = DEFAULTS, ) -> None: - parse_cli = os.getenv("BT_PARSE_CLI_ARGS", "").lower() in ( + no_parse_cli = os.getenv("BT_NO_PARSE_CLI_ARGS", "").lower() in ( "1", "true", "yes", @@ -73,7 +73,7 @@ def __init__( self.__is_set = {} # If CLI parsing disabled, stop here - if not parse_cli or parser is None: + if no_parse_cli or parser is None: return self._add_default_arguments(parser) diff --git a/tests/integration_tests/test_config_does_not_process_cli_args.py b/tests/integration_tests/test_config_does_not_process_cli_args.py index 235e96901c..63c67a3224 100644 --- a/tests/integration_tests/test_config_does_not_process_cli_args.py +++ b/tests/integration_tests/test_config_does_not_process_cli_args.py @@ -30,7 +30,6 @@ def _config_call(): def test_bittensor_cli_parser_enabled(monkeypatch): """Tests that the bt cli args are processed.""" - monkeypatch.setenv("BT_PARSE_CLI_ARGS", "true") monkeypatch.setattr(sys, "argv", TEST_ARGS) @@ -42,6 +41,7 @@ def test_bittensor_cli_parser_enabled(monkeypatch): def test_bittensor_cli_parser_disabled(monkeypatch): """Tests that the bt cli args are not processed.""" + monkeypatch.setenv("BT_NO_PARSE_CLI_ARGS", "true") monkeypatch.setattr(sys, "argv", TEST_ARGS) config = _config_call() From 3f103709f0483927b34300e2755d5d872be7f6d1 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 20:11:43 -0700 Subject: [PATCH 367/416] flaky test --- tests/e2e_tests/test_commit_weights.py | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 9cc7fd7d2a..48e79383fb 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -298,7 +298,7 @@ def test_commit_weights_uses_next_nonce(subtensor, alice_wallet): Raises: AssertionError: If any of the checks or verifications fail """ - TEMPO_TO_SET = 50 if subtensor.chain.is_fast_blocks() else 20 + TEMPO_TO_SET = 100 if subtensor.chain.is_fast_blocks() else 20 # Create and prepare subnet alice_sn = TestSubnet(subtensor) @@ -422,7 +422,7 @@ async def test_commit_weights_uses_next_nonce_async(async_subtensor, alice_walle Raises: AssertionError: If any of the checks or verifications fail """ - TEMPO_TO_SET = 50 if await async_subtensor.chain.is_fast_blocks() else 20 + TEMPO_TO_SET = 100 if await async_subtensor.chain.is_fast_blocks() else 20 # Create and prepare subnet alice_sn = TestSubnet(async_subtensor) @@ -555,12 +555,21 @@ async def send_commit_(): ) await async_subtensor.wait_for_block(waiting_block) + weight_commits = None + counter = 0 # Query the WeightCommits storage map for all three salts - weight_commits = await async_subtensor.queries.query_module( - module="SubtensorModule", - name="WeightCommits", - params=[alice_sn.netuid, alice_wallet.hotkey.ss58_address], - ) + while not weight_commits or not len(weight_commits) == AMOUNT_OF_COMMIT_WEIGHTS: + weight_commits = await async_subtensor.queries.query_module( + module="SubtensorModule", + name="WeightCommits", + params=[alice_sn.netuid, alice_wallet.hotkey.ss58_address], + ) + await async_subtensor.wait_for_block() + logging.console.info(f"len(weight_commits): {len(weight_commits)}") + counter += 1 + if counter > TEMPO_TO_SET: + break + # 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] From b6a7d06bf52d7676fe4c6d7389c8879104cfee09 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Mon, 6 Oct 2025 22:59:07 -0700 Subject: [PATCH 368/416] alphabetical order of methods --- bittensor/core/async_subtensor.py | 2223 +++++++++++++++-------------- bittensor/core/subtensor.py | 1666 ++++++++++----------- 2 files changed, 1945 insertions(+), 1944 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 3ee86ea1bb..5ffa1004d5 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -300,6 +300,60 @@ async def __aenter__(self): async def __aexit__(self, exc_type, exc_val, exc_tb): await self.substrate.close() + # Helpers ========================================================================================================== + + @a.lru_cache(maxsize=128) + async def _get_block_hash(self, block_id: int): + return await self.substrate.get_block_hash(block_id) + + def _get_substrate( + self, + fallback_endpoints: Optional[list[str]] = None, + retry_forever: bool = False, + _mock: bool = False, + archive_endpoints: Optional[list[str]] = None, + ws_shutdown_timer: float = 5.0, + ) -> Union[AsyncSubstrateInterface, RetryAsyncSubstrate]: + """Creates the Substrate instance based on provided arguments. + + This internal method creates either a standard AsyncSubstrateInterface or a RetryAsyncSubstrate depending on the + configuration parameters. + + Parameters: + fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. + retry_forever: Whether to retry forever on connection errors. + _mock: Whether this is a mock instance. Mainly for testing purposes. + archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in + cases where you are requesting a block that is too old for your current (presumably lite) node. + ws_shutdown_timer: Amount of time, in seconds, to wait after the last response from the chain to close the + connection. + + Returns: + Either AsyncSubstrateInterface or RetryAsyncSubstrate. + """ + if fallback_endpoints or retry_forever or archive_endpoints: + return RetryAsyncSubstrate( + url=self.chain_endpoint, + fallback_chains=fallback_endpoints, + ss58_format=SS58_FORMAT, + type_registry=TYPE_REGISTRY, + retry_forever=retry_forever, + use_remote_preset=True, + chain_name="Bittensor", + _mock=_mock, + archive_nodes=archive_endpoints, + ws_shutdown_timer=ws_shutdown_timer, + ) + return AsyncSubstrateInterface( + url=self.chain_endpoint, + ss58_format=SS58_FORMAT, + type_registry=TYPE_REGISTRY, + use_remote_preset=True, + chain_name="Bittensor", + _mock=_mock, + ws_shutdown_timer=ws_shutdown_timer, + ) + async def determine_block_hash( self, block: Optional[int] = None, @@ -461,53 +515,10 @@ async def get_hyperparameter( return getattr(result, "value", result) - def _get_substrate( - self, - fallback_endpoints: Optional[list[str]] = None, - retry_forever: bool = False, - _mock: bool = False, - archive_endpoints: Optional[list[str]] = None, - ws_shutdown_timer: float = 5.0, - ) -> Union[AsyncSubstrateInterface, RetryAsyncSubstrate]: - """Creates the Substrate instance based on provided arguments. - - This internal method creates either a standard AsyncSubstrateInterface or a RetryAsyncSubstrate depending on the - configuration parameters. - - Parameters: - fallback_endpoints: List of fallback endpoints to use if default or provided network is not available. - retry_forever: Whether to retry forever on connection errors. - _mock: Whether this is a mock instance. Mainly for testing purposes. - archive_endpoints: Similar to fallback_endpoints, but specifically only archive nodes. Will be used in - cases where you are requesting a block that is too old for your current (presumably lite) node. - ws_shutdown_timer: Amount of time, in seconds, to wait after the last response from the chain to close the - connection. - - Returns: - Either AsyncSubstrateInterface or RetryAsyncSubstrate. - """ - if fallback_endpoints or retry_forever or archive_endpoints: - return RetryAsyncSubstrate( - url=self.chain_endpoint, - fallback_chains=fallback_endpoints, - ss58_format=SS58_FORMAT, - type_registry=TYPE_REGISTRY, - retry_forever=retry_forever, - use_remote_preset=True, - chain_name="Bittensor", - _mock=_mock, - archive_nodes=archive_endpoints, - ws_shutdown_timer=ws_shutdown_timer, - ) - return AsyncSubstrateInterface( - url=self.chain_endpoint, - ss58_format=SS58_FORMAT, - type_registry=TYPE_REGISTRY, - use_remote_preset=True, - chain_name="Bittensor", - _mock=_mock, - ws_shutdown_timer=ws_shutdown_timer, - ) + @property + async def block(self): + """Provides an asynchronous property to retrieve the current block.""" + return await self.get_current_block() # Subtensor queries =========================================================================================== @@ -773,11 +784,6 @@ async def state_call( # Common subtensor methods ========================================================================================= - @property - async def block(self): - """Provides an asynchronous property to retrieve the current block.""" - return await self.get_current_block() - async def all_subnets( self, block: Optional[int] = None, @@ -1164,158 +1170,321 @@ async def get_all_subnets_info( return SubnetInfo.list_from_dicts(result) - async def get_auto_stakes( - self, - coldkey_ss58: str, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> dict[int, str]: - """Fetches auto stake destinations for a given wallet across all subnets. - - Parameters: - coldkey_ss58: Coldkey ss58 address. - block: The block number for the query. - block_hash: The block hash for the query. - reuse_block: Whether to reuse the last-used block hash. - - Returns: - dict[int, str]: - - netuid: The unique identifier of the subnet. - - hotkey: The hotkey of the wallet. - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query_map( - module="SubtensorModule", - storage_function="AutoStakeDestination", - params=[coldkey_ss58], - block_hash=block_hash, - ) - - pairs = {} - async for netuid, destination in query: - hotkey_ss58 = decode_account_id(destination.value[0]) - if hotkey_ss58: - pairs[int(netuid)] = hotkey_ss58 - - return pairs - - async def get_balance( + async def get_all_commitments( self, - address: str, + netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Balance: - """Retrieves the balance for given coldkey. + ) -> dict[str, str]: + """Retrieves raw commitment metadata from a given subnet. - This method queries the System module's Account storage to get the current balance of a coldkey address. The - balance represents the amount of TAO tokens held by the specified address. + This method retrieves all commitment data for all neurons in a specific subnet. This is useful for analyzing the + commit-reveal patterns across an entire subnet. Parameters: - address: The coldkey address in SS58 format. + netuid: The unique identifier of the subnetwork. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - Balance: The balance object containing the account's TAO balance. + A mapping of the ss58:commitment with the commitment as a string. Example: - # Get balance for an address - balance = await subtensor.get_balance(address="5F...") - print(f"Balance: {balance.tao} TAO") + # Get all commitments for subnet 1 + commitments = await subtensor.get_all_commitments(netuid=1) - # Get balance at specific block - balance = await subtensor.get_balance(address="5F...", block=1000000) + # Iterate over all commitments + for hotkey, commitment in commitments.items(): + print(f"Hotkey {hotkey}: {commitment}") """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - balance = await self.substrate.query( - module="System", - storage_function="Account", - params=[address], + query = await self.query_map( + module="Commitments", + name="CommitmentOf", + params=[netuid], + block=block, block_hash=block_hash, - reuse_block_hash=reuse_block, + reuse_block=reuse_block, ) - return Balance(balance["data"]["free"]) + result = {} + async for id_, value in query: + try: + result[decode_account_id(id_[0])] = decode_metadata(value) + except Exception as error: + logging.error( + f"Error decoding [red]{id_}[/red] and [red]{value}[/red]: {error}" + ) + return result - async def get_balances( + async def get_all_metagraphs_info( self, - *addresses: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict[str, Balance]: - """Retrieves the balance for given coldkey(s). - - This method efficiently queries multiple coldkey addresses in a single batch operation, returning a dictionary - mapping each address to its corresponding balance. This is more efficient than calling get_balance multiple - times. + all_mechanisms: bool = False, + ) -> Optional[list[MetagraphInfo]]: + """ + Retrieves a list of MetagraphInfo objects for all subnets Parameters: - *addresses: Variable number of coldkey addresses in SS58 format. - block: The block number to query. Do not specify if using block_hash or reuse_block. - block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. + block: The blockchain block number for the query. + block_hash: The hash of the blockchain block number at which to perform the query. + reuse_block: Whether to reuse the last-used block hash when retrieving info. + all_mechanisms: If True then returns all mechanisms, otherwise only those with index 0 for all subnets. Returns: - A dictionary mapping each address to its Balance object. + List of MetagraphInfo objects for all existing subnets. - Example: - # Get balances for multiple addresses - balances = await subtensor.get_balances("5F...", "5G...", "5H...") + Notes: + See also: See """ - if reuse_block: + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash - elif block_hash is None and block is None: - # Neither block nor block_hash provided, default to head - block_hash = await self.get_block_hash() - else: - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - calls = [ - ( - await self.substrate.create_storage_key( - "System", "Account", [address], block_hash=block_hash - ) - ) - for address in addresses - ] - batch_call = await self.substrate.query_multi(calls, block_hash=block_hash) - results = {} - for item in batch_call: - value = item[1] or {"data": {"free": 0}} - results.update({item[0].params[0]: Balance(value["data"]["free"])}) - return results + method = "get_all_mechagraphs" if all_mechanisms else "get_all_metagraphs" + query = await self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method=method, + block_hash=block_hash, + ) + if query is None or not hasattr(query, "value"): + return None - async def get_commitment_metadata( + return MetagraphInfo.list_from_dicts(query.value) + + async def get_all_neuron_certificates( self, netuid: int, - hotkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Union[str, dict]: - """Fetches raw commitment metadata from specific subnet for given hotkey. + ) -> dict[str, Certificate]: + """ + Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network. Parameters: - netuid: The unique subnet identifier. - hotkey_ss58: The hotkey ss58 address. + netuid: The unique identifier of the subnet. block: The blockchain block number for the query. - block_hash: The hash of the block at which to check the hotkey ownership. - reuse_block: Whether to reuse the last-used blockchain hash. + 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: - The raw commitment metadata from specific subnet for given hotkey. + {ss58: Certificate} for the key/Certificate pairs on the subnet + + This function is used for certificate discovery for setting up mutual tls communication between neurons. """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - commit_data = await self.substrate.query( + query_certificates = await self.query_map( + module="SubtensorModule", + name="NeuronCertificates", + params=[netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + output = {} + async for key, item in query_certificates: + output[decode_account_id(key)] = Certificate(item.value) + return output + + async def get_all_revealed_commitments( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> dict[str, tuple[tuple[int, str], ...]]: + """Retrieves all revealed commitments for a given subnet. + + Parameters: + netuid: The unique identifier of the subnetwork. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. + + Returns: + result: A dictionary of all revealed commitments in view {ss58_address: (reveal block, commitment message)}. + + Example of result: + { + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY": ( (12, "Alice message 1"), (152, "Alice message 2") ), + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty": ( (12, "Bob message 1"), (147, "Bob message 2") ), + } + """ + query = await self.query_map( module="Commitments", - storage_function="CommitmentOf", - params=[netuid, hotkey_ss58], + name="RevealedCommitments", + params=[netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + + result = {} + async for pair in query: + hotkey_ss58_address, commitment_message = ( + decode_revealed_commitment_with_hotkey(pair) + ) + result[hotkey_ss58_address] = commitment_message + return result + + async def get_all_subnets_netuid( + self, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> list[int]: + """ + Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network. + + Parameters: + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. + + Returns: + A list of subnet netuids. + + This function provides a comprehensive view of the subnets within the Bittensor network, offering insights into + its diversity and scale. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + result = await self.substrate.query_map( + module="SubtensorModule", + storage_function="NetworksAdded", block_hash=block_hash, reuse_block_hash=reuse_block, ) - return commit_data + subnets = [] + if result.records: + async for netuid, exists in result: + if exists: + subnets.append(netuid) + return subnets + + async def get_auto_stakes( + self, + coldkey_ss58: str, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> dict[int, str]: + """Fetches auto stake destinations for a given wallet across all subnets. + + Parameters: + coldkey_ss58: Coldkey ss58 address. + block: The block number for the query. + block_hash: The block hash for the query. + reuse_block: Whether to reuse the last-used block hash. + + Returns: + dict[int, str]: + - netuid: The unique identifier of the subnet. + - hotkey: The hotkey of the wallet. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + query = await self.substrate.query_map( + module="SubtensorModule", + storage_function="AutoStakeDestination", + params=[coldkey_ss58], + block_hash=block_hash, + ) + + pairs = {} + async for netuid, destination in query: + hotkey_ss58 = decode_account_id(destination.value[0]) + if hotkey_ss58: + pairs[int(netuid)] = hotkey_ss58 + + return pairs + + async def get_balance( + self, + address: str, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Balance: + """Retrieves the balance for given coldkey. + + This method queries the System module's Account storage to get the current balance of a coldkey address. The + balance represents the amount of TAO tokens held by the specified address. + + Parameters: + address: The coldkey address in SS58 format. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. + + Returns: + Balance: The balance object containing the account's TAO balance. + + Example: + # Get balance for an address + balance = await subtensor.get_balance(address="5F...") + print(f"Balance: {balance.tao} TAO") + + # Get balance at specific block + balance = await subtensor.get_balance(address="5F...", block=1000000) + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + balance = await self.substrate.query( + module="System", + storage_function="Account", + params=[address], + block_hash=block_hash, + reuse_block_hash=reuse_block, + ) + return Balance(balance["data"]["free"]) + + async def get_balances( + self, + *addresses: str, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> dict[str, Balance]: + """Retrieves the balance for given coldkey(s). + + This method efficiently queries multiple coldkey addresses in a single batch operation, returning a dictionary + mapping each address to its corresponding balance. This is more efficient than calling get_balance multiple + times. + + Parameters: + *addresses: Variable number of coldkey addresses in SS58 format. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. + + Returns: + A dictionary mapping each address to its Balance object. + + Example: + # Get balances for multiple addresses + balances = await subtensor.get_balances("5F...", "5G...", "5H...") + """ + if reuse_block: + block_hash = self.substrate.last_block_hash + elif block_hash is None and block is None: + # Neither block nor block_hash provided, default to head + block_hash = await self.get_block_hash() + else: + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + calls = [ + ( + await self.substrate.create_storage_key( + "System", "Account", [address], block_hash=block_hash + ) + ) + for address in addresses + ] + batch_call = await self.substrate.query_multi(calls, block_hash=block_hash) + results = {} + for item in batch_call: + value = item[1] or {"data": {"free": 0}} + results.update({item[0].params[0]: Balance(value["data"]["free"])}) + return results async def get_current_block(self) -> int: """Returns the current block number on the Bittensor blockchain. @@ -1341,10 +1510,6 @@ async def get_current_block(self) -> int: """ return await self.substrate.get_block_number(None) - @a.lru_cache(maxsize=128) - async def _get_block_hash(self, block_id: int): - return await self.substrate.get_block_hash(block_id) - async def get_block_hash(self, block: Optional[int] = None) -> str: """Retrieves the hash of a specific block on the Bittensor blockchain. @@ -1423,78 +1588,38 @@ async def get_block_info( ) return None - async def get_parents( + async def get_children( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> list[tuple[float, str]]: - """This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys - storage function to get the children and formats them before returning as a tuple. + ) -> tuple[bool, list[tuple[float, str]], str]: + """Retrieves the children of a given hotkey and netuid. + + This method queries the SubtensorModule's ChildKeys storage function to get the children and formats them before + returning as a tuple. It provides information about the child neurons that a validator has set for weight + distribution. Parameters: - hotkey_ss58: The child hotkey SS58. + hotkey_ss58: The hotkey value. netuid: The netuid value. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - A list of formatted parents [(proportion, parent)] - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - parents = await self.substrate.query( - module="SubtensorModule", - storage_function="ParentKeys", - params=[hotkey_ss58, netuid], - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) - if parents: - formatted_parents = [] - for proportion, parent in parents.value: - # Convert U64 to int - formatted_child = decode_account_id(parent[0]) - normalized_proportion = u64_normalized_float(proportion) - formatted_parents.append((normalized_proportion, formatted_child)) - return formatted_parents - - return [] - - async def get_children( - self, - hotkey_ss58: str, - netuid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> tuple[bool, list[tuple[float, str]], str]: - """Retrieves the children of a given hotkey and netuid. - - This method queries the SubtensorModule's ChildKeys storage function to get the children and formats them before - returning as a tuple. It provides information about the child neurons that a validator has set for weight - distribution. - - Parameters: - hotkey_ss58: The hotkey value. - netuid: The netuid value. - block: The block number to query. Do not specify if using block_hash or reuse_block. - block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - - Returns: - A tuple containing a boolean indicating success or failure, a list of formatted children with their - proportions, and an error message (if applicable). - - Example: - # Get children for a hotkey in subnet 1 - success, children, error = await subtensor.get_children(hotkey="5F...", netuid=1) - - if success: - for proportion, child_hotkey in children: - print(f"Child {child_hotkey}: {proportion}") + A tuple containing a boolean indicating success or failure, a list of formatted children with their + proportions, and an error message (if applicable). + + Example: + # Get children for a hotkey in subnet 1 + success, children, error = await subtensor.get_children(hotkey="5F...", netuid=1) + + if success: + for proportion, child_hotkey in children: + print(f"Child {child_hotkey}: {proportion}") """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) try: @@ -1626,353 +1751,158 @@ async def get_commitment( logging.error(error) return "" - async def get_last_bonds_reset( + async def get_commitment_metadata( self, netuid: int, hotkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> bytes: - """ - Retrieves the last bonds reset triggered at commitment from given subnet for a specific hotkey. + ) -> Union[str, dict]: + """Fetches raw commitment metadata from specific subnet for given hotkey. Parameters: - netuid: The network uid to fetch from. - hotkey_ss58: The hotkey of the neuron for which to fetch the last bonds reset. - block: The block number to query. - 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. + netuid: The unique subnet identifier. + hotkey_ss58: The hotkey ss58 address. + block: The blockchain block number for the query. + block_hash: The hash of the block at which to check the hotkey ownership. + reuse_block: Whether to reuse the last-used blockchain hash. Returns: - bytes: The last bonds reset data for the specified hotkey and netuid. + The raw commitment metadata from specific subnet for given hotkey. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - block = await self.substrate.query( + commit_data = await self.substrate.query( module="Commitments", - storage_function="LastBondsReset", + storage_function="CommitmentOf", params=[netuid, hotkey_ss58], block_hash=block_hash, reuse_block_hash=reuse_block, ) - return block + return commit_data - async def get_last_commitment_bonds_reset_block( + async def get_delegate_by_hotkey( self, - netuid: int, - uid: int, + hotkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[int]: + ) -> Optional[DelegateInfo]: """ - Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. + Retrieves detailed information about a delegate neuron based on its hotkey. This function provides a + comprehensive view of the delegate's status, including its stakes, nominators, and reward distribution. Parameters: - netuid: The unique identifier of the subnetwork. - uid: The unique identifier of the neuron. - block: The block number to query. - 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. + hotkey_ss58: The ``SS58`` address of the delegate's hotkey. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - The block number when the bonds were last reset, or None if not found. + Detailed information about the delegate neuron, ``None`` if not found. + + This function is essential for understanding the roles and influence of delegate neurons within the Bittensor + network's consensus and governance structures. """ - metagraph = await self.metagraph(netuid, block=block) - try: - hotkey = metagraph.hotkeys[uid] - except IndexError: - logging.error( - "Your uid is not in the hotkeys. Please double-check your UID." - ) - return None - block_data = await self.get_last_bonds_reset( - netuid, hotkey, block, block_hash, reuse_block + result = await self.query_runtime_api( + runtime_api="DelegateInfoRuntimeApi", + method="get_delegate", + params=[hotkey_ss58], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, ) - try: - return decode_block(block_data) - except TypeError: + + if not result: return None - async def get_all_commitments( + return DelegateInfo.from_dict(result) + + async def get_delegate_identities( self, - netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict[str, str]: - """Retrieves raw commitment metadata from a given subnet. - - This method retrieves all commitment data for all neurons in a specific subnet. This is useful for analyzing the - commit-reveal patterns across an entire subnet. + ) -> dict[str, ChainIdentity]: + """ + Fetches delegates identities from the chain. Parameters: - netuid: The unique identifier of the subnetwork. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - A mapping of the ss58:commitment with the commitment as a string. - - Example: - # Get all commitments for subnet 1 - commitments = await subtensor.get_all_commitments(netuid=1) - - # Iterate over all commitments - for hotkey, commitment in commitments.items(): - print(f"Hotkey {hotkey}: {commitment}") + Dict {ss58: ChainIdentity, ...} """ - query = await self.query_map( - module="Commitments", - name="CommitmentOf", - params=[netuid], - block=block, + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + identities = await self.substrate.query_map( + module="SubtensorModule", + storage_function="IdentitiesV2", block_hash=block_hash, - reuse_block=reuse_block, + reuse_block_hash=reuse_block, ) - result = {} - async for id_, value in query: - try: - result[decode_account_id(id_[0])] = decode_metadata(value) - except Exception as error: - logging.error( - f"Error decoding [red]{id_}[/red] and [red]{value}[/red]: {error}" - ) - return result - async def get_revealed_commitment_by_hotkey( + return { + decode_account_id(ss58_address[0]): ChainIdentity.from_dict( + decode_hex_identity_dict(identity.value), + ) + async for ss58_address, identity in identities + } + + async def get_delegate_take( self, - netuid: int, - hotkey_ss58: Optional[str] = None, + hotkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[tuple[tuple[int, str], ...]]: - """Retrieves hotkey related revealed commitment for a given subnet. + ) -> float: + """ + Retrieves 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. Parameters: - netuid: The unique identifier of the subnetwork. - hotkey_ss58: The ss58 address of the committee member. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - A tuple of reveal block and commitment message. - """ - if not is_valid_ss58_address(address=hotkey_ss58): - raise ValueError(f"Invalid ss58 address {hotkey_ss58} provided.") + float: The delegate take percentage. - query = await self.query_module( - module="Commitments", - name="RevealedCommitments", - params=[netuid, hotkey_ss58], - block=block, + The delegate take is a critical parameter in the network's incentive structure, influencing the distribution of + rewards among neurons and their nominators. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + result = await self.query_subtensor( + name="Delegates", block_hash=block_hash, reuse_block=reuse_block, + params=[hotkey_ss58], ) - if query is None: - return None - return tuple(decode_revealed_commitment(pair) for pair in query) - - async def get_revealed_commitment( - self, - netuid: int, - uid: int, - block: Optional[int] = None, - ) -> Optional[tuple[tuple[int, str], ...]]: - """Returns uid related revealed commitment for a given netuid. - - Parameters: - netuid: The unique identifier of the subnetwork. - uid: The neuron uid to retrieve the commitment from. - block: The block number to retrieve the commitment from. - - Returns: - A tuple of reveal block and commitment message. - Example of result: - ( (12, "Alice message 1"), (152, "Alice message 2") ) - ( (12, "Bob message 1"), (147, "Bob message 2") ) - """ - try: - meta_info = await self.get_metagraph_info(netuid, block=block) - if meta_info: - hotkey_ss58 = meta_info.hotkeys[uid] - else: - raise ValueError(f"Subnet with netuid {netuid} does not exist.") - except IndexError: - raise ValueError(f"Subnet {netuid} does not have a neuron with uid {uid}.") - - return await self.get_revealed_commitment_by_hotkey( - netuid=netuid, hotkey_ss58=hotkey_ss58, block=block - ) + return u16_normalized_float(result.value) # type: ignore - async def get_all_revealed_commitments( + async def get_delegated( self, - netuid: int, + coldkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict[str, tuple[tuple[int, str], ...]]: - """Retrieves all revealed commitments for a given subnet. + ) -> list[DelegatedInfo]: + """ + Retrieves a list of delegates and their associated stakes for a given coldkey. This function identifies the + delegates that a specific account has staked tokens on. Parameters: - netuid: The unique identifier of the subnetwork. + coldkey_ss58: The ``SS58`` address of the account's coldkey. block: The block number to query. Do not specify if using block_hash or reuse_block. block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - result: A dictionary of all revealed commitments in view {ss58_address: (reveal block, commitment message)}. - - Example of result: - { - "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY": ( (12, "Alice message 1"), (152, "Alice message 2") ), - "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty": ( (12, "Bob message 1"), (147, "Bob message 2") ), - } - """ - query = await self.query_map( - module="Commitments", - name="RevealedCommitments", - params=[netuid], - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - ) - - result = {} - async for pair in query: - hotkey_ss58_address, commitment_message = ( - decode_revealed_commitment_with_hotkey(pair) - ) - result[hotkey_ss58_address] = commitment_message - return result - - async def get_delegate_by_hotkey( - self, - hotkey_ss58: str, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> Optional[DelegateInfo]: - """ - Retrieves detailed information about a delegate neuron based on its hotkey. This function provides a - comprehensive view of the delegate's status, including its stakes, nominators, and reward distribution. - - Parameters: - hotkey_ss58: The ``SS58`` address of the delegate's hotkey. - block: The block number to query. Do not specify if using block_hash or reuse_block. - block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - - Returns: - Detailed information about the delegate neuron, ``None`` if not found. - - This function is essential for understanding the roles and influence of delegate neurons within the Bittensor - network's consensus and governance structures. - """ - - result = await self.query_runtime_api( - runtime_api="DelegateInfoRuntimeApi", - method="get_delegate", - params=[hotkey_ss58], - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - ) - - if not result: - return None - - return DelegateInfo.from_dict(result) - - async def get_delegate_identities( - self, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> dict[str, ChainIdentity]: - """ - Fetches delegates identities from the chain. - - Parameters: - block: The block number to query. Do not specify if using block_hash or reuse_block. - block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - - Returns: - Dict {ss58: ChainIdentity, ...} - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - identities = await self.substrate.query_map( - module="SubtensorModule", - storage_function="IdentitiesV2", - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) - - return { - decode_account_id(ss58_address[0]): ChainIdentity.from_dict( - decode_hex_identity_dict(identity.value), - ) - async for ss58_address, identity in identities - } - - async def get_delegate_take( - self, - hotkey_ss58: str, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> float: - """ - Retrieves 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. - - Parameters: - hotkey_ss58: The ``SS58`` address of the neuron's hotkey. - block: The block number to query. Do not specify if using block_hash or reuse_block. - block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - - Returns: - float: The delegate take percentage. - - The delegate take is a critical parameter in the network's incentive structure, influencing the distribution of - rewards among neurons and their nominators. - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.query_subtensor( - name="Delegates", - block_hash=block_hash, - reuse_block=reuse_block, - params=[hotkey_ss58], - ) - - return u16_normalized_float(result.value) # type: ignore - - async def get_delegated( - self, - coldkey_ss58: str, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> list[DelegatedInfo]: - """ - Retrieves a list of delegates and their associated stakes for a given coldkey. This function identifies the - delegates that a specific account has staked tokens on. - - Parameters: - coldkey_ss58: The ``SS58`` address of the account's coldkey. - block: The block number to query. Do not specify if using block_hash or reuse_block. - block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - - Returns: - A list containing the delegated information for the specified coldkey. + A list containing the delegated information for the specified coldkey. This function is important for account holders to understand their stake allocations and their involvement in the network's delegation and consensus mechanisms. @@ -2098,251 +2028,74 @@ async def get_hotkey_owner( hotkey_owner = hk_owner_query if exists else None return hotkey_owner - async def get_minimum_required_stake(self): - """ - Returns the minimum required stake for nominators in the Subtensor network. - - Returns: - Balance: The minimum required stake as a Balance object. - """ - result = await self.substrate.query( - module="SubtensorModule", storage_function="NominatorMinRequiredStake" - ) - - return Balance.from_rao(getattr(result, "value", 0)) - - async def get_metagraph_info( + async def get_last_bonds_reset( self, netuid: int, - mechid: int = 0, - selected_indices: Optional[ - Union[list[SelectiveMetagraphIndex], list[int]] - ] = None, + hotkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[MetagraphInfo]: + ) -> bytes: """ - Retrieves full or partial metagraph information for the specified subnet (netuid). - - A metagraph is a data structure that contains comprehensive information about the current state of a subnet, - including detailed information on all the nodes (neurons) such as subnet validator stakes and subnet weights - in the subnet. Metagraph aids in calculating emissions. + Retrieves the last bonds reset triggered at commitment from given subnet for a specific hotkey. Parameters: - netuid: The unique identifier of the subnet to query. - selected_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. - If not provided, all available fields will be returned. - block: The blockchain block number for the query. - block_hash: The hash of the blockchain block number at which to perform the query. - reuse_block: Whether to reuse the last-used block hash when retrieving info. - mechid: Subnet mechanism unique identifier. + netuid: The network uid to fetch from. + hotkey_ss58: The hotkey of the neuron for which to fetch the last bonds reset. + block: The block number to query. + 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: - MetagraphInfo object with the requested subnet mechanism data, None if the subnet mechanism does not exist. - - Example: - # Retrieve all fields from the metagraph from subnet 2 mechanism 0 - meta_info = subtensor.get_metagraph_info(netuid=2) - - # Retrieve all fields from the metagraph from subnet 2 mechanism 1 - meta_info = subtensor.get_metagraph_info(netuid=2, mechid=1) - - # Retrieve selective data from the metagraph from subnet 2 mechanism 0 - partial_meta_info = subtensor.get_metagraph_info( - netuid=2, - selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] - ) - - # Retrieve selective data from the metagraph from subnet 2 mechanism 1 - partial_meta_info = subtensor.get_metagraph_info( - netuid=2, - mechid=1, - selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] - ) - - Notes: - See also: - - - - + bytes: The last bonds reset data for the specified hotkey and netuid. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - if not block_hash and reuse_block: - block_hash = self.substrate.last_block_hash - - indexes = ( - [ - f.value if isinstance(f, SelectiveMetagraphIndex) else f - for f in selected_indices - ] - if selected_indices is not None - else [f for f in range(len(SelectiveMetagraphIndex))] - ) - - query = await self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_selective_mechagraph", - params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], + block = await self.substrate.query( + module="Commitments", + storage_function="LastBondsReset", + params=[netuid, hotkey_ss58], block_hash=block_hash, + reuse_block_hash=reuse_block, ) - if getattr(query, "value", None) is None: - logging.error( - f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." - ) - return None - - return MetagraphInfo.from_dict(query.value) + return block - # TODO: update parameters order in SDKv10 - async def get_all_metagraphs_info( + async def get_last_commitment_bonds_reset_block( self, + netuid: int, + uid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - all_mechanisms: bool = False, - ) -> Optional[list[MetagraphInfo]]: + ) -> Optional[int]: """ - Retrieves a list of MetagraphInfo objects for all subnets + Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. Parameters: - block: The blockchain block number for the query. - block_hash: The hash of the blockchain block number at which to perform the query. - reuse_block: Whether to reuse the last-used block hash when retrieving info. - all_mechanisms: If True then returns all mechanisms, otherwise only those with index 0 for all subnets. - - Returns: - List of MetagraphInfo objects for all existing subnets. - - Notes: - See also: See - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - if not block_hash and reuse_block: - block_hash = self.substrate.last_block_hash - method = "get_all_mechagraphs" if all_mechanisms else "get_all_metagraphs" - query = await self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method=method, - block_hash=block_hash, - ) - if query is None or not hasattr(query, "value"): - return None - - return MetagraphInfo.list_from_dicts(query.value) - - async def get_netuids_for_hotkey( - self, - hotkey_ss58: str, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> list[int]: - """ - Retrieves a list of subnet UIDs (netuids) for which a given hotkey is a member. This function identifies the - specific subnets within the Bittensor network where the neuron associated with the hotkey is active. - - Parameters: - hotkey_ss58: The ``SS58`` address of the neuron's hotkey. - block: The blockchain block number for the query. - block_hash: The hash of the blockchain block number at which to perform the query. - reuse_block: Whether to reuse the last-used block hash when retrieving info. - - Returns: - A list of netuids where the neuron is a member. - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.substrate.query_map( - module="SubtensorModule", - storage_function="IsNetworkMember", - params=[hotkey_ss58], - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) - netuids = [] - if result.records: - async for record in result: - if record[1].value: - netuids.append(record[0]) - return netuids - - async def get_neuron_certificate( - self, - hotkey_ss58: str, - netuid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> Optional[Certificate]: - """ - Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a specified - subnet (netuid) of the Bittensor network. - - Parameters: - hotkey_ss58: The hotkey to query. - netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the parameter from. Do not specify if using block or - reuse_block. + netuid: The unique identifier of the subnetwork. + uid: The unique identifier of the neuron. + block: The block number to query. + 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: - the certificate of the neuron if found, ``None`` otherwise. - - This function is used for certificate discovery for setting up mutual tls communication between neurons. + The block number when the bonds were last reset, or None if not found. """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - certificate = cast( - Union[str, dict], - await self.query_module( - module="SubtensorModule", - name="NeuronCertificates", - block_hash=block_hash, - reuse_block=reuse_block, - params=[netuid, hotkey_ss58], - ), - ) - try: - if certificate: - return Certificate(certificate) - except AttributeError: + metagraph = await self.metagraph(netuid, block=block) + try: + hotkey = metagraph.hotkeys[uid] + except IndexError: + logging.error( + "Your uid is not in the hotkeys. Please double-check your UID." + ) return None - return None - - async def get_all_neuron_certificates( - self, - netuid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> dict[str, Certificate]: - """ - Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network. - - Parameters: - netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - 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: - {ss58: Certificate} for the key/Certificate pairs on the subnet - - This function is used for certificate discovery for setting up mutual tls communication between neurons. - """ - query_certificates = await self.query_map( - module="SubtensorModule", - name="NeuronCertificates", - params=[netuid], - block=block, - block_hash=block_hash, - reuse_block=reuse_block, + block_data = await self.get_last_bonds_reset( + netuid, hotkey, block, block_hash, reuse_block ) - output = {} - async for key, item in query_certificates: - output[decode_account_id(key)] = Certificate(item.value) - return output + try: + return decode_block(block_data) + except TypeError: + return None async def get_liquidity_list( self, @@ -2527,418 +2280,529 @@ async def get_liquidity_list( return positions - async def get_neuron_for_pubkey_and_subnet( + async def get_mechanism_emission_split( self, - hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> "NeuronInfo": - """ - Retrieves information about a neuron based on its public key (hotkey SS58 address) and the specific subnet UID - (netuid). This function provides detailed neuron information for a particular subnet within the Bittensor - network. + ) -> Optional[list[int]]: + """Returns the emission percentages allocated to each subnet mechanism. Parameters: - hotkey_ss58: The ``SS58`` address of the neuron's hotkey. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. - block_hash: The blockchain block number at which to perform the query. - reuse_block: Whether to reuse the last-used blockchain block hash. + block_hash: The hash of the block to retrieve the stake 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: - Detailed information about the neuron if found, ``None`` otherwise. - - This function is crucial for accessing specific neuron data and understanding its status, stake, and other - attributes within a particular subnet of the Bittensor ecosystem. + A list of integers representing the percentage of emission allocated to each subnet mechanism (rounded to + whole numbers). Returns None if emission is evenly split or if the data is unavailable. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - uid_query = await self.substrate.query( + result = await self.substrate.query( module="SubtensorModule", - storage_function="Uids", - params=[netuid, hotkey_ss58], + storage_function="MechanismEmissionSplit", + params=[netuid], block_hash=block_hash, - reuse_block_hash=reuse_block, ) - if (uid := getattr(uid_query, "value", None)) is None: - return NeuronInfo.get_null_neuron() - else: - return await self.neuron_for_uid( - uid=uid, - netuid=netuid, - block_hash=block_hash, - reuse_block=reuse_block, - ) + if result is None or not hasattr(result, "value"): + return None - async def get_next_epoch_start_block( + return [round(i / sum(result.value) * 100) for i in result.value] + + async def get_mechanism_count( self, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[int]: - """ - Calculates the first block number of the next epoch for the given subnet. - - If ``block`` is not provided, the current chain block will be used. Epochs are determined based on the subnet's - tempo (i.e., blocks per epoch). The result is the block number at which the next epoch will begin. + ) -> int: + """Retrieves the number of mechanisms for the given subnet. Parameters: - netuid: The unique identifier of the subnet. - block: The reference block to calculate from. If None, uses the current chain block height. - block_hash: The blockchain block number at which to perform the query. - reuse_block: Whether to reuse the last-used blockchain block hash. + netuid: Subnet identifier. + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake 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: - int: The block number at which the next epoch will start. - - Notes: - See also: + The number of mechanisms for the given subnet. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - blocks_since_last_step = await self.blocks_since_last_step( - netuid=netuid, block=block, block_hash=block_hash, reuse_block=reuse_block - ) - tempo = await self.tempo( - netuid=netuid, block=block, block_hash=block_hash, reuse_block=reuse_block + query = await self.substrate.query( + module="SubtensorModule", + storage_function="MechanismCountCurrent", + params=[netuid], + block_hash=block_hash, ) + return getattr(query, "value", 1) - block = block or await self.substrate.get_block_number(block_hash=block_hash) - if block and blocks_since_last_step is not None and tempo: - return block - blocks_since_last_step + tempo + 1 - return None - - async def get_owned_hotkeys( + async def get_metagraph_info( self, - coldkey_ss58: str, + netuid: int, + mechid: int = 0, + selected_indices: Optional[ + Union[list[SelectiveMetagraphIndex], list[int]] + ] = None, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> list[str]: + ) -> Optional[MetagraphInfo]: """ - Retrieves all hotkeys owned by a specific coldkey address. + Retrieves full or partial metagraph information for the specified subnet (netuid). + + A metagraph is a data structure that contains comprehensive information about the current state of a subnet, + including detailed information on all the nodes (neurons) such as subnet validator stakes and subnet weights + in the subnet. Metagraph aids in calculating emissions. Parameters: - coldkey_ss58: The SS58 address of the coldkey to query. + netuid: The unique identifier of the subnet to query. + selected_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. + If not provided, all available fields will be returned. block: The blockchain block number for the query. - block_hash: The hash of the blockchain block number for the query. - reuse_block: Whether to reuse the last-used blockchain block hash. + block_hash: The hash of the blockchain block number at which to perform the query. + reuse_block: Whether to reuse the last-used block hash when retrieving info. + mechid: Subnet mechanism unique identifier. Returns: - A list of hotkey SS58 addresses owned by the coldkey. - """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - owned_hotkeys = await self.substrate.query( - module="SubtensorModule", - storage_function="OwnedHotkeys", - params=[coldkey_ss58], - block_hash=block_hash, - reuse_block_hash=reuse_block, - ) + MetagraphInfo object with the requested subnet mechanism data, None if the subnet mechanism does not exist. - return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []] + Example: + # Retrieve all fields from the metagraph from subnet 2 mechanism 0 + meta_info = subtensor.get_metagraph_info(netuid=2) - async def get_stake( - self, - coldkey_ss58: str, - hotkey_ss58: str, - netuid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> Balance: - """ - Returns the stake under a coldkey - hotkey pairing. + # Retrieve all fields from the metagraph from subnet 2 mechanism 1 + meta_info = subtensor.get_metagraph_info(netuid=2, mechid=1) - Parameters: - hotkey_ss58: The SS58 address of the hotkey. - coldkey_ss58: The SS58 address of the coldkey. - netuid: The subnet ID. - block: The block number at which to query the stake information. - block_hash: The hash of the block to retrieve the stake 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. + # Retrieve selective data from the metagraph from subnet 2 mechanism 0 + partial_meta_info = subtensor.get_metagraph_info( + netuid=2, + selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + ) - Returns: - Balance: The stake under the coldkey - hotkey pairing. + # Retrieve selective data from the metagraph from subnet 2 mechanism 1 + partial_meta_info = subtensor.get_metagraph_info( + netuid=2, + mechid=1, + selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + ) + + Notes: + See also: + - + - """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + if not block_hash and reuse_block: + block_hash = self.substrate.last_block_hash - alpha_shares = await self.query_subtensor( - name="Alpha", - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - params=[hotkey_ss58, coldkey_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], + indexes = ( + [ + f.value if isinstance(f, SelectiveMetagraphIndex) else f + for f in selected_indices + ] + if selected_indices is not None + else [f for f in range(len(SelectiveMetagraphIndex))] ) - hotkey_shares = await self.query_subtensor( - name="TotalHotkeyShares", - block=block, + + query = await self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_selective_mechagraph", + params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], block_hash=block_hash, - reuse_block=reuse_block, - params=[hotkey_ss58, netuid], ) + if getattr(query, "value", None) is None: + logging.error( + f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." + ) + return None - hotkey_alpha: int = getattr(hotkey_alpha_result, "value", 0) - alpha_shares_as_float = fixed_to_float(alpha_shares) - hotkey_shares_as_float = fixed_to_float(hotkey_shares) + return MetagraphInfo.from_dict(query.value) - if hotkey_shares_as_float == 0: - return Balance.from_rao(0).set_unit(netuid=netuid) + async def get_minimum_required_stake(self): + """ + Returns the minimum required stake for nominators in the Subtensor network. - stake = alpha_shares_as_float / hotkey_shares_as_float * hotkey_alpha + Returns: + Balance: The minimum required stake as a Balance object. + """ + result = await self.substrate.query( + module="SubtensorModule", storage_function="NominatorMinRequiredStake" + ) - return Balance.from_rao(int(stake)).set_unit(netuid=netuid) + return Balance.from_rao(getattr(result, "value", 0)) - # TODO: update related with fee calculation - async def get_stake_add_fee( + async def get_netuids_for_hotkey( self, - amount: Balance, - netuid: int, + hotkey_ss58: str, block: Optional[int] = None, - ) -> Balance: + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> list[int]: """ - Calculates the fee for adding new stake to a hotkey. + Retrieves a list of subnet UIDs (netuids) for which a given hotkey is a member. This function identifies the + specific subnets within the Bittensor network where the neuron associated with the hotkey is active. Parameters: - amount: Amount of stake to add in TAO - netuid: Netuid of subnet - block: Block number at which to perform the calculation + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + block: The blockchain block number for the query. + block_hash: The hash of the blockchain block number at which to perform the query. + reuse_block: Whether to reuse the last-used block hash when retrieving info. Returns: - The calculated stake fee as a Balance object + A list of netuids where the neuron is a member. """ - check_balance_amount(amount) - return await self.get_stake_operations_fee( - amount=amount, netuid=netuid, block=block + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + result = await self.substrate.query_map( + module="SubtensorModule", + storage_function="IsNetworkMember", + params=[hotkey_ss58], + block_hash=block_hash, + reuse_block_hash=reuse_block, ) + netuids = [] + if result.records: + async for record in result: + if record[1].value: + netuids.append(record[0]) + return netuids - async def get_mechanism_emission_split( + async def get_neuron_certificate( self, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[list[int]]: - """Returns the emission percentages allocated to each subnet mechanism. + ) -> Optional[Certificate]: + """ + Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a specified + subnet (netuid) of the Bittensor network. Parameters: + hotkey_ss58: The hotkey to query. netuid: The unique identifier of the subnet. block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + 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: - A list of integers representing the percentage of emission allocated to each subnet mechanism (rounded to - whole numbers). Returns None if emission is evenly split or if the data is unavailable. + the certificate of the neuron if found, ``None`` otherwise. + + This function is used for certificate discovery for setting up mutual tls communication between neurons. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.substrate.query( - module="SubtensorModule", - storage_function="MechanismEmissionSplit", - params=[netuid], - block_hash=block_hash, + certificate = cast( + Union[str, dict], + await self.query_module( + module="SubtensorModule", + name="NeuronCertificates", + block_hash=block_hash, + reuse_block=reuse_block, + params=[netuid, hotkey_ss58], + ), ) - if result is None or not hasattr(result, "value"): - return None + try: + if certificate: + return Certificate(certificate) - return [round(i / sum(result.value) * 100) for i in result.value] + except AttributeError: + return None + return None - async def get_mechanism_count( + async def get_neuron_for_pubkey_and_subnet( self, + hotkey_ss58: str, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> int: - """Retrieves the number of mechanisms for the given subnet. + ) -> "NeuronInfo": + """ + Retrieves information about a neuron based on its public key (hotkey SS58 address) and the specific subnet UID + (netuid). This function provides detailed neuron information for a particular subnet within the Bittensor + network. Parameters: - netuid: Subnet identifier. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + netuid: The unique identifier of the subnet. block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the stake 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. + block_hash: The blockchain block number at which to perform the query. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - The number of mechanisms for the given subnet. + Detailed information about the neuron if found, ``None`` otherwise. + + This function is crucial for accessing specific neuron data and understanding its status, stake, and other + attributes within a particular subnet of the Bittensor ecosystem. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - query = await self.substrate.query( + uid_query = await self.substrate.query( module="SubtensorModule", - storage_function="MechanismCountCurrent", - params=[netuid], + storage_function="Uids", + params=[netuid, hotkey_ss58], block_hash=block_hash, + reuse_block_hash=reuse_block, ) - return getattr(query, "value", 1) + if (uid := getattr(uid_query, "value", None)) is None: + return NeuronInfo.get_null_neuron() + else: + return await self.neuron_for_uid( + uid=uid, + netuid=netuid, + block_hash=block_hash, + reuse_block=reuse_block, + ) - async def get_subnet_info( + async def get_next_epoch_start_block( self, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional["SubnetInfo"]: + ) -> Optional[int]: """ - Retrieves detailed information about subnet within the Bittensor network. - This function provides comprehensive data on subnet, including its characteristics and operational parameters. + Calculates the first block number of the next epoch for the given subnet. + + If ``block`` is not provided, the current chain block will be used. Epochs are determined based on the subnet's + tempo (i.e., blocks per epoch). The result is the block number at which the next epoch will begin. Parameters: netuid: The unique identifier of the subnet. - block: The block number for which the children are to be retrieved. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block: The reference block to calculate from. If None, uses the current chain block height. + block_hash: The blockchain block number at which to perform the query. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet. + int: The block number at which the next epoch will start. - Gaining insights into the subnet's details assists in understanding the network's composition, the roles of - different subnets, and their unique features. + Notes: + See also: """ - result = await self.query_runtime_api( - runtime_api="SubnetInfoRuntimeApi", - method="get_subnet_info_v2", - params=[netuid], - block=block, - block_hash=block_hash, - reuse_block=reuse_block, + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + blocks_since_last_step = await self.blocks_since_last_step( + netuid=netuid, block=block, block_hash=block_hash, reuse_block=reuse_block + ) + tempo = await self.tempo( + netuid=netuid, block=block, block_hash=block_hash, reuse_block=reuse_block ) - if not result: - return None - return SubnetInfo.from_dict(result) - async def get_subnet_price( + block = block or await self.substrate.get_block_number(block_hash=block_hash) + if block and blocks_since_last_step is not None and tempo: + return block - blocks_since_last_step + tempo + 1 + return None + + async def get_owned_hotkeys( self, - netuid: int, + coldkey_ss58: str, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Balance: - """Gets the current Alpha price in TAO for all subnets. + ) -> list[str]: + """ + Retrieves all hotkeys owned by a specific coldkey address. Parameters: - netuid: The unique identifier of the subnet. + coldkey_ss58: The SS58 address of the coldkey to query. block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the stake 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. + block_hash: The hash of the blockchain block number for the query. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - The current Alpha price in TAO units for the specified subnet. + A list of hotkey SS58 addresses owned by the coldkey. """ - # SN0 price is always 1 TAO - if netuid == 0: - return Balance.from_tao(1) - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - call = await self.substrate.runtime_call( - api="SwapRuntimeApi", - method="current_alpha_price", - params=[netuid], + owned_hotkeys = await self.substrate.query( + module="SubtensorModule", + storage_function="OwnedHotkeys", + params=[coldkey_ss58], block_hash=block_hash, + reuse_block_hash=reuse_block, ) - price_rao = call.value - return Balance.from_rao(price_rao) - async def get_subnet_prices( + return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []] + + async def get_parents( self, + hotkey_ss58: str, + netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> dict[int, Balance]: - """Gets the current Alpha price in TAO for a specified subnet. + ) -> list[tuple[float, str]]: + """This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys + storage function to get the children and formats them before returning as a tuple. Parameters: - block: The block number for which the children are to be retrieved. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + hotkey_ss58: The child hotkey SS58. + netuid: The netuid value. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. Returns: - dict: - - subnet unique ID - - The current Alpha price in TAO units for the specified subnet. + A list of formatted parents [(proportion, parent)] """ - block_hash = await self.determine_block_hash( - block=block, block_hash=block_hash, reuse_block=reuse_block - ) - - current_sqrt_prices = await self.substrate.query_map( - module="Swap", - storage_function="AlphaSqrtPrice", + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + parents = await self.substrate.query( + module="SubtensorModule", + storage_function="ParentKeys", + params=[hotkey_ss58, netuid], block_hash=block_hash, - page_size=129, # total number of subnets + reuse_block_hash=reuse_block, ) + if parents: + formatted_parents = [] + for proportion, parent in parents.value: + # Convert U64 to int + formatted_child = decode_account_id(parent[0]) + normalized_proportion = u64_normalized_float(proportion) + formatted_parents.append((normalized_proportion, formatted_child)) + return formatted_parents - prices = {} - async for id_, current_sqrt_price in current_sqrt_prices: - current_sqrt_price = fixed_to_float(current_sqrt_price) - current_price = current_sqrt_price * current_sqrt_price - current_price_in_tao = Balance.from_rao(int(current_price * 1e9)) - prices.update({id_: current_price_in_tao}) + return [] - # SN0 price is always 1 TAO - prices.update({0: Balance.from_tao(1)}) - return prices + async def get_revealed_commitment( + self, + netuid: int, + uid: int, + block: Optional[int] = None, + ) -> Optional[tuple[tuple[int, str], ...]]: + """Returns uid related revealed commitment for a given netuid. - async def get_timelocked_weight_commits( + Parameters: + netuid: The unique identifier of the subnetwork. + uid: The neuron uid to retrieve the commitment from. + block: The block number to retrieve the commitment from. + + Returns: + A tuple of reveal block and commitment message. + + Example of result: + ( (12, "Alice message 1"), (152, "Alice message 2") ) + ( (12, "Bob message 1"), (147, "Bob message 2") ) + """ + try: + meta_info = await self.get_metagraph_info(netuid, block=block) + if meta_info: + hotkey_ss58 = meta_info.hotkeys[uid] + else: + raise ValueError(f"Subnet with netuid {netuid} does not exist.") + except IndexError: + raise ValueError(f"Subnet {netuid} does not have a neuron with uid {uid}.") + + return await self.get_revealed_commitment_by_hotkey( + netuid=netuid, hotkey_ss58=hotkey_ss58, block=block + ) + + async def get_revealed_commitment_by_hotkey( self, netuid: int, - mechid: int = 0, + hotkey_ss58: Optional[str] = None, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> list[tuple[str, int, str, int]]: - """ - Retrieves CRv4 weight commit information for a specific subnet. + ) -> Optional[tuple[tuple[int, str], ...]]: + """Retrieves hotkey related revealed commitment for a given subnet. Parameters: - netuid: Subnet identifier. - mechid: Subnet mechanism identifier. - block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the stake from. Do not specify if using block or reuse_block. + netuid: The unique identifier of the subnetwork. + hotkey_ss58: The ss58 address of the committee member. + block: The block number to query. Do not specify if using block_hash or reuse_block. + block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. + reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. + + Returns: + A tuple of reveal block and commitment message. + """ + if not is_valid_ss58_address(address=hotkey_ss58): + raise ValueError(f"Invalid ss58 address {hotkey_ss58} provided.") + + query = await self.query_module( + module="Commitments", + name="RevealedCommitments", + params=[netuid, hotkey_ss58], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + if query is None: + return None + return tuple(decode_revealed_commitment(pair) for pair in query) + + async def get_stake( + self, + coldkey_ss58: str, + hotkey_ss58: str, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Balance: + """ + Returns the stake under a coldkey - hotkey pairing. + + Parameters: + hotkey_ss58: The SS58 address of the hotkey. + coldkey_ss58: The SS58 address of the coldkey. + netuid: The subnet ID. + block: The block number at which to query the stake information. + block_hash: The hash of the block to retrieve the stake 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: - A list of commit details, where each item contains: - - ss58_address: The address of the committer. - - commit_block: The block number when the commitment was made. - - commit_message: The commit message. - - reveal_round: The round when the commitment was revealed. - - The list may be empty if there are no commits found. + Balance: The stake under the coldkey - hotkey pairing. """ - storage_index = get_mechid_storage_index(netuid, mechid) - block_hash = await self.determine_block_hash( - block=block, block_hash=block_hash, reuse_block=reuse_block + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + + alpha_shares = await self.query_subtensor( + name="Alpha", + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + params=[hotkey_ss58, coldkey_ss58, netuid], ) - result = await self.substrate.query_map( - module="SubtensorModule", - storage_function="TimelockedWeightCommits", - params=[storage_index], + 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], ) - commits = result.records[0][1] if result.records else [] - return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] + hotkey_alpha: int = getattr(hotkey_alpha_result, "value", 0) + alpha_shares_as_float = fixed_to_float(alpha_shares) + hotkey_shares_as_float = fixed_to_float(hotkey_shares) + + if hotkey_shares_as_float == 0: + return Balance.from_rao(0).set_unit(netuid=netuid) + + stake = alpha_shares_as_float / hotkey_shares_as_float * hotkey_alpha + + return Balance.from_rao(int(stake)).set_unit(netuid=netuid) # TODO: update related with fee calculation - async def get_unstake_fee( + async def get_stake_add_fee( self, amount: Balance, netuid: int, block: Optional[int] = None, ) -> Balance: """ - Calculates the fee for unstaking from a hotkey. + Calculates the fee for adding new stake to a hotkey. Parameters: - amount: Amount of stake to unstake in TAO + amount: Amount of stake to add in TAO netuid: Netuid of subnet block: Block number at which to perform the calculation @@ -3140,127 +3004,326 @@ async def get_stake_weight( block_hash = await self.determine_block_hash( block=block, block_hash=block_hash, reuse_block=reuse_block ) - result = await self.substrate.query( - module="SubtensorModule", - storage_function="StakeWeight", - params=[netuid], + result = await self.substrate.query( + module="SubtensorModule", + storage_function="StakeWeight", + params=[netuid], + block_hash=block_hash, + ) + return [u16_normalized_float(w) for w in result] + + async def get_subnet_burn_cost( + self, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional[Balance]: + """ + Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the + amount of Tao that needs to be locked or burned to establish a new + + Parameters: + block: The blockchain block number for the query. + block_hash: The blockchain block_hash of the block id. + reuse_block: Whether to reuse the last-used block hash. + + Returns: + int: The burn cost for subnet registration. + + The subnet burn cost is an important economic parameter, reflecting the network's mechanisms for controlling + the proliferation of subnets and ensuring their commitment to the network's long-term viability. + """ + lock_cost = await self.query_runtime_api( + runtime_api="SubnetRegistrationRuntimeApi", + method="get_network_registration_cost", + params=[], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + if lock_cost is not None: + return Balance.from_rao(lock_cost) + else: + return lock_cost + + async def get_subnet_hyperparameters( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional["SubnetHyperparameters"]: + """ + Retrieves the hyperparameters for a specific subnet within the Bittensor network. These hyperparameters define + the operational settings and rules governing the subnet's behavior. + + Parameters: + netuid: The network UID of the subnet to query. + block: The blockchain block number for the query. + block_hash: The hash of the blockchain block number for the query. + reuse_block: Whether to reuse the last-used blockchain hash. + + Returns: + The subnet's hyperparameters, or ``None`` if not available. + + Understanding the hyperparameters is crucial for comprehending how subnets are configured and managed, and how + they interact with the network's consensus and incentive mechanisms. + """ + result = await self.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_hyperparams_v2", + params=[netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + + if not result: + return None + + return SubnetHyperparameters.from_dict(result) + + async def get_subnet_info( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional["SubnetInfo"]: + """ + Retrieves detailed information about subnet within the Bittensor network. + This function provides comprehensive data on subnet, including its characteristics and operational parameters. + + Parameters: + netuid: The unique identifier of the subnet. + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. + + Returns: + SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet. + + Gaining insights into the subnet's details assists in understanding the network's composition, the roles of + different subnets, and their unique features. + """ + result = await self.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_info_v2", + params=[netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + if not result: + return None + return SubnetInfo.from_dict(result) + + async def get_subnet_owner_hotkey( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Optional[str]: + """ + Retrieves the hotkey of the subnet owner for a given network UID. + + This function queries the subtensor network to fetch the hotkey of the owner of a subnet specified by its + netuid. If no data is found or the query fails, the function returns None. + + Parameters: + netuid: The network UID of the subnet to fetch the owner's hotkey for. + block: The blockchain block number for the query. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. + + Returns: + The hotkey of the subnet owner if available; None otherwise. + """ + return await self.query_subtensor( + name="SubnetOwnerHotkey", + params=[netuid], + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + + async def get_subnet_price( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> Balance: + """Gets the current Alpha price in TAO for all subnets. + + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + block_hash: The hash of the block to retrieve the stake 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: + The current Alpha price in TAO units for the specified subnet. + """ + # SN0 price is always 1 TAO + if netuid == 0: + return Balance.from_tao(1) + + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + call = await self.substrate.runtime_call( + api="SwapRuntimeApi", + method="current_alpha_price", + params=[netuid], + block_hash=block_hash, + ) + price_rao = call.value + return Balance.from_rao(price_rao) + + async def get_subnet_prices( + self, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> dict[int, Balance]: + """Gets the current Alpha price in TAO for a specified subnet. + + Parameters: + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. + + Returns: + dict: + - subnet unique ID + - The current Alpha price in TAO units for the specified subnet. + """ + block_hash = await self.determine_block_hash( + block=block, block_hash=block_hash, reuse_block=reuse_block + ) + + current_sqrt_prices = await self.substrate.query_map( + module="Swap", + storage_function="AlphaSqrtPrice", block_hash=block_hash, + page_size=129, # total number of subnets ) - return [u16_normalized_float(w) for w in result] - async def get_subnet_burn_cost( + prices = {} + async for id_, current_sqrt_price in current_sqrt_prices: + current_sqrt_price = fixed_to_float(current_sqrt_price) + current_price = current_sqrt_price * current_sqrt_price + current_price_in_tao = Balance.from_rao(int(current_price * 1e9)) + prices.update({id_: current_price_in_tao}) + + # SN0 price is always 1 TAO + prices.update({0: Balance.from_tao(1)}) + return prices + + async def get_subnet_reveal_period_epochs( + self, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None + ) -> int: + """Retrieve the SubnetRevealPeriodEpochs hyperparameter.""" + block_hash = await self.determine_block_hash(block, block_hash) + return await self.get_hyperparameter( + param_name="RevealPeriodEpochs", block_hash=block_hash, netuid=netuid + ) + + async def get_subnet_validator_permits( self, + netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional[Balance]: + ) -> Optional[list[bool]]: """ - Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the - amount of Tao that needs to be locked or burned to establish a new + Retrieves the list of validator permits for a given subnet as boolean values. Parameters: + netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. - block_hash: The blockchain block_hash of the block id. - reuse_block: Whether to reuse the last-used block hash. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - int: The burn cost for subnet registration. - - The subnet burn cost is an important economic parameter, reflecting the network's mechanisms for controlling - the proliferation of subnets and ensuring their commitment to the network's long-term viability. + A list of boolean values representing validator permits, or None if not available. """ - lock_cost = await self.query_runtime_api( - runtime_api="SubnetRegistrationRuntimeApi", - method="get_network_registration_cost", - params=[], + query = await self.query_subtensor( + name="ValidatorPermit", + params=[netuid], block=block, block_hash=block_hash, reuse_block=reuse_block, ) - if lock_cost is not None: - return Balance.from_rao(lock_cost) - else: - return lock_cost + return query.value if query is not None and hasattr(query, "value") else query - async def get_subnet_hyperparameters( + async def get_timelocked_weight_commits( self, netuid: int, + mechid: int = 0, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> Optional["SubnetHyperparameters"]: + ) -> list[tuple[str, int, str, int]]: """ - Retrieves the hyperparameters for a specific subnet within the Bittensor network. These hyperparameters define - the operational settings and rules governing the subnet's behavior. + Retrieves CRv4 weight commit information for a specific subnet. Parameters: - netuid: The network UID of the subnet to query. + netuid: Subnet identifier. + mechid: Subnet mechanism identifier. block: The blockchain block number for the query. - block_hash: The hash of the blockchain block number for the query. - reuse_block: Whether to reuse the last-used blockchain hash. + block_hash: The hash of the block to retrieve the stake 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: - The subnet's hyperparameters, or ``None`` if not available. + A list of commit details, where each item contains: + - ss58_address: The address of the committer. + - commit_block: The block number when the commitment was made. + - commit_message: The commit message. + - reveal_round: The round when the commitment was revealed. - Understanding the hyperparameters is crucial for comprehending how subnets are configured and managed, and how - they interact with the network's consensus and incentive mechanisms. + The list may be empty if there are no commits found. """ - result = await self.query_runtime_api( - runtime_api="SubnetInfoRuntimeApi", - method="get_subnet_hyperparams_v2", - params=[netuid], - block=block, + storage_index = get_mechid_storage_index(netuid, mechid) + block_hash = await self.determine_block_hash( + block=block, block_hash=block_hash, reuse_block=reuse_block + ) + result = await self.substrate.query_map( + module="SubtensorModule", + storage_function="TimelockedWeightCommits", + params=[storage_index], block_hash=block_hash, - reuse_block=reuse_block, ) - if not result: - return None - - return SubnetHyperparameters.from_dict(result) - - async def get_subnet_reveal_period_epochs( - self, netuid: int, block: Optional[int] = None, block_hash: Optional[str] = None - ) -> int: - """Retrieve the SubnetRevealPeriodEpochs hyperparameter.""" - block_hash = await self.determine_block_hash(block, block_hash) - return await self.get_hyperparameter( - param_name="RevealPeriodEpochs", block_hash=block_hash, netuid=netuid - ) + commits = result.records[0][1] if result.records else [] + return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] - async def get_all_subnets_netuid( + async def get_timestamp( self, block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> list[int]: + ) -> datetime: """ - Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network. + Retrieves the datetime timestamp for a given block. Parameters: block: The blockchain block number for the query. - block_hash: The hash of the block to retrieve the subnet unique identifiers from. - reuse_block: Whether to reuse the last-used block hash. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - A list of subnet netuids. - - This function provides a comprehensive view of the subnets within the Bittensor network, offering insights into - its diversity and scale. + datetime object for the timestamp of the block. """ - block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - result = await self.substrate.query_map( - module="SubtensorModule", - storage_function="NetworksAdded", + res = await self.query_module( + "Timestamp", + "Now", + block=block, block_hash=block_hash, - reuse_block_hash=reuse_block, + reuse_block=reuse_block, ) - subnets = [] - if result.records: - async for netuid, exists in result: - if exists: - subnets.append(netuid) - return subnets + unix = res.value + return datetime.fromtimestamp(unix / 1000, tz=timezone.utc) async def get_total_subnets( self, @@ -3337,6 +3400,29 @@ async def get_transfer_fee( return Balance.from_rao(payment_info["partial_fee"]) + # TODO: update related with fee calculation + async def get_unstake_fee( + self, + amount: Balance, + netuid: int, + block: Optional[int] = None, + ) -> Balance: + """ + Calculates the fee for unstaking from a hotkey. + + Parameters: + amount: Amount of stake to unstake in TAO + netuid: Netuid of subnet + block: Block number at which to perform the calculation + + Returns: + The calculated stake fee as a Balance object + """ + check_balance_amount(amount) + return await self.get_stake_operations_fee( + amount=amount, netuid=netuid, block=block + ) + async def get_vote_data( self, proposal_hash: str, @@ -4267,91 +4353,6 @@ async def weights_rate_limit( ) return None if call is None else int(call) - async def get_timestamp( - self, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> datetime: - """ - Retrieves the datetime timestamp for a given block. - - Parameters: - block: The blockchain block number for the query. - block_hash: The blockchain block_hash representation of the block id. - reuse_block: Whether to reuse the last-used blockchain block hash. - - Returns: - datetime object for the timestamp of the block. - """ - res = await self.query_module( - "Timestamp", - "Now", - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - ) - unix = res.value - return datetime.fromtimestamp(unix / 1000, tz=timezone.utc) - - async def get_subnet_owner_hotkey( - self, - netuid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> Optional[str]: - """ - Retrieves the hotkey of the subnet owner for a given network UID. - - This function queries the subtensor network to fetch the hotkey of the owner of a subnet specified by its - netuid. If no data is found or the query fails, the function returns None. - - Parameters: - netuid: The network UID of the subnet to fetch the owner's hotkey for. - block: The blockchain block number for the query. - block_hash: The blockchain block_hash representation of the block id. - reuse_block: Whether to reuse the last-used blockchain block hash. - - Returns: - The hotkey of the subnet owner if available; None otherwise. - """ - return await self.query_subtensor( - name="SubnetOwnerHotkey", - params=[netuid], - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - ) - - async def get_subnet_validator_permits( - self, - netuid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ) -> Optional[list[bool]]: - """ - Retrieves the list of validator permits for a given subnet as boolean values. - - Parameters: - netuid: The unique identifier of the subnetwork. - block: The blockchain block number for the query. - block_hash: The blockchain block_hash representation of the block id. - reuse_block: Whether to reuse the last-used blockchain block hash. - - Returns: - A list of boolean values representing validator permits, or None if not available. - """ - query = await self.query_subtensor( - name="ValidatorPermit", - params=[netuid], - block=block, - block_hash=block_hash, - reuse_block=reuse_block, - ) - return query.value if query is not None and hasattr(query, "value") else query - # Extrinsics helper ================================================================================================ async def sign_and_send_extrinsic( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index cfb2da2cdb..17ddd0006f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -187,15 +187,21 @@ def __init__( f"Connected to {self.network} network and {self.chain_endpoint}." ) + def close(self): + """Closes the websocket connection.""" + self.substrate.close() + def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close() - def close(self): - """Closes the websocket connection.""" - self.substrate.close() + # Helpers ========================================================================================================== + + @lru_cache(maxsize=128) + def _get_block_hash(self, block_id: int): + return self.substrate.get_block_hash(block_id) def _get_substrate( self, @@ -237,6 +243,64 @@ def _get_substrate( _mock=_mock, ) + def determine_block_hash(self, block: Optional[int]) -> Optional[str]: + if block is None: + return None + else: + return self.get_block_hash(block=block) + + def encode_params( + self, + call_definition: dict[str, list["ParamWithTypes"]], + params: Union[list[Any], dict[str, Any]], + ) -> str: + """Returns a hex encoded string of the params using their types.""" + param_data = scalecodec.ScaleBytes(b"") + + for i, param in enumerate(call_definition["params"]): + scale_obj = self.substrate.create_scale_object(param["type"]) + if isinstance(params, list): + param_data += scale_obj.encode(params[i]) + else: + if param["name"] not in params: + raise ValueError(f"Missing param {param['name']} in params dict.") + + param_data += scale_obj.encode(params[param["name"]]) + + return param_data.to_hex() + + def get_hyperparameter( + self, param_name: str, netuid: int, block: Optional[int] = None + ) -> Optional[Any]: + """ + Retrieves a specified hyperparameter for a specific subnet. + + Parameters: + param_name: The name of the hyperparameter to retrieve. + netuid: The unique identifier of the subnet. + block: the block number at which to retrieve the hyperparameter. + + Returns: + The value of the specified hyperparameter if the subnet exists, or None + """ + block_hash = self.determine_block_hash(block) + if not self.subnet_exists(netuid, block=block): + logging.error(f"subnet {netuid} does not exist") + return None + + result = self.substrate.query( + module="SubtensorModule", + storage_function=param_name, + params=[netuid], + block_hash=block_hash, + ) + + return getattr(result, "value", result) + + @property + def block(self) -> int: + return self.get_current_block() + # Subtensor queries =========================================================================================== def query_constant( @@ -437,10 +501,6 @@ def state_call( # Common subtensor calls =========================================================================================== - @property - def block(self) -> int: - return self.get_current_block() - def all_subnets(self, block: Optional[int] = None) -> Optional[list["DynamicInfo"]]: """ Retrieves the subnet information for all subnets in the network. @@ -661,6 +721,151 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> list["SubnetInfo" return SubnetInfo.list_from_dicts(result) + def get_all_commitments( + self, netuid: int, block: Optional[int] = None + ) -> dict[str, str]: + """Retrieves raw commitment metadata from a given subnet. + + This method retrieves all commitment data for all neurons in a specific subnet. This is useful for analyzing the + commit-reveal patterns across an entire subnet. + + Parameters: + netuid: The unique subnet identifier. + block: The blockchain block number for the query. + + Returns: + The raw on-chain commitment metadata (as SCALE-decoded object or raw bytes) from specific subnet. + """ + query = self.query_map( + module="Commitments", + name="CommitmentOf", + params=[netuid], + block=block, + ) + result = {} + for id_, value in query: + try: + result[decode_account_id(id_[0])] = decode_metadata(value) + except Exception as error: + logging.error( + f"Error decoding [red]{id_}[/red] and [red]{value}[/red]: {error}" + ) + return result + + def get_all_metagraphs_info( + self, + block: Optional[int] = None, + all_mechanisms: bool = False, + ) -> Optional[list[MetagraphInfo]]: + """ + Retrieves a list of MetagraphInfo objects for all subnets + + Parameters: + block: The blockchain block number for the query. + all_mechanisms: If True then returns all mechanisms, otherwise only those with index 0 for all subnets. + + Returns: + List of MetagraphInfo objects for all existing subnets. + + Notes: + See also: See + """ + block_hash = self.determine_block_hash(block) + method = "get_all_mechagraphs" if all_mechanisms else "get_all_metagraphs" + query = self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method=method, + block_hash=block_hash, + ) + if query is None or not hasattr(query, "value"): + return None + + return MetagraphInfo.list_from_dicts(query.value) + + def get_all_neuron_certificates( + self, netuid: int, block: Optional[int] = None + ) -> dict[str, Certificate]: + """ + Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network. + + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + + Returns: + {ss58: Certificate} for the key/Certificate pairs on the subnet + + This function is used for certificate discovery for setting up mutual tls communication between neurons. + """ + query_certificates = self.query_map( + module="SubtensorModule", + name="NeuronCertificates", + params=[netuid], + block=block, + ) + output = {} + for key, item in query_certificates: + output[decode_account_id(key)] = Certificate(item.value) + return output + + def get_all_revealed_commitments( + self, netuid: int, block: Optional[int] = None + ) -> dict[str, tuple[tuple[int, str], ...]]: + """Retrieves all revealed commitments for a given subnet. + + Parameters: + netuid: The unique identifier of the subnetwork. + block: The block number to retrieve the commitment from. + + Returns: + result: A dictionary of all revealed commitments in view {ss58_address: (reveal block, commitment message)}. + + Example of result: + { + "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY": ( (12, "Alice message 1"), (152, "Alice message 2") ), + "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty": ( (12, "Bob message 1"), (147, "Bob message 2") ), + } + """ + query = self.query_map( + module="Commitments", + name="RevealedCommitments", + params=[netuid], + block=block, + ) + + result = {} + for pair in query: + hotkey_ss58_address, commitment_message = ( + decode_revealed_commitment_with_hotkey(pair) + ) + result[hotkey_ss58_address] = commitment_message + return result + + def get_all_subnets_netuid(self, block: Optional[int] = None) -> UIDs: + """ + Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network. + + Parameters: + block: The blockchain block number for the query. + + Returns: + A list of subnet netuids. + + This function provides a comprehensive view of the subnets within the Bittensor network, + offering insights into its diversity and scale. + """ + result = self.substrate.query_map( + module="SubtensorModule", + storage_function="NetworksAdded", + block_hash=self.determine_block_hash(block), + ) + subnets = [] + if result.records: + for netuid, exists in result: + if exists: + subnets.append(netuid) + return subnets + def get_auto_stakes( self, coldkey_ss58: str, @@ -744,27 +949,6 @@ def get_balances( results.update({item[0].params[0]: Balance(value["data"]["free"])}) return results - def get_commitment_metadata( - self, netuid: int, hotkey_ss58: str, block: Optional[int] = None - ) -> Union[str, dict]: - """Fetches raw commitment metadata from specific subnet for given hotkey. - - Parameters: - netuid: The unique subnet identifier. - hotkey_ss58: The hotkey ss58 address. - block: The blockchain block number for the query. - - Returns: - The raw commitment metadata from specific subnet for given hotkey. - """ - commit_data = self.substrate.query( - module="Commitments", - storage_function="CommitmentOf", - params=[netuid, hotkey_ss58], - block_hash=self.determine_block_hash(block), - ) - return commit_data - def get_current_block(self) -> int: """ Returns the current block number on the Bittensor blockchain. This function provides the latest block number, @@ -779,10 +963,6 @@ def get_current_block(self) -> int: """ return self.substrate.get_block_number(None) - @lru_cache(maxsize=128) - def _get_block_hash(self, block_id: int): - return self.substrate.get_block_hash(block_id) - def get_block_hash(self, block: Optional[int] = None) -> str: """ Retrieves the hash of a specific block on the Bittensor blockchain. The block hash is a unique identifier @@ -850,98 +1030,12 @@ def get_block_info( ) return None - def determine_block_hash(self, block: Optional[int]) -> Optional[str]: - if block is None: - return None - else: - return self.get_block_hash(block=block) - - def encode_params( - self, - call_definition: dict[str, list["ParamWithTypes"]], - params: Union[list[Any], dict[str, Any]], - ) -> str: - """Returns a hex encoded string of the params using their types.""" - param_data = scalecodec.ScaleBytes(b"") - - for i, param in enumerate(call_definition["params"]): - scale_obj = self.substrate.create_scale_object(param["type"]) - if isinstance(params, list): - param_data += scale_obj.encode(params[i]) - else: - if param["name"] not in params: - raise ValueError(f"Missing param {param['name']} in params dict.") - - param_data += scale_obj.encode(params[param["name"]]) - - return param_data.to_hex() - - def get_hyperparameter( - self, param_name: str, netuid: int, block: Optional[int] = None - ) -> Optional[Any]: - """ - Retrieves a specified hyperparameter for a specific subnet. - - Parameters: - param_name: The name of the hyperparameter to retrieve. - netuid: The unique identifier of the subnet. - block: the block number at which to retrieve the hyperparameter. - - Returns: - The value of the specified hyperparameter if the subnet exists, or None - """ - block_hash = self.determine_block_hash(block) - if not self.subnet_exists(netuid, block=block): - logging.error(f"subnet {netuid} does not exist") - return None - - result = self.substrate.query( - module="SubtensorModule", - storage_function=param_name, - params=[netuid], - block_hash=block_hash, - ) - - return getattr(result, "value", result) - - def get_parents( - self, hotkey_ss58: str, netuid: int, block: Optional[int] = None - ) -> list[tuple[float, str]]: - """ - This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys - storage function to get the children and formats them before returning as a tuple. - - Parameters: - hotkey_ss58: The child hotkey SS58. - netuid: The netuid. - block: The block number for which the children are to be retrieved. - - Returns: - A list of formatted parents [(proportion, parent)] - """ - parents = self.substrate.query( - module="SubtensorModule", - storage_function="ParentKeys", - params=[hotkey_ss58, netuid], - block_hash=self.determine_block_hash(block), - ) - if parents: - formatted_parents = [] - for proportion, parent in parents.value: - # Convert U64 to int - formatted_child = decode_account_id(parent[0]) - normalized_proportion = u64_normalized_float(proportion) - formatted_parents.append((normalized_proportion, formatted_child)) - return formatted_parents - - return [] - - def get_children( - self, hotkey_ss58: str, netuid: int, block: Optional[int] = None - ) -> tuple[bool, list[tuple[float, str]], str]: - """ - This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys - storage function to get the children and formats them before returning as a tuple. + def get_children( + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None + ) -> tuple[bool, list[tuple[float, str]], str]: + """ + This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys + storage function to get the children and formats them before returning as a tuple. Parameters: hotkey_ss58: The hotkey value. @@ -1041,280 +1135,122 @@ def get_commitment(self, netuid: int, uid: int, block: Optional[int] = None) -> logging.error(error) return "" - def get_last_bonds_reset( + def get_commitment_metadata( self, netuid: int, hotkey_ss58: str, block: Optional[int] = None - ) -> bytes: - """ - Retrieves the last bonds reset triggered at commitment from given subnet for a specific hotkey. + ) -> Union[str, dict]: + """Fetches raw commitment metadata from specific subnet for given hotkey. Parameters: - netuid: The network uid to fetch from. - hotkey_ss58: The hotkey of the neuron for which to fetch the last bonds reset. - block: The block number to query. + netuid: The unique subnet identifier. + hotkey_ss58: The hotkey ss58 address. + block: The blockchain block number for the query. Returns: - bytes: The last bonds reset data from given subnet for the specified hotkey. + The raw commitment metadata from specific subnet for given hotkey. """ - return self.substrate.query( + commit_data = self.substrate.query( module="Commitments", - storage_function="LastBondsReset", + storage_function="CommitmentOf", params=[netuid, hotkey_ss58], block_hash=self.determine_block_hash(block), ) + return commit_data - def get_last_commitment_bonds_reset_block( - self, - netuid: int, - uid: int, - block: Optional[int] = None, - ) -> Optional[int]: + def get_delegate_by_hotkey( + self, hotkey_ss58: str, block: Optional[int] = None + ) -> Optional["DelegateInfo"]: """ - Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. + Retrieves detailed information about a delegate neuron based on its hotkey. This function provides a + comprehensive view of the delegate's status, including its stakes, nominators, and reward distribution. Parameters: - netuid: The unique identifier of the subnetwork. - uid: The unique identifier of the neuron. - block: The block number to query. + hotkey_ss58: The ``SS58`` address of the delegate's hotkey. + block: The blockchain block number for the query. Returns: - The block number when the bonds were last reset, or None if not found. + Detailed information about the delegate neuron, ``None`` if not found. + + This function is essential for understanding the roles and influence of delegate neurons within the Bittensor + network's consensus and governance structures. """ - metagraph = self.metagraph(netuid, block=block) - try: - hotkey_ss58 = metagraph.hotkeys[uid] - except IndexError: - logging.error( - "Your uid is not in the hotkeys. Please double-check your UID." - ) - return None - block_data = self.get_last_bonds_reset(netuid, hotkey_ss58, block) - try: - return decode_block(block_data) - except TypeError: + result = self.query_runtime_api( + runtime_api="DelegateInfoRuntimeApi", + method="get_delegate", + params=[hotkey_ss58], + block=block, + ) + + if not result: return None - def get_all_commitments( - self, netuid: int, block: Optional[int] = None - ) -> dict[str, str]: - """Retrieves raw commitment metadata from a given subnet. + return DelegateInfo.from_dict(result) - This method retrieves all commitment data for all neurons in a specific subnet. This is useful for analyzing the - commit-reveal patterns across an entire subnet. + def get_delegate_identities( + self, block: Optional[int] = None + ) -> dict[str, ChainIdentity]: + """ + Fetches delegates identities from the chain. Parameters: - netuid: The unique subnet identifier. block: The blockchain block number for the query. Returns: - The raw on-chain commitment metadata (as SCALE-decoded object or raw bytes) from specific subnet. + Dict {ss58: ChainIdentity, ...} + """ - query = self.query_map( - module="Commitments", - name="CommitmentOf", - params=[netuid], - block=block, + identities = self.substrate.query_map( + module="SubtensorModule", + storage_function="IdentitiesV2", + block_hash=self.determine_block_hash(block), ) - result = {} - for id_, value in query: - try: - result[decode_account_id(id_[0])] = decode_metadata(value) - except Exception as error: - logging.error( - f"Error decoding [red]{id_}[/red] and [red]{value}[/red]: {error}" - ) - return result - def get_revealed_commitment_by_hotkey( - self, - netuid: int, - hotkey_ss58: str, - block: Optional[int] = None, - ) -> Optional[tuple[tuple[int, str], ...]]: - """Retrieves hotkey related revealed commitment for a given subnet. + return { + decode_account_id(ss58_address[0]): ChainIdentity.from_dict( + decode_hex_identity_dict(identity.value), + ) + for ss58_address, identity in identities + } + + def get_delegate_take(self, hotkey_ss58: str, block: Optional[int] = None) -> float: + """ + Retrieves 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. Parameters: - netuid: The unique identifier of the subnetwork. - hotkey_ss58: The ss58 address of the committee member. - block: The block number to retrieve the commitment from. + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + block: The blockchain block number for the query. Returns: - A tuple of reveal block and commitment message. - """ - if not is_valid_ss58_address(address=hotkey_ss58): - raise ValueError(f"Invalid ss58 address {hotkey_ss58} provided.") + float: The delegate take percentage. - query = self.query_module( - module="Commitments", - name="RevealedCommitments", - params=[netuid, hotkey_ss58], + The delegate take is a critical parameter in the network's incentive structure, influencing the distribution of + rewards among neurons and their nominators. + """ + result = self.query_subtensor( + name="Delegates", block=block, + params=[hotkey_ss58], ) - if query is None: - return None - return tuple(decode_revealed_commitment(pair) for pair in query) - def get_revealed_commitment( - self, - netuid: int, - uid: int, - block: Optional[int] = None, - ) -> Optional[tuple[tuple[int, str], ...]]: - """Returns uid related revealed commitment for a given netuid. + return u16_normalized_float(result.value) # type: ignore + + def get_delegated( + self, coldkey_ss58: str, block: Optional[int] = None + ) -> list[DelegatedInfo]: + """ + Retrieves a list of delegates and their associated stakes for a given coldkey. This function identifies the + delegates that a specific account has staked tokens on. Parameters: - netuid: The unique identifier of the subnetwork. - uid: The neuron uid to retrieve the commitment from. - block: The block number to retrieve the commitment from. + coldkey_ss58: The `SS58` address of the account's coldkey. + block: The blockchain block number for the query. Returns: - A tuple of reveal block and commitment message. + A list containing the delegated information for the specified coldkey. - Example of result: - ( (12, "Alice message 1"), (152, "Alice message 2") ) - ( (12, "Bob message 1"), (147, "Bob message 2") ) - """ - try: - meta_info = self.get_metagraph_info(netuid, block=block) - if meta_info: - hotkey_ss58 = meta_info.hotkeys[uid] - else: - raise ValueError(f"Subnet with netuid {netuid} does not exist.") - except IndexError: - raise ValueError(f"Subnet {netuid} does not have a neuron with uid {uid}.") - - return self.get_revealed_commitment_by_hotkey( - netuid=netuid, hotkey_ss58=hotkey_ss58, block=block - ) - - def get_all_revealed_commitments( - self, netuid: int, block: Optional[int] = None - ) -> dict[str, tuple[tuple[int, str], ...]]: - """Retrieves all revealed commitments for a given subnet. - - Parameters: - netuid: The unique identifier of the subnetwork. - block: The block number to retrieve the commitment from. - - Returns: - result: A dictionary of all revealed commitments in view {ss58_address: (reveal block, commitment message)}. - - Example of result: - { - "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY": ( (12, "Alice message 1"), (152, "Alice message 2") ), - "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty": ( (12, "Bob message 1"), (147, "Bob message 2") ), - } - """ - query = self.query_map( - module="Commitments", - name="RevealedCommitments", - params=[netuid], - block=block, - ) - - result = {} - for pair in query: - hotkey_ss58_address, commitment_message = ( - decode_revealed_commitment_with_hotkey(pair) - ) - result[hotkey_ss58_address] = commitment_message - return result - - def get_delegate_by_hotkey( - self, hotkey_ss58: str, block: Optional[int] = None - ) -> Optional["DelegateInfo"]: - """ - Retrieves detailed information about a delegate neuron based on its hotkey. This function provides a - comprehensive view of the delegate's status, including its stakes, nominators, and reward distribution. - - Parameters: - hotkey_ss58: The ``SS58`` address of the delegate's hotkey. - block: The blockchain block number for the query. - - Returns: - Detailed information about the delegate neuron, ``None`` if not found. - - This function is essential for understanding the roles and influence of delegate neurons within the Bittensor - network's consensus and governance structures. - """ - - result = self.query_runtime_api( - runtime_api="DelegateInfoRuntimeApi", - method="get_delegate", - params=[hotkey_ss58], - block=block, - ) - - if not result: - return None - - return DelegateInfo.from_dict(result) - - def get_delegate_identities( - self, block: Optional[int] = None - ) -> dict[str, ChainIdentity]: - """ - Fetches delegates identities from the chain. - - Parameters: - block: The blockchain block number for the query. - - Returns: - Dict {ss58: ChainIdentity, ...} - - """ - identities = self.substrate.query_map( - module="SubtensorModule", - storage_function="IdentitiesV2", - block_hash=self.determine_block_hash(block), - ) - - return { - decode_account_id(ss58_address[0]): ChainIdentity.from_dict( - decode_hex_identity_dict(identity.value), - ) - for ss58_address, identity in identities - } - - def get_delegate_take(self, hotkey_ss58: str, block: Optional[int] = None) -> float: - """ - Retrieves 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. - - Parameters: - hotkey_ss58: The ``SS58`` address of the neuron's hotkey. - block: The blockchain block number for the query. - - Returns: - float: The delegate take percentage. - - The delegate take is a critical parameter in the network's incentive structure, influencing the distribution of - rewards among neurons and their nominators. - """ - result = self.query_subtensor( - name="Delegates", - block=block, - params=[hotkey_ss58], - ) - - return u16_normalized_float(result.value) # type: ignore - - def get_delegated( - self, coldkey_ss58: str, block: Optional[int] = None - ) -> list[DelegatedInfo]: - """ - Retrieves a list of delegates and their associated stakes for a given coldkey. This function identifies the - delegates that a specific account has staked tokens on. - - Parameters: - coldkey_ss58: The `SS58` address of the account's coldkey. - block: The blockchain block number for the query. - - Returns: - A list containing the delegated information for the specified coldkey. - - This function is important for account holders to understand their stake allocations and their involvement in - the network's delegation and consensus mechanisms. + This function is important for account holders to understand their stake allocations and their involvement in + the network's delegation and consensus mechanisms. """ result = self.query_runtime_api( @@ -1403,223 +1339,76 @@ def get_hotkey_owner( hotkey_owner = hk_owner_query if exists else None return hotkey_owner - def get_minimum_required_stake(self) -> Balance: + def get_last_bonds_reset( + self, netuid: int, hotkey_ss58: str, block: Optional[int] = None + ) -> bytes: """ - Returns the minimum required stake for nominators in the Subtensor network. + Retrieves the last bonds reset triggered at commitment from given subnet for a specific hotkey. + + Parameters: + netuid: The network uid to fetch from. + hotkey_ss58: The hotkey of the neuron for which to fetch the last bonds reset. + block: The block number to query. Returns: - The minimum required stake as a Balance object in TAO. + bytes: The last bonds reset data from given subnet for the specified hotkey. """ - result = self.substrate.query( - module="SubtensorModule", storage_function="NominatorMinRequiredStake" + return self.substrate.query( + module="Commitments", + storage_function="LastBondsReset", + params=[netuid, hotkey_ss58], + block_hash=self.determine_block_hash(block), ) - return Balance.from_rao(getattr(result, "value", 0)) - - def get_metagraph_info( + def get_last_commitment_bonds_reset_block( self, netuid: int, - selected_indices: Optional[ - Union[list[SelectiveMetagraphIndex], list[int]] - ] = None, + uid: int, block: Optional[int] = None, - mechid: int = 0, - ) -> Optional[MetagraphInfo]: + ) -> Optional[int]: """ - Retrieves full or partial metagraph information for the specified subnet mechanism (netuid, mechid). + Retrieves the last block number when the bonds reset were triggered by publish_metadata for a specific neuron. Parameters: - netuid: Subnet unique identifier. - mechid: Subnet mechanism unique identifier. - selected_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. - If not provided, all available fields will be returned. - block: The block number at which to query the data. + netuid: The unique identifier of the subnetwork. + uid: The unique identifier of the neuron. + block: The block number to query. Returns: - MetagraphInfo object with the requested subnet mechanism data, None if the subnet mechanism does not exist. - - Example: - # Retrieve all fields from the metagraph from subnet 2 mechanism 0 - meta_info = subtensor.get_metagraph_info(netuid=2) - - # Retrieve all fields from the metagraph from subnet 2 mechanism 1 - meta_info = subtensor.get_metagraph_info(netuid=2, mechid=1) - - # Retrieve selective data from the metagraph from subnet 2 mechanism 0 - partial_meta_info = subtensor.get_metagraph_info( - netuid=2, - selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] - ) - - # Retrieve selective data from the metagraph from subnet 2 mechanism 1 - partial_meta_info = subtensor.get_metagraph_info( - netuid=2, - mechid=1, - selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] - ) - - Notes: - See also: - - - - + The block number when the bonds were last reset, or None if not found. """ - block_hash = self.determine_block_hash(block=block) - - indexes = ( - [ - f.value if isinstance(f, SelectiveMetagraphIndex) else f - for f in selected_indices - ] - if selected_indices is not None - else [f for f in range(len(SelectiveMetagraphIndex))] - ) - query = self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method="get_selective_mechagraph", - params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], - block_hash=block_hash, - ) - if query is None or not hasattr(query, "value") or query.value is None: + metagraph = self.metagraph(netuid, block=block) + try: + hotkey_ss58 = metagraph.hotkeys[uid] + except IndexError: logging.error( - f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." + "Your uid is not in the hotkeys. Please double-check your UID." ) return None + block_data = self.get_last_bonds_reset(netuid, hotkey_ss58, block) + try: + return decode_block(block_data) + except TypeError: + return None - return MetagraphInfo.from_dict(query.value) - - # TODO: update parameters order in SDKv10 - def get_all_metagraphs_info( + def get_liquidity_list( self, + wallet: "Wallet", + netuid: int, block: Optional[int] = None, - all_mechanisms: bool = False, - ) -> Optional[list[MetagraphInfo]]: + ) -> Optional[list[LiquidityPosition]]: """ - Retrieves a list of MetagraphInfo objects for all subnets + Retrieves all liquidity positions for the given wallet on a specified subnet (netuid). + Calculates associated fee rewards based on current global and tick-level fee data. Parameters: + wallet: Wallet instance to fetch positions for. + netuid: Subnet unique id. block: The blockchain block number for the query. - all_mechanisms: If True then returns all mechanisms, otherwise only those with index 0 for all subnets. Returns: - List of MetagraphInfo objects for all existing subnets. - - Notes: - See also: See - """ - block_hash = self.determine_block_hash(block) - method = "get_all_mechagraphs" if all_mechanisms else "get_all_metagraphs" - query = self.substrate.runtime_call( - api="SubnetInfoRuntimeApi", - method=method, - block_hash=block_hash, - ) - if query is None or not hasattr(query, "value"): - return None - - return MetagraphInfo.list_from_dicts(query.value) - - def get_netuids_for_hotkey( - self, hotkey_ss58: str, block: Optional[int] = None - ) -> list[int]: - """ - Retrieves a list of subnet UIDs (netuids) for which a given hotkey is a member. This function identifies the - specific subnets within the Bittensor network where the neuron associated with the hotkey is active. - - Parameters: - hotkey_ss58: The ``SS58`` address of the neuron's hotkey. - block: The blockchain block number for the query. - - Returns: - A list of netuids where the neuron is a member. - """ - result = self.substrate.query_map( - module="SubtensorModule", - storage_function="IsNetworkMember", - params=[hotkey_ss58], - block_hash=self.determine_block_hash(block), - ) - netuids = [] - if result.records: - for record in result: - if record[1].value: - netuids.append(record[0]) - return netuids - - def get_neuron_certificate( - self, hotkey_ss58: str, netuid: int, block: Optional[int] = None - ) -> Optional[Certificate]: - """ - Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a specified - subnet (netuid) of the Bittensor network. - - Parameters: - hotkey_ss58: The hotkey to query. - netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - - Returns: - the certificate of the neuron if found, `None` otherwise. - - This function is used for certificate discovery for setting up mutual tls communication between neurons. - """ - certificate_query = self.query_module( - module="SubtensorModule", - name="NeuronCertificates", - block=block, - params=[netuid, hotkey_ss58], - ) - try: - if certificate_query: - certificate = cast(dict, certificate_query) - return Certificate(certificate) - except AttributeError: - return None - return None - - def get_all_neuron_certificates( - self, netuid: int, block: Optional[int] = None - ) -> dict[str, Certificate]: - """ - Retrieves the TLS certificates for neurons within a specified subnet (netuid) of the Bittensor network. - - Parameters: - netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - - Returns: - {ss58: Certificate} for the key/Certificate pairs on the subnet - - This function is used for certificate discovery for setting up mutual tls communication between neurons. - """ - query_certificates = self.query_map( - module="SubtensorModule", - name="NeuronCertificates", - params=[netuid], - block=block, - ) - output = {} - for key, item in query_certificates: - output[decode_account_id(key)] = Certificate(item.value) - return output - - def get_liquidity_list( - self, - wallet: "Wallet", - netuid: int, - block: Optional[int] = None, - ) -> Optional[list[LiquidityPosition]]: - """ - Retrieves all liquidity positions for the given wallet on a specified subnet (netuid). - Calculates associated fee rewards based on current global and tick-level fee data. - - Parameters: - wallet: Wallet instance to fetch positions for. - netuid: Subnet unique id. - block: The blockchain block number for the query. - - Returns: - List of liquidity positions, or None if subnet does not exist. + List of liquidity positions, or None if subnet does not exist. """ if not self.subnet_exists(netuid=netuid): logging.debug(f"Subnet {netuid} does not exist.") @@ -1776,6 +1565,197 @@ def get_liquidity_list( return positions + def get_mechanism_emission_split( + self, netuid: int, block: Optional[int] = None + ) -> Optional[list[int]]: + """Returns the emission percentages allocated to each subnet mechanism. + + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + + Returns: + A list of integers representing the percentage of emission allocated to each subnet mechanism (rounded to + whole numbers). Returns None if emission is evenly split or if the data is unavailable. + """ + block_hash = self.determine_block_hash(block) + result = self.substrate.query( + module="SubtensorModule", + storage_function="MechanismEmissionSplit", + params=[netuid], + block_hash=block_hash, + ) + if result is None or not hasattr(result, "value"): + return None + + return [round(i / sum(result.value) * 100) for i in result.value] + + def get_mechanism_count( + self, + netuid: int, + block: Optional[int] = None, + ) -> int: + """Retrieves the number of mechanisms for the given subnet. + + Parameters: + netuid: Subnet identifier. + block: The blockchain block number for the query. + + Returns: + The number of mechanisms for the given subnet. + """ + block_hash = self.determine_block_hash(block) + query = self.substrate.query( + module="SubtensorModule", + storage_function="MechanismCountCurrent", + params=[netuid], + block_hash=block_hash, + ) + return query.value if query is not None and hasattr(query, "value") else 1 + + def get_metagraph_info( + self, + netuid: int, + selected_indices: Optional[ + Union[list[SelectiveMetagraphIndex], list[int]] + ] = None, + block: Optional[int] = None, + mechid: int = 0, + ) -> Optional[MetagraphInfo]: + """ + Retrieves full or partial metagraph information for the specified subnet mechanism (netuid, mechid). + + Parameters: + netuid: Subnet unique identifier. + mechid: Subnet mechanism unique identifier. + selected_indices: Optional list of SelectiveMetagraphIndex or int values specifying which fields to retrieve. + If not provided, all available fields will be returned. + block: The block number at which to query the data. + + Returns: + MetagraphInfo object with the requested subnet mechanism data, None if the subnet mechanism does not exist. + + Example: + # Retrieve all fields from the metagraph from subnet 2 mechanism 0 + meta_info = subtensor.get_metagraph_info(netuid=2) + + # Retrieve all fields from the metagraph from subnet 2 mechanism 1 + meta_info = subtensor.get_metagraph_info(netuid=2, mechid=1) + + # Retrieve selective data from the metagraph from subnet 2 mechanism 0 + partial_meta_info = subtensor.get_metagraph_info( + netuid=2, + selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + ) + + # Retrieve selective data from the metagraph from subnet 2 mechanism 1 + partial_meta_info = subtensor.get_metagraph_info( + netuid=2, + mechid=1, + selected_indices=[SelectiveMetagraphIndex.Name, SelectiveMetagraphIndex.OwnerHotkeys] + ) + + Notes: + See also: + - + - + """ + block_hash = self.determine_block_hash(block=block) + + indexes = ( + [ + f.value if isinstance(f, SelectiveMetagraphIndex) else f + for f in selected_indices + ] + if selected_indices is not None + else [f for f in range(len(SelectiveMetagraphIndex))] + ) + + query = self.substrate.runtime_call( + api="SubnetInfoRuntimeApi", + method="get_selective_mechagraph", + params=[netuid, mechid, indexes if 0 in indexes else [0] + indexes], + block_hash=block_hash, + ) + if query is None or not hasattr(query, "value") or query.value is None: + logging.error( + f"Subnet mechanism {netuid}.{mechid if mechid else 0} does not exist." + ) + return None + + return MetagraphInfo.from_dict(query.value) + + def get_minimum_required_stake(self) -> Balance: + """ + Returns the minimum required stake for nominators in the Subtensor network. + + Returns: + The minimum required stake as a Balance object in TAO. + """ + result = self.substrate.query( + module="SubtensorModule", storage_function="NominatorMinRequiredStake" + ) + + return Balance.from_rao(getattr(result, "value", 0)) + + def get_netuids_for_hotkey( + self, hotkey_ss58: str, block: Optional[int] = None + ) -> list[int]: + """ + Retrieves a list of subnet UIDs (netuids) for which a given hotkey is a member. This function identifies the + specific subnets within the Bittensor network where the neuron associated with the hotkey is active. + + Parameters: + hotkey_ss58: The ``SS58`` address of the neuron's hotkey. + block: The blockchain block number for the query. + + Returns: + A list of netuids where the neuron is a member. + """ + result = self.substrate.query_map( + module="SubtensorModule", + storage_function="IsNetworkMember", + params=[hotkey_ss58], + block_hash=self.determine_block_hash(block), + ) + netuids = [] + if result.records: + for record in result: + if record[1].value: + netuids.append(record[0]) + return netuids + + def get_neuron_certificate( + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None + ) -> Optional[Certificate]: + """ + Retrieves the TLS certificate for a specific neuron identified by its unique identifier (UID) within a specified + subnet (netuid) of the Bittensor network. + + Parameters: + hotkey_ss58: The hotkey to query. + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + + Returns: + the certificate of the neuron if found, `None` otherwise. + + This function is used for certificate discovery for setting up mutual tls communication between neurons. + """ + certificate_query = self.query_module( + module="SubtensorModule", + name="NeuronCertificates", + block=block, + params=[netuid, hotkey_ss58], + ) + try: + if certificate_query: + certificate = cast(dict, certificate_query) + return Certificate(certificate) + except AttributeError: + return None + return None + def get_neuron_for_pubkey_and_subnet( self, hotkey_ss58: str, netuid: int, block: Optional[int] = None ) -> Optional["NeuronInfo"]: @@ -1858,7 +1838,101 @@ def get_owned_hotkeys( params=[coldkey_ss58], block_hash=block_hash, ) - return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []] + return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []] + + def get_parents( + self, hotkey_ss58: str, netuid: int, block: Optional[int] = None + ) -> list[tuple[float, str]]: + """ + This method retrieves the parent of a given hotkey and netuid. It queries the SubtensorModule's ParentKeys + storage function to get the children and formats them before returning as a tuple. + + Parameters: + hotkey_ss58: The child hotkey SS58. + netuid: The netuid. + block: The block number for which the children are to be retrieved. + + Returns: + A list of formatted parents [(proportion, parent)] + """ + parents = self.substrate.query( + module="SubtensorModule", + storage_function="ParentKeys", + params=[hotkey_ss58, netuid], + block_hash=self.determine_block_hash(block), + ) + if parents: + formatted_parents = [] + for proportion, parent in parents.value: + # Convert U64 to int + formatted_child = decode_account_id(parent[0]) + normalized_proportion = u64_normalized_float(proportion) + formatted_parents.append((normalized_proportion, formatted_child)) + return formatted_parents + + return [] + + def get_revealed_commitment( + self, + netuid: int, + uid: int, + block: Optional[int] = None, + ) -> Optional[tuple[tuple[int, str], ...]]: + """Returns uid related revealed commitment for a given netuid. + + Parameters: + netuid: The unique identifier of the subnetwork. + uid: The neuron uid to retrieve the commitment from. + block: The block number to retrieve the commitment from. + + Returns: + A tuple of reveal block and commitment message. + + Example of result: + ( (12, "Alice message 1"), (152, "Alice message 2") ) + ( (12, "Bob message 1"), (147, "Bob message 2") ) + """ + try: + meta_info = self.get_metagraph_info(netuid, block=block) + if meta_info: + hotkey_ss58 = meta_info.hotkeys[uid] + else: + raise ValueError(f"Subnet with netuid {netuid} does not exist.") + except IndexError: + raise ValueError(f"Subnet {netuid} does not have a neuron with uid {uid}.") + + return self.get_revealed_commitment_by_hotkey( + netuid=netuid, hotkey_ss58=hotkey_ss58, block=block + ) + + def get_revealed_commitment_by_hotkey( + self, + netuid: int, + hotkey_ss58: str, + block: Optional[int] = None, + ) -> Optional[tuple[tuple[int, str], ...]]: + """Retrieves hotkey related revealed commitment for a given subnet. + + Parameters: + netuid: The unique identifier of the subnetwork. + hotkey_ss58: The ss58 address of the committee member. + block: The block number to retrieve the commitment from. + + Returns: + A tuple of reveal block and commitment message. + """ + if not is_valid_ss58_address(address=hotkey_ss58): + raise ValueError(f"Invalid ss58 address {hotkey_ss58} provided.") + + query = self.query_module( + module="Commitments", + name="RevealedCommitments", + params=[netuid, hotkey_ss58], + block=block, + ) + if query is None: + return None + return tuple(decode_revealed_commitment(pair) for pair in query) def get_stake( self, @@ -1936,220 +2010,6 @@ def get_stake_add_fee( check_balance_amount(amount) return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) - def get_mechanism_emission_split( - self, netuid: int, block: Optional[int] = None - ) -> Optional[list[int]]: - """Returns the emission percentages allocated to each subnet mechanism. - - Parameters: - netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - - Returns: - A list of integers representing the percentage of emission allocated to each subnet mechanism (rounded to - whole numbers). Returns None if emission is evenly split or if the data is unavailable. - """ - block_hash = self.determine_block_hash(block) - result = self.substrate.query( - module="SubtensorModule", - storage_function="MechanismEmissionSplit", - params=[netuid], - block_hash=block_hash, - ) - if result is None or not hasattr(result, "value"): - return None - - return [round(i / sum(result.value) * 100) for i in result.value] - - def get_mechanism_count( - self, - netuid: int, - block: Optional[int] = None, - ) -> int: - """Retrieves the number of mechanisms for the given subnet. - - Parameters: - netuid: Subnet identifier. - block: The blockchain block number for the query. - - Returns: - The number of mechanisms for the given subnet. - """ - block_hash = self.determine_block_hash(block) - query = self.substrate.query( - module="SubtensorModule", - storage_function="MechanismCountCurrent", - params=[netuid], - block_hash=block_hash, - ) - return query.value if query is not None and hasattr(query, "value") else 1 - - def get_subnet_info( - self, netuid: int, block: Optional[int] = None - ) -> Optional["SubnetInfo"]: - """ - Retrieves detailed information about subnet within the Bittensor network. - This function provides comprehensive data on subnet, including its characteristics and operational parameters. - - Parameters: - netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - - Returns: - SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet. - - Gaining insights into the subnet's details assists in understanding the network's composition, the roles of - different subnets, and their unique features. - """ - result = self.query_runtime_api( - runtime_api="SubnetInfoRuntimeApi", - method="get_subnet_info_v2", - params=[netuid], - block=block, - ) - if not result: - return None - return SubnetInfo.from_dict(result) - - def get_subnet_price( - self, - netuid: int, - block: Optional[int] = None, - ) -> Balance: - """Gets the current Alpha price in TAO for all subnets. - - Parameters: - netuid: The unique identifier of the subnet. - block: The blockchain block number for the query. - - Returns: - The current Alpha price in TAO units for the specified subnet. - """ - # SN0 price is always 1 TAO - if netuid == 0: - return Balance.from_tao(1) - - block_hash = self.determine_block_hash(block=block) - price_rao = self.substrate.runtime_call( - api="SwapRuntimeApi", - method="current_alpha_price", - params=[netuid], - block_hash=block_hash, - ).value - return Balance.from_rao(price_rao) - - def get_subnet_prices( - self, - block: Optional[int] = None, - ) -> dict[int, Balance]: - """Gets the current Alpha price in TAO for a specified subnet. - - Parameters: - block: The blockchain block number for the query. - - Returns: - dict: - - subnet unique ID - - The current Alpha price in TAO units for the specified subnet. - """ - block_hash = self.determine_block_hash(block=block) - - current_sqrt_prices = self.substrate.query_map( - module="Swap", - storage_function="AlphaSqrtPrice", - block_hash=block_hash, - page_size=129, # total number of subnets - ) - - prices = {} - for id_, current_sqrt_price in current_sqrt_prices: - current_sqrt_price = fixed_to_float(current_sqrt_price) - current_price = current_sqrt_price * current_sqrt_price - current_price_in_tao = Balance.from_rao(int(current_price * 1e9)) - prices.update({id_: current_price_in_tao}) - - # SN0 price is always 1 TAO - prices.update({0: Balance.from_tao(1)}) - return prices - - def get_timelocked_weight_commits( - self, - netuid: int, - mechid: int = 0, - block: Optional[int] = None, - ) -> list[tuple[str, int, str, int]]: - """ - Retrieves CRv4 weight commit information for a specific subnet. - - Parameters: - netuid: Subnet identifier. - mechid: Subnet mechanism identifier. - block: The blockchain block number for the query. - - Returns: - A list of commit details, where each item contains: - - ss58_address: The address of the committer. - - commit_block: The block number when the commitment was made. - - commit_message: The commit message. - - reveal_round: The round when the commitment was revealed. - - The list may be empty if there are no commits found. - """ - storage_index = get_mechid_storage_index(netuid, mechid) - result = self.substrate.query_map( - module="SubtensorModule", - storage_function="TimelockedWeightCommits", - params=[storage_index], - block_hash=self.determine_block_hash(block=block), - ) - - commits = result.records[0][1] if result.records else [] - return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] - - # TODO: update related with fee calculation - def get_unstake_fee( - self, - amount: Balance, - netuid: int, - block: Optional[int] = None, - ) -> Balance: - """ - Calculates the fee for unstaking from a hotkey. - - Parameters: - amount: Amount of stake to unstake in TAO - netuid: Netuid of subnet - block: Block number at which to perform the calculation - - Returns: - The calculated stake fee as a Balance object - """ - check_balance_amount(amount) - return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) - - # TODO: update related with fee calculation - def get_stake_movement_fee( - self, - amount: Balance, - origin_netuid: int, - block: Optional[int] = None, - ) -> Balance: - """ - Calculates the fee for moving stake between hotkeys/subnets/coldkeys. - - Parameters: - amount: Amount of stake to move in TAO - origin_netuid: Netuid of origin subnet - block: Block number at which to perform the calculation - - Returns: - The calculated stake fee as a Balance object - """ - check_balance_amount(amount) - return self.get_stake_operations_fee( - amount=amount, netuid=origin_netuid, block=block - ) - def get_stake_for_coldkey_and_hotkey( self, coldkey_ss58: str, @@ -2226,12 +2086,35 @@ def get_stake_for_hotkey( hotkey_alpha_query = self.query_subtensor( name="TotalHotkeyAlpha", params=[hotkey_ss58, netuid], block=block ) - hotkey_alpha = cast(ScaleObj, hotkey_alpha_query) - balance = Balance.from_rao(hotkey_alpha.value) - balance.set_unit(netuid=netuid) - return balance - - get_hotkey_stake = get_stake_for_hotkey + hotkey_alpha = cast(ScaleObj, hotkey_alpha_query) + balance = Balance.from_rao(hotkey_alpha.value) + balance.set_unit(netuid=netuid) + return balance + + get_hotkey_stake = get_stake_for_hotkey + + # TODO: update related with fee calculation + def get_stake_movement_fee( + self, + amount: Balance, + origin_netuid: int, + block: Optional[int] = None, + ) -> Balance: + """ + Calculates the fee for moving stake between hotkeys/subnets/coldkeys. + + Parameters: + amount: Amount of stake to move in TAO + origin_netuid: Netuid of origin subnet + block: Block number at which to perform the calculation + + Returns: + The calculated stake fee as a Balance object + """ + check_balance_amount(amount) + return self.get_stake_operations_fee( + amount=amount, netuid=origin_netuid, block=block + ) def get_stake_operations_fee( self, @@ -2334,6 +2217,114 @@ def get_subnet_hyperparameters( return SubnetHyperparameters.from_dict(result) + def get_subnet_info( + self, netuid: int, block: Optional[int] = None + ) -> Optional["SubnetInfo"]: + """ + Retrieves detailed information about subnet within the Bittensor network. + This function provides comprehensive data on subnet, including its characteristics and operational parameters. + + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + + Returns: + SubnetInfo: A SubnetInfo objects, each containing detailed information about a subnet. + + Gaining insights into the subnet's details assists in understanding the network's composition, the roles of + different subnets, and their unique features. + """ + result = self.query_runtime_api( + runtime_api="SubnetInfoRuntimeApi", + method="get_subnet_info_v2", + params=[netuid], + block=block, + ) + if not result: + return None + return SubnetInfo.from_dict(result) + + def get_subnet_owner_hotkey( + self, netuid: int, block: Optional[int] = None + ) -> Optional[str]: + """ + Retrieves the hotkey of the subnet owner for a given network UID. + + This function queries the subtensor network to fetch the hotkey of the owner of a subnet specified by its + netuid. If no data is found or the query fails, the function returns None. + + Parameters: + netuid: The network UID of the subnet to fetch the owner's hotkey for. + block: The specific block number to query the data from. + + Returns: + The hotkey of the subnet owner if available; None otherwise. + """ + return self.query_subtensor( + name="SubnetOwnerHotkey", params=[netuid], block=block + ) + + def get_subnet_price( + self, + netuid: int, + block: Optional[int] = None, + ) -> Balance: + """Gets the current Alpha price in TAO for all subnets. + + Parameters: + netuid: The unique identifier of the subnet. + block: The blockchain block number for the query. + + Returns: + The current Alpha price in TAO units for the specified subnet. + """ + # SN0 price is always 1 TAO + if netuid == 0: + return Balance.from_tao(1) + + block_hash = self.determine_block_hash(block=block) + price_rao = self.substrate.runtime_call( + api="SwapRuntimeApi", + method="current_alpha_price", + params=[netuid], + block_hash=block_hash, + ).value + return Balance.from_rao(price_rao) + + def get_subnet_prices( + self, + block: Optional[int] = None, + ) -> dict[int, Balance]: + """Gets the current Alpha price in TAO for a specified subnet. + + Parameters: + block: The blockchain block number for the query. + + Returns: + dict: + - subnet unique ID + - The current Alpha price in TAO units for the specified subnet. + """ + block_hash = self.determine_block_hash(block=block) + + current_sqrt_prices = self.substrate.query_map( + module="Swap", + storage_function="AlphaSqrtPrice", + block_hash=block_hash, + page_size=129, # total number of subnets + ) + + prices = {} + for id_, current_sqrt_price in current_sqrt_prices: + current_sqrt_price = fixed_to_float(current_sqrt_price) + current_price = current_sqrt_price * current_sqrt_price + current_price_in_tao = Balance.from_rao(int(current_price * 1e9)) + prices.update({id_: current_price_in_tao}) + + # SN0 price is always 1 TAO + prices.update({0: Balance.from_tao(1)}) + return prices + def get_subnet_reveal_period_epochs( self, netuid: int, block: Optional[int] = None ) -> int: @@ -2345,30 +2336,72 @@ def get_subnet_reveal_period_epochs( ), ) - def get_all_subnets_netuid(self, block: Optional[int] = None) -> UIDs: + def get_subnet_validator_permits( + self, netuid: int, block: Optional[int] = None + ) -> Optional[list[bool]]: """ - Retrieves the list of all subnet unique identifiers (netuids) currently present in the Bittensor network. + Retrieves the list of validator permits for a given subnet as boolean values. Parameters: + netuid: The unique identifier of the subnetwork. block: The blockchain block number for the query. Returns: - A list of subnet netuids. + A list of boolean values representing validator permits, or None if not available. + """ + query = self.query_subtensor( + name="ValidatorPermit", + params=[netuid], + block=block, + ) + return query.value if query is not None and hasattr(query, "value") else query - This function provides a comprehensive view of the subnets within the Bittensor network, - offering insights into its diversity and scale. + def get_timelocked_weight_commits( + self, + netuid: int, + mechid: int = 0, + block: Optional[int] = None, + ) -> list[tuple[str, int, str, int]]: + """ + Retrieves CRv4 weight commit information for a specific subnet. + + Parameters: + netuid: Subnet identifier. + mechid: Subnet mechanism identifier. + block: The blockchain block number for the query. + + Returns: + A list of commit details, where each item contains: + - ss58_address: The address of the committer. + - commit_block: The block number when the commitment was made. + - commit_message: The commit message. + - reveal_round: The round when the commitment was revealed. + + The list may be empty if there are no commits found. """ + storage_index = get_mechid_storage_index(netuid, mechid) result = self.substrate.query_map( module="SubtensorModule", - storage_function="NetworksAdded", - block_hash=self.determine_block_hash(block), + storage_function="TimelockedWeightCommits", + params=[storage_index], + block_hash=self.determine_block_hash(block=block), ) - subnets = [] - if result.records: - for netuid, exists in result: - if exists: - subnets.append(netuid) - return subnets + + commits = result.records[0][1] if result.records else [] + return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] + + def get_timestamp(self, block: Optional[int] = None) -> datetime: + """ + Retrieves the datetime timestamp for a given block + + Parameters: + block: The blockchain block number for the query. + + Returns: + datetime object for the timestamp of the block + """ + unix = cast(ScaleObj, self.query_module("Timestamp", "Now", block=block)).value + return datetime.fromtimestamp(unix / 1000, tz=timezone.utc) def get_total_subnets(self, block: Optional[int] = None) -> Optional[int]: """ @@ -2438,6 +2471,27 @@ def get_transfer_fee( return Balance.from_rao(payment_info["partial_fee"]) + # TODO: update related with fee calculation + def get_unstake_fee( + self, + amount: Balance, + netuid: int, + block: Optional[int] = None, + ) -> Balance: + """ + Calculates the fee for unstaking from a hotkey. + + Parameters: + amount: Amount of stake to unstake in TAO + netuid: Netuid of subnet + block: Block number at which to perform the calculation + + Returns: + The calculated stake fee as a Balance object + """ + check_balance_amount(amount) + return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) + def get_vote_data( self, proposal_hash: str, block: Optional[int] = None ) -> Optional["ProposalVoteData"]: @@ -2728,7 +2782,6 @@ def max_weight_limit( ) return None if call is None else u16_normalized_float(int(call)) - # TODO: update parameters order in SDKv10 def metagraph( self, netuid: int, @@ -3126,59 +3179,6 @@ def weights_rate_limit( ) return None if call is None else int(call) - def get_timestamp(self, block: Optional[int] = None) -> datetime: - """ - Retrieves the datetime timestamp for a given block - - Parameters: - block: The blockchain block number for the query. - - Returns: - datetime object for the timestamp of the block - """ - unix = cast(ScaleObj, self.query_module("Timestamp", "Now", block=block)).value - return datetime.fromtimestamp(unix / 1000, tz=timezone.utc) - - def get_subnet_owner_hotkey( - self, netuid: int, block: Optional[int] = None - ) -> Optional[str]: - """ - Retrieves the hotkey of the subnet owner for a given network UID. - - This function queries the subtensor network to fetch the hotkey of the owner of a subnet specified by its - netuid. If no data is found or the query fails, the function returns None. - - Parameters: - netuid: The network UID of the subnet to fetch the owner's hotkey for. - block: The specific block number to query the data from. - - Returns: - The hotkey of the subnet owner if available; None otherwise. - """ - return self.query_subtensor( - name="SubnetOwnerHotkey", params=[netuid], block=block - ) - - def get_subnet_validator_permits( - self, netuid: int, block: Optional[int] = None - ) -> Optional[list[bool]]: - """ - Retrieves the list of validator permits for a given subnet as boolean values. - - Parameters: - netuid: The unique identifier of the subnetwork. - block: The blockchain block number for the query. - - Returns: - A list of boolean values representing validator permits, or None if not available. - """ - query = self.query_subtensor( - name="ValidatorPermit", - params=[netuid], - block=block, - ) - return query.value if query is not None and hasattr(query, "value") else query - # Extrinsics helper ================================================================================================ def sign_and_send_extrinsic( From 3b8d0a99543686b1fcebb1058400bd2347d72160 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 10:31:12 -0700 Subject: [PATCH 369/416] add `SimSwapResult` --- bittensor/core/chain_data/sim_swap.py | 47 +++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 bittensor/core/chain_data/sim_swap.py diff --git a/bittensor/core/chain_data/sim_swap.py b/bittensor/core/chain_data/sim_swap.py new file mode 100644 index 0000000000..7710d9a3e3 --- /dev/null +++ b/bittensor/core/chain_data/sim_swap.py @@ -0,0 +1,47 @@ +from dataclasses import dataclass + +from bittensor.utils.balance import Balance + + +@dataclass +class SimSwapResult: + """ + Represents the result of a simulated swap operation. + + This class is used to encapsulate the amounts and fees for the simulated swap process, including both tao and alpha token values. + It provides a convenient way to manage and interpret the swap results. + + Attributes: + tao_amount: The amount of tao tokens obtained as the result of the swap. + alpha_amount: The amount of alpha tokens obtained as the result of the swap. + tao_fee: The fee associated with the tao token portion of the swap. + alpha_fee: The fee associated with the alpha token portion of the swap. + """ + tao_amount: Balance + alpha_amount: Balance + tao_fee: Balance + alpha_fee: Balance + + @classmethod + def from_dict(cls, data: dict, netuid: int) -> "SimSwapResult": + """ + Converts a dictionary to a SimSwapResult instance. + + This method acts as a factory to create a SimSwapResult object using the data + from a dictionary. It parses the specified dictionary, converts values into + Balance objects, and sets associated units based on parameters and context. + + Args: + data: A dictionary containing the swap result data. It must include the keys "tao_amount", "alpha_amount", + "tao_fee", and "alpha_fee" with their respective values. + netuid: A network-specific unit identifier used to set the unit for alpha-related amounts. + + Returns: + SimSwapResult: An instance of SimSwapResult initialized with the parsed and converted data. + """ + return cls( + tao_amount=Balance.from_rao(data["tao_amount"]).set_unit(0), + alpha_amount=Balance.from_rao(data["alpha_amount"]).set_unit(netuid), + tao_fee=Balance.from_rao(data["tao_fee"]).set_unit(0), + alpha_fee=Balance.from_rao(data["alpha_fee"]).set_unit(netuid), + ) From 4fb186981936f498c43d907b99f6e6f55e99a80c Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 11:05:53 -0700 Subject: [PATCH 370/416] Added `bittensor.core.extrinsics.params` subpackage. This package will contain all extrinsic parameters. --- bittensor/core/extrinsics/params/__init__.py | 29 ++++++ bittensor/core/extrinsics/params/children.py | 32 +++++++ bittensor/core/extrinsics/params/liquidity.py | 67 ++++++++++++++ .../core/extrinsics/params/move_stake.py | 90 +++++++++++++++++++ .../core/extrinsics/params/registration.py | 72 +++++++++++++++ bittensor/core/extrinsics/params/root.py | 12 +++ bittensor/core/extrinsics/params/serving.py | 43 +++++++++ bittensor/core/extrinsics/params/staking.py | 57 ++++++++++++ .../core/extrinsics/params/start_call.py | 12 +++ bittensor/core/extrinsics/params/take.py | 13 +++ bittensor/core/extrinsics/params/transfer.py | 71 +++++++++++++++ bittensor/core/extrinsics/params/unstaking.py | 63 +++++++++++++ bittensor/core/extrinsics/params/weights.py | 76 ++++++++++++++++ 13 files changed, 637 insertions(+) create mode 100644 bittensor/core/extrinsics/params/__init__.py create mode 100644 bittensor/core/extrinsics/params/children.py create mode 100644 bittensor/core/extrinsics/params/liquidity.py create mode 100644 bittensor/core/extrinsics/params/move_stake.py create mode 100644 bittensor/core/extrinsics/params/registration.py create mode 100644 bittensor/core/extrinsics/params/root.py create mode 100644 bittensor/core/extrinsics/params/serving.py create mode 100644 bittensor/core/extrinsics/params/staking.py create mode 100644 bittensor/core/extrinsics/params/start_call.py create mode 100644 bittensor/core/extrinsics/params/take.py create mode 100644 bittensor/core/extrinsics/params/transfer.py create mode 100644 bittensor/core/extrinsics/params/unstaking.py create mode 100644 bittensor/core/extrinsics/params/weights.py diff --git a/bittensor/core/extrinsics/params/__init__.py b/bittensor/core/extrinsics/params/__init__.py new file mode 100644 index 0000000000..516dcf1f63 --- /dev/null +++ b/bittensor/core/extrinsics/params/__init__.py @@ -0,0 +1,29 @@ +from .children import ChildrenParams +from .liquidity import LiquidityParams +from .move_stake import MoveStakeParams +from .registration import RegistrationParams +from .root import RootParams +from .serving import ServingParams +from .staking import StakingParams +from .start_call import StartCallParams +from .take import TakeParams +from .transfer import TransferParams, get_transfer_fn_params +from .unstaking import UnstakingParams +from .weights import WeightsParams + + +__all__ = [ + "get_transfer_fn_params", + "ChildrenParams", + "LiquidityParams", + "MoveStakeParams", + "RegistrationParams", + "RootParams", + "ServingParams", + "StakingParams", + "StartCallParams", + "TakeParams", + "TransferParams", + "UnstakingParams", + "WeightsParams", +] diff --git a/bittensor/core/extrinsics/params/children.py b/bittensor/core/extrinsics/params/children.py new file mode 100644 index 0000000000..eeefa44371 --- /dev/null +++ b/bittensor/core/extrinsics/params/children.py @@ -0,0 +1,32 @@ +from dataclasses import dataclass + +from bittensor.utils import float_to_u64 + + +@dataclass +class ChildrenParams: + @classmethod + def set_children( + cls, + hotkey_ss58: str, + netuid: int, + children: list[tuple[float, str]], + ) -> dict: + """Returns the parameters for the `set_children`.""" + params = { + "children": [ + (float_to_u64(proportion), child_hotkey) + for proportion, child_hotkey in children + ], + "hotkey": hotkey_ss58, + "netuid": netuid, + } + return params + + @classmethod + def set_pending_childkey_cooldown( + cls, + cooldown: int, + ) -> dict: + """Returns the parameters for the `set_pending_childkey_cooldown`.""" + return {"cooldown": cooldown} diff --git a/bittensor/core/extrinsics/params/liquidity.py b/bittensor/core/extrinsics/params/liquidity.py new file mode 100644 index 0000000000..be37883a00 --- /dev/null +++ b/bittensor/core/extrinsics/params/liquidity.py @@ -0,0 +1,67 @@ +from dataclasses import dataclass + +from bittensor.utils.balance import Balance +from bittensor.utils.liquidity import price_to_tick + + +@dataclass +class LiquidityParams: + @classmethod + def add_liquidity( + cls, + netuid: int, + hotkey_ss58: str, + liquidity: Balance, + price_low: Balance, + price_high: Balance, + ) -> dict: + """Returns the parameters for the `add_liquidity`.""" + tick_low = price_to_tick(price_low.tao) + tick_high = price_to_tick(price_high.tao) + + return { + "hotkey": hotkey_ss58, + "netuid": netuid, + "tick_low": tick_low, + "tick_high": tick_high, + "liquidity": liquidity.rao, + } + + @classmethod + def modify_position( + cls, + netuid: int, + hotkey_ss58: str, + position_id: int, + liquidity_delta: Balance, + ) -> dict: + """Returns the parameters for the `modify_position`.""" + return { + "hotkey": hotkey_ss58, + "netuid": netuid, + "position_id": position_id, + "liquidity_delta": liquidity_delta.rao, + } + + @classmethod + def remove_liquidity( + cls, + netuid: int, + hotkey_ss58: str, + position_id: int, + ) -> dict: + """Returns the parameters for the `remove_liquidity`.""" + return { + "hotkey": hotkey_ss58, + "netuid": netuid, + "position_id": position_id, + } + + @classmethod + def toggle_user_liquidity( + cls, + netuid: int, + enable: bool, + ) -> dict: + """Returns the parameters for the `toggle_user_liquidity`.""" + return {"netuid": netuid, "enable": enable} diff --git a/bittensor/core/extrinsics/params/move_stake.py b/bittensor/core/extrinsics/params/move_stake.py new file mode 100644 index 0000000000..7ab9d94d5f --- /dev/null +++ b/bittensor/core/extrinsics/params/move_stake.py @@ -0,0 +1,90 @@ +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from bittensor.utils.balance import Balance + +if TYPE_CHECKING: + from bittensor.core.chain_data import DynamicInfo + + +@dataclass +class MoveStakeParams: + @classmethod + def move_stake( + cls, + origin_netuid: int, + origin_hotkey_ss58: str, + destination_netuid: int, + destination_hotkey_ss58: str, + amount: Balance, + ) -> dict: + """Returns the parameters for the `move_stake`.""" + return { + "origin_netuid": origin_netuid, + "origin_hotkey": origin_hotkey_ss58, + "destination_netuid": destination_netuid, + "destination_hotkey": destination_hotkey_ss58, + "alpha_amount": amount.rao, + } + + @classmethod + def transfer_stake( + cls, + destination_coldkey_ss58: str, + hotkey_ss58: str, + origin_netuid: int, + destination_netuid: int, + amount: Balance, + ) -> dict: + """Returns the parameters for the `transfer_stake`.""" + return { + "destination_coldkey": destination_coldkey_ss58, + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + } + + @classmethod + def swap_stake( + cls, + hotkey_ss58: str, + origin_netuid: int, + destination_netuid: int, + amount: Balance, + ) -> dict: + """Returns the parameters for the `swap_stake`.""" + return { + "hotkey": hotkey_ss58, + "origin_netuid": origin_netuid, + "destination_netuid": destination_netuid, + "alpha_amount": amount.rao, + } + + @classmethod + def swap_stake_limit( + cls, + hotkey_ss58: str, + origin_netuid: int, + destination_netuid: int, + amount: Balance, + allow_partial_stake: bool, + rate_tolerance: float, + origin_pool: "DynamicInfo", + destination_pool: "DynamicInfo", + ) -> dict: + """Returns the parameters for the `swap_stake_limit`.""" + call_params = cls.swap_stake( + hotkey_ss58, origin_netuid, destination_netuid, amount + ) + + swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao + swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) + + call_params.update( + { + "limit_price": swap_rate_ratio_with_tolerance, + "allow_partial": allow_partial_stake, + } + ) + return call_params diff --git a/bittensor/core/extrinsics/params/registration.py b/bittensor/core/extrinsics/params/registration.py new file mode 100644 index 0000000000..816db7a186 --- /dev/null +++ b/bittensor/core/extrinsics/params/registration.py @@ -0,0 +1,72 @@ +from dataclasses import dataclass + + +@dataclass +class RegistrationParams: + @classmethod + def burned_register( + cls, + netuid: int, + hotkey_ss58: str, + ) -> dict: + """Returns the parameters for the `burned_register`.""" + return { + "netuid": netuid, + "hotkey": hotkey_ss58, + } + + @classmethod + def register_network( + cls, + hotkey_ss58: str, + ) -> dict: + """Returns the parameters for the `register_network`.""" + return {"hotkey": hotkey_ss58} + + @classmethod + def register( + cls, + netuid: int, + coldkey_ss58: str, + hotkey_ss58: str, + block_number: int, + nonce: int, + work: list[int], + ) -> dict: + """Returns the parameters for the `register`.""" + return { + "coldkey": coldkey_ss58, + "hotkey": hotkey_ss58, + "netuid": netuid, + "block_number": block_number, + "nonce": nonce, + "work": work, + } + + @classmethod + def set_subnet_identity( + cls, + netuid: int, + hotkey_ss58: str, + subnet_name: str, + github_repo: str, + subnet_contact: str, + subnet_url: str, + logo_url: str, + discord: str, + description: str, + additional: str, + ) -> dict: + """Returns the parameters for the `set_subnet_identity`.""" + return { + "hotkey": hotkey_ss58, + "netuid": netuid, + "subnet_name": subnet_name, + "github_repo": github_repo, + "subnet_contact": subnet_contact, + "subnet_url": subnet_url, + "logo_url": logo_url, + "discord": discord, + "description": description, + "additional": additional, + } diff --git a/bittensor/core/extrinsics/params/root.py b/bittensor/core/extrinsics/params/root.py new file mode 100644 index 0000000000..add3cc8c50 --- /dev/null +++ b/bittensor/core/extrinsics/params/root.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass + + +@dataclass +class RootParams: + @classmethod + def root_register( + cls, + hotkey_ss58: str, + ) -> dict: + """Returns the parameters for the `root_register`.""" + return {"hotkey": hotkey_ss58} diff --git a/bittensor/core/extrinsics/params/serving.py b/bittensor/core/extrinsics/params/serving.py new file mode 100644 index 0000000000..5bfaf54aa8 --- /dev/null +++ b/bittensor/core/extrinsics/params/serving.py @@ -0,0 +1,43 @@ +from dataclasses import dataclass +from typing import Optional +from bittensor.utils import Certificate, networking as net +from bittensor.core.types import AxonServeCallParams +from bittensor.core.settings import version_as_int + + +@dataclass +class ServingParams: + @classmethod + def serve_axon_and_tls( + cls, + hotkey_ss58: str, + coldkey_ss58: str, + netuid: int, + ip: str, + port: int, + protocol: int, + placeholder1: int, + placeholder2: int, + certificate: Optional[Certificate] = None, + ) -> AxonServeCallParams: + """Returns the parameters for the `root_register`.""" + return AxonServeCallParams( + **{ + "hotkey": hotkey_ss58, + "coldkey": coldkey_ss58, + "netuid": netuid, + "ip": net.ip_to_int(ip), + "port": port, + "protocol": protocol, + "certificate": certificate, + "ip_type": net.ip_version(ip), + "version": version_as_int, + "placeholder1": placeholder1, + "placeholder2": placeholder2, + } + ) + + @classmethod + def set_commitment(cls, netuid: int, info_fields: list) -> dict: + """Returns the parameters for the `set_commitment`.""" + return {"netuid": netuid, "info": {"fields": [info_fields]}} diff --git a/bittensor/core/extrinsics/params/staking.py b/bittensor/core/extrinsics/params/staking.py new file mode 100644 index 0000000000..a4bcb33a63 --- /dev/null +++ b/bittensor/core/extrinsics/params/staking.py @@ -0,0 +1,57 @@ +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from bittensor.utils.balance import Balance + +if TYPE_CHECKING: + from bittensor.core.chain_data import DynamicInfo + + +@dataclass +class StakingParams: + @classmethod + def add_stake( + cls, + netuid: int, + hotkey_ss58: str, + amount: Balance, + ) -> dict: + """Returns the parameters for the `safe` parameters.""" + return { + "netuid": netuid, + "hotkey": hotkey_ss58, + "amount_staked": amount.rao, + } + + @classmethod + def add_stake_limit( + cls, + netuid: int, + hotkey_ss58: str, + amount: "Balance", + allow_partial_stake: bool, + rate_tolerance: float, + pool: "DynamicInfo", + ) -> dict: + """Returns the parameters for the `add_stake_limit`.""" + call_params = cls.add_stake(netuid, hotkey_ss58, amount) + + base_price = pool.price.tao + 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} + ) + return call_params + + @classmethod + def set_coldkey_auto_stake_hotkey( + cls, + netuid: int, + hotkey_ss58: str, + ) -> dict: + """Returns the parameters for the `set_auto_stake_extrinsic`.""" + return {"hotkey": hotkey_ss58, "netuid": netuid} diff --git a/bittensor/core/extrinsics/params/start_call.py b/bittensor/core/extrinsics/params/start_call.py new file mode 100644 index 0000000000..cab30bdeea --- /dev/null +++ b/bittensor/core/extrinsics/params/start_call.py @@ -0,0 +1,12 @@ +from dataclasses import dataclass + + +@dataclass +class StartCallParams: + @classmethod + def start_call( + cls, + netuid: int, + ) -> dict: + """Returns the parameters for the `start_call`.""" + return {"netuid": netuid} diff --git a/bittensor/core/extrinsics/params/take.py b/bittensor/core/extrinsics/params/take.py new file mode 100644 index 0000000000..bc11b8b93c --- /dev/null +++ b/bittensor/core/extrinsics/params/take.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass + + +@dataclass +class TakeParams: + @classmethod + def increase_decrease_take( + cls, + hotkey_ss58: str, + take: int, + ) -> dict: + """Returns the parameters for the `increase_take` and `decrease_take`.""" + return {"hotkey": hotkey_ss58, "take": take} diff --git a/bittensor/core/extrinsics/params/transfer.py b/bittensor/core/extrinsics/params/transfer.py new file mode 100644 index 0000000000..b0240f1bb2 --- /dev/null +++ b/bittensor/core/extrinsics/params/transfer.py @@ -0,0 +1,71 @@ +from dataclasses import dataclass +from typing import Optional, Union + +from bittensor.utils.balance import Balance + + +def get_transfer_fn_params( + amount: Optional["Balance"], destination: str, keep_alive: bool +) -> tuple[str, dict[str, Union[str, int, bool]]]: + """ + Helper function to get the transfer call function and call params, depending on the value and keep_alive flag + provided. + + Parameters: + amount: the amount of Tao to transfer. `None` if transferring all. + destination: the destination SS58 of the transfer + keep_alive: whether to enforce a retention of the existential deposit in the account after transfer. + + Returns: + tuple[call function, call params] + """ + call_params: dict[str, Union[str, int, bool]] = {"dest": destination} + if amount is None: + call_function = "transfer_all" + if keep_alive: + call_params["keep_alive"] = True + else: + call_params["keep_alive"] = False + else: + call_params["value"] = amount.rao + if keep_alive: + call_function = "transfer_keep_alive" + else: + call_function = "transfer_allow_death" + return call_function, call_params + + +@dataclass +class TransferParams: + @classmethod + def transfer_all( + cls, + destination: str, + amount: Optional[Balance] = None, + keep_alive: bool = True, + ) -> dict: + """Returns the parameters for the `transfer_all`.""" + _, call_params = get_transfer_fn_params(amount, destination, keep_alive) + return call_params + + @classmethod + def transfer_keep_alive( + cls, + destination: str, + amount: Optional[Balance] = None, + keep_alive: bool = True, + ) -> dict: + """Returns the parameters for the `transfer_keep_alive`.""" + _, call_params = get_transfer_fn_params(amount, destination, keep_alive) + return call_params + + @classmethod + def transfer_allow_death( + cls, + destination: str, + amount: Optional[Balance] = None, + keep_alive: bool = True, + ) -> dict: + """Returns the parameters for the `transfer_allow_death`.""" + _, call_params = get_transfer_fn_params(amount, destination, keep_alive) + return call_params diff --git a/bittensor/core/extrinsics/params/unstaking.py b/bittensor/core/extrinsics/params/unstaking.py new file mode 100644 index 0000000000..e959cc63ec --- /dev/null +++ b/bittensor/core/extrinsics/params/unstaking.py @@ -0,0 +1,63 @@ +from dataclasses import dataclass +from typing import Optional + +from bittensor.core.chain_data import DynamicInfo +from bittensor.utils.balance import Balance + + +@dataclass +class UnstakingParams: + @classmethod + def remove_stake(cls, netuid: int, hotkey_ss58: str, amount: "Balance") -> dict: + """Returns the parameters for the `remove_stake`.""" + return {"netuid": netuid, "hotkey": hotkey_ss58, "amount_unstaked": amount.rao} + + @classmethod + def remove_stake_limit( + cls, + netuid: int, + hotkey_ss58: str, + amount: "Balance", + allow_partial_stake: bool, + rate_tolerance: float, + pool: "DynamicInfo", + ) -> dict: + """Returns the parameters for the `remove_stake_limit`.""" + call_params = cls.remove_stake(netuid, hotkey_ss58, amount) + + base_price = pool.price.tao + + if pool.netuid == 0: + price_with_tolerance = base_price + else: + price_with_tolerance = 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, + } + ) + return call_params + + @classmethod + def remove_stake_full_limit( + cls, + netuid: int, + hotkey_ss58: str, + rate_tolerance: Optional[float] = None, + pool: Optional["DynamicInfo"] = None, + ) -> dict: + """Returns the parameters for the `remove_stake_full_limit`.""" + call_params = { + "hotkey": hotkey_ss58, + "netuid": netuid, + "limit_price": None, + } + + if rate_tolerance: + limit_price = pool.price * (1 - rate_tolerance) + call_params.update({"limit_price": limit_price}) + + return call_params diff --git a/bittensor/core/extrinsics/params/weights.py b/bittensor/core/extrinsics/params/weights.py new file mode 100644 index 0000000000..c56a8df8f6 --- /dev/null +++ b/bittensor/core/extrinsics/params/weights.py @@ -0,0 +1,76 @@ +from dataclasses import dataclass + +from bittensor.core.types import UIDs, Weights, Salt + + +@dataclass +class WeightsParams: + @classmethod + def commit_timelocked_mechanism_weights( + cls, + netuid: int, + mechid: int, + commit_for_reveal: bytes, + reveal_round: int, + commit_reveal_version: int, + ) -> dict: + """Returns the parameters for the `commit_timelocked_mechanism_weights`.""" + return { + "netuid": netuid, + "mecid": mechid, + "commit": commit_for_reveal, + "reveal_round": reveal_round, + "commit_reveal_version": commit_reveal_version, + } + + @classmethod + def commit_mechanism_weights( + cls, + netuid: int, + mechid: int, + commit_hash: str, + ) -> dict: + """Returns the parameters for the `commit_mechanism_weights`.""" + return { + "netuid": netuid, + "mecid": mechid, + "commit_hash": commit_hash, + } + + @classmethod + def reveal_mechanism_weights( + cls, + netuid: int, + mechid: int, + uids: UIDs, + weights: Weights, + salt: Salt, + version_key: int, + ) -> dict: + """Returns the parameters for the `reveal_mechanism_weights`.""" + return { + "netuid": netuid, + "mecid": mechid, + "uids": uids, + "values": weights, + "salt": salt, + "version_key": version_key, + } + + @classmethod + def set_mechanism_weights( + cls, + netuid: int, + mechid: int, + uids: UIDs, + weights: Weights, + version_key: int, + ) -> dict: + """Returns the parameters for the `set_mechanism_weights`.""" + return { + "netuid": netuid, + "mecid": mechid, + "dests": uids, + "weights": weights, + "version_key": version_key, + } From de0cbd9b754304b0b6bcbcddf905cb035b2637f6 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 11:09:50 -0700 Subject: [PATCH 371/416] update `check_balance_amount` --- bittensor/utils/balance.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index f585dd0b11..a5b5056e2d 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -851,7 +851,7 @@ def rao(amount: int, netuid: int = 0) -> Balance: return Balance.from_rao(amount).set_unit(netuid) -def check_balance_amount(amount: Optional[Balance]) -> None: +def check_balance_amount(amount: Optional[Balance], allow_none: bool = True) -> None: """ Validate that the provided value is a Balance instance. @@ -860,6 +860,7 @@ def check_balance_amount(amount: Optional[Balance]) -> None: Args: amount: The value to validate. + allow_none: if False then a `BalanceTypeError` is raised if the value is None. Returns: None: Always returns None if validation passes. @@ -867,7 +868,7 @@ def check_balance_amount(amount: Optional[Balance]) -> None: Raises: BalanceTypeError: If amount is not a Balance instance and not None. """ - if amount is None: + if amount is None and allow_none is True: return None if not isinstance(amount, Balance): From a53d4d5d2eb3aa4a097650ee02341aa057862e30 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 11:41:04 -0700 Subject: [PATCH 372/416] move `get_transfer_fn_params` to new subpackage --- bittensor/core/chain_data/__init__.py | 2 ++ bittensor/utils/__init__.py | 31 --------------------------- 2 files changed, 2 insertions(+), 31 deletions(-) diff --git a/bittensor/core/chain_data/__init__.py b/bittensor/core/chain_data/__init__.py index e2aee44a4a..6a423501f7 100644 --- a/bittensor/core/chain_data/__init__.py +++ b/bittensor/core/chain_data/__init__.py @@ -24,6 +24,7 @@ from .proposal_vote_data import ProposalVoteData from .scheduled_coldkey_swap_info import ScheduledColdkeySwapInfo from .stake_info import StakeInfo +from .sim_swap import SimSwapResult from .subnet_hyperparameters import SubnetHyperparameters from .subnet_identity import SubnetIdentity from .subnet_info import SubnetInfo @@ -52,6 +53,7 @@ "ProposalVoteData", "ScheduledColdkeySwapInfo", "SelectiveMetagraphIndex", + "SimSwapResult", "StakeInfo", "SubnetHyperparameters", "SubnetIdentity", diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index f821928842..c4ce11e924 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -486,37 +486,6 @@ def deprecated_message(message: str) -> None: warnings.warn(message=message, category=DeprecationWarning, stacklevel=2) -def get_transfer_fn_params( - amount: Optional["Balance"], destination: str, keep_alive: bool -) -> tuple[str, dict[str, Union[str, int, bool]]]: - """ - Helper function to get the transfer call function and call params, depending on the value and keep_alive flag - provided. - - Parameters: - amount: the amount of Tao to transfer. `None` if transferring all. - destination: the destination SS58 of the transfer - keep_alive: whether to enforce a retention of the existential deposit in the account after transfer. - - Returns: - tuple[call function, call params] - """ - call_params = {"dest": destination} - if amount is None: - call_function = "transfer_all" - if keep_alive: - call_params["keep_alive"] = True - else: - call_params["keep_alive"] = False - else: - call_params["value"] = amount.rao - if keep_alive: - call_function = "transfer_keep_alive" - else: - call_function = "transfer_allow_death" - return call_function, call_params - - def get_function_name() -> str: """Return the current function's name.""" return inspect.currentframe().f_back.f_code.co_name From 04ad6e1b25cb57fd8c690af42870f75b5d6a93bb Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 11:42:44 -0700 Subject: [PATCH 373/416] apply params logic to all extrinsics --- bittensor/core/extrinsics/asyncex/children.py | 20 +- .../core/extrinsics/asyncex/liquidity.py | 69 ++-- .../core/extrinsics/asyncex/move_stake.py | 336 ++++++++++-------- .../core/extrinsics/asyncex/registration.py | 90 +++-- bittensor/core/extrinsics/asyncex/root.py | 9 +- bittensor/core/extrinsics/asyncex/serving.py | 52 +-- bittensor/core/extrinsics/asyncex/staking.py | 55 +-- .../core/extrinsics/asyncex/start_call.py | 30 +- bittensor/core/extrinsics/asyncex/take.py | 8 +- bittensor/core/extrinsics/asyncex/transfer.py | 6 +- .../core/extrinsics/asyncex/unstaking.py | 101 +++--- bittensor/core/extrinsics/asyncex/weights.py | 63 ++-- bittensor/core/extrinsics/children.py | 20 +- bittensor/core/extrinsics/liquidity.py | 69 ++-- bittensor/core/extrinsics/move_stake.py | 317 +++++++++-------- bittensor/core/extrinsics/registration.py | 89 +++-- bittensor/core/extrinsics/root.py | 9 +- bittensor/core/extrinsics/serving.py | 54 +-- bittensor/core/extrinsics/staking.py | 54 +-- bittensor/core/extrinsics/start_call.py | 5 +- bittensor/core/extrinsics/take.py | 8 +- bittensor/core/extrinsics/transfer.py | 6 +- bittensor/core/extrinsics/unstaking.py | 77 ++-- bittensor/core/extrinsics/weights.py | 77 ++-- bittensor/extras/dev_framework/subnet.py | 39 +- bittensor/extras/subtensor_api/staking.py | 2 +- 26 files changed, 851 insertions(+), 814 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/children.py b/bittensor/core/extrinsics/asyncex/children.py index f758d3e4cc..0bff616565 100644 --- a/bittensor/core/extrinsics/asyncex/children.py +++ b/bittensor/core/extrinsics/asyncex/children.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING, Optional -from bittensor.core.types import ExtrinsicResponse from bittensor.core.extrinsics.asyncex.utils import sudo_call_extrinsic -from bittensor.utils import float_to_u64 +from bittensor.core.extrinsics.params import ChildrenParams +from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -58,20 +58,10 @@ async def set_children_extrinsic( ).success: return unlocked - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="set_children", - call_params={ - "children": [ - ( - float_to_u64(proportion), - child_hotkey, - ) - for proportion, child_hotkey in children - ], - "hotkey": hotkey_ss58, - "netuid": netuid, - }, + call_params=ChildrenParams.set_children(hotkey_ss58, netuid, children), ) response = await subtensor.sign_and_send_extrinsic( @@ -119,7 +109,7 @@ async def root_set_pending_childkey_cooldown_extrinsic( wallet=wallet, call_module="SubtensorModule", call_function="set_pending_childkey_cooldown", - call_params={"cooldown": cooldown}, + call_params=ChildrenParams.set_pending_childkey_cooldown(cooldown), period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/asyncex/liquidity.py b/bittensor/core/extrinsics/asyncex/liquidity.py index eb42ce2679..3f6cba7a7e 100644 --- a/bittensor/core/extrinsics/asyncex/liquidity.py +++ b/bittensor/core/extrinsics/asyncex/liquidity.py @@ -1,8 +1,8 @@ from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.params import LiquidityParams from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance -from bittensor.utils.liquidity import price_to_tick if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -47,24 +47,24 @@ async def add_liquidity_extrinsic( `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ try: + unlock_type = "coldkey" if hotkey_ss58 else "both" if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type=unlock_type + ) ).success: return unlocked - tick_low = price_to_tick(price_low.tao) - tick_high = price_to_tick(price_high.tao) - - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="Swap", call_function="add_liquidity", - call_params={ - "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, - "netuid": netuid, - "tick_low": tick_low, - "tick_high": tick_high, - "liquidity": liquidity.rao, - }, + call_params=LiquidityParams.add_liquidity( + netuid=netuid, + hotkey_ss58=hotkey_ss58 or wallet.hotkey.ss58_address, + liquidity=liquidity, + price_low=price_low, + price_high=price_high, + ), ) return await subtensor.sign_and_send_extrinsic( @@ -114,20 +114,23 @@ async def modify_liquidity_extrinsic( Call `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ try: + unlock_type = "coldkey" if hotkey_ss58 else "both" if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type + ) ).success: return unlocked - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="Swap", call_function="modify_position", - call_params={ - "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - "liquidity_delta": liquidity_delta.rao, - }, + call_params=LiquidityParams.modify_position( + netuid=netuid, + hotkey_ss58=hotkey_ss58 or wallet.hotkey.ss58_address, + position_id=position_id, + liquidity_delta=liquidity_delta, + ), ) return await subtensor.sign_and_send_extrinsic( @@ -175,19 +178,22 @@ async def remove_liquidity_extrinsic( `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ try: + unlock_type = "coldkey" if hotkey_ss58 else "both" if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type + ) ).success: return unlocked - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="Swap", call_function="remove_liquidity", - call_params={ - "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - }, + call_params=LiquidityParams.remove_liquidity( + netuid=netuid, + hotkey_ss58=hotkey_ss58 or wallet.hotkey.ss58_address, + position_id=position_id, + ), ) return await subtensor.sign_and_send_extrinsic( @@ -235,10 +241,13 @@ async def toggle_user_liquidity_extrinsic( ).success: return unlocked - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="Swap", call_function="toggle_user_liquidity", - call_params={"netuid": netuid, "enable": enable}, + call_params=LiquidityParams.toggle_user_liquidity( + netuid=netuid, + enable=enable, + ), ) return await subtensor.sign_and_send_extrinsic( diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 192b94e7e0..5a0beb60c4 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -1,6 +1,7 @@ import asyncio from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.params import MoveStakeParams from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -38,30 +39,32 @@ async def _get_stake_in_origin_and_dest( return stake_in_origin, stake_in_destination -async def transfer_stake_extrinsic( +async def move_stake_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - destination_coldkey_ss58: str, - hotkey_ss58: str, origin_netuid: int, + origin_hotkey_ss58: str, destination_netuid: int, - amount: Balance, + destination_hotkey_ss58: str, + amount: Optional[Balance] = None, + move_all_stake: bool = False, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> ExtrinsicResponse: """ - Transfers stake from one coldkey to another in the Bittensor network. + Moves stake from one hotkey to another within subnets in the Bittensor network. 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. + subtensor: Subtensor instance. + wallet: The wallet to move stake from. + origin_netuid: The netuid of the source subnet. + origin_hotkey_ss58: The SS58 address of the source hotkey. + destination_netuid: The netuid of the destination subnet. + destination_hotkey_ss58: The SS58 address of the destination hotkey. + 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. @@ -78,42 +81,48 @@ async def transfer_stake_extrinsic( ).success: return unlocked - amount.set_unit(netuid=origin_netuid) + if not amount and not move_all_stake: + return ExtrinsicResponse( + False, + "Please specify an `amount` or `move_all_stake` argument to move stake.", + ).with_log() # Check sufficient stake stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, - origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=destination_coldkey_ss58, origin_netuid=origin_netuid, + origin_hotkey_ss58=origin_hotkey_ss58, destination_netuid=destination_netuid, + destination_hotkey_ss58=destination_hotkey_ss58, + origin_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) - if stake_in_origin < amount: + if move_all_stake: + amount = stake_in_origin + + elif stake_in_origin < amount: return ExtrinsicResponse( False, - f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", ).with_log() + amount.set_unit(netuid=origin_netuid) + logging.debug( - f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " - f"[blue]{destination_coldkey_ss58}[/blue]" - ) - logging.debug( + 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]" ) - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": destination_coldkey_ss58, - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, + call_function="move_stake", + call_params=MoveStakeParams.move_stake( + origin_netuid=origin_netuid, + origin_hotkey_ss58=origin_hotkey_ss58, + destination_netuid=destination_netuid, + destination_hotkey_ss58=destination_hotkey_ss58, + amount=amount, + ), ) response = await subtensor.sign_and_send_extrinsic( @@ -126,16 +135,26 @@ async def transfer_stake_extrinsic( ) if response.success: + sim_swap = await subtensor.sim_swap( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) + if not wait_for_finalization and not wait_for_inclusion: return response + logging.debug("[green]Finalized[/green]") + # Get updated stakes origin_stake, dest_stake = await _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, + origin_hotkey_ss58=origin_hotkey_ss58, + destination_hotkey_ss58=destination_hotkey_ss58, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=destination_coldkey_ss58, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, origin_netuid=origin_netuid, destination_netuid=destination_netuid, ) @@ -146,6 +165,12 @@ async def transfer_stake_extrinsic( f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" ) + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } return response logging.error(f"[red]{response.message}[/red]") @@ -155,34 +180,30 @@ async def transfer_stake_extrinsic( return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) -async def swap_stake_extrinsic( +async def transfer_stake_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", + destination_coldkey_ss58: str, hotkey_ss58: str, origin_netuid: int, destination_netuid: int, amount: Balance, - safe_swapping: 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, ) -> ExtrinsicResponse: """ - Swaps stake from one subnet to another for a given hotkey in the Bittensor network. + Transfers stake from one coldkey to another in the Bittensor network. 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_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%). + 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. @@ -190,11 +211,9 @@ async def swap_stake_extrinsic( 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: ExtrinsicResponse: The result object of the extrinsic execution. """ - try: if not ( unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) @@ -209,58 +228,34 @@ async def swap_stake_extrinsic( origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, origin_netuid=origin_netuid, destination_netuid=destination_netuid, ) - if stake_in_origin < amount: return ExtrinsicResponse( False, f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", ).with_log() - call_params = { - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - } - - if safe_swapping: - origin_pool, destination_pool = await asyncio.gather( - subtensor.subnet(netuid=origin_netuid), - subtensor.subnet(netuid=destination_netuid), - ) - swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao - swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - - logging.debug( - f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]\n" - f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " - f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" - ) - call_params.update( - { - "limit_price": swap_rate_ratio_with_tolerance, - "allow_partial": allow_partial_stake, - } - ) - call_function = "swap_stake_limit" - else: - logging.debug( - f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]" - ) - call_function = "swap_stake" - - call = await subtensor.substrate.compose_call( + logging.debug( + f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " + f"[blue]{destination_coldkey_ss58}[/blue]" + ) + logging.debug( + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " + f"[yellow]{destination_netuid}[/yellow]" + ) + call = await subtensor.compose_call( call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, + call_function="transfer_stake", + call_params=MoveStakeParams.transfer_stake( + hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_coldkey_ss58=destination_coldkey_ss58, + destination_netuid=destination_netuid, + amount=amount, + ), ) response = await subtensor.sign_and_send_extrinsic( @@ -273,22 +268,27 @@ async def swap_stake_extrinsic( ) if response.success: + sim_swap = await subtensor.sim_swap( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) + if not wait_for_finalization and not wait_for_inclusion: return response - logging.debug("[green]Finalized[/green]") - # Get updated stakes origin_stake, dest_stake = await _get_stake_in_origin_and_dest( - subtensor, + subtensor=subtensor, origin_hotkey_ss58=hotkey_ss58, destination_hotkey_ss58=hotkey_ss58, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, origin_netuid=origin_netuid, destination_netuid=destination_netuid, ) - logging.debug( f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" ) @@ -296,17 +296,8 @@ async def swap_stake_extrinsic( f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" ) - response.data = { - "origin_stake_before": stake_in_origin, - "origin_stake_after": origin_stake, - "destination_stake_before": stake_in_destination, - "destination_stake_after": dest_stake, - } return response - if safe_swapping and "Custom error: 8" in response.message: - response.message = "Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." - logging.error(f"[red]{response.message}[/red]") return response @@ -314,32 +305,34 @@ async def swap_stake_extrinsic( return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) -async def move_stake_extrinsic( +async def swap_stake_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - origin_hotkey_ss58: str, + hotkey_ss58: str, origin_netuid: int, - destination_hotkey_ss58: str, destination_netuid: int, - amount: Optional[Balance] = None, - move_all_stake: bool = False, + amount: Balance, + safe_swapping: 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, ) -> ExtrinsicResponse: """ - Moves stake from one hotkey to another within subnets in the Bittensor network. + Swaps stake from one subnet to another for a given hotkey in the Bittensor network. 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. + 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_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 transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. @@ -347,57 +340,88 @@ async def move_stake_extrinsic( 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: ExtrinsicResponse: The result object of the extrinsic execution. """ + try: if not ( unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) ).success: return unlocked - if not amount and not move_all_stake: - return ExtrinsicResponse( - False, - "Please specify an `amount` or `move_all_stake` argument to move stake.", - ).with_log() + amount.set_unit(netuid=origin_netuid) # Check sufficient stake stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=origin_hotkey_ss58, - destination_hotkey_ss58=destination_hotkey_ss58, - origin_netuid=origin_netuid, - destination_netuid=destination_netuid, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, ) - if move_all_stake: - amount = stake_in_origin - elif stake_in_origin < amount: + if stake_in_origin < amount: return ExtrinsicResponse( False, - f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", ).with_log() - amount.set_unit(netuid=origin_netuid) + if safe_swapping: + origin_pool, destination_pool = await asyncio.gather( + subtensor.subnet(netuid=origin_netuid), + subtensor.subnet(netuid=destination_netuid), + ) + swap_rate_ratio = origin_pool.price.rao / destination_pool.price.rao + swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - logging.debug( - 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]" - ) - call = await subtensor.substrate.compose_call( + call_function = "swap_stake_limit" + call_params = MoveStakeParams.swap_stake_limit( + hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + allow_partial_stake=allow_partial_stake, + rate_tolerance=rate_tolerance, + origin_pool=origin_pool, + destination_pool=destination_pool, + ) + + logging.debug( + f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]\n" + f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " + f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" + ) + call_params.update( + { + "limit_price": swap_rate_ratio_with_tolerance, + "allow_partial": allow_partial_stake, + } + ) + + else: + call_function = "swap_stake" + call_params = MoveStakeParams.swap_stake( + hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + ) + logging.debug( + f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]" + ) + + call = await subtensor.compose_call( call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": origin_hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey_ss58, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, + call_function=call_function, + call_params=call_params, ) response = await subtensor.sign_and_send_extrinsic( @@ -410,6 +434,14 @@ async def move_stake_extrinsic( ) if response.success: + sim_swap = await subtensor.sim_swap( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) + if not wait_for_finalization and not wait_for_inclusion: return response @@ -418,13 +450,14 @@ 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_ss58, - destination_hotkey_ss58=destination_hotkey_ss58, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=wallet.coldkeypub.ss58_address, origin_netuid=origin_netuid, destination_netuid=destination_netuid, ) + logging.debug( f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" ) @@ -440,6 +473,9 @@ async def move_stake_extrinsic( } return response + if safe_swapping and "Custom error: 8" in response.message: + response.message = "Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." + logging.error(f"[red]{response.message}[/red]") return response diff --git a/bittensor/core/extrinsics/asyncex/registration.py b/bittensor/core/extrinsics/asyncex/registration.py index 7b455d087b..ccc9d602cb 100644 --- a/bittensor/core/extrinsics/asyncex/registration.py +++ b/bittensor/core/extrinsics/asyncex/registration.py @@ -6,6 +6,7 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.errors import RegistrationError +from bittensor.core.extrinsics.params import RegistrationParams from bittensor.core.types import ExtrinsicResponse from bittensor.utils.btlogging import logging from bittensor.utils.registration import create_pow_async, log_no_torch_error, torch @@ -42,22 +43,26 @@ async def burned_register_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked block_hash = await subtensor.substrate.get_chain_head() - if not await subtensor.subnet_exists(netuid, block_hash=block_hash): + if not await subtensor.subnet_exists(netuid=netuid, block_hash=block_hash): return ExtrinsicResponse( False, f"Subnet {netuid} does not exist." ).with_log() neuron, old_balance, recycle_amount = await asyncio.gather( subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid, block_hash=block_hash + netuid=netuid, + hotkey_ss58=wallet.hotkey.ss58_address, + block_hash=block_hash, ), subtensor.get_balance( - wallet.coldkeypub.ss58_address, block_hash=block_hash + address=wallet.coldkeypub.ss58_address, block_hash=block_hash ), subtensor.recycle(netuid=netuid, block_hash=block_hash), ) @@ -75,13 +80,12 @@ async def burned_register_extrinsic( logging.debug(f"Recycling {recycle_amount} to register on subnet:{netuid}") - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="burned_register", - call_params={ - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - }, + call_params=RegistrationParams.burned_register( + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address + ), ) response = await subtensor.sign_and_send_extrinsic( call=call, @@ -101,7 +105,9 @@ async def burned_register_extrinsic( return response # Successful registration, final check for neuron and pubkey - new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address) + new_balance = await subtensor.get_balance( + address=wallet.coldkeypub.ss58_address + ) logging.debug( f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" @@ -160,7 +166,9 @@ async def register_subnet_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked @@ -173,12 +181,12 @@ async def register_subnet_extrinsic( f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO.", ).with_log() - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="register_network", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - }, + call_params=RegistrationParams.register_network( + hotkey_ss58=wallet.hotkey.ss58_address + ), ) response = await subtensor.sign_and_send_extrinsic( @@ -250,7 +258,9 @@ async def register_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked @@ -332,17 +342,17 @@ async def register_extrinsic( else: # check if a pow result is still valid while not await pow_result.is_stale_async(subtensor=subtensor): - call = await subtensor.substrate.compose_call( + call = await subtensor.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, - }, + call_params=RegistrationParams.register( + netuid=netuid, + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=wallet.hotkey.ss58_address, + block_number=pow_result.block_number, + nonce=pow_result.nonce, + work=[int(byte_) for byte_ in pow_result.seal], + ), ) response = await subtensor.sign_and_send_extrinsic( call=call, @@ -437,25 +447,27 @@ async def set_subnet_identity_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="set_subnet_identity", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "netuid": netuid, - "subnet_name": subnet_name, - "github_repo": github_repo, - "subnet_contact": subnet_contact, - "subnet_url": subnet_url, - "logo_url": logo_url, - "discord": discord, - "description": description, - "additional": additional, - }, + call_params=RegistrationParams.set_subnet_identity( + hotkey_ss58=wallet.hotkey.ss58_address, + netuid=netuid, + subnet_name=subnet_name, + github_repo=github_repo, + subnet_contact=subnet_contact, + subnet_url=subnet_url, + logo_url=logo_url, + discord=discord, + description=description, + additional=additional, + ), ) response = await subtensor.sign_and_send_extrinsic( diff --git a/bittensor/core/extrinsics/asyncex/root.py b/bittensor/core/extrinsics/asyncex/root.py index cae2149052..45ea91878e 100644 --- a/bittensor/core/extrinsics/asyncex/root.py +++ b/bittensor/core/extrinsics/asyncex/root.py @@ -1,6 +1,7 @@ import asyncio from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.params import RootParams from bittensor.core.types import ExtrinsicResponse from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -61,7 +62,9 @@ async def root_register_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked @@ -100,10 +103,10 @@ async def root_register_extrinsic( if is_registered: return ExtrinsicResponse(message="Already registered on root network.") - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="root_register", - call_params={"hotkey": wallet.hotkey.ss58_address}, + call_params=RootParams.root_register(wallet.hotkey.ss58_address), ) response = await subtensor.sign_and_send_extrinsic( call=call, diff --git a/bittensor/core/extrinsics/asyncex/serving.py b/bittensor/core/extrinsics/asyncex/serving.py index 98c9938db6..71868f0381 100644 --- a/bittensor/core/extrinsics/asyncex/serving.py +++ b/bittensor/core/extrinsics/asyncex/serving.py @@ -2,8 +2,8 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.errors import MetadataError -from bittensor.core.settings import version_as_int -from bittensor.core.types import AxonServeCallParams, ExtrinsicResponse +from bittensor.core.extrinsics.params.serving import ServingParams +from bittensor.core.types import ExtrinsicResponse from bittensor.utils import ( networking as net, Certificate, @@ -11,9 +11,9 @@ from bittensor.utils.btlogging import logging if TYPE_CHECKING: + from bittensor_wallet import Wallet from bittensor.core.axon import Axon from bittensor.core.async_subtensor import AsyncSubtensor - from bittensor_wallet import Wallet async def serve_extrinsic( @@ -55,29 +55,25 @@ async def serve_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: - signing_keypair = "hotkey" if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, signing_keypair + wallet, raise_error, unlock_type="both" ) ).success: return unlocked - params = AxonServeCallParams( - **{ - "version": version_as_int, - "ip": net.ip_to_int(ip), - "port": port, - "ip_type": net.ip_version(ip), - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - "protocol": protocol, - "placeholder1": placeholder1, - "placeholder2": placeholder2, - "certificate": certificate, - } + params = ServingParams.serve_axon_and_tls( + hotkey_ss58=wallet.hotkey.ss58_address, + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuid=netuid, + ip=ip, + port=port, + protocol=protocol, + placeholder1=placeholder1, + placeholder2=placeholder2, + certificate=certificate, ) + logging.debug("Checking axon ...") neuron = await subtensor.get_neuron_for_pubkey_and_subnet( wallet.hotkey.ss58_address, netuid=netuid @@ -98,7 +94,7 @@ async def serve_extrinsic( else: call_function = "serve_axon_tls" - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function=call_function, call_params=params.dict(), @@ -108,7 +104,7 @@ async def serve_extrinsic( wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - sign_with=signing_keypair, + sign_with="hotkey", period=period, raise_error=raise_error, ) @@ -156,13 +152,6 @@ async def serve_axon_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: - if not ( - unlocked := ExtrinsicResponse.unlock_wallet( - axon.wallet, raise_error, "hotkey" - ) - ).success: - return unlocked - external_port = axon.external_port # ---- Get external ip ---- @@ -260,13 +249,10 @@ async def publish_metadata_extrinsic( if reset_bonds: fields.append({"ResetBondsFlag": b""}) - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="Commitments", call_function="set_commitment", - call_params={ - "netuid": netuid, - "info": {"fields": [fields]}, - }, + call_params=ServingParams.set_commitment(netuid=netuid, info_fields=fields), ) response = await subtensor.sign_and_send_extrinsic( diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 0aacf21c64..7849323fd1 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -4,6 +4,7 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.errors import BalanceTypeError +from bittensor.core.extrinsics.params import StakingParams from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils import format_error_message @@ -95,45 +96,40 @@ async def add_stake_extrinsic( logging.debug(f"\t\twallet: {wallet.name}") return ExtrinsicResponse(False, f"{message}.").with_log() - 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 - price_with_tolerance = ( - base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) + call_function = "add_stake_limit" + call_params = StakingParams.add_stake_limit( + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + allow_partial_stake=allow_partial_stake, + rate_tolerance=rate_tolerance, + pool=pool, ) logging.debug( f"Safe Staking to: [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"price limit: [green]{call_params.get('limit_price')}[/green], " + f"original price: [green]{pool.price}[/green], " f"with partial stake: [green]{allow_partial_stake}[/green] " f"on [blue]{subtensor.network}[/blue]." ) - 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: + call_function = "add_stake" + call_params = StakingParams.add_stake( + netuid=netuid, hotkey_ss58=hotkey_ss58, amount=amount + ) + logging.debug( f"Staking to: [blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " f"on [blue]{subtensor.network}[/blue]." ) - call_function = "add_stake" - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function=call_function, call_params=call_params, @@ -149,6 +145,14 @@ async def add_stake_extrinsic( raise_error=raise_error, ) if response.success: + sim_swap = await subtensor.sim_swap( + origin_netuid=0, + destination_netuid=netuid, + amount=(amount - response.extrinsic_fee), + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(netuid) + if not wait_for_finalization and not wait_for_inclusion: return response logging.debug("[green]Finalized.[/green]") @@ -446,13 +450,12 @@ async def set_auto_stake_extrinsic( ).success: return unlocked - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="set_coldkey_auto_stake_hotkey", - call_params={ - "netuid": netuid, - "hotkey": hotkey_ss58, - }, + call_params=StakingParams.set_coldkey_auto_stake_hotkey( + netuid, hotkey_ss58 + ), ) response = await subtensor.sign_and_send_extrinsic( call=call, diff --git a/bittensor/core/extrinsics/asyncex/start_call.py b/bittensor/core/extrinsics/asyncex/start_call.py index f40f8e77e1..7b933eec10 100644 --- a/bittensor/core/extrinsics/asyncex/start_call.py +++ b/bittensor/core/extrinsics/asyncex/start_call.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.params import StartCallParams from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: @@ -40,21 +41,20 @@ async def start_call_extrinsic( ).success: return unlocked - async with subtensor.substrate as substrate: - start_call = await substrate.compose_call( - call_module="SubtensorModule", - call_function="start_call", - call_params={"netuid": netuid}, - ) - - return await subtensor.sign_and_send_extrinsic( - call=start_call, - wallet=wallet, - wait_for_inclusion=wait_for_inclusion, - wait_for_finalization=wait_for_finalization, - period=period, - raise_error=raise_error, - ) + call = await subtensor.compose_call( + call_module="SubtensorModule", + call_function="start_call", + call_params=StartCallParams.start_call(netuid=netuid), + ) + + 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, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/take.py b/bittensor/core/extrinsics/asyncex/take.py index 96416e18c2..f5fadf4111 100644 --- a/bittensor/core/extrinsics/asyncex/take.py +++ b/bittensor/core/extrinsics/asyncex/take.py @@ -2,6 +2,7 @@ from bittensor_wallet.bittensor_wallet import Wallet +from bittensor.core.extrinsics.params import TakeParams from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: @@ -43,13 +44,10 @@ async def set_take_extrinsic( ).success: return unlocked - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function=action, - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, + call_params=TakeParams.increase_decrease_take(hotkey_ss58, take), ) return await subtensor.sign_and_send_extrinsic( call=call, diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index 3d4b718dfc..7e5eea288f 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -1,11 +1,11 @@ import asyncio from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.params import get_transfer_fn_params from bittensor.core.settings import NETWORK_EXPLORER_MAP, DEFAULT_NETWORK from bittensor.core.types import ExtrinsicResponse from bittensor.utils import ( get_explorer_url_for_network, - get_transfer_fn_params, is_valid_bittensor_address_or_public_key, ) from bittensor.utils.balance import Balance @@ -100,7 +100,7 @@ async def transfer_extrinsic( amount, destination, keep_alive ) - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="Balances", call_function=call_function, call_params=call_params, @@ -114,7 +114,7 @@ async def transfer_extrinsic( period=period, raise_error=raise_error, ) - response.transaction_fee = fee + response.transaction_tao_fee = fee if response.success: block_hash = await subtensor.get_block_hash() diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index d666716b56..9c90a05257 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -4,6 +4,7 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.errors import BalanceTypeError +from bittensor.core.extrinsics.params import UnstakingParams from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.core.types import ExtrinsicResponse from bittensor.core.types import UIDs @@ -87,41 +88,42 @@ async def unstake_extrinsic( } if safe_unstaking: 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) + call_function = "remove_stake_limit" + call_params = UnstakingParams.remove_stake_limit( + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + allow_partial_stake=allow_partial_stake, + rate_tolerance=rate_tolerance, + pool=pool, + ) logging_message = ( f"Safe Unstaking from: " 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], " + f"price limit: [green]{Balance.from_rao(call_params['limit_price'])}[/green], " + f"original price: [green]{pool.price.tao}[/green], " f"with partial unstake: [green]{allow_partial_stake}[/green] " f"on [blue]{subtensor.network}[/blue]" ) - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "remove_stake_limit" else: + call_function = "remove_stake" + call_params = UnstakingParams.remove_stake( + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + ) logging_message = ( - f"Unstaking from: " + f":satellite: [magenta]Unstaking from:[/magenta] " f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " f"on [blue]{subtensor.network}[/blue]" ) - call_function = "remove_stake" logging.debug(logging_message) - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function=call_function, call_params=call_params, @@ -138,8 +140,15 @@ async def unstake_extrinsic( raise_error=raise_error, ) - if response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. + if response.success: + sim_swap = await subtensor.sim_swap( + origin_netuid=netuid, + destination_netuid=0, + amount=amount, + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(netuid) + if not wait_for_finalization and not wait_for_inclusion: return response @@ -184,8 +193,8 @@ async def unstake_extrinsic( async def unstake_all_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", - hotkey_ss58: str, netuid: int, + hotkey_ss58: str, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, raise_error: bool = False, @@ -197,8 +206,8 @@ async def unstake_all_extrinsic( Parameters: subtensor: Subtensor instance. wallet: The wallet of the stake owner. - hotkey_ss58: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. + hotkey_ss58: The SS58 address of the hotkey to unstake from. 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. period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -217,34 +226,30 @@ async def unstake_all_extrinsic( ).success: return unlocked - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "limit_price": None, - } - - if rate_tolerance: - current_price = (await subtensor.subnet(netuid=netuid)).price - limit_price = current_price * (1 - rate_tolerance) - call_params.update({"limit_price": limit_price}) + pool = await subtensor.subnet(netuid=netuid) if rate_tolerance else None + call_params = UnstakingParams.remove_stake_full_limit( + netuid=netuid, + hotkey_ss58=hotkey_ss58, + rate_tolerance=rate_tolerance, + pool=pool, + ) - async with subtensor.substrate as substrate: - call = await substrate.compose_call( - call_module="SubtensorModule", - call_function="remove_stake_full_limit", - call_params=call_params, - ) + call = await subtensor.compose_call( + call_module="SubtensorModule", + call_function="remove_stake_full_limit", + call_params=call_params, + ) - return 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", - use_nonce=True, - period=period, - raise_error=raise_error, - ) + return 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", + use_nonce=True, + period=period, + raise_error=raise_error, + ) except Exception as error: return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) diff --git a/bittensor/core/extrinsics/asyncex/weights.py b/bittensor/core/extrinsics/asyncex/weights.py index e0bda0effa..b6ab9eefba 100644 --- a/bittensor/core/extrinsics/asyncex/weights.py +++ b/bittensor/core/extrinsics/asyncex/weights.py @@ -4,6 +4,7 @@ from bittensor_drand import get_encrypted_commit +from bittensor.core.extrinsics.params import WeightsParams from bittensor.core.settings import version_as_int from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights from bittensor.utils import get_mechid_storage_index @@ -89,16 +90,16 @@ async def commit_timelocked_weights_extrinsic( hotkey=wallet.hotkey.public_key, ) - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="commit_timelocked_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit": commit_for_reveal, - "reveal_round": reveal_round, - "commit_reveal_version": commit_reveal_version, - }, + call_params=WeightsParams.commit_timelocked_mechanism_weights( + netuid=netuid, + mechid=mechid, + commit_for_reveal=commit_for_reveal, + reveal_round=reveal_round, + commit_reveal_version=commit_reveal_version, + ), ) response = await subtensor.sign_and_send_extrinsic( call=call, @@ -182,14 +183,14 @@ async def commit_weights_extrinsic( version_key=version_key, ) - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="commit_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit_hash": commit_hash, - }, + call_params=WeightsParams.commit_mechanism_weights( + netuid=netuid, + mechid=mechid, + commit_hash=commit_hash, + ), ) response = await subtensor.sign_and_send_extrinsic( call=call, @@ -262,17 +263,17 @@ async def reveal_weights_extrinsic( # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="reveal_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "uids": uids, - "values": weights, - "salt": salt, - "version_key": version_key, - }, + call_params=WeightsParams.reveal_mechanism_weights( + netuid=netuid, + mechid=mechid, + uids=uids, + weights=weights, + salt=salt, + version_key=version_key, + ), ) response = await subtensor.sign_and_send_extrinsic( call=call, @@ -343,16 +344,16 @@ async def set_weights_extrinsic( # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="SubtensorModule", call_function="set_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "dests": uids, - "weights": weights, - "version_key": version_key, - }, + call_params=WeightsParams.set_mechanism_weights( + netuid=netuid, + mechid=mechid, + uids=uids, + weights=weights, + version_key=version_key, + ), ) response = await subtensor.sign_and_send_extrinsic( call=call, diff --git a/bittensor/core/extrinsics/children.py b/bittensor/core/extrinsics/children.py index 1d81ea0a8a..31f241dd7b 100644 --- a/bittensor/core/extrinsics/children.py +++ b/bittensor/core/extrinsics/children.py @@ -1,8 +1,8 @@ from typing import TYPE_CHECKING, Optional -from bittensor.core.types import ExtrinsicResponse -from bittensor.utils import float_to_u64 +from bittensor.core.extrinsics.params import ChildrenParams from bittensor.core.extrinsics.utils import sudo_call_extrinsic +from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -58,20 +58,10 @@ def set_children_extrinsic( ).success: return unlocked - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="set_children", - call_params={ - "children": [ - ( - float_to_u64(proportion), - child_hotkey, - ) - for proportion, child_hotkey in children - ], - "hotkey": hotkey_ss58, - "netuid": netuid, - }, + call_params=ChildrenParams.set_children(hotkey_ss58, netuid, children), ) response = subtensor.sign_and_send_extrinsic( @@ -119,7 +109,7 @@ def root_set_pending_childkey_cooldown_extrinsic( wallet=wallet, call_module="SubtensorModule", call_function="set_pending_childkey_cooldown", - call_params={"cooldown": cooldown}, + call_params=ChildrenParams.set_pending_childkey_cooldown(cooldown), period=period, raise_error=raise_error, wait_for_inclusion=wait_for_inclusion, diff --git a/bittensor/core/extrinsics/liquidity.py b/bittensor/core/extrinsics/liquidity.py index f86604a202..e3d1fba25f 100644 --- a/bittensor/core/extrinsics/liquidity.py +++ b/bittensor/core/extrinsics/liquidity.py @@ -1,8 +1,8 @@ from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.params import LiquidityParams from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance -from bittensor.utils.liquidity import price_to_tick if TYPE_CHECKING: from bittensor_wallet import Wallet @@ -47,24 +47,26 @@ def add_liquidity_extrinsic( `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ try: + unlock_type = "coldkey" if hotkey_ss58 else "both" if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type + ) ).success: return unlocked - tick_low = price_to_tick(price_low.tao) - tick_high = price_to_tick(price_high.tao) + hotkey_ss58 = hotkey_ss58 or wallet.hotkey.ss58_address - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="Swap", call_function="add_liquidity", - call_params={ - "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, - "netuid": netuid, - "tick_low": tick_low, - "tick_high": tick_high, - "liquidity": liquidity.rao, - }, + call_params=LiquidityParams.add_liquidity( + netuid=netuid, + hotkey_ss58=hotkey_ss58 or wallet.hotkey.ss58_address, + liquidity=liquidity, + price_low=price_low, + price_high=price_high, + ), ) return subtensor.sign_and_send_extrinsic( @@ -114,20 +116,23 @@ def modify_liquidity_extrinsic( `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ try: + unlock_type = "coldkey" if hotkey_ss58 else "both" if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type + ) ).success: return unlocked - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="Swap", call_function="modify_position", - call_params={ - "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - "liquidity_delta": liquidity_delta.rao, - }, + call_params=LiquidityParams.modify_position( + netuid=netuid, + hotkey_ss58=hotkey_ss58 or wallet.hotkey.ss58_address, + position_id=position_id, + liquidity_delta=liquidity_delta, + ), ) return subtensor.sign_and_send_extrinsic( @@ -175,19 +180,22 @@ def remove_liquidity_extrinsic( `toggle_user_liquidity_extrinsic` to enable/disable user liquidity. """ try: + unlock_type = "coldkey" if hotkey_ss58 else "both" if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type + ) ).success: return unlocked - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="Swap", call_function="remove_liquidity", - call_params={ - "hotkey": hotkey_ss58 or wallet.hotkey.ss58_address, - "netuid": netuid, - "position_id": position_id, - }, + call_params=LiquidityParams.remove_liquidity( + netuid=netuid, + hotkey_ss58=hotkey_ss58 or wallet.hotkey.ss58_address, + position_id=position_id, + ), ) return subtensor.sign_and_send_extrinsic( @@ -235,10 +243,13 @@ def toggle_user_liquidity_extrinsic( ).success: return unlocked - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="Swap", call_function="toggle_user_liquidity", - call_params={"netuid": netuid, "enable": enable}, + call_params=LiquidityParams.toggle_user_liquidity( + netuid=netuid, + enable=enable, + ), ) return subtensor.sign_and_send_extrinsic( diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index 610c4c218a..a275488956 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -1,5 +1,6 @@ from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.params import MoveStakeParams from bittensor.core.types import ExtrinsicResponse from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -35,30 +36,32 @@ def _get_stake_in_origin_and_dest( return stake_in_origin, stake_in_destination -def transfer_stake_extrinsic( +def move_stake_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - destination_coldkey_ss58: str, - hotkey_ss58: str, origin_netuid: int, + origin_hotkey_ss58: str, destination_netuid: int, - amount: Balance, + destination_hotkey_ss58: str, + amount: Optional[Balance] = None, + move_all_stake: bool = False, period: Optional[int] = None, raise_error: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ) -> ExtrinsicResponse: """ - Transfers stake from one subnet to another while changing the coldkey owner. + Moves stake to a different hotkey and/or subnet while keeping the same coldkey owner. 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. + subtensor: Subtensor instance. + wallet: The wallet to move stake from. + origin_netuid: The netuid of the source subnet. + origin_hotkey_ss58: The SS58 address of the source hotkey. + destination_netuid: The netuid of the destination subnet. + destination_hotkey_ss58: The SS58 address of the destination hotkey. + 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. @@ -75,42 +78,47 @@ def transfer_stake_extrinsic( ).success: return unlocked - amount.set_unit(netuid=origin_netuid) + if not amount and not move_all_stake: + return ExtrinsicResponse( + False, + "Please specify an `amount` or `move_all_stake` argument to move stake.", + ).with_log() # Check sufficient stake stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, origin_netuid=origin_netuid, + origin_hotkey_ss58=origin_hotkey_ss58, destination_netuid=destination_netuid, + destination_hotkey_ss58=destination_hotkey_ss58, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=destination_coldkey_ss58, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) - if stake_in_origin < amount: + if move_all_stake: + amount = stake_in_origin + + elif stake_in_origin < amount: return ExtrinsicResponse( False, - f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", ).with_log() + amount.set_unit(netuid=origin_netuid) + logging.debug( - f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " - f"[blue]{destination_coldkey_ss58}[/blue]" - ) - logging.debug( - f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " - f"[yellow]{destination_netuid}[/yellow]" + 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 = subtensor.compose_call( call_module="SubtensorModule", - call_function="transfer_stake", - call_params={ - "destination_coldkey": destination_coldkey_ss58, - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, + call_function="move_stake", + call_params=MoveStakeParams.move_stake( + origin_netuid=origin_netuid, + origin_hotkey_ss58=origin_hotkey_ss58, + destination_netuid=destination_netuid, + destination_hotkey_ss58=destination_hotkey_ss58, + amount=amount, + ), ) response = subtensor.sign_and_send_extrinsic( @@ -123,18 +131,28 @@ def transfer_stake_extrinsic( ) if response.success: + sim_swap = subtensor.sim_swap( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) + if not wait_for_finalization and not wait_for_inclusion: return response + logging.debug("[green]Finalized[/green]") + # Get updated stakes origin_stake, dest_stake = _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=hotkey_ss58, - destination_hotkey_ss58=hotkey_ss58, + 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=destination_coldkey_ss58, + destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) logging.debug( f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" @@ -143,6 +161,12 @@ def transfer_stake_extrinsic( f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" ) + response.data = { + "origin_stake_before": stake_in_origin, + "origin_stake_after": origin_stake, + "destination_stake_before": stake_in_destination, + "destination_stake_after": dest_stake, + } return response logging.error(f"[red]{response.message}[/red]") @@ -152,34 +176,30 @@ def transfer_stake_extrinsic( return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) -def swap_stake_extrinsic( +def transfer_stake_extrinsic( subtensor: "Subtensor", wallet: "Wallet", + destination_coldkey_ss58: str, hotkey_ss58: str, origin_netuid: int, destination_netuid: int, amount: Balance, - safe_swapping: 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, ) -> ExtrinsicResponse: """ - Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. + Transfers stake from one subnet to another while changing the coldkey owner. 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_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%). + 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. @@ -187,7 +207,6 @@ def swap_stake_extrinsic( 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: ExtrinsicResponse: The result object of the extrinsic execution. """ @@ -207,54 +226,32 @@ def swap_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, ) - if stake_in_origin < amount: return ExtrinsicResponse( False, f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", ).with_log() - call_params = { - "hotkey": hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - } - - 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 - swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - - logging.debug( - f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]\n" - f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " - f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" - ) - call_params.update( - { - "limit_price": swap_rate_ratio_with_tolerance, - "allow_partial": allow_partial_stake, - } - ) - call_function = "swap_stake_limit" - else: - logging.debug( - f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" - f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " - f"[green]{destination_netuid}[/green]" - ) - call_function = "swap_stake" - - call = subtensor.substrate.compose_call( + logging.debug( + f"Transferring stake from coldkey [blue]{wallet.coldkeypub.ss58_address}[/blue] to coldkey " + f"[blue]{destination_coldkey_ss58}[/blue]" + ) + logging.debug( + f"Amount: [green]{amount}[/green] from netuid [yellow]{origin_netuid}[/yellow] to netuid " + f"[yellow]{destination_netuid}[/yellow]" + ) + call = subtensor.compose_call( call_module="SubtensorModule", - call_function=call_function, - call_params=call_params, + call_function="transfer_stake", + call_params=MoveStakeParams.transfer_stake( + hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_coldkey_ss58=destination_coldkey_ss58, + destination_netuid=destination_netuid, + amount=amount, + ), ) response = subtensor.sign_and_send_extrinsic( @@ -267,11 +264,17 @@ def swap_stake_extrinsic( ) if response.success: + sim_swap = subtensor.sim_swap( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) + if not wait_for_finalization and not wait_for_inclusion: return response - logging.debug("[green]Finalized[/green]") - # Get updated stakes origin_stake, dest_stake = _get_stake_in_origin_and_dest( subtensor=subtensor, @@ -280,9 +283,8 @@ def swap_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, - destination_coldkey_ss58=wallet.coldkeypub.ss58_address, + destination_coldkey_ss58=destination_coldkey_ss58, ) - logging.debug( f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" ) @@ -290,17 +292,8 @@ def swap_stake_extrinsic( f"Destination Stake: [blue]{stake_in_destination}[/blue] :arrow_right: [green]{dest_stake}[/green]" ) - response.data = { - "origin_stake_before": stake_in_origin, - "origin_stake_after": origin_stake, - "destination_stake_before": stake_in_destination, - "destination_stake_after": dest_stake, - } return response - if safe_swapping and "Custom error: 8" in response.message: - response.message = "Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." - logging.error(f"[red]{response.message}[/red]") return response @@ -308,32 +301,34 @@ def swap_stake_extrinsic( return ExtrinsicResponse.from_exception(raise_error=raise_error, error=error) -def move_stake_extrinsic( +def swap_stake_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - origin_hotkey_ss58: str, + hotkey_ss58: str, origin_netuid: int, - destination_hotkey_ss58: str, destination_netuid: int, - amount: Optional[Balance] = None, - move_all_stake: bool = False, + amount: Balance, + safe_swapping: 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, ) -> ExtrinsicResponse: """ - Moves stake to a different hotkey and/or subnet while keeping the same coldkey owner. + Moves stake between subnets while keeping the same coldkey-hotkey pair ownership. 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. + 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_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 transaction is not included in a block within that number of blocks, it will expire and be rejected. You can think of it as an expiration date for the transaction. @@ -341,6 +336,7 @@ def move_stake_extrinsic( 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: ExtrinsicResponse: The result object of the extrinsic execution. """ @@ -350,47 +346,68 @@ def move_stake_extrinsic( ).success: return unlocked - if not amount and not move_all_stake: - return ExtrinsicResponse( - False, - "Please specify an `amount` or `move_all_stake` argument to move stake.", - ).with_log() + amount.set_unit(netuid=origin_netuid) # Check sufficient stake stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=origin_hotkey_ss58, - destination_hotkey_ss58=destination_hotkey_ss58, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=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: + if stake_in_origin < amount: return ExtrinsicResponse( False, - f"Insufficient stake in origin hotkey: {origin_hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", + f"Insufficient stake in origin hotkey: {hotkey_ss58}. Stake: {stake_in_origin}, amount: {amount}.", ).with_log() - amount.set_unit(netuid=origin_netuid) + 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 + swap_rate_ratio_with_tolerance = swap_rate_ratio * (1 + rate_tolerance) - logging.debug( - 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_function = "swap_stake_limit" + call_params = MoveStakeParams.swap_stake_limit( + hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + allow_partial_stake=allow_partial_stake, + rate_tolerance=rate_tolerance, + origin_pool=origin_pool, + destination_pool=destination_pool, + ) + + logging.debug( + f"Swapping stake with safety for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]\n" + f"Current price ratio: [green]{swap_rate_ratio:.4f}[/green], " + f"Ratio with tolerance: [green]{swap_rate_ratio_with_tolerance:.4f}[/green]" + ) + else: + call_function = "swap_stake" + call_params = MoveStakeParams.swap_stake( + hotkey_ss58=hotkey_ss58, + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + ) + logging.debug( + f"Swapping stake for hotkey [blue]{hotkey_ss58}[/blue]\n" + f"Amount: [green]{amount}[/green] from netuid [green]{origin_netuid}[/green] to netuid " + f"[green]{destination_netuid}[/green]" + ) + + call = subtensor.compose_call( call_module="SubtensorModule", - call_function="move_stake", - call_params={ - "origin_hotkey": origin_hotkey_ss58, - "origin_netuid": origin_netuid, - "destination_hotkey": destination_hotkey_ss58, - "destination_netuid": destination_netuid, - "alpha_amount": amount.rao, - }, + call_function=call_function, + call_params=call_params, ) response = subtensor.sign_and_send_extrinsic( @@ -403,6 +420,14 @@ def move_stake_extrinsic( ) if response.success: + sim_swap = subtensor.sim_swap( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) + if not wait_for_finalization and not wait_for_inclusion: return response @@ -411,13 +436,14 @@ def move_stake_extrinsic( # Get updated stakes origin_stake, dest_stake = _get_stake_in_origin_and_dest( subtensor=subtensor, - origin_hotkey_ss58=origin_hotkey_ss58, - destination_hotkey_ss58=destination_hotkey_ss58, + origin_hotkey_ss58=hotkey_ss58, + destination_hotkey_ss58=hotkey_ss58, origin_netuid=origin_netuid, destination_netuid=destination_netuid, origin_coldkey_ss58=wallet.coldkeypub.ss58_address, destination_coldkey_ss58=wallet.coldkeypub.ss58_address, ) + logging.debug( f"Origin Stake: [blue]{stake_in_origin}[/blue] :arrow_right: [green]{origin_stake}[/green]" ) @@ -433,6 +459,9 @@ def move_stake_extrinsic( } return response + if safe_swapping and "Custom error: 8" in response.message: + response.message = "Price ratio exceeded tolerance limit. Either increase price tolerance or enable partial staking." + logging.error(f"[red]{response.message}[/red]") return response diff --git a/bittensor/core/extrinsics/registration.py b/bittensor/core/extrinsics/registration.py index 351af9ecc1..95abed4bd2 100644 --- a/bittensor/core/extrinsics/registration.py +++ b/bittensor/core/extrinsics/registration.py @@ -6,6 +6,7 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.errors import RegistrationError +from bittensor.core.extrinsics.params import RegistrationParams from bittensor.core.types import ExtrinsicResponse from bittensor.utils.btlogging import logging from bittensor.utils.registration import create_pow, log_no_torch_error, torch @@ -42,21 +43,25 @@ def burned_register_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked block = subtensor.get_current_block() - if not subtensor.subnet_exists(netuid, block=block): + if not subtensor.subnet_exists(netuid=netuid, block=block): return ExtrinsicResponse( False, f"Subnet {netuid} does not exist." ).with_log() neuron = subtensor.get_neuron_for_pubkey_and_subnet( - wallet.hotkey.ss58_address, netuid=netuid, block=block + netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address, block=block ) - old_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address, block=block) + old_balance = subtensor.get_balance( + address=wallet.coldkeypub.ss58_address, block=block + ) if not neuron.is_null: message = "Already registered." @@ -72,13 +77,13 @@ def burned_register_extrinsic( recycle_amount = subtensor.recycle(netuid=netuid, block=block) logging.debug(f"Recycling {recycle_amount} to register on subnet:{netuid}") - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="burned_register", - call_params={ - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - }, + call_params=RegistrationParams.burned_register( + netuid=netuid, + hotkey_ss58=wallet.hotkey.ss58_address, + ), ) response = subtensor.sign_and_send_extrinsic( call=call, @@ -98,7 +103,7 @@ def burned_register_extrinsic( return response # Successful registration, final check for neuron and pubkey - new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) + new_balance = subtensor.get_balance(address=wallet.coldkeypub.ss58_address) logging.debug( f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" @@ -157,7 +162,9 @@ def register_subnet_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked @@ -170,12 +177,12 @@ def register_subnet_extrinsic( f"Insufficient balance {balance} to register subnet. Current burn cost is {burn_cost} TAO.", ).with_log() - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="register_network", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - }, + call_params=RegistrationParams.register_network( + hotkey_ss58=wallet.hotkey.ss58_address + ), ) response = subtensor.sign_and_send_extrinsic( @@ -244,7 +251,9 @@ def register_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked @@ -327,17 +336,17 @@ def register_extrinsic( # check if a pow result is still valid while not pow_result.is_stale(subtensor=subtensor): # create extrinsic call - call = subtensor.substrate.compose_call( + call = subtensor.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, - }, + call_params=RegistrationParams.register( + netuid=netuid, + coldkey_ss58=wallet.coldkeypub.ss58_address, + hotkey_ss58=wallet.hotkey.ss58_address, + block_number=pow_result.block_number, + nonce=pow_result.nonce, + work=[int(byte_) for byte_ in pow_result.seal], + ), ) response = subtensor.sign_and_send_extrinsic( call=call, @@ -432,25 +441,27 @@ def set_subnet_identity_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="set_subnet_identity", - call_params={ - "hotkey": wallet.hotkey.ss58_address, - "netuid": netuid, - "subnet_name": subnet_name, - "github_repo": github_repo, - "subnet_contact": subnet_contact, - "subnet_url": subnet_url, - "logo_url": logo_url, - "discord": discord, - "description": description, - "additional": additional, - }, + call_params=RegistrationParams.set_subnet_identity( + hotkey_ss58=wallet.hotkey.ss58_address, + netuid=netuid, + subnet_name=subnet_name, + github_repo=github_repo, + subnet_contact=subnet_contact, + subnet_url=subnet_url, + logo_url=logo_url, + discord=discord, + description=description, + additional=additional, + ), ) response = subtensor.sign_and_send_extrinsic( diff --git a/bittensor/core/extrinsics/root.py b/bittensor/core/extrinsics/root.py index 91f3753c16..b924a60018 100644 --- a/bittensor/core/extrinsics/root.py +++ b/bittensor/core/extrinsics/root.py @@ -1,6 +1,7 @@ import time from typing import Optional, TYPE_CHECKING +from bittensor.core.extrinsics.params import RootParams from bittensor.core.types import ExtrinsicResponse from bittensor.utils import u16_normalized_float from bittensor.utils.balance import Balance @@ -59,7 +60,9 @@ def root_register_extrinsic( """ try: if not ( - unlocked := ExtrinsicResponse.unlock_wallet(wallet, raise_error) + unlocked := ExtrinsicResponse.unlock_wallet( + wallet, raise_error, unlock_type="both" + ) ).success: return unlocked @@ -96,10 +99,10 @@ def root_register_extrinsic( if is_registered: return ExtrinsicResponse(message="Already registered on root network.") - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="root_register", - call_params={"hotkey": wallet.hotkey.ss58_address}, + call_params=RootParams.root_register(wallet.hotkey.ss58_address), ) response = subtensor.sign_and_send_extrinsic( call=call, diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index a9b1184dee..4adf2fb04b 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -1,8 +1,8 @@ from typing import Optional, Union, TYPE_CHECKING from bittensor.core.errors import MetadataError -from bittensor.core.settings import version_as_int -from bittensor.core.types import AxonServeCallParams, ExtrinsicResponse +from bittensor.core.extrinsics.params.serving import ServingParams +from bittensor.core.types import ExtrinsicResponse from bittensor.utils import ( networking as net, Certificate, @@ -54,29 +54,24 @@ def serve_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: - signing_keypair = "hotkey" if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, signing_keypair + wallet, raise_error, unlock_type="both" ) ).success: return unlocked - - params = AxonServeCallParams( - **{ - "version": version_as_int, - "ip": net.ip_to_int(ip), - "port": port, - "ip_type": net.ip_version(ip), - "netuid": netuid, - "hotkey": wallet.hotkey.ss58_address, - "coldkey": wallet.coldkeypub.ss58_address, - "protocol": protocol, - "placeholder1": placeholder1, - "placeholder2": placeholder2, - "certificate": certificate, - } + params = ServingParams.serve_axon_and_tls( + hotkey_ss58=wallet.hotkey.ss58_address, + coldkey_ss58=wallet.coldkeypub.ss58_address, + netuid=netuid, + ip=ip, + port=port, + protocol=protocol, + placeholder1=placeholder1, + placeholder2=placeholder2, + certificate=certificate, ) + logging.debug("Checking axon ...") neuron = subtensor.get_neuron_for_pubkey_and_subnet( wallet.hotkey.ss58_address, netuid=netuid @@ -97,7 +92,7 @@ def serve_extrinsic( else: call_function = "serve_axon_tls" - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function=call_function, call_params=params.dict(), @@ -108,7 +103,7 @@ def serve_extrinsic( wallet=wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, - sign_with=signing_keypair, + sign_with="hotkey", period=period, raise_error=raise_error, ) @@ -156,13 +151,6 @@ def serve_axon_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: - if not ( - unlocked := ExtrinsicResponse.unlock_wallet( - axon.wallet, raise_error, "hotkey" - ) - ).success: - return unlocked - external_port = axon.external_port # ---- Get external ip ---- @@ -245,10 +233,9 @@ def publish_metadata_extrinsic( failure. """ try: - signing_keypair = "hotkey" if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, signing_keypair + wallet, raise_error, unlock_type="both" ) ).success: return unlocked @@ -257,13 +244,10 @@ def publish_metadata_extrinsic( if reset_bonds: fields.append({"ResetBondsFlag": b""}) - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="Commitments", call_function="set_commitment", - call_params={ - "netuid": netuid, - "info": {"fields": [fields]}, - }, + call_params=ServingParams.set_commitment(netuid=netuid, info_fields=fields), ) response = subtensor.sign_and_send_extrinsic( diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 79e26303ee..0350a224d2 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -3,6 +3,7 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.errors import BalanceTypeError +from bittensor.core.extrinsics.params import StakingParams from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils import format_error_message @@ -90,45 +91,39 @@ def add_stake_extrinsic( logging.debug(f"\t\twallet: {wallet.name}") return ExtrinsicResponse(False, f"{message}.").with_log() - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_staked": amount.rao, - } - if safe_staking: pool = subtensor.subnet(netuid=netuid) - base_price = pool.price.tao - price_with_tolerance = ( - base_price if pool.netuid == 0 else base_price * (1 + rate_tolerance) + call_function = "add_stake_limit" + call_params = StakingParams.add_stake_limit( + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + allow_partial_stake=allow_partial_stake, + rate_tolerance=rate_tolerance, + pool=pool, ) logging.debug( f"Safe Staking to: [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"price limit: [green]{call_params.get('limit_price')}[/green], " + f"original price: [green]{pool.price}[/green], " f"with partial stake: [green]{allow_partial_stake}[/green] " f"on [blue]{subtensor.network}[/blue]." ) - 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: + call_function = "add_stake" + call_params = StakingParams.add_stake( + netuid=netuid, hotkey_ss58=hotkey_ss58, amount=amount + ) logging.debug( f"Staking to: [blue]netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " f"on [blue]{subtensor.network}[/blue]." ) - call_function = "add_stake" - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function=call_function, call_params=call_params, @@ -145,6 +140,14 @@ def add_stake_extrinsic( raise_error=raise_error, ) if response.success: + sim_swap = subtensor.sim_swap( + origin_netuid=0, + destination_netuid=netuid, + amount=(amount - response.extrinsic_fee), + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(netuid) + if not wait_for_finalization and not wait_for_inclusion: return response logging.debug("[green]Finalized.[/green]") @@ -436,13 +439,12 @@ def set_auto_stake_extrinsic( ).success: return unlocked - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="set_coldkey_auto_stake_hotkey", - call_params={ - "netuid": netuid, - "hotkey": hotkey_ss58, - }, + call_params=StakingParams.set_coldkey_auto_stake_hotkey( + netuid, hotkey_ss58 + ), ) response = subtensor.sign_and_send_extrinsic( call=call, diff --git a/bittensor/core/extrinsics/start_call.py b/bittensor/core/extrinsics/start_call.py index af4de4620c..9cc1131ad6 100644 --- a/bittensor/core/extrinsics/start_call.py +++ b/bittensor/core/extrinsics/start_call.py @@ -1,5 +1,6 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.params import StartCallParams from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: @@ -40,10 +41,10 @@ def start_call_extrinsic( ).success: return unlocked - start_call = subtensor.substrate.compose_call( + start_call = subtensor.compose_call( call_module="SubtensorModule", call_function="start_call", - call_params={"netuid": netuid}, + call_params=StartCallParams.start_call(netuid=netuid), ) return subtensor.sign_and_send_extrinsic( diff --git a/bittensor/core/extrinsics/take.py b/bittensor/core/extrinsics/take.py index 325a50ee9c..4a0c38cf83 100644 --- a/bittensor/core/extrinsics/take.py +++ b/bittensor/core/extrinsics/take.py @@ -2,6 +2,7 @@ from bittensor_wallet.bittensor_wallet import Wallet +from bittensor.core.extrinsics.params import TakeParams from bittensor.core.types import ExtrinsicResponse if TYPE_CHECKING: @@ -43,13 +44,10 @@ def set_take_extrinsic( ).success: return unlocked - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function=action, - call_params={ - "hotkey": hotkey_ss58, - "take": take, - }, + call_params=TakeParams.increase_decrease_take(hotkey_ss58, take), ) return subtensor.sign_and_send_extrinsic( call=call, diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index 56bd44f0e8..1667e79ad5 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -1,10 +1,10 @@ from typing import TYPE_CHECKING, Optional +from bittensor.core.extrinsics.params import get_transfer_fn_params from bittensor.core.settings import NETWORK_EXPLORER_MAP, DEFAULT_NETWORK from bittensor.core.types import ExtrinsicResponse from bittensor.utils import ( get_explorer_url_for_network, - get_transfer_fn_params, is_valid_bittensor_address_or_public_key, ) from bittensor.utils.balance import Balance @@ -95,7 +95,7 @@ def transfer_extrinsic( amount, destination, keep_alive ) - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="Balances", call_function=call_function, call_params=call_params, @@ -109,7 +109,7 @@ def transfer_extrinsic( period=period, raise_error=raise_error, ) - response.transaction_fee = fee + response.transaction_tao_fee = fee if response.success: block_hash = subtensor.get_block_hash() diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 11c19f1423..134e34fe25 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -3,6 +3,7 @@ from async_substrate_interface.errors import SubstrateRequestException from bittensor.core.errors import BalanceTypeError +from bittensor.core.extrinsics.params import UnstakingParams from bittensor.core.extrinsics.utils import get_old_stakes from bittensor.core.types import ExtrinsicResponse, UIDs from bittensor.utils import format_error_message @@ -77,49 +78,45 @@ def unstake_extrinsic( f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {hotkey_ss58}", ).with_log() - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_unstaked": amount.rao, - } - if safe_unstaking: 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) + call_function = "remove_stake_limit" + + call_params = UnstakingParams.remove_stake_limit( + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + allow_partial_stake=allow_partial_stake, + rate_tolerance=rate_tolerance, + pool=pool, + ) logging_message = ( f":satellite: [magenta]Safe Unstaking from:[/magenta] " 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], " + f"price limit: [green]{Balance.from_rao(call_params['limit_price'])}[/green], " + f"original price: [green]{pool.price.tao}[/green], " f"with partial unstake: [green]{allow_partial_stake}[/green] " f"on [blue]{subtensor.network}[/blue]" ) - limit_price = Balance.from_tao(price_with_tolerance).rao - call_params.update( - { - "limit_price": limit_price, - "allow_partial": allow_partial_stake, - } - ) - call_function = "remove_stake_limit" else: + call_function = "remove_stake" + call_params = UnstakingParams.remove_stake( + netuid=netuid, + hotkey_ss58=hotkey_ss58, + amount=amount, + ) logging_message = ( f":satellite: [magenta]Unstaking from:[/magenta] " f"netuid: [green]{netuid}[/green], amount: [green]{amount}[/green] " f"on [blue]{subtensor.network}[/blue]" ) - call_function = "remove_stake" logging.debug(logging_message) - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function=call_function, call_params=call_params, @@ -136,8 +133,15 @@ def unstake_extrinsic( raise_error=raise_error, ) - if response.success: # If we successfully unstaked. - # We only wait here if we expect finalization. + if response.success: + sim_swap = subtensor.sim_swap( + origin_netuid=netuid, + destination_netuid=0, + amount=amount, + ) + response.transaction_tao_fee = sim_swap.tao_fee + response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(netuid) + if not wait_for_finalization and not wait_for_inclusion: return response @@ -180,8 +184,8 @@ def unstake_extrinsic( def unstake_all_extrinsic( subtensor: "Subtensor", wallet: "Wallet", - hotkey_ss58: str, netuid: int, + hotkey_ss58: str, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, raise_error: bool = False, @@ -193,8 +197,8 @@ def unstake_all_extrinsic( Parameters: subtensor: Subtensor instance. wallet: The wallet of the stake owner. - hotkey_ss58: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. + hotkey_ss58: The SS58 address of the hotkey to unstake from. 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. period: The number of blocks during which the transaction will remain valid after it's submitted. If the @@ -213,18 +217,15 @@ def unstake_all_extrinsic( ).success: return unlocked - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "limit_price": None, - } - - if rate_tolerance: - current_price = subtensor.subnet(netuid=netuid).price - limit_price = current_price * (1 - rate_tolerance) - call_params.update({"limit_price": limit_price}) + pool = subtensor.subnet(netuid=netuid) if rate_tolerance else None + call_params = UnstakingParams.remove_stake_full_limit( + netuid=netuid, + hotkey_ss58=hotkey_ss58, + rate_tolerance=rate_tolerance, + pool=pool, + ) - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="remove_stake_full_limit", call_params=call_params, diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index 934190c72d..d44aa8f88d 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -4,6 +4,7 @@ from bittensor_drand import get_encrypted_commit +from bittensor.core.extrinsics.params import WeightsParams from bittensor.core.settings import version_as_int from bittensor.core.types import ExtrinsicResponse, Salt, UIDs, Weights from bittensor.utils import get_mechid_storage_index @@ -57,10 +58,9 @@ def commit_timelocked_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: - signing_keypair = "hotkey" if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, signing_keypair + wallet, raise_error, unlock_type="both" ) ).success: return unlocked @@ -90,16 +90,16 @@ def commit_timelocked_weights_extrinsic( hotkey=wallet.hotkey.public_key, ) - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="commit_timelocked_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit": commit_for_reveal, - "reveal_round": reveal_round, - "commit_reveal_version": commit_reveal_version, - }, + call_params=WeightsParams.commit_timelocked_mechanism_weights( + netuid=netuid, + mechid=mechid, + commit_for_reveal=commit_for_reveal, + reveal_round=reveal_round, + commit_reveal_version=commit_reveal_version, + ), ) response = subtensor.sign_and_send_extrinsic( call=call, @@ -108,8 +108,8 @@ def commit_timelocked_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with=signing_keypair, - nonce_key=signing_keypair, + sign_with="hotkey", + nonce_key="hotkey", raise_error=raise_error, ) @@ -164,10 +164,9 @@ def commit_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: - signing_keypair = "hotkey" if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, signing_keypair + wallet, raise_error, unlock_type="both" ) ).success: return unlocked @@ -183,14 +182,14 @@ def commit_weights_extrinsic( version_key=version_key, ) - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="commit_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "commit_hash": commit_hash, - }, + call_params=WeightsParams.commit_mechanism_weights( + netuid=netuid, + mechid=mechid, + commit_hash=commit_hash, + ), ) response = subtensor.sign_and_send_extrinsic( call=call, @@ -199,8 +198,8 @@ def commit_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with=signing_keypair, - nonce_key=signing_keypair, + sign_with="hotkey", + nonce_key="hotkey", raise_error=raise_error, ) @@ -263,17 +262,17 @@ def reveal_weights_extrinsic( # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="reveal_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "uids": uids, - "values": weights, - "salt": salt, - "version_key": version_key, - }, + call_params=WeightsParams.reveal_mechanism_weights( + netuid=netuid, + mechid=mechid, + uids=uids, + weights=weights, + salt=salt, + version_key=version_key, + ), ) response = subtensor.sign_and_send_extrinsic( call=call, @@ -344,16 +343,16 @@ def set_weights_extrinsic( # Convert, reformat and normalize uids and weights. uids, weights = convert_and_normalize_weights_and_uids(uids, weights) - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="SubtensorModule", call_function="set_mechanism_weights", - call_params={ - "netuid": netuid, - "mecid": mechid, - "dests": uids, - "weights": weights, - "version_key": version_key, - }, + call_params=WeightsParams.set_mechanism_weights( + netuid=netuid, + mechid=mechid, + uids=uids, + weights=weights, + version_key=version_key, + ), ) response = subtensor.sign_and_send_extrinsic( call=call, diff --git a/bittensor/extras/dev_framework/subnet.py b/bittensor/extras/dev_framework/subnet.py index af6f5f03df..c68d48a005 100644 --- a/bittensor/extras/dev_framework/subnet.py +++ b/bittensor/extras/dev_framework/subnet.py @@ -114,11 +114,7 @@ def execute_one(self, step: Union[STEPS, tuple]) -> ExtrinsicResponse: if call_function.startswith("sudo_") and sudo_call is None: sudo_call = True - self._check_set_hp( - function_module=call_module, - function_name=call_function, - function_params=call_params, - ) + response = self.set_hyperparameter( sudo_or_owner_wallet=sudo_or_owner_wallet, call_function=call_function, @@ -179,11 +175,7 @@ async def async_execute_one(self, step: Union[STEPS, tuple]) -> ExtrinsicRespons if call_function.startswith("sudo_") and sudo_call is None: sudo_call = True - self._check_set_hp( - function_module=call_module, - function_name=call_function, - function_params=call_params, - ) + response = await self.async_set_hyperparameter( sudo_or_owner_wallet=sudo_or_owner_wallet, call_function=call_function, @@ -478,33 +470,6 @@ def _add_call_record(self, operation: str, response: ExtrinsicResponse): """Add extrinsic response to the calls list.""" self._calls.append(CALL_RECORD(len(self._calls), operation, response)) - def _check_set_hp( - self, - function_module: str, - function_name: str, - function_params: Optional[dict] = None, - ): - """Check if the function exists in the Subtensor node and parameters are provided for the function correctly.""" - pallet = self.s.substrate.metadata.get_metadata_pallet(function_module) - assert pallet is not None, f"Pallet {function_module} is not found." - functions = getattr(pallet.calls, "value", None) - functions_call = [f for f in functions if f.get("name", None) == function_name] - function_call = functions_call[0] if functions_call else None - assert function_call is not None, ( - f"Function {function_name} is not found in pallet {function_module}." - ) - function_call_fields = function_call.get("fields", []) - parameters = [field.get("name") for field in function_call_fields] - assert parameters is not None, ( - f"Parameters are required for function {function_name}." - ) - if "netuid" in parameters and not function_params.get("netuid", None): - function_params.update({"netuid": self._netuid}) - assert len(parameters) == len(function_params) is not False, ( - f"Not all parameters {function_params} where provided for function {function_name}. " - f"All required parameters: {parameters}." - ) - def _check_response(self, response: ExtrinsicResponse) -> bool: """Check if the call was successful.""" if response.success: diff --git a/bittensor/extras/subtensor_api/staking.py b/bittensor/extras/subtensor_api/staking.py index 470bd9eeb2..12f798fca7 100644 --- a/bittensor/extras/subtensor_api/staking.py +++ b/bittensor/extras/subtensor_api/staking.py @@ -19,11 +19,11 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): ) self.get_stake_info_for_coldkey = subtensor.get_stake_info_for_coldkey self.get_stake_movement_fee = subtensor.get_stake_movement_fee - self.get_stake_operations_fee = subtensor.get_stake_operations_fee self.get_stake_weight = subtensor.get_stake_weight self.get_unstake_fee = subtensor.get_unstake_fee self.move_stake = subtensor.move_stake self.set_auto_stake = subtensor.set_auto_stake + self.sim_swap = subtensor.sim_swap self.swap_stake = subtensor.swap_stake self.transfer_stake = subtensor.transfer_stake self.unstake = subtensor.unstake From 444fcf744a70baf97ca42702340445e47be8fe64 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 11:43:06 -0700 Subject: [PATCH 374/416] improve `ExtrinsicResponse` --- bittensor/core/types.py | 47 +++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 08280c6d2a..98ec97922f 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -10,12 +10,13 @@ from bittensor.core.chain_data import NeuronInfo, NeuronInfoLite from bittensor.core.config import Config from bittensor.utils import ( - Certificate, determine_chain_endpoint_and_network, get_caller_name, format_error_message, networking, unlock_key, + Certificate, + UnlockStatus, ) from bittensor.utils.btlogging import logging @@ -305,7 +306,8 @@ class ExtrinsicResponse: extrinsic: The raw extrinsic object used in the call, if available. extrinsic_fee: The fee charged by the extrinsic, if available. extrinsic_receipt: The receipt object of the submitted extrinsic. - transaction_fee: The fee charged by the transaction (e.g., fee for add_stake or transfer_stake), if available. + transaction_tao_fee: TAO fee charged by the transaction in TAO (e.g., fee for add_stake), if available. + transaction_alpha_fee: Alpha fee charged by the transaction (e.g., fee for transfer_stake), if available. error: Captures the underlying exception if the extrinsic failed, otherwise `None`. data: Arbitrary data returned from the extrinsic, such as decoded events, balance or another extra context. @@ -332,7 +334,8 @@ class ExtrinsicResponse: message: Successfully registered subnet extrinsic_function: register_subnet_extrinsic extrinsic: {'account_id': '0xd43593c715fdd31c... - extrinsic_fee: τ1.0 + transaction_tao_fee: τ1.0 + transaction_alpha_fee: 1.0β extrinsic_receipt: Extrinsic Receipt data of of the submitted extrinsic transaction_fee: τ1.0 error: None @@ -357,7 +360,8 @@ class ExtrinsicResponse: extrinsic_receipt: Optional[Union["AsyncExtrinsicReceipt", "ExtrinsicReceipt"]] = ( None ) - transaction_fee: Optional["Balance"] = None + transaction_tao_fee: Optional["Balance"] = None + transaction_alpha_fee: Optional["Balance"] = None error: Optional[Exception] = None data: Optional[Any] = None @@ -379,7 +383,8 @@ def __str__(self): f"\textrinsic: {self.extrinsic}\n" f"\textrinsic_fee: {self.extrinsic_fee}\n" f"\textrinsic_receipt: {_extrinsic_receipt}" - f"\ttransaction_fee: {self.transaction_fee}\n" + f"\ttransaction_tao_fee: {self.transaction_tao_fee}\n" + f"\ttransaction_alpha_fee: {self.transaction_alpha_fee}\n" f"\tdata: {self.data}\n" f"\terror: {self.error}" ) @@ -394,9 +399,12 @@ def as_dict(self) -> dict: "message": self.message, "extrinsic_function": self.extrinsic_function, "extrinsic": self.extrinsic, - "extrinsic_fee": str(self.extrinsic_fee) if self.extrinsic_fee else None, - "transaction_fee": str(self.transaction_fee) - if self.transaction_fee + "extrinsic_fee": self.extrinsic_fee.rao if self.extrinsic_fee else None, + "transaction_tao_fee": self.transaction_tao_fee.rao + if self.transaction_tao_fee + else None, + "transaction_alpha_fee": str(self.transaction_alpha_fee) + if self.transaction_alpha_fee else None, "extrinsic_receipt": self.extrinsic_receipt, "error": str(self.error) if self.error else None, @@ -413,7 +421,8 @@ def __eq__(self, other: Any) -> bool: and self.extrinsic_function == other.extrinsic_function and self.extrinsic == other.extrinsic and self.extrinsic_fee == other.extrinsic_fee - and self.transaction_fee == other.transaction_fee + and self.transaction_tao_fee == other.transaction_tao_fee + and self.transaction_alpha_fee == other.transaction_alpha_fee and self.extrinsic_receipt == other.extrinsic_receipt and self.error == other.error and self.data == other.data @@ -450,15 +459,27 @@ def unlock_wallet( Parameters: wallet: Bittensor Wallet instance. raise_error: Raises a relevant exception rather than returning `False` if unsuccessful. - unlock_type: The key type, 'coldkey' or 'hotkey'. + unlock_type: The key type, 'coldkey' or 'hotkey'. Or 'both' to check both. nonce_key: Key used for generating nonce in extrinsic function. Returns: Extrinsic Response is used to check if the key is unlocked. + + Note: + When an extrinsic is signed with the coldkey but internally references or uses the hotkey, both keypairs + must be validated. Passing unlock_type='both' ensures that authentication is performed against both the + coldkey and hotkey. """ - unlock = unlock_key(wallet, unlock_type=unlock_type, raise_error=raise_error) - if not unlock.success: - logging.error(unlock.message) + both = ["coldkey", "hotkey"] + keys = [unlock_type] if unlock_type in both else both + unlock = UnlockStatus(False, "") + + for unlock_type in keys: + unlock = unlock_key( + wallet, unlock_type=unlock_type, raise_error=raise_error + ) + if not unlock.success: + logging.error(unlock.message) # If extrinsic uses `unlock_type` and `nonce_key` and `nonce_key` is not public, we need to check the # availability of both keys. From 2ab789a6a020100827364e97af27cf1eefc7ad69 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 11:43:57 -0700 Subject: [PATCH 375/416] update SubtensorApi --- bittensor/extras/subtensor_api/__init__.py | 1 + bittensor/extras/subtensor_api/extrinsics.py | 2 ++ bittensor/extras/subtensor_api/utils.py | 10 ++++++---- bittensor/extras/subtensor_api/wallets.py | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/bittensor/extras/subtensor_api/__init__.py b/bittensor/extras/subtensor_api/__init__.py index 7f9a8e500a..e55c46df0c 100644 --- a/bittensor/extras/subtensor_api/__init__.py +++ b/bittensor/extras/subtensor_api/__init__.py @@ -109,6 +109,7 @@ def __init__( self.determine_block_hash = self.inner_subtensor.determine_block_hash self.encode_params = self.inner_subtensor.encode_params + self.compose_call = self.inner_subtensor.compose_call self.sign_and_send_extrinsic = self.inner_subtensor.sign_and_send_extrinsic self.start_call = self.inner_subtensor.start_call self.wait_for_block = self.inner_subtensor.wait_for_block diff --git a/bittensor/extras/subtensor_api/extrinsics.py b/bittensor/extras/subtensor_api/extrinsics.py index 62c6c5c1b4..b428f5e247 100644 --- a/bittensor/extras/subtensor_api/extrinsics.py +++ b/bittensor/extras/subtensor_api/extrinsics.py @@ -12,6 +12,7 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.add_stake_multiple = subtensor.add_stake_multiple self.burned_register = subtensor.burned_register self.commit_weights = subtensor.commit_weights + self.get_extrinsic_fee = subtensor.get_extrinsic_fee self.modify_liquidity = subtensor.modify_liquidity self.move_stake = subtensor.move_stake self.register = subtensor.register @@ -35,3 +36,4 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.unstake = subtensor.unstake self.unstake_all = subtensor.unstake_all self.unstake_multiple = subtensor.unstake_multiple + self.validate_extrinsic_params = subtensor.validate_extrinsic_params diff --git a/bittensor/extras/subtensor_api/utils.py b/bittensor/extras/subtensor_api/utils.py index 8f4c500801..762957145c 100644 --- a/bittensor/extras/subtensor_api/utils.py +++ b/bittensor/extras/subtensor_api/utils.py @@ -40,6 +40,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.inner_subtensor.get_all_revealed_commitments ) subtensor.get_all_subnets_info = subtensor.inner_subtensor.get_all_subnets_info + subtensor.get_all_subnets_netuid = subtensor.inner_subtensor.get_all_subnets_netuid subtensor.get_auto_stakes = subtensor.inner_subtensor.get_auto_stakes subtensor.get_balance = subtensor.inner_subtensor.get_balance subtensor.get_balances = subtensor.inner_subtensor.get_balances @@ -62,6 +63,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_existential_deposit = ( subtensor.inner_subtensor.get_existential_deposit ) + subtensor.get_extrinsic_fee = subtensor.inner_subtensor.get_extrinsic_fee subtensor.get_hotkey_owner = subtensor.inner_subtensor.get_hotkey_owner subtensor.get_hotkey_stake = subtensor.inner_subtensor.get_hotkey_stake subtensor.get_hyperparameter = subtensor.inner_subtensor.get_hyperparameter @@ -104,9 +106,6 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.inner_subtensor.get_stake_info_for_coldkey ) subtensor.get_stake_movement_fee = subtensor.inner_subtensor.get_stake_movement_fee - subtensor.get_stake_operations_fee = ( - subtensor.inner_subtensor.get_stake_operations_fee - ) subtensor.get_stake_weight = subtensor.inner_subtensor.get_stake_weight subtensor.get_subnet_burn_cost = subtensor.inner_subtensor.get_subnet_burn_cost subtensor.get_subnet_hyperparameters = ( @@ -124,7 +123,6 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_subnet_validator_permits = ( subtensor.inner_subtensor.get_subnet_validator_permits ) - subtensor.get_all_subnets_netuid = subtensor.inner_subtensor.get_all_subnets_netuid subtensor.get_timelocked_weight_commits = ( subtensor.inner_subtensor.get_timelocked_weight_commits ) @@ -189,6 +187,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.sign_and_send_extrinsic = ( subtensor.inner_subtensor.sign_and_send_extrinsic ) + subtensor.sim_swap = subtensor.inner_subtensor.sim_swap subtensor.start_call = subtensor.inner_subtensor.start_call subtensor.state_call = subtensor.inner_subtensor.state_call subtensor.subnet = subtensor.inner_subtensor.subnet @@ -204,6 +203,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.unstake = subtensor.inner_subtensor.unstake subtensor.unstake_all = subtensor.inner_subtensor.unstake_all subtensor.unstake_multiple = subtensor.inner_subtensor.unstake_multiple + subtensor.validate_extrinsic_params = ( + subtensor.inner_subtensor.validate_extrinsic_params + ) subtensor.wait_for_block = subtensor.inner_subtensor.wait_for_block subtensor.weights = subtensor.inner_subtensor.weights subtensor.weights_rate_limit = subtensor.inner_subtensor.weights_rate_limit diff --git a/bittensor/extras/subtensor_api/wallets.py b/bittensor/extras/subtensor_api/wallets.py index 12034ed641..b6822159ed 100644 --- a/bittensor/extras/subtensor_api/wallets.py +++ b/bittensor/extras/subtensor_api/wallets.py @@ -39,3 +39,4 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): self.get_transfer_fee = subtensor.get_transfer_fee self.get_unstake_fee = subtensor.get_unstake_fee self.set_children = subtensor.set_children + self.transfer = subtensor.transfer From fcb16c989c2ccb7d575620483089e9f70bbca59a Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 11:44:31 -0700 Subject: [PATCH 376/416] ruff --- bittensor/core/chain_data/sim_swap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bittensor/core/chain_data/sim_swap.py b/bittensor/core/chain_data/sim_swap.py index 7710d9a3e3..42afae7fd4 100644 --- a/bittensor/core/chain_data/sim_swap.py +++ b/bittensor/core/chain_data/sim_swap.py @@ -17,6 +17,7 @@ class SimSwapResult: tao_fee: The fee associated with the tao token portion of the swap. alpha_fee: The fee associated with the alpha token portion of the swap. """ + tao_amount: Balance alpha_amount: Balance tao_fee: Balance From 62b357d8ccac8d6f0afce041c5cb541937a76a7d Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 11:44:57 -0700 Subject: [PATCH 377/416] move `utils.get_extrinsic_fee` to `subtensor.get_extrinsic_fee` --- bittensor/core/extrinsics/asyncex/utils.py | 33 ++-------------------- bittensor/core/extrinsics/utils.py | 31 ++------------------ 2 files changed, 5 insertions(+), 59 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/utils.py b/bittensor/core/extrinsics/asyncex/utils.py index f8fa39e794..a0a9fa2422 100644 --- a/bittensor/core/extrinsics/asyncex/utils.py +++ b/bittensor/core/extrinsics/asyncex/utils.py @@ -1,41 +1,12 @@ from typing import TYPE_CHECKING, Optional from bittensor.core.types import ExtrinsicResponse -from bittensor.utils.balance import Balance if TYPE_CHECKING: - from scalecodec import GenericCall - from bittensor_wallet import Keypair from bittensor.core.async_subtensor import AsyncSubtensor from bittensor_wallet import Wallet -async def get_extrinsic_fee( - subtensor: "AsyncSubtensor", - call: "GenericCall", - keypair: "Keypair", - netuid: Optional[int] = None, -): - """ - Get extrinsic fee for a given extrinsic call and keypair for a given SN's netuid. - - Parameters: - subtensor: The Subtensor instance. - netuid: The SN's netuid. - call: The extrinsic call. - keypair: The keypair associated with the extrinsic. - - Returns: - Balance object representing the extrinsic fee in RAO. - """ - payment_info = await subtensor.substrate.get_payment_info( - call=call, keypair=keypair - ) - return Balance.from_rao(amount=payment_info["partial_fee"]).set_unit( - netuid=netuid or 0 - ) - - async def sudo_call_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", @@ -81,13 +52,13 @@ async def sudo_call_extrinsic( ).success: return unlocked - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module=call_module, call_function=call_function, call_params=call_params, ) if not root_call: - call = await subtensor.substrate.compose_call( + call = await subtensor.compose_call( call_module="Sudo", call_function="sudo", call_params={"call": call}, diff --git a/bittensor/core/extrinsics/utils.py b/bittensor/core/extrinsics/utils.py index 6327e98e74..b1a94f749d 100644 --- a/bittensor/core/extrinsics/utils.py +++ b/bittensor/core/extrinsics/utils.py @@ -6,8 +6,7 @@ from bittensor.utils.balance import Balance if TYPE_CHECKING: - from scalecodec import GenericCall - from bittensor_wallet import Wallet, Keypair + from bittensor_wallet import Wallet from bittensor.core.chain_data import StakeInfo from bittensor.core.subtensor import Subtensor @@ -47,30 +46,6 @@ def get_old_stakes( ] -def get_extrinsic_fee( - subtensor: "Subtensor", - call: "GenericCall", - keypair: "Keypair", - netuid: Optional[int] = None, -): - """ - Get extrinsic fee for a given extrinsic call and keypair for a given SN's netuid. - - Parameters: - subtensor: The Subtensor instance. - call: The extrinsic call. - keypair: The keypair associated with the extrinsic. - netuid: The SN's netuid. - - Returns: - Balance object representing the extrinsic fee in RAO. - """ - payment_info = subtensor.substrate.get_payment_info(call=call, keypair=keypair) - return Balance.from_rao(amount=payment_info["partial_fee"]).set_unit( - netuid=netuid or 0 - ) - - def sudo_call_extrinsic( subtensor: "Subtensor", wallet: "Wallet", @@ -116,13 +91,13 @@ def sudo_call_extrinsic( ).success: return unlocked - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module=call_module, call_function=call_function, call_params=call_params, ) if not root_call: - call = subtensor.substrate.compose_call( + call = subtensor.compose_call( call_module="Sudo", call_function="sudo", call_params={"call": call}, From fb40f1a47c5f66ff4fe03154983bb7843531376e Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 11:45:33 -0700 Subject: [PATCH 378/416] let's use `subtensor.compose_call` instead of `substrate.compose_call` --- tests/e2e_tests/utils/__init__.py | 12 ++++++------ tests/e2e_tests/utils/e2e_test_utils.py | 16 +++++++++------- tests/helpers/helpers.py | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tests/e2e_tests/utils/__init__.py b/tests/e2e_tests/utils/__init__.py index fbfe8b4cdc..7e6ba3ec3b 100644 --- a/tests/e2e_tests/utils/__init__.py +++ b/tests/e2e_tests/utils/__init__.py @@ -73,7 +73,7 @@ def set_identity( additional="", ): return subtensor.sign_and_send_extrinsic( - call=subtensor.substrate.compose_call( + call=subtensor.compose_call( call_module="SubtensorModule", call_function="set_identity", call_params={ @@ -104,7 +104,7 @@ async def async_set_identity( additional="", ): return await subtensor.sign_and_send_extrinsic( - call=await subtensor.substrate.compose_call( + call=await subtensor.compose_call( call_module="SubtensorModule", call_function="set_identity", call_params={ @@ -125,7 +125,7 @@ async def async_set_identity( def propose(subtensor, wallet, proposal, duration): return subtensor.sign_and_send_extrinsic( - call=subtensor.substrate.compose_call( + call=subtensor.compose_call( call_module="Triumvirate", call_function="propose", call_params={ @@ -147,7 +147,7 @@ async def async_propose( duration, ): return await subtensor.sign_and_send_extrinsic( - call=await subtensor.substrate.compose_call( + call=await subtensor.compose_call( call_module="Triumvirate", call_function="propose", call_params={ @@ -171,7 +171,7 @@ def vote( approve, ): return subtensor.sign_and_send_extrinsic( - call=subtensor.substrate.compose_call( + call=subtensor.compose_call( call_module="SubtensorModule", call_function="vote", call_params={ @@ -196,7 +196,7 @@ async def async_vote( approve, ): return await subtensor.sign_and_send_extrinsic( - call=await subtensor.substrate.compose_call( + call=await subtensor.compose_call( call_module="SubtensorModule", call_function="vote", call_params={ diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index d004a26ace..47342ac774 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -3,7 +3,7 @@ import shutil import subprocess import sys - +from typing import Optional from bittensor_wallet import Keypair, Wallet from bittensor.extras import SubtensorApi @@ -15,15 +15,16 @@ def setup_wallet( uri: str, - encrypt_hotkey: bool = False, encrypt_coldkey: bool = False, + encrypt_hotkey: bool = False, + coldkey_password: Optional[str] = None, + hotkey_password: Optional[str] = None, ) -> tuple[Keypair, Wallet]: """ Sets up a wallet using the provided URI. - This function creates a keypair from the given URI and initializes a wallet - at a temporary path. It sets the coldkey, coldkeypub, and hotkey for the wallet - using the generated keypair. + This function creates a keypair from the given URI and initializes a wallet at a temporary path. It sets the + coldkey, coldkeypub, and hotkey for the wallet using the generated keypair. Side Effects: - Creates a wallet in a temporary directory. @@ -33,9 +34,10 @@ def setup_wallet( name = uri.strip("/") wallet_path = f"/tmp/btcli-e2e-wallet-{name}" wallet = Wallet(name=name, path=wallet_path) - wallet.set_coldkey(keypair=keypair, encrypt=encrypt_coldkey, overwrite=True) + wallet.set_coldkey(keypair=keypair, encrypt=encrypt_coldkey, overwrite=True, coldkey_password=coldkey_password) wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=True) - wallet.set_hotkey(keypair=keypair, encrypt=encrypt_hotkey, overwrite=True) + wallet.set_hotkey(keypair=keypair, encrypt=encrypt_hotkey, overwrite=True, hotkey_password=hotkey_password) + wallet.set_hotkeypub(keypair=keypair, encrypt=False, overwrite=True) return keypair, wallet diff --git a/tests/helpers/helpers.py b/tests/helpers/helpers.py index c4fa5b9151..71c7ddcfc2 100644 --- a/tests/helpers/helpers.py +++ b/tests/helpers/helpers.py @@ -87,7 +87,7 @@ def assert_submit_signed_extrinsic( wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ): - substrate.compose_call.assert_called_with( + substrate.assert_called_with( call_module, call_function, call_params, From d484bb50d4fb2db0a32989d8e8042b067b323d19 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 12:04:19 -0700 Subject: [PATCH 379/416] update Subtensor class and MIGRATION.md --- MIGRATION.md | 19 +- bittensor/core/async_subtensor.py | 350 +++++++++++++++++++++++------- bittensor/core/subtensor.py | 327 +++++++++++++++++++++------- 3 files changed, 542 insertions(+), 154 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index d248ac331b..824a05ee93 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -12,7 +12,7 @@ ## 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. +2. ✅ In all methods where we `sim_swap` is called, remove unused arguments. Consider combining all methods using `sim_swap` 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. @@ -86,9 +86,12 @@ - header (dict) - extrinsics (list) - block_explorer (link to tao.app) - + This implementation has been repeatedly requested by the community in the past. -5. Implement `Crowdloan` logic. Issue: https://github.com/opentensor/bittensor/issues/3017 +5. ✅ Added `bittensor.core.extrinsics.params` subpackage. This package will contain all extrinsic parameters. Due to + the duplication of extrinsics (async and sync implementations), it's easy to miss the sequence of changes. This also + makes it easier to obtain the parameter list. +6. Implement `Crowdloan` logic. Issue: https://github.com/opentensor/bittensor/issues/3017 ## Testing 1. ✅ When running tests via Docker, ensure no lingering processes occupy required ports before launch. @@ -147,6 +150,7 @@ wait_for_inclusion: bool = True, wait_for_finalization: bool = True, ``` - [x] `.set_children_extrinsic` and `.root_set_pending_childkey_cooldown_extrinsic`. `subtensor.set_children` and `subtensor.root_set_pending_childkey_cooldown` methods. + - parameters re-ordered. All extrinsics and related call has the same oder schema: `subtensor, netuid, hotkey_ss58` ... Another order confuses ppl. - [x] `.add_liquidity_extrinsic` and `subtensor.add_liquidity` - [x] `.modify_liquidity_extrinsic` and `subtensor.modify_liquidity` - [x] `.remove_liquidity_extrinsic` and `subtensor.remove_liquidity` @@ -159,6 +163,7 @@ wait_for_finalization: bool = True, - 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` + - parameters re-ordered. All extrinsics and related call has the same oder schema: `subtensor, netuid, hotkey_ss58` ... Another order confuses ppl. - [x] `.burned_register_extrinsic` and `subtensor.burned_register` - [x] `.register_subnet_extrinsic` and `subtensor.register_subnet` - [x] `.register_extrinsic` and `subtensor.register` @@ -193,6 +198,7 @@ wait_for_finalization: bool = True, - 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` + - parameters re-ordered. All extrinsics and related call has the same oder schema: `subtensor, netuid, hotkey_ss58` ... Another order confuses ppl. - [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) @@ -256,7 +262,12 @@ Removing deprecated extrinsics and replacing them with consistent ones: - method `get_traansfer_fee` has renamed parameter `value` to `amount` - `bittensor.core.extrinsic.serving.get_metadata` functions moved to `subtensor.get_commitment_metadata` method - `bittensor.core.extrinsic.serving.get_last_bonds_reset` function moved to `subtensor.get_last_bonds_reset` method - +- added method `subtensor.get_extrinsic_fee` +- added method `subtensor.compose_call` +- added method `subtensor.sim_swap` +- added method `subtensor.validate_extrinsic_params` +- methods `get_stake_add_fee`, `get_stake_movement_fee`, `get_unstake_fee` have updated parameters order. + Added sub-package `bittensor.core.addons` to host optional extensions and experimental logic enhancing the core functionality. - `bittensor.core.subtensor_api` moved to `bittensor.core.addons.subtensor_api` - `bittensor.core.timelock` moved to `bittensor.core.addons.timelock` diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 5ffa1004d5..beb90c93c7 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -21,6 +21,7 @@ NeuronInfo, ProposalVoteData, SelectiveMetagraphIndex, + SimSwapResult, StakeInfo, SubnetHyperparameters, SubnetIdentity, @@ -77,13 +78,13 @@ unstake_extrinsic, unstake_multiple_extrinsic, ) -from bittensor.core.extrinsics.asyncex.utils import get_extrinsic_fee from bittensor.core.extrinsics.asyncex.weights import ( commit_timelocked_weights_extrinsic, commit_weights_extrinsic, reveal_weights_extrinsic, set_weights_extrinsic, ) +from bittensor.core.extrinsics.params.transfer import get_transfer_fn_params from bittensor.core.metagraph import AsyncMetagraph from bittensor.core.settings import ( version_as_int, @@ -104,7 +105,6 @@ decode_hex_identity_dict, format_error_message, get_caller_name, - get_transfer_fn_params, get_mechid_storage_index, is_valid_ss58_address, u16_normalized_float, @@ -123,13 +123,10 @@ price_to_tick, LiquidityPosition, ) -from bittensor.utils.weight_utils import ( - U16_MAX, -) if TYPE_CHECKING: from async_substrate_interface.types import ScaleObj - from bittensor_wallet import Wallet + from bittensor_wallet import Keypair, Wallet from bittensor.core.axon import Axon from async_substrate_interface import AsyncQueryMapResult @@ -354,6 +351,11 @@ def _get_substrate( ws_shutdown_timer=ws_shutdown_timer, ) + @property + async def block(self): + """Provides an asynchronous property to retrieve the current block.""" + return await self.get_current_block() + async def determine_block_hash( self, block: Optional[int] = None, @@ -515,10 +517,80 @@ async def get_hyperparameter( return getattr(result, "value", result) - @property - async def block(self): - """Provides an asynchronous property to retrieve the current block.""" - return await self.get_current_block() + async def sim_swap( + self, + origin_netuid: int, + destination_netuid: int, + amount: "Balance", + block_hash: Optional[str] = None, + ) -> SimSwapResult: + """ + Hits the SimSwap Runtime API to calculate the fee and result for a given transaction. The SimSwapResult contains + the staking fees and expected returned amounts of a given transaction. This does not include the transaction + (extrinsic) fee. + + Args: + origin_netuid: Netuid of the source subnet (0 if add stake). + destination_netuid: Netuid of the destination subnet. + amount: Amount to stake operation. + block_hash: The hash of the blockchain block number for the query. + + Returns: + SimSwapResult object representing the result. + """ + check_balance_amount(amount) + block_hash = block_hash or await self.substrate.get_chain_head() + if origin_netuid > 0 and destination_netuid > 0: + # for cross-subnet moves where neither origin nor destination is root + intermediate_result_, sn_price = await asyncio.gather( + self.query_runtime_api( + runtime_api="SwapRuntimeApi", + method="sim_swap_alpha_for_tao", + params={"netuid": origin_netuid, "alpha": amount.rao}, + block_hash=block_hash, + ), + self.get_subnet_price(origin_netuid, block_hash=block_hash), + ) + intermediate_result = SimSwapResult.from_dict( + intermediate_result_, origin_netuid + ) + result = SimSwapResult.from_dict( + await self.query_runtime_api( + runtime_api="SwapRuntimeApi", + method="sim_swap_tao_for_alpha", + params={ + "netuid": destination_netuid, + "tao": intermediate_result.tao_amount.rao, + }, + block_hash=block_hash, + ), + origin_netuid, + ) + secondary_fee = (result.tao_fee / sn_price.tao).set_unit(origin_netuid) + result.alpha_fee = result.alpha_fee + secondary_fee + return result + elif origin_netuid > 0: + # dynamic to tao + return SimSwapResult.from_dict( + await self.query_runtime_api( + runtime_api="SwapRuntimeApi", + method="sim_swap_alpha_for_tao", + params={"netuid": origin_netuid, "alpha": amount.rao}, + block_hash=block_hash, + ), + origin_netuid, + ) + else: + # tao to dynamic or unstaked to staked tao (SN0) + return SimSwapResult.from_dict( + await self.query_runtime_api( + runtime_api="SwapRuntimeApi", + method="sim_swap_tao_for_alpha", + params={"netuid": destination_netuid, "tao": amount.rao}, + block_hash=block_hash, + ), + destination_netuid, + ) # Subtensor queries =========================================================================================== @@ -2791,12 +2863,13 @@ async def get_stake( return Balance.from_rao(int(stake)).set_unit(netuid=netuid) - # TODO: update related with fee calculation async def get_stake_add_fee( self, amount: Balance, netuid: int, block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Balance: """ Calculates the fee for adding new stake to a hotkey. @@ -2804,38 +2877,55 @@ async def get_stake_add_fee( Parameters: amount: Amount of stake to add in TAO netuid: Netuid of subnet - block: Block number at which to perform the calculation + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. Returns: - The calculated stake fee as a Balance object + The calculated stake fee as a Balance object in TAO. """ check_balance_amount(amount) - return await self.get_stake_operations_fee( - amount=amount, netuid=netuid, block=block + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + sim_swap_result = await self.sim_swap( + origin_netuid=0, + destination_netuid=netuid, + amount=amount, + block_hash=block_hash, ) + return sim_swap_result.tao_fee - # TODO: update related with fee calculation async def get_stake_movement_fee( self, - amount: Balance, origin_netuid: int, + destination_netuid: int, + amount: Balance, block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Balance: """ Calculates the fee for moving stake between hotkeys/subnets/coldkeys. Parameters: - amount: Amount of stake to move in TAO - origin_netuid: Netuid of source subnet - block: Block number at which to perform the calculation + origin_netuid: Netuid of source subnet. + destination_netuid: Netuid of the destination subnet. + amount: Amount of stake to move. + block: The block number for which the children are to be retrieved. + block_hash: The hash of the block to retrieve the subnet unique identifiers from. + reuse_block: Whether to reuse the last-used block hash. Returns: The calculated stake fee as a Balance object """ check_balance_amount(amount) - return await self.get_stake_operations_fee( - amount=amount, netuid=origin_netuid, block=block + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + sim_swap_result = await self.sim_swap( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + block_hash=block_hash, ) + return sim_swap_result.tao_fee async def get_stake_for_coldkey_and_hotkey( self, @@ -2950,38 +3040,6 @@ async def get_stake_for_hotkey( get_hotkey_stake = get_stake_for_hotkey - async def get_stake_operations_fee( - self, - amount: Balance, - netuid: int, - block: Optional[int] = None, - block_hash: Optional[str] = None, - reuse_block: bool = False, - ): - """Returns fee for any stake operation in specified subnet. - - Parameters: - amount: Amount of stake to add in Alpha/TAO. - netuid: Netuid of subnet. - block: The block number to query. Do not specify if using block_hash or reuse_block. - block_hash: The block hash at which to check the parameter. Do not set if using block or reuse_block. - reuse_block: Whether to reuse the last-used block hash. Do not set if using block_hash or block. - - Returns: - The calculated stake fee as a Balance object. - """ - check_balance_amount(amount) - block_hash = await self.determine_block_hash( - block=block, block_hash=block_hash, reuse_block=reuse_block - ) - result = await self.substrate.query( - module="Swap", - storage_function="FeeRate", - params=[netuid], - block_hash=block_hash, - ) - return amount * (result.value / U16_MAX) - async def get_stake_weight( self, netuid: int, @@ -3355,7 +3413,6 @@ async def get_total_subnets( ) return getattr(result, "value", None) - # TODO: update related with fee calculation async def get_transfer_fee( self, wallet: "Wallet", dest: str, amount: Balance, keep_alive: bool = True ) -> Balance: @@ -3384,7 +3441,7 @@ async def get_transfer_fee( call_params: dict[str, Union[int, str, bool]] call_function, call_params = get_transfer_fn_params(amount, dest, keep_alive) - call = await self.substrate.compose_call( + call = await self.compose_call( call_module="Balances", call_function=call_function, call_params=call_params, @@ -3400,28 +3457,36 @@ async def get_transfer_fee( return Balance.from_rao(payment_info["partial_fee"]) - # TODO: update related with fee calculation async def get_unstake_fee( self, - amount: Balance, netuid: int, + amount: Balance, block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, ) -> Balance: """ Calculates the fee for unstaking from a hotkey. Parameters: - amount: Amount of stake to unstake in TAO - netuid: Netuid of subnet - block: Block number at which to perform the calculation + netuid: The unique identifier of the subnet. + amount: Amount of stake to unstake in TAO. + block: The blockchain block number for the query. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. Returns: - The calculated stake fee as a Balance object + The calculated stake fee as a Balance object in Alpha. """ check_balance_amount(amount) - return await self.get_stake_operations_fee( - amount=amount, netuid=netuid, block=block + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + sim_swap_result = await self.sim_swap( + origin_netuid=netuid, + destination_netuid=0, + amount=amount, + block_hash=block_hash, ) + return sim_swap_result.alpha_fee.set_unit(netuid=netuid) async def get_vote_data( self, @@ -4353,7 +4418,119 @@ async def weights_rate_limit( ) return None if call is None else int(call) - # Extrinsics helper ================================================================================================ + # Extrinsics helpers =============================================================================================== + async def validate_extrinsic_params( + self, + call_module: str, + call_function: str, + call_params: dict[str, Any], + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ): + """ + Validate and filter extrinsic parameters against on-chain metadata. + + This method checks that the provided parameters match the expected signature of the given extrinsic (module and + function) as defined in the Substrate metadata. It raises explicit errors for missing or invalid parameters and + silently ignores any extra keys not present in the function definition. + + Args: + call_module: The pallet name, e.g. "SubtensorModule" or "AdminUtils". + call_function: The extrinsic function name, e.g. "set_weights" or "sudo_set_tempo". + call_params: A dictionary of parameters to validate. + block: The blockchain block number for the query. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. + + Returns: + A filtered dictionary containing only the parameters that are valid for the specified extrinsic. + + Raises: + ValueError: If the given module or function is not found in the chain metadata. + KeyError: If one or more required parameters are missing. + + Notes: + This method does not compose or submit the extrinsic. It only ensures that `call_params` conforms to the + expected schema derived from on-chain metadata. + """ + block_hash = await self.determine_block_hash( + block=block, block_hash=block_hash, reuse_block=reuse_block + ) + + func_meta = await self.substrate.get_metadata_call_function( + module_name=call_module, + call_function_name=call_function, + block_hash=block_hash, + ) + + if not func_meta: + raise ValueError( + f"Call {call_module}.{call_function} not found in chain metadata." + ) + + # Expected params from metadata + expected_params = func_meta.get_param_info() + provided_params = {} + + # Validate and filter parameters + for param_name in expected_params.keys(): + if param_name not in call_params: + raise KeyError(f"Missing required parameter: '{param_name}'") + provided_params[param_name] = call_params[param_name] + + # Warn about extra params not defined in metadata + extra_params = set(call_params.keys()) - set(expected_params.keys()) + if extra_params: + logging.debug( + f"Ignoring extra parameters for {call_module}.{call_function}: {extra_params}." + ) + return provided_params + + async def compose_call( + self, + call_module: str, + call_function: str, + call_params: dict[str, Any], + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> "GenericCall": + """ + Dynamically compose a GenericCall using on-chain Substrate metadata after validating the provided parameters. + + Args: + call_module: Pallet name (e.g. "SubtensorModule", "AdminUtils"). + call_function: Function name (e.g. "set_weights", "sudo_set_tempo"). + call_params: Dictionary of parameters for the call. + block: The blockchain block number for the query. + block_hash: The blockchain block_hash representation of the block id. + reuse_block: Whether to reuse the last-used blockchain block hash. + + Returns: + GenericCall: Composed call object ready for extrinsic submission. + """ + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) + + call_params = await self.validate_extrinsic_params( + call_module=call_module, + call_function=call_function, + call_params=call_params, + block=block, + block_hash=block_hash, + reuse_block=reuse_block, + ) + + logging.debug( + f"Composing GenericCall -> {call_module}.{call_function} " + f"with params: {call_params}." + ) + return await self.substrate.compose_call( + call_module=call_module, + call_function=call_function, + call_params=call_params, + block_hash=block_hash, + ) async def sign_and_send_extrinsic( self, @@ -4415,8 +4592,8 @@ async def sign_and_send_extrinsic( if period is not None: extrinsic_data["era"] = {"period": period} - extrinsic_response.extrinsic_fee = await get_extrinsic_fee( - subtensor=self, call=call, keypair=signing_keypair + extrinsic_response.extrinsic_fee = await self.get_extrinsic_fee( + call=call, keypair=signing_keypair ) extrinsic_response.extrinsic = await self.substrate.create_signed_extrinsic( **extrinsic_data @@ -4459,6 +4636,27 @@ async def sign_and_send_extrinsic( extrinsic_response.error = error return extrinsic_response + async def get_extrinsic_fee( + self, + call: "GenericCall", + keypair: "Keypair", + ): + """ + Get extrinsic fee for a given extrinsic call and keypair for a given SN's netuid. + + Parameters: + call: The extrinsic GenericCall. + keypair: The keypair associated with the extrinsic. + + Returns: + Balance object representing the extrinsic fee in RAO. + + Note: + To create the GenericCall object use `compose_call` method with proper parameters. + """ + payment_info = await self.substrate.get_payment_info(call=call, keypair=keypair) + return Balance.from_rao(amount=payment_info["partial_fee"]) + # Extrinsics ======================================================================================================= async def add_stake( @@ -4819,10 +5017,10 @@ async def modify_liquidity( async def move_stake( self, wallet: "Wallet", - origin_hotkey_ss58: str, origin_netuid: int, - destination_hotkey_ss58: str, + origin_hotkey_ss58: str, destination_netuid: int, + destination_hotkey_ss58: str, amount: Optional[Balance] = None, move_all_stake: bool = False, period: Optional[int] = None, @@ -4835,10 +5033,10 @@ async def move_stake( 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. + origin_hotkey_ss58: The SS58 address of the source hotkey. destination_netuid: The netuid of the destination subnet. + destination_hotkey_ss58: The SS58 address of the destination hotkey. 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 @@ -4855,10 +5053,10 @@ async def move_stake( return await move_stake_extrinsic( subtensor=self, wallet=wallet, - origin_hotkey_ss58=origin_hotkey_ss58, origin_netuid=origin_netuid, - destination_hotkey_ss58=destination_hotkey_ss58, + origin_hotkey_ss58=origin_hotkey_ss58, destination_netuid=destination_netuid, + destination_hotkey_ss58=destination_hotkey_ss58, amount=amount, move_all_stake=move_all_stake, period=period, @@ -5954,8 +6152,8 @@ async def unstake( async def unstake_all( self, wallet: "Wallet", - hotkey_ss58: str, netuid: int, + hotkey_ss58: str, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, raise_error: bool = False, @@ -5966,8 +6164,8 @@ async def unstake_all( Parameters: wallet: The wallet of the stake owner. - hotkey_ss58: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. + hotkey_ss58: The SS58 address of the hotkey to unstake from. 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. period: The number of blocks during which the transaction will remain valid after it's submitted. If @@ -6022,8 +6220,8 @@ async def unstake_all( return await unstake_all_extrinsic( subtensor=self, wallet=wallet, - hotkey_ss58=hotkey_ss58, netuid=netuid, + hotkey_ss58=hotkey_ss58, rate_tolerance=rate_tolerance, period=period, raise_error=raise_error, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 17ddd0006f..a6e0087ca9 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -10,6 +10,7 @@ from async_substrate_interface.types import ScaleObj from async_substrate_interface.utils.storage import StorageKey from bittensor_drand import get_encrypted_commitment +from bittensor_wallet.utils import SS58_FORMAT from bittensor.core.async_subtensor import ProposalVoteData from bittensor.core.axon import Axon @@ -21,6 +22,7 @@ NeuronInfo, NeuronInfoLite, SelectiveMetagraphIndex, + SimSwapResult, StakeInfo, SubnetInfo, SubnetIdentity, @@ -52,6 +54,7 @@ swap_stake_extrinsic, move_stake_extrinsic, ) +from bittensor.core.extrinsics.params.transfer import get_transfer_fn_params from bittensor.core.extrinsics.registration import ( burned_register_extrinsic, register_extrinsic, @@ -76,7 +79,6 @@ unstake_extrinsic, unstake_multiple_extrinsic, ) -from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.core.extrinsics.weights import ( commit_timelocked_weights_extrinsic, commit_weights_extrinsic, @@ -84,7 +86,6 @@ set_weights_extrinsic, ) from bittensor.core.metagraph import Metagraph -from bittensor_wallet.utils import SS58_FORMAT from bittensor.core.settings import ( version_as_int, TAO_APP_BLOCK_EXPLORER, @@ -104,7 +105,6 @@ decode_hex_identity_dict, format_error_message, get_caller_name, - get_transfer_fn_params, get_mechid_storage_index, is_valid_ss58_address, u16_normalized_float, @@ -124,12 +124,9 @@ price_to_tick, LiquidityPosition, ) -from bittensor.utils.weight_utils import ( - U16_MAX, -) if TYPE_CHECKING: - from bittensor_wallet import Wallet + from bittensor_wallet import Keypair, Wallet from async_substrate_interface.sync_substrate import QueryMapResult from scalecodec.types import GenericCall @@ -244,6 +241,14 @@ def _get_substrate( ) def determine_block_hash(self, block: Optional[int]) -> Optional[str]: + """Determine the appropriate block hash based on the provided block. + + Parameters: + block: The block number to query. + + Returns: + The block hash if one can be determined, None otherwise. + """ if block is None: return None else: @@ -301,6 +306,78 @@ def get_hyperparameter( def block(self) -> int: return self.get_current_block() + def sim_swap( + self, + origin_netuid: int, + destination_netuid: int, + amount: "Balance", + block: Optional[int] = None, + ) -> SimSwapResult: + """ + Hits the SimSwap Runtime API to calculate the fee and result for a given transaction. The SimSwapResult contains + the staking fees and expected returned amounts of a given transaction. This does not include the transaction + (extrinsic) fee. + + Args: + origin_netuid: Netuid of the source subnet (0 if add stake). + destination_netuid: Netuid of the destination subnet. + amount: Amount to stake operation. + block: The blockchain block number at which to perform the query. + + Returns: + SimSwapResult object representing the result. + """ + check_balance_amount(amount) + if origin_netuid > 0 and destination_netuid > 0: + # for cross-subnet moves where neither origin nor destination is root + intermediate_result_ = self.query_runtime_api( + runtime_api="SwapRuntimeApi", + method="sim_swap_alpha_for_tao", + params={"netuid": origin_netuid, "alpha": amount.rao}, + block=block, + ) + sn_price = self.get_subnet_price(origin_netuid, block=block) + intermediate_result = SimSwapResult.from_dict( + intermediate_result_, origin_netuid + ) + result = SimSwapResult.from_dict( + self.query_runtime_api( + runtime_api="SwapRuntimeApi", + method="sim_swap_tao_for_alpha", + params={ + "netuid": destination_netuid, + "tao": intermediate_result.tao_amount.rao, + }, + block=block, + ), + origin_netuid, + ) + secondary_fee = (result.tao_fee / sn_price.tao).set_unit(origin_netuid) + result.alpha_fee = result.alpha_fee + secondary_fee + return result + elif origin_netuid > 0: + # dynamic to tao + return SimSwapResult.from_dict( + self.query_runtime_api( + runtime_api="SwapRuntimeApi", + method="sim_swap_alpha_for_tao", + params={"netuid": origin_netuid, "alpha": amount.rao}, + block=block, + ), + origin_netuid, + ) + else: + # tao to dynamic or unstaked to staked tao (SN0) + return SimSwapResult.from_dict( + self.query_runtime_api( + runtime_api="SwapRuntimeApi", + method="sim_swap_tao_for_alpha", + params={"netuid": destination_netuid, "tao": amount.rao}, + block=block, + ), + destination_netuid, + ) + # Subtensor queries =========================================================================================== def query_constant( @@ -1989,27 +2066,6 @@ def get_stake( return Balance.from_rao(int(stake)).set_unit(netuid=netuid) - # TODO: update related with fee calculation - def get_stake_add_fee( - self, - amount: Balance, - netuid: int, - block: Optional[int] = None, - ) -> Balance: - """ - Calculates the fee for adding new stake to a hotkey. - - Parameters: - amount: Amount of stake to add in TAO - netuid: Netuid of subnet - block: Block number at which to perform the calculation - - Returns: - The calculated stake fee as a Balance object - """ - check_balance_amount(amount) - return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) - def get_stake_for_coldkey_and_hotkey( self, coldkey_ss58: str, @@ -2093,54 +2149,56 @@ def get_stake_for_hotkey( get_hotkey_stake = get_stake_for_hotkey - # TODO: update related with fee calculation - def get_stake_movement_fee( + def get_stake_add_fee( self, amount: Balance, - origin_netuid: int, + netuid: int, block: Optional[int] = None, ) -> Balance: """ - Calculates the fee for moving stake between hotkeys/subnets/coldkeys. + Calculates the fee for adding new stake to a hotkey. Parameters: - amount: Amount of stake to move in TAO - origin_netuid: Netuid of origin subnet + amount: Amount of stake to add in TAO + netuid: Netuid of subnet block: Block number at which to perform the calculation Returns: - The calculated stake fee as a Balance object + The calculated stake fee as a Balance object in TAO. """ check_balance_amount(amount) - return self.get_stake_operations_fee( - amount=amount, netuid=origin_netuid, block=block + sim_swap_result = self.sim_swap( + origin_netuid=0, destination_netuid=netuid, amount=amount, block=block ) + return sim_swap_result.tao_fee - def get_stake_operations_fee( + def get_stake_movement_fee( self, + origin_netuid: int, + destination_netuid: int, amount: Balance, - netuid: int, block: Optional[int] = None, - ): - """Returns fee for any stake operation in specified subnet. + ) -> Balance: + """ + Calculates the fee for moving stake between hotkeys/subnets/coldkeys. Parameters: - amount: Amount of stake to add in Alpha/TAO. - netuid: Netuid of subnet. - block: Block number at which to perform the calculation. + origin_netuid: Netuid of source subnet. + destination_netuid: Netuid of the destination subnet. + amount: Amount of stake to move. + block: The block number for which the children are to be retrieved. Returns: - The calculated stake fee as a Balance object. + The calculated stake fee as a Balance object """ check_balance_amount(amount) - block_hash = self.determine_block_hash(block=block) - result = self.substrate.query( - module="Swap", - storage_function="FeeRate", - params=[netuid], - block_hash=block_hash, + sim_swap_result = self.sim_swap( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + block=block, ) - return amount * (result.value / U16_MAX) + return sim_swap_result.tao_fee def get_stake_weight(self, netuid: int, block: Optional[int] = None) -> list[float]: """ @@ -2424,7 +2482,6 @@ def get_total_subnets(self, block: Optional[int] = None) -> Optional[int]: ) return getattr(result, "value", None) - # TODO: update related with fee calculation def get_transfer_fee( self, wallet: "Wallet", @@ -2455,7 +2512,7 @@ def get_transfer_fee( call_params: dict[str, Union[int, str, bool]] call_function, call_params = get_transfer_fn_params(amount, dest, keep_alive) - call = self.substrate.compose_call( + call = self.compose_call( call_module="Balances", call_function=call_function, call_params=call_params, @@ -2471,26 +2528,31 @@ def get_transfer_fee( return Balance.from_rao(payment_info["partial_fee"]) - # TODO: update related with fee calculation def get_unstake_fee( self, - amount: Balance, netuid: int, + amount: Balance, block: Optional[int] = None, ) -> Balance: """ Calculates the fee for unstaking from a hotkey. Parameters: - amount: Amount of stake to unstake in TAO - netuid: Netuid of subnet - block: Block number at which to perform the calculation + netuid: The unique identifier of the subnet. + amount: Amount of stake to unstake in TAO. + block: Block number at which to perform the calculation. Returns: - The calculated stake fee as a Balance object + The calculated stake fee as a Balance object in Alpha. """ check_balance_amount(amount) - return self.get_stake_operations_fee(amount=amount, netuid=netuid, block=block) + sim_swap_result = self.sim_swap( + origin_netuid=netuid, + destination_netuid=0, + amount=amount, + block=block, + ) + return sim_swap_result.alpha_fee.set_unit(netuid=netuid) def get_vote_data( self, proposal_hash: str, block: Optional[int] = None @@ -3179,7 +3241,103 @@ def weights_rate_limit( ) return None if call is None else int(call) - # Extrinsics helper ================================================================================================ + # Extrinsics helpers =============================================================================================== + + def validate_extrinsic_params( + self, + call_module: str, + call_function: str, + call_params: dict[str, Any], + block: Optional[int] = None, + ): + """ + Validate and filter extrinsic parameters against on-chain metadata. + + This method checks that the provided parameters match the expected signature of the given extrinsic (module and + function) as defined in the Substrate metadata. It raises explicit errors for missing or invalid parameters and + silently ignores any extra keys not present in the function definition. + + Args: + call_module: The pallet name, e.g. "SubtensorModule" or "AdminUtils". + call_function: The extrinsic function name, e.g. "set_weights" or "sudo_set_tempo". + call_params: A dictionary of parameters to validate. + block: Optional block number to query metadata from. If not provided, the latest metadata is used. + + Returns: + A filtered dictionary containing only the parameters that are valid for the specified extrinsic. + + Raises: + ValueError: If the given module or function is not found in the chain metadata. + KeyError: If one or more required parameters are missing. + + Notes: + This method does not compose or submit the extrinsic. It only ensures that `call_params` conforms to the + expected schema derived from on-chain metadata. + """ + block_hash = self.determine_block_hash(block=block) + + func_meta = self.substrate.get_metadata_call_function( + module_name=call_module, + call_function_name=call_function, + block_hash=block_hash, + ) + + if not func_meta: + raise ValueError( + f"Call {call_module}.{call_function} not found in chain metadata." + ) + + # Expected params from metadata + expected_params = func_meta.get_param_info() + provided_params = {} + + # Validate and filter parameters + for param_name in expected_params.keys(): + if param_name not in call_params: + raise KeyError(f"Missing required parameter: '{param_name}'") + provided_params[param_name] = call_params[param_name] + + # Warn about extra params not defined in metadata + extra_params = set(call_params.keys()) - set(expected_params.keys()) + if extra_params: + logging.debug( + f"Ignoring extra parameters for {call_module}.{call_function}: {extra_params}." + ) + return provided_params + + def compose_call( + self, + call_module: str, + call_function: str, + call_params: dict[str, Any], + block: Optional[int] = None, + ) -> "GenericCall": + """ + Dynamically compose a GenericCall using on-chain Substrate metadata after validating the provided parameters. + + Args: + call_module: Pallet name (e.g. "SubtensorModule", "AdminUtils"). + call_function: Function name (e.g. "set_weights", "sudo_set_tempo"). + call_params: Dictionary of parameters for the call. + block: Block number for querying metadata. + + Returns: + GenericCall: Composed call object ready for extrinsic submission. + """ + call_params = self.validate_extrinsic_params( + call_module, call_function, call_params, block + ) + block_hash = self.determine_block_hash(block=block) + logging.debug( + f"Composing GenericCall -> {call_module}.{call_function} " + f"with params: {call_params}." + ) + return self.substrate.compose_call( + call_module=call_module, + call_function=call_function, + call_params=call_params, + block_hash=block_hash, + ) def sign_and_send_extrinsic( self, @@ -3242,8 +3400,8 @@ def sign_and_send_extrinsic( if period is not None: extrinsic_data["era"] = {"period": period} - extrinsic_response.extrinsic_fee = get_extrinsic_fee( - subtensor=self, call=call, keypair=signing_keypair + extrinsic_response.extrinsic_fee = self.get_extrinsic_fee( + call=call, keypair=signing_keypair ) extrinsic_response.extrinsic = self.substrate.create_signed_extrinsic( **extrinsic_data @@ -3286,6 +3444,27 @@ def sign_and_send_extrinsic( extrinsic_response.error = error return extrinsic_response + def get_extrinsic_fee( + self, + call: "GenericCall", + keypair: "Keypair", + ): + """ + Get extrinsic fee for a given extrinsic call and keypair for a given SN's netuid. + + Parameters: + call: The extrinsic GenericCall. + keypair: The keypair associated with the extrinsic. + + Returns: + Balance object representing the extrinsic fee in RAO. + + Note: + To create the GenericCall object use `compose_call` method with proper parameters. + """ + payment_info = self.substrate.get_payment_info(call=call, keypair=keypair) + return Balance.from_rao(amount=payment_info["partial_fee"]) + # Extrinsics ======================================================================================================= def add_stake( @@ -3643,10 +3822,10 @@ def modify_liquidity( def move_stake( self, wallet: "Wallet", - origin_hotkey_ss58: str, origin_netuid: int, - destination_hotkey_ss58: str, + origin_hotkey_ss58: str, destination_netuid: int, + destination_hotkey_ss58: str, amount: Optional[Balance] = None, move_all_stake: bool = False, period: Optional[int] = None, @@ -3659,10 +3838,10 @@ def move_stake( 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. + origin_hotkey_ss58: The SS58 address of the source hotkey. destination_netuid: The netuid of the destination subnet. + destination_hotkey_ss58: The SS58 address of the destination hotkey. 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 @@ -3679,10 +3858,10 @@ def move_stake( return move_stake_extrinsic( subtensor=self, wallet=wallet, - origin_hotkey_ss58=origin_hotkey_ss58, origin_netuid=origin_netuid, - destination_hotkey_ss58=destination_hotkey_ss58, + origin_hotkey_ss58=origin_hotkey_ss58, destination_netuid=destination_netuid, + destination_hotkey_ss58=destination_hotkey_ss58, amount=amount, move_all_stake=move_all_stake, period=period, @@ -4019,8 +4198,8 @@ def set_auto_stake( def set_children( self, wallet: "Wallet", - hotkey_ss58: str, netuid: int, + hotkey_ss58: str, children: list[tuple[float, str]], period: Optional[int] = None, raise_error: bool = False, @@ -4760,8 +4939,8 @@ def unstake( def unstake_all( self, wallet: "Wallet", - hotkey_ss58: str, netuid: int, + hotkey_ss58: str, rate_tolerance: Optional[float] = 0.005, period: Optional[int] = None, raise_error: bool = False, @@ -4772,8 +4951,8 @@ def unstake_all( Parameters: wallet: The wallet of the stake owner. - hotkey_ss58: The SS58 address of the hotkey to unstake from. netuid: The unique identifier of the subnet. + hotkey_ss58: The SS58 address of the hotkey to unstake from. 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. period: The number of blocks during which the transaction will remain valid after it's submitted. If @@ -4827,8 +5006,8 @@ def unstake_all( return unstake_all_extrinsic( subtensor=self, wallet=wallet, - hotkey_ss58=hotkey_ss58, netuid=netuid, + hotkey_ss58=hotkey_ss58, rate_tolerance=rate_tolerance, period=period, raise_error=raise_error, From 813cb85599ce5f56a7511fdb8cbff99a224be767 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 12:05:28 -0700 Subject: [PATCH 380/416] new logic for e2e tests staking fee --- tests/e2e_tests/test_stake_fee.py | 139 +++++++++++++++--------------- 1 file changed, 70 insertions(+), 69 deletions(-) diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index 39b2fb522e..ee13e34a65 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -1,5 +1,6 @@ import pytest +from bittensor.utils.btlogging import logging from bittensor.utils.balance import Balance from tests.e2e_tests.utils import TestSubnet, REGISTER_SUBNET @@ -16,16 +17,20 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): - Moving stake between hotkeys/subnets/coldkeys """ root_netuid = 0 - stake_amount = Balance.from_tao(100) # 100 TAO - min_stake_fee = Balance.from_tao(0.050354772) + stake_amount = Balance.from_tao(1) # 1 TAO + min_stake_fee = Balance.from_tao(0.000503547) - sn = TestSubnet(subtensor) - sn.execute_one(REGISTER_SUBNET(alice_wallet)) + sn2 = TestSubnet(subtensor) + sn2.execute_one(REGISTER_SUBNET(alice_wallet)) + + # Test cross-subnet movement + sn3 = TestSubnet(subtensor) + sn3.execute_one(REGISTER_SUBNET(bob_wallet)) # Test add_stake fee stake_fee_0 = subtensor.staking.get_stake_add_fee( amount=stake_amount, - netuid=sn.netuid, + netuid=sn2.netuid, ) assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object." assert stake_fee_0 == min_stake_fee, ( @@ -34,67 +39,63 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): # Test unstake fee unstake_fee_root = subtensor.staking.get_unstake_fee( - amount=stake_amount, netuid=root_netuid, + amount=stake_amount, ) assert isinstance(unstake_fee_root, Balance), ( "Stake fee should be a Balance object." ) - assert unstake_fee_root == min_stake_fee, ( - "Root unstake fee should be equal the minimum stake fee." + assert unstake_fee_root == Balance.from_tao(0), ( + "Root unstake fee should be equal o TAO fee." ) # Test various stake movement scenarios movement_scenarios = [ - # Move from root to non-root { + "title": "Move from root to non-root", "origin_netuid": root_netuid, + "destination_netuid": sn2.netuid, "stake_fee": min_stake_fee, }, - # Move between hotkeys on root + { + "title": "Move between hotkeys on root", "origin_netuid": root_netuid, + "destination_netuid": root_netuid, "stake_fee": 0, }, - # Move between coldkeys on root + { + "title": "Move between coldkeys on root", "origin_netuid": root_netuid, + "destination_netuid": root_netuid, "stake_fee": 0, }, - # Move between coldkeys on non-root + { - "origin_netuid": sn.netuid, + "title": "Move between coldkeys on non-root", + "origin_netuid": sn2.netuid, + "destination_netuid": sn2.netuid, + "stake_fee": min_stake_fee, + }, + + { + "title": "Move between different subnets", + "origin_netuid": sn2.netuid, + "destination_netuid": sn3.netuid, "stake_fee": min_stake_fee, }, ] for scenario in movement_scenarios: + logging.console.info(f"Scenario: {scenario.get("title")}") stake_fee = subtensor.staking.get_stake_movement_fee( + origin_netuid=scenario.get("origin_netuid"), + destination_netuid=scenario.get("destination_netuid"), amount=stake_amount, - origin_netuid=scenario["origin_netuid"], ) 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=sn.netuid, - ) - 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" - ) + assert scenario["stake_fee"] >= stake_fee @pytest.mark.asyncio @@ -110,16 +111,20 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): - Moving stake between hotkeys/subnets/coldkeys """ root_netuid = 0 - stake_amount = Balance.from_tao(100) # 100 TAO - min_stake_fee = Balance.from_tao(0.050354772) + stake_amount = Balance.from_tao(1) # 1 TAO + min_stake_fee = Balance.from_tao(0.000503547) - sn = TestSubnet(async_subtensor) - await sn.async_execute_one(REGISTER_SUBNET(alice_wallet)) + sn2 = TestSubnet(async_subtensor) + await sn2.async_execute_one(REGISTER_SUBNET(bob_wallet)) + + # Test cross-subnet movement + sn3 = TestSubnet(async_subtensor) + await sn3.async_execute_one(REGISTER_SUBNET(bob_wallet)) # Test add_stake fee stake_fee_0 = await async_subtensor.staking.get_stake_add_fee( amount=stake_amount, - netuid=sn.netuid, + netuid=sn2.netuid, ) assert isinstance(stake_fee_0, Balance), "Stake fee should be a Balance object." assert stake_fee_0 == min_stake_fee, ( @@ -128,64 +133,60 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): # Test unstake fee unstake_fee_root = await async_subtensor.staking.get_unstake_fee( - amount=stake_amount, netuid=root_netuid, + amount=stake_amount, ) assert isinstance(unstake_fee_root, Balance), ( "Stake fee should be a Balance object." ) - assert unstake_fee_root == min_stake_fee, ( + assert unstake_fee_root == Balance.from_tao(0), ( "Root unstake fee should be equal the minimum stake fee." ) # Test various stake movement scenarios movement_scenarios = [ - # Move from root to non-root { + "title": "Move from root to non-root", "origin_netuid": root_netuid, + "destination_netuid": sn2.netuid, "stake_fee": min_stake_fee, }, - # Move between hotkeys on root + { + "title": "Move between hotkeys on root", "origin_netuid": root_netuid, + "destination_netuid": root_netuid, "stake_fee": 0, }, - # Move between coldkeys on root + { + "title": "Move between coldkeys on root", "origin_netuid": root_netuid, + "destination_netuid": root_netuid, "stake_fee": 0, }, - # Move between coldkeys on non-root + + { + "title": "Move between coldkeys on non-root", + "origin_netuid": sn2.netuid, + "destination_netuid": sn2.netuid, + "stake_fee": min_stake_fee, + }, + { - "origin_netuid": sn.netuid, + "title": "Move between different subnets", + "origin_netuid": sn2.netuid, + "destination_netuid": sn3.netuid, "stake_fee": min_stake_fee, }, ] for scenario in movement_scenarios: + logging.console.info(f"Scenario: {scenario.get("title")}") stake_fee = await async_subtensor.staking.get_stake_movement_fee( + origin_netuid=scenario.get("origin_netuid"), + destination_netuid=scenario.get("destination_netuid"), amount=stake_amount, - origin_netuid=scenario["origin_netuid"], ) 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 await async_subtensor.subnets.register_subnet(alice_wallet), ( - "Unable to register the second subnet" - ) - assert await async_subtensor.subnets.subnet_exists(netuid2), ( - "Second subnet wasn't created successfully" - ) - - stake_fee = await async_subtensor.staking.get_stake_movement_fee( - amount=stake_amount, - origin_netuid=sn.netuid, - ) - 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" - ) + assert scenario["stake_fee"] >= stake_fee From 9d5a45e3fba3f821786fc7e852f6b6d1d6e7bba6 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 12:05:41 -0700 Subject: [PATCH 381/416] remove debug --- tests/e2e_tests/test_staking.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/e2e_tests/test_staking.py b/tests/e2e_tests/test_staking.py index 692bfe2704..0d17987864 100644 --- a/tests/e2e_tests/test_staking.py +++ b/tests/e2e_tests/test_staking.py @@ -1399,8 +1399,6 @@ async def test_move_stake_async(async_subtensor, alice_wallet, bob_wallet, dave_ logging.console.info(f"[orange]Dave stake after moving all: {dave_stake}[orange]") assert dave_stake.rao == CloseInValue(0, 0.00001) - print(">>> alice", alice_sn.calls) - print(">>> alice", alice_sn.calls) def test_transfer_stake(subtensor, alice_wallet, bob_wallet, dave_wallet): From 907015d78ecd1b5a4a2f7d646b1cf5fbdb53db1e Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 12:06:30 -0700 Subject: [PATCH 382/416] `subtensor.compose_call` applied --- tests/e2e_tests/test_delegate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_delegate.py b/tests/e2e_tests/test_delegate.py index 8c1f849ea1..583225a263 100644 --- a/tests/e2e_tests/test_delegate.py +++ b/tests/e2e_tests/test_delegate.py @@ -735,7 +735,7 @@ def test_get_vote_data(subtensor, alice_wallet): success, message = propose( subtensor=subtensor, wallet=alice_wallet, - proposal=subtensor.substrate.compose_call( + proposal=subtensor.compose_call( call_module="Triumvirate", call_function="set_members", call_params={ @@ -839,7 +839,7 @@ async def test_get_vote_data_async(async_subtensor, alice_wallet): success, message = await async_propose( subtensor=async_subtensor, wallet=alice_wallet, - proposal=await async_subtensor.substrate.compose_call( + proposal=await async_subtensor.compose_call( call_module="Triumvirate", call_function="set_members", call_params={ From 9410070030cd3c0dad055e2c15fe110fa1831a54 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 12:06:46 -0700 Subject: [PATCH 383/416] remove unused import --- tests/e2e_tests/test_subtensor_functions.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index b236f9bd53..03e56f57db 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -2,10 +2,6 @@ import re import pytest -from bittensor.core.extrinsics.asyncex.utils import ( - get_extrinsic_fee as get_extrinsic_fee_async, -) -from bittensor.core.extrinsics.utils import get_extrinsic_fee from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging from tests.e2e_tests.utils import ( From 75c7aa86a4765a0c4d9d2572794ddb761de1dabe Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 12:32:50 -0700 Subject: [PATCH 384/416] update unit test regarding a new logic --- .../extrinsics/asyncex/test_children.py | 4 +- .../extrinsics/asyncex/test_liquidity.py | 27 ++-- .../extrinsics/asyncex/test_registration.py | 10 +- .../extrinsics/asyncex/test_root.py | 18 +-- .../extrinsics/asyncex/test_staking.py | 2 +- .../extrinsics/asyncex/test_start_call.py | 7 +- .../extrinsics/asyncex/test_transfer.py | 6 +- .../extrinsics/asyncex/test_unstaking.py | 18 +-- .../extrinsics/asyncex/test_weights.py | 8 +- tests/unit_tests/extrinsics/test_children.py | 11 +- tests/unit_tests/extrinsics/test_liquidity.py | 18 +-- .../extrinsics/test_registration.py | 4 +- tests/unit_tests/extrinsics/test_root.py | 4 +- tests/unit_tests/extrinsics/test_serving.py | 4 +- tests/unit_tests/extrinsics/test_staking.py | 10 +- .../unit_tests/extrinsics/test_start_call.py | 6 +- tests/unit_tests/extrinsics/test_transfer.py | 6 +- tests/unit_tests/extrinsics/test_unstaking.py | 8 +- tests/unit_tests/extrinsics/test_weights.py | 8 +- tests/unit_tests/test_async_subtensor.py | 153 +++++++++--------- tests/unit_tests/test_subtensor.py | 86 +++++----- 21 files changed, 201 insertions(+), 217 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_children.py b/tests/unit_tests/extrinsics/asyncex/test_children.py index 8ce1fe9563..0d3ad36cad 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_children.py +++ b/tests/unit_tests/extrinsics/asyncex/test_children.py @@ -17,7 +17,7 @@ async def test_set_children_extrinsic(subtensor, mocker, fake_wallet): ), ] - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -70,7 +70,7 @@ async def test_root_set_pending_childkey_cooldown_extrinsic( # Preps cooldown = 100 - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", diff --git a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py index 92cc03ff18..acc158fff5 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py +++ b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py @@ -6,16 +6,16 @@ async def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): """Test that the add `add_liquidity_extrinsic` executes correct calls.""" # Preps - fake_netuid = 1 + fake_netuid = mocker.Mock() fake_liquidity = mocker.Mock() fake_price_low = mocker.Mock() fake_price_high = mocker.Mock() - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" ) - mocked_price_to_tick = mocker.patch.object(liquidity, "price_to_tick") + mocked_param_add_liquidity = mocker.patch.object(liquidity.LiquidityParams, "add_liquidity") # Call result = await liquidity.add_liquidity_extrinsic( @@ -28,16 +28,17 @@ async def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): ) # Asserts + mocked_param_add_liquidity.assert_called_once_with( + netuid=fake_netuid, + hotkey_ss58=fake_wallet.hotkey.ss58_address, + liquidity=fake_liquidity, + price_low=fake_price_low, + price_high=fake_price_high, + ) mocked_compose_call.assert_awaited_once_with( call_module="Swap", call_function="add_liquidity", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "netuid": fake_netuid, - "tick_low": mocked_price_to_tick.return_value, - "tick_high": mocked_price_to_tick.return_value, - "liquidity": fake_liquidity.rao, - }, + call_params=mocked_param_add_liquidity.return_value, ) mocked_sign_and_send_extrinsic.assert_awaited_once_with( call=mocked_compose_call.return_value, @@ -58,7 +59,7 @@ async def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): fake_position_id = 2 fake_liquidity_delta = mocker.Mock() - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" ) @@ -101,7 +102,7 @@ async def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): fake_netuid = 1 fake_position_id = 2 - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" ) @@ -142,7 +143,7 @@ async def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): fake_netuid = 1 fake_enable = mocker.Mock() - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" ) diff --git a/tests/unit_tests/extrinsics/asyncex/test_registration.py b/tests/unit_tests/extrinsics/asyncex/test_registration.py index 047b653111..a9ba797d00 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_registration.py +++ b/tests/unit_tests/extrinsics/asyncex/test_registration.py @@ -26,7 +26,7 @@ async def test_register_extrinsic_success(subtensor, fake_wallet, mocker): is_stale_async=mocker.AsyncMock(return_value=False), seal=[] ), ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) @@ -92,7 +92,7 @@ async def test_register_extrinsic_success_with_cuda(subtensor, fake_wallet, mock is_stale_async=mocker.AsyncMock(return_value=False), seal=[] ), ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) @@ -263,7 +263,7 @@ async def is_stale_side_effect(*_, **__): "create_pow_async", return_value=fake_pow_result, ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -318,7 +318,7 @@ async def test_set_subnet_identity_extrinsic_is_success(subtensor, fake_wallet, description = "mock_description" additional = "mock_additional" - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" @@ -383,7 +383,7 @@ async def test_set_subnet_identity_extrinsic_is_failed(subtensor, fake_wallet, m additional = "mock_additional" fake_error_message = "error message" - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, diff --git a/tests/unit_tests/extrinsics/asyncex/test_root.py b/tests/unit_tests/extrinsics/asyncex/test_root.py index cc0b7112aa..aa3b4a0de8 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_root.py +++ b/tests/unit_tests/extrinsics/asyncex/test_root.py @@ -52,7 +52,7 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): "is_hotkey_registered", return_value=False, ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -83,7 +83,7 @@ async def test_root_register_extrinsic_success(subtensor, fake_wallet, mocker): ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet, False) + mocked_unlock_key.assert_called_once_with(fake_wallet, False, unlock_type="both") mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -129,7 +129,7 @@ async def test_root_register_extrinsic_insufficient_balance( wait_for_finalization=True, ) - mocked_unlock_key.assert_called_once_with(fake_wallet, False) + mocked_unlock_key.assert_called_once_with(fake_wallet, False, unlock_type="both") assert result.success is False subtensor.get_balance.assert_called_once_with( @@ -168,7 +168,7 @@ async def test_root_register_extrinsic_unlock_failed(subtensor, fake_wallet, moc ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet, False) + mocked_unlock_key.assert_called_once_with(fake_wallet, False, unlock_type="both") assert result.success is False @@ -210,7 +210,7 @@ async def test_root_register_extrinsic_already_registered( ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet, False) + mocked_unlock_key.assert_called_once_with(fake_wallet, False, unlock_type="both") mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -245,7 +245,7 @@ async def test_root_register_extrinsic_transaction_failed( "is_hotkey_registered", return_value=False, ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -261,7 +261,7 @@ async def test_root_register_extrinsic_transaction_failed( ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet, False) + mocked_unlock_key.assert_called_once_with(fake_wallet, False, unlock_type="both") mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) @@ -296,7 +296,7 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc "is_hotkey_registered", return_value=False, ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -317,7 +317,7 @@ async def test_root_register_extrinsic_uid_not_found(subtensor, fake_wallet, moc ) # Asserts - mocked_unlock_key.assert_called_once_with(fake_wallet, False) + mocked_unlock_key.assert_called_once_with(fake_wallet, False, unlock_type="both") mocked_is_hotkey_registered.assert_called_once_with( netuid=0, hotkey_ss58="fake_hotkey_address" ) diff --git a/tests/unit_tests/extrinsics/asyncex/test_staking.py b/tests/unit_tests/extrinsics/asyncex/test_staking.py index 497362094f..6d1571fcf7 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_staking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_staking.py @@ -20,7 +20,7 @@ async def test_set_auto_stake_extrinsic( netuid = mocker.Mock() hotkey_ss58 = mocker.Mock() - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, diff --git a/tests/unit_tests/extrinsics/asyncex/test_start_call.py b/tests/unit_tests/extrinsics/asyncex/test_start_call.py index 23d9bf755c..1f23111544 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_start_call.py +++ b/tests/unit_tests/extrinsics/asyncex/test_start_call.py @@ -10,8 +10,7 @@ async def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wallet = fake_wallet wallet.name = "fake_wallet" wallet.coldkey = "fake_coldkey" - substrate = subtensor.substrate.__aenter__.return_value - substrate.compose_call = mocker.AsyncMock() + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=(True, "Success") ) @@ -24,14 +23,14 @@ async def test_start_call_extrinsics(subtensor, mocker, fake_wallet): ) # Assertions - substrate.compose_call.assert_awaited_once_with( + mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", call_function="start_call", call_params={"netuid": netuid}, ) mocked_sign_and_send_extrinsic.assert_awaited_once_with( - call=substrate.compose_call.return_value, + call=mocked_compose_call.return_value, wallet=wallet, wait_for_inclusion=True, wait_for_finalization=False, diff --git a/tests/unit_tests/extrinsics/asyncex/test_transfer.py b/tests/unit_tests/extrinsics/asyncex/test_transfer.py index 4fe074e8b7..c27880ece0 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_transfer.py +++ b/tests/unit_tests/extrinsics/asyncex/test_transfer.py @@ -37,7 +37,7 @@ 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_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) @@ -109,7 +109,7 @@ 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_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(False, "") ) @@ -300,7 +300,7 @@ 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_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) diff --git a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py index 4a68883f29..6d4efe2360 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_unstaking.py +++ b/tests/unit_tests/extrinsics/asyncex/test_unstaking.py @@ -24,6 +24,8 @@ async def test_unstake_extrinsic(fake_wallet, mocker): } ) + mocked_params = mocker.patch.object(unstaking.UnstakingParams, "remove_stake") + fake_wallet.coldkeypub.ss58_address = "hotkey_owner" hotkey_ss58 = "hotkey" amount = Balance.from_tao(1.1) @@ -44,17 +46,13 @@ async def test_unstake_extrinsic(fake_wallet, mocker): # Asserts assert result.success is True - fake_subtensor.substrate.compose_call.assert_awaited_once_with( + fake_subtensor.compose_call.assert_awaited_once_with( call_module="SubtensorModule", call_function="remove_stake", - call_params={ - "hotkey": "hotkey", - "amount_unstaked": 1100000000, - "netuid": fake_netuid, - }, + call_params=mocked_params.return_value, ) fake_subtensor.sign_and_send_extrinsic.assert_awaited_once_with( - call=fake_subtensor.substrate.compose_call.return_value, + call=fake_subtensor.compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, @@ -74,7 +72,7 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): "sign_and_send_extrinsic.return_value": (True, ""), } ) - fake_substrate = fake_subtensor.substrate.__aenter__.return_value + mocked_compose_call = mocker.patch.object(fake_subtensor, "compose_call") hotkey = "hotkey" fake_netuid = 1 @@ -90,7 +88,7 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): assert result[0] is True assert result[1] == "" - fake_substrate.compose_call.assert_awaited_once_with( + mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", call_function="remove_stake_full_limit", call_params={ @@ -100,7 +98,7 @@ async def test_unstake_all_extrinsic(fake_wallet, mocker): }, ) fake_subtensor.sign_and_send_extrinsic.assert_awaited_once_with( - call=fake_substrate.compose_call.return_value, + call=mocked_compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, diff --git a/tests/unit_tests/extrinsics/asyncex/test_weights.py b/tests/unit_tests/extrinsics/asyncex/test_weights.py index 5b6260a580..649dc79b15 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_weights.py +++ b/tests/unit_tests/extrinsics/asyncex/test_weights.py @@ -23,7 +23,7 @@ async def test_commit_weights_extrinsic(mocker, subtensor, fake_wallet): mocked_generate_weight_hash = mocker.patch.object( weights_module, "generate_weight_hash" ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) @@ -103,7 +103,7 @@ async def test_commit_timelocked_weights_extrinsic(mocker, subtensor, fake_walle "get_encrypted_commit", return_value=(mocker.Mock(), mocker.Mock()), ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -182,7 +182,7 @@ async def test_reveal_weights_extrinsic(mocker, subtensor, fake_wallet): "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) @@ -243,7 +243,7 @@ async def test_set_weights_extrinsic(mocker, subtensor, fake_wallet): "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", diff --git a/tests/unit_tests/extrinsics/test_children.py b/tests/unit_tests/extrinsics/test_children.py index ca5b53b0d8..8ec0dc2f68 100644 --- a/tests/unit_tests/extrinsics/test_children.py +++ b/tests/unit_tests/extrinsics/test_children.py @@ -14,7 +14,7 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): ), ] - subtensor.substrate.compose_call = mocker.Mock() + subtensor.compose_call = mocker.Mock() mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -31,7 +31,7 @@ def test_set_children_extrinsic(subtensor, mocker, fake_wallet): ) # Asserts - subtensor.substrate.compose_call.assert_called_once_with( + subtensor.compose_call.assert_called_once_with( call_module="SubtensorModule", call_function="set_children", call_params={ @@ -47,7 +47,7 @@ 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, + call=subtensor.compose_call.return_value, wallet=fake_wallet, period=None, raise_error=False, @@ -64,7 +64,7 @@ def test_root_set_pending_childkey_cooldown_extrinsic(subtensor, mocker, fake_wa # Preps cooldown = 100 - subtensor.substrate.compose_call = mocker.Mock() + subtensor.compose_call = mocker.Mock() mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -79,9 +79,8 @@ def test_root_set_pending_childkey_cooldown_extrinsic(subtensor, mocker, fake_wa ) # Asserts - subtensor.substrate.compose_call.call_count == 2 mocked_sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, + call=subtensor.compose_call.return_value, wallet=fake_wallet, nonce_key="hotkey", sign_with="coldkey", diff --git a/tests/unit_tests/extrinsics/test_liquidity.py b/tests/unit_tests/extrinsics/test_liquidity.py index 26ab1686d1..0281ae6235 100644 --- a/tests/unit_tests/extrinsics/test_liquidity.py +++ b/tests/unit_tests/extrinsics/test_liquidity.py @@ -9,11 +9,11 @@ def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): fake_price_low = mocker.Mock() fake_price_high = mocker.Mock() - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" ) - mocked_price_to_tick = mocker.patch.object(liquidity, "price_to_tick") + mocked_params = mocker.patch.object(liquidity.LiquidityParams, "add_liquidity") # Call result = liquidity.add_liquidity_extrinsic( @@ -29,13 +29,7 @@ def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): mocked_compose_call.assert_called_once_with( call_module="Swap", call_function="add_liquidity", - call_params={ - "hotkey": fake_wallet.hotkey.ss58_address, - "netuid": fake_netuid, - "tick_low": mocked_price_to_tick.return_value, - "tick_high": mocked_price_to_tick.return_value, - "liquidity": fake_liquidity.rao, - }, + call_params=mocked_params.return_value, ) mocked_sign_and_send_extrinsic.assert_called_once_with( call=mocked_compose_call.return_value, @@ -55,7 +49,7 @@ def test_modify_liquidity_extrinsic(subtensor, fake_wallet, mocker): fake_position_id = 2 fake_liquidity_delta = mocker.Mock() - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" ) @@ -97,7 +91,7 @@ def test_remove_liquidity_extrinsic(subtensor, fake_wallet, mocker): fake_netuid = 1 fake_position_id = 2 - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" ) @@ -137,7 +131,7 @@ def test_toggle_user_liquidity_extrinsic(subtensor, fake_wallet, mocker): fake_netuid = 1 fake_enable = mocker.Mock() - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" ) diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index d14685e1fa..abf65beb24 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -254,7 +254,7 @@ def test_set_subnet_identity_extrinsic_is_success(mock_subtensor, mock_wallet, m description = "mock_description" additional = "mock_additional" - mocked_compose_call = mocker.patch.object(mock_subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(mock_subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic" ) @@ -318,7 +318,7 @@ def test_set_subnet_identity_extrinsic_is_failed(mock_subtensor, mock_wallet, mo fake_error_message = "error message" - mocked_compose_call = mocker.patch.object(mock_subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(mock_subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( mock_subtensor, "sign_and_send_extrinsic", diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index 7d887964bf..bd40eae28a 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -101,13 +101,13 @@ def test_root_register_extrinsic( assert result.success == expected_result if not hotkey_registered[0]: - mock_subtensor.substrate.compose_call.assert_called_once_with( + mock_subtensor.compose_call.assert_called_once_with( call_module="SubtensorModule", call_function="root_register", call_params={"hotkey": "fake_hotkey_address"}, ) mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mock_subtensor.substrate.compose_call.return_value, + call=mock_subtensor.compose_call.return_value, wallet=mock_wallet, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 619ca2e565..427609cf85 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -351,7 +351,7 @@ def test_publish_metadata( ): # Arrange with ( - patch.object(mock_subtensor.substrate, "compose_call"), + patch.object(mock_subtensor, "compose_call"), patch.object( mock_subtensor, "sign_and_send_extrinsic", return_value=response_success ) as mocked_sign_and_send_extrinsic, @@ -369,7 +369,7 @@ def test_publish_metadata( # Assert assert result.success is True, f"Test ID: {test_id}" mocked_sign_and_send_extrinsic.assert_called_once_with( - call=mock_subtensor.substrate.compose_call.return_value, + call=mock_subtensor.compose_call.return_value, wallet=mock_wallet, sign_with="hotkey", wait_for_inclusion=wait_for_inclusion, diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index d0d9c5276c..6f9fc03a81 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -8,12 +8,13 @@ def test_add_stake_extrinsic(mocker): """Verify that sync `add_stake_extrinsic` method calls proper async method.""" # Preps + fake_extrinsic_fee = Balance.from_tao(0.1) fake_subtensor = mocker.Mock( **{ "get_balance.return_value": Balance(10), "get_existential_deposit.return_value": Balance(1), "get_hotkey_owner.return_value": "hotkey_owner", - "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, "Success"), + "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, "Success", extrinsic_fee=fake_extrinsic_fee), } ) fake_wallet_ = mocker.Mock( @@ -40,14 +41,15 @@ def test_add_stake_extrinsic(mocker): # Asserts assert result.success is True + assert result.extrinsic_fee == fake_extrinsic_fee - fake_subtensor.substrate.compose_call.assert_called_once_with( + fake_subtensor.compose_call.assert_called_once_with( call_module="SubtensorModule", call_function="add_stake", call_params={"hotkey": "hotkey", "amount_staked": 9, "netuid": 1}, ) fake_subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=fake_subtensor.substrate.compose_call.return_value, + call=fake_subtensor.compose_call.return_value, wallet=fake_wallet_, wait_for_inclusion=True, wait_for_finalization=True, @@ -119,7 +121,7 @@ def test_set_auto_stake_extrinsic( netuid = mocker.Mock() hotkey_ss58 = mocker.Mock() - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, diff --git a/tests/unit_tests/extrinsics/test_start_call.py b/tests/unit_tests/extrinsics/test_start_call.py index 7f85675620..8cd36c4ffd 100644 --- a/tests/unit_tests/extrinsics/test_start_call.py +++ b/tests/unit_tests/extrinsics/test_start_call.py @@ -9,7 +9,7 @@ def test_start_call_extrinsics(subtensor, mocker, fake_wallet): wallet.name = "fake_wallet" wallet.coldkey = "fake_coldkey" - subtensor.substrate.compose_call = mocker.Mock() + subtensor.compose_call = mocker.Mock() mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=(True, "Success") ) @@ -22,14 +22,14 @@ def test_start_call_extrinsics(subtensor, mocker, fake_wallet): ) # Assertions - subtensor.substrate.compose_call.assert_called_once_with( + subtensor.compose_call.assert_called_once_with( call_module="SubtensorModule", call_function="start_call", call_params={"netuid": netuid}, ) mocked_sign_and_send_extrinsic.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, + call=subtensor.compose_call.return_value, wallet=wallet, wait_for_inclusion=True, wait_for_finalization=False, diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index e8bce586ed..c6bdeca975 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -35,7 +35,7 @@ def test_transfer_extrinsic_success(subtensor, fake_wallet, mocker): 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_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) @@ -106,7 +106,7 @@ def test_transfer_extrinsic_call_successful_with_failed_response( 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_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(False, "") ) @@ -298,7 +298,7 @@ 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_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 807ee472eb..92f1192954 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -40,7 +40,7 @@ def test_unstake_extrinsic(fake_wallet, mocker): # Asserts assert result.success is True - fake_subtensor.substrate.compose_call.assert_called_once_with( + fake_subtensor.compose_call.assert_called_once_with( call_module="SubtensorModule", call_function="remove_stake", call_params={ @@ -50,7 +50,7 @@ def test_unstake_extrinsic(fake_wallet, mocker): }, ) fake_subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=fake_subtensor.substrate.compose_call.return_value, + call=fake_subtensor.compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, @@ -85,7 +85,7 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): assert result[0] is True assert result[1] == "" - fake_subtensor.substrate.compose_call.assert_called_once_with( + fake_subtensor.compose_call.assert_called_once_with( call_module="SubtensorModule", call_function="remove_stake_full_limit", call_params={ @@ -95,7 +95,7 @@ def test_unstake_all_extrinsic(fake_wallet, mocker): }, ) fake_subtensor.sign_and_send_extrinsic.assert_called_once_with( - call=fake_subtensor.substrate.compose_call.return_value, + call=fake_subtensor.compose_call.return_value, wallet=fake_wallet, wait_for_inclusion=True, wait_for_finalization=True, diff --git a/tests/unit_tests/extrinsics/test_weights.py b/tests/unit_tests/extrinsics/test_weights.py index a052d69bb5..99c0b233b3 100644 --- a/tests/unit_tests/extrinsics/test_weights.py +++ b/tests/unit_tests/extrinsics/test_weights.py @@ -30,7 +30,7 @@ def test_commit_timelocked_weights_extrinsic(mocker, subtensor, fake_wallet): "get_encrypted_commit", return_value=(mocker.Mock(), mocker.Mock()), ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", @@ -109,7 +109,7 @@ def test_commit_weights_extrinsic(mocker, subtensor, fake_wallet): mocked_generate_weight_hash = mocker.patch.object( weights_module, "generate_weight_hash" ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) @@ -176,7 +176,7 @@ def test_reveal_weights_extrinsic(mocker, subtensor, fake_wallet): "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") ) @@ -236,7 +236,7 @@ def test_set_weights_extrinsic(mocker, subtensor, fake_wallet): "convert_and_normalize_weights_and_uids", return_value=(uids, weights), ) - mocked_compose_call = mocker.patch.object(subtensor.substrate, "compose_call") + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic", diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 37d02cf381..da34985b80 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -19,7 +19,6 @@ from bittensor.core.types import ExtrinsicResponse from bittensor.utils import U64_MAX, get_function_name from bittensor.utils.balance import Balance -from tests.helpers.helpers import assert_submit_signed_extrinsic @pytest.fixture @@ -166,47 +165,60 @@ async def test_async_subtensor_aenter_connection_refused_error( @pytest.mark.asyncio -async def test_burned_register(mock_substrate, subtensor, fake_wallet, mocker): - mock_substrate.submit_extrinsic.return_value = mocker.AsyncMock( - is_success=mocker.AsyncMock(return_value=True)(), - ) - mock_substrate.get_payment_info.return_value = {"partial_fee": 10} - mocker.patch.object( +async def test_burned_register(subtensor, fake_wallet, mocker): + # Preps + mocked_compose_call = mocker.patch.object(subtensor, "compose_call") + mocked_sign_and_send_extrinsic = mocker.patch.object(subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "")) + mocked_get_neuron_for_pubkey_and_subnet = mocker.patch.object( subtensor, "get_neuron_for_pubkey_and_subnet", return_value=NeuronInfo.get_null_neuron(), ) - mocker.patch.object( + mocked_get_balance = mocker.patch.object( subtensor, "get_balance", - return_value=Balance(1), + return_value=Balance.from_tao(1), ) + mocked_recycle = mocker.patch.object(subtensor, "recycle") + fake_netuid = 14 + # Call success, _ = await subtensor.burned_register( - fake_wallet, - netuid=1, + wallet=fake_wallet, + netuid=fake_netuid, ) + # Asserts assert success is True - subtensor.get_neuron_for_pubkey_and_subnet.assert_called_once_with( - fake_wallet.hotkey.ss58_address, - netuid=1, - block_hash=mock_substrate.get_chain_head.return_value, - ) - - assert_submit_signed_extrinsic( - mock_substrate, - fake_wallet.coldkey, + mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", call_function="burned_register", call_params={ - "netuid": 1, "hotkey": fake_wallet.hotkey.ss58_address, - }, + "netuid": fake_netuid, + } + ) + mocked_sign_and_send_extrinsic.assert_awaited_once_with( + call=mocked_compose_call.return_value, + wallet=fake_wallet, + period=None, + raise_error=False, wait_for_finalization=True, wait_for_inclusion=True, ) + mocked_get_neuron_for_pubkey_and_subnet.assert_awaited_once_with( + hotkey_ss58=fake_wallet.hotkey.ss58_address, + block_hash=subtensor.substrate.get_chain_head.return_value, + netuid=fake_netuid, + ) + mocked_get_balance.assert_awaited_with( + address=fake_wallet.coldkeypub.ss58_address + ) + mocked_recycle.assert_awaited_with( + netuid=fake_netuid, + block_hash=subtensor.substrate.get_chain_head.return_value, + ) @pytest.mark.asyncio @@ -673,7 +685,7 @@ async def test_get_transfer_fee(subtensor, fake_wallet, mocker, balance): fake_value = balance mocked_compose_call = mocker.AsyncMock() - subtensor.substrate.compose_call = mocked_compose_call + subtensor.compose_call = mocked_compose_call mocked_get_payment_info = mocker.AsyncMock(return_value={"partial_fee": 100}) subtensor.substrate.get_payment_info = mocked_get_payment_info @@ -708,7 +720,7 @@ async def test_get_transfer_with_exception(subtensor, mocker): fake_value = 123 mocked_compose_call = mocker.AsyncMock() - subtensor.substrate.compose_call = mocked_compose_call + subtensor.compose_call = mocked_compose_call subtensor.substrate.get_payment_info.side_effect = Exception # Call + Assertions @@ -1681,7 +1693,7 @@ async def test_sign_and_send_extrinsic_success_finalization( fake_extrinsic = mocker.Mock() fake_response = mocker.Mock() - mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_get_extrinsic_fee = mocker.patch.object(subtensor, "get_extrinsic_fee") mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1706,7 +1718,7 @@ async def fake_is_success(): # Asserts mocked_get_extrinsic_fee.assert_awaited_once_with( - subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + call=fake_call, keypair=fake_wallet.coldkey ) mocked_create_signed_extrinsic.assert_called_once_with( call=fake_call, keypair=fake_wallet.coldkey @@ -1735,7 +1747,7 @@ async def test_sign_and_send_extrinsic_error_finalization( fake_response = mocker.Mock() fake_error = {"some error": "message"} - mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_get_extrinsic_fee = mocker.patch.object(subtensor, "get_extrinsic_fee") mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1768,7 +1780,7 @@ async def fake_error_message(): # Asserts mocked_get_extrinsic_fee.assert_awaited_once_with( - subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + call=fake_call, keypair=fake_wallet.coldkey ) mocked_create_signed_extrinsic.assert_called_once_with( call=fake_call, keypair=fake_wallet.coldkey @@ -1795,7 +1807,7 @@ async def test_sign_and_send_extrinsic_success_without_inclusion_finalization( fake_call = mocker.Mock() fake_extrinsic = mocker.Mock() - mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_get_extrinsic_fee = mocker.patch.object(subtensor, "get_extrinsic_fee") mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1813,7 +1825,7 @@ async def test_sign_and_send_extrinsic_success_without_inclusion_finalization( # Asserts mocked_get_extrinsic_fee.assert_awaited_once_with( - subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + call=fake_call, keypair=fake_wallet.coldkey ) mocked_create_signed_extrinsic.assert_awaited_once() mocked_create_signed_extrinsic.assert_called_once_with( @@ -1843,7 +1855,7 @@ async def test_sign_and_send_extrinsic_substrate_request_exception( fake_extrinsic = mocker.Mock() fake_exception = async_subtensor.SubstrateRequestException("Test Exception") - mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_get_extrinsic_fee = mocker.patch.object(subtensor, "get_extrinsic_fee") mocked_create_signed_extrinsic = mocker.AsyncMock(return_value=fake_extrinsic) subtensor.substrate.create_signed_extrinsic = mocked_create_signed_extrinsic @@ -1867,7 +1879,7 @@ async def test_sign_and_send_extrinsic_substrate_request_exception( # Asserts mocked_get_extrinsic_fee.assert_awaited_once_with( - subtensor=subtensor, call=fake_call, keypair=fake_wallet.coldkey + call=fake_call, keypair=fake_wallet.coldkey ) assert result == (False, str(fake_exception)) assert result.extrinsic_function == get_function_name() @@ -1883,7 +1895,7 @@ async def test_sign_and_send_extrinsic_raises_error( ): """Tests sign_and_send_extrinsic when an error is raised.""" # Preps - mocked_get_extrinsic_fee = mocker.patch.object(async_subtensor, "get_extrinsic_fee") + mocked_get_extrinsic_fee = mocker.patch.object(subtensor, "get_extrinsic_fee") mock_substrate.submit_extrinsic.return_value = mocker.AsyncMock( error_message=mocker.AsyncMock( @@ -3778,41 +3790,14 @@ async def test_subnet(subtensor, mocker): assert result == mocked_di_from_dict.return_value -@pytest.mark.asyncio -async def test_get_stake_operations_fee(subtensor, mocker): - """Verify that `get_stake_operations_fee` calls proper methods and returns the correct value.""" - # Preps - netuid = 1 - amount = Balance.from_rao(100_000_000_000) # 100 Tao - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_query_map = mocker.patch.object( - subtensor.substrate, "query", return_value=mocker.Mock(value=196) - ) - - # Call - result = await subtensor.get_stake_operations_fee(netuid=netuid, amount=amount) - - # Assert - mocked_determine_block_hash.assert_awaited_once_with( - block=None, block_hash=None, reuse_block=False - ) - mocked_query_map.assert_awaited_once_with( - module="Swap", - storage_function="FeeRate", - params=[netuid], - block_hash=mocked_determine_block_hash.return_value, - ) - assert result == Balance.from_rao(299076829) - - @pytest.mark.asyncio async def test_get_stake_add_fee(subtensor, mocker): """Verify that `get_stake_add_fee` calls proper methods and returns the correct value.""" # Preps netuid = mocker.Mock() amount = mocker.Mock(spec=Balance) - mocked_get_stake_operations_fee = mocker.patch.object( - subtensor, "get_stake_operations_fee" + mocked_sim_swap = mocker.patch.object( + subtensor, "sim_swap" ) # Call @@ -3822,10 +3807,13 @@ async def test_get_stake_add_fee(subtensor, mocker): ) # Asserts - mocked_get_stake_operations_fee.assert_awaited_once_with( - amount=amount, netuid=netuid, block=None + mocked_sim_swap.assert_awaited_once_with( + origin_netuid=0, + destination_netuid=netuid, + amount=amount, + block_hash=None, ) - assert result == mocked_get_stake_operations_fee.return_value + assert result == mocked_sim_swap.return_value.tao_fee @pytest.mark.asyncio @@ -3834,8 +3822,9 @@ async def test_get_unstake_fee(subtensor, mocker): # Preps netuid = mocker.Mock() amount = mocker.Mock(spec=Balance) - mocked_get_stake_operations_fee = mocker.patch.object( - subtensor, "get_stake_operations_fee" + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_sim_swap = mocker.patch.object( + subtensor, "sim_swap", return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()) ) # Call @@ -3845,33 +3834,43 @@ async def test_get_unstake_fee(subtensor, mocker): ) # Asserts - mocked_get_stake_operations_fee.assert_awaited_once_with( - amount=amount, netuid=netuid, block=None + mocked_sim_swap.assert_awaited_once_with( + origin_netuid=netuid, + destination_netuid=0, + amount=amount, + block_hash=mocked_determine_block_hash.return_value, ) - assert result == mocked_get_stake_operations_fee.return_value + assert result == mocked_sim_swap.return_value.alpha_fee.set_unit.return_value @pytest.mark.asyncio async def test_get_stake_movement_fee(subtensor, mocker): """Verify that `get_stake_movement_fee` calls proper methods and returns the correct value.""" # Preps - netuid = mocker.Mock() + origin_netuid = mocker.Mock() + destination_netuid = mocker.Mock() amount = mocker.Mock(spec=Balance) - mocked_get_stake_operations_fee = mocker.patch.object( - subtensor, "get_stake_operations_fee" + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_sim_swap = mocker.patch.object( + subtensor, "sim_swap", return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()) ) # Call result = await subtensor.get_stake_movement_fee( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, amount=amount, - origin_netuid=netuid, ) # Asserts - mocked_get_stake_operations_fee.assert_awaited_once_with( - amount=amount, netuid=netuid, block=None + mocked_sim_swap.assert_awaited_once_with( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + block_hash=mocked_determine_block_hash.return_value, ) - assert result == mocked_get_stake_operations_fee.return_value + assert result == mocked_sim_swap.return_value.tao_fee @pytest.mark.asyncio diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index ab35d422bb..ad39ae2054 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1734,6 +1734,7 @@ def test_get_transfer_fee(subtensor, fake_wallet, mocker): fake_payment_info = {"partial_fee": int(2e10)} subtensor.substrate.get_payment_info.return_value = fake_payment_info + mocker_compose_call = mocker.patch.object(subtensor, "compose_call") # Call result = subtensor.get_transfer_fee( @@ -1741,14 +1742,14 @@ def test_get_transfer_fee(subtensor, fake_wallet, mocker): ) # Asserts - subtensor.substrate.compose_call.assert_called_once_with( + mocker_compose_call.assert_called_once_with( call_module="Balances", call_function="transfer_keep_alive", call_params={"dest": fake_dest, "value": value.rao}, ) subtensor.substrate.get_payment_info.assert_called_once_with( - call=subtensor.substrate.compose_call.return_value, + call=mocker_compose_call.return_value, keypair=fake_wallet.coldkeypub, ) @@ -3545,6 +3546,7 @@ def test_get_parents_no_parents(subtensor, mocker): def test_set_children(subtensor, fake_wallet, mocker): """Tests set_children extrinsic calls properly.""" # Preps + fake_netuid = mocker.Mock() mocked_set_children_extrinsic = mocker.Mock() mocker.patch.object( subtensor_module, "set_children_extrinsic", mocked_set_children_extrinsic @@ -3558,9 +3560,9 @@ def test_set_children(subtensor, fake_wallet, mocker): # Call result = subtensor.set_children( - fake_wallet, - fake_wallet.hotkey.ss58_address, - netuid=1, + wallet=fake_wallet, + netuid=fake_netuid, + hotkey_ss58=fake_wallet.hotkey.ss58_address, children=fake_children, ) @@ -3569,7 +3571,7 @@ def test_set_children(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, hotkey_ss58=fake_wallet.hotkey.ss58_address, - netuid=1, + netuid=fake_netuid, children=fake_children, wait_for_finalization=True, wait_for_inclusion=True, @@ -3974,37 +3976,13 @@ def test_subnet(subtensor, mocker): assert result == mocked_di_from_dict.return_value -def test_get_stake_operations_fee(subtensor, mocker): - """Verify that `get_stake_operations_fee` calls proper methods and returns the correct value.""" - # Preps - netuid = 1 - amount = Balance.from_rao(100_000_000_000) # 100 Tao - mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") - mocked_query_map = mocker.patch.object( - subtensor.substrate, "query", return_value=mocker.Mock(value=196) - ) - - # Call - result = subtensor.get_stake_operations_fee(amount=amount, netuid=netuid) - - # Assert - mocked_determine_block_hash.assert_called_once_with(block=None) - mocked_query_map.assert_called_once_with( - module="Swap", - storage_function="FeeRate", - params=[netuid], - block_hash=mocked_determine_block_hash.return_value, - ) - assert result == Balance.from_rao(299076829) - - def test_get_stake_add_fee(subtensor, mocker): """Verify that `get_stake_add_fee` calls proper methods and returns the correct value.""" # Preps netuid = mocker.Mock() amount = mocker.Mock(spec=Balance) - mocked_get_stake_operations_fee = mocker.patch.object( - subtensor, "get_stake_operations_fee" + mocked_sim_swap = mocker.patch.object( + subtensor, "sim_swap" ) # Call @@ -4014,10 +3992,13 @@ def test_get_stake_add_fee(subtensor, mocker): ) # Asserts - mocked_get_stake_operations_fee.assert_called_once_with( - amount=amount, netuid=netuid, block=None + mocked_sim_swap.assert_called_once_with( + origin_netuid=0, + destination_netuid=netuid, + amount=amount, + block=None, ) - assert result == mocked_get_stake_operations_fee.return_value + assert result == mocked_sim_swap.return_value.tao_fee def test_get_unstake_fee(subtensor, mocker): @@ -4025,8 +4006,9 @@ def test_get_unstake_fee(subtensor, mocker): # Preps netuid = mocker.Mock() amount = mocker.Mock(spec=Balance) - mocked_get_stake_operations_fee = mocker.patch.object( - subtensor, "get_stake_operations_fee" + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_sim_swap = mocker.patch.object( + subtensor, "sim_swap", return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()) ) # Call @@ -4036,32 +4018,42 @@ def test_get_unstake_fee(subtensor, mocker): ) # Asserts - mocked_get_stake_operations_fee.assert_called_once_with( - amount=amount, netuid=netuid, block=None + mocked_sim_swap.assert_called_once_with( + origin_netuid=netuid, + destination_netuid=0, + amount=amount, + block=None, ) - assert result == mocked_get_stake_operations_fee.return_value + assert result == mocked_sim_swap.return_value.alpha_fee.set_unit.return_value def test_get_stake_movement_fee(subtensor, mocker): """Verify that `get_stake_movement_fee` calls proper methods and returns the correct value.""" # Preps - netuid = mocker.Mock() + origin_netuid = mocker.Mock() + destination_netuid = mocker.Mock() amount = mocker.Mock(spec=Balance) - mocked_get_stake_operations_fee = mocker.patch.object( - subtensor, "get_stake_operations_fee" + + mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") + mocked_sim_swap = mocker.patch.object( + subtensor, "sim_swap", return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()) ) # Call result = subtensor.get_stake_movement_fee( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, amount=amount, - origin_netuid=netuid, ) # Asserts - mocked_get_stake_operations_fee.assert_called_once_with( - amount=amount, netuid=netuid, block=None + mocked_sim_swap.assert_called_once_with( + origin_netuid=origin_netuid, + destination_netuid=destination_netuid, + amount=amount, + block=None, ) - assert result == mocked_get_stake_operations_fee.return_value + assert result == mocked_sim_swap.return_value.tao_fee def test_get_stake_weight(subtensor, mocker): From 2e55338f65be4b06f67f26cef2838591c243b391 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 12:33:27 -0700 Subject: [PATCH 385/416] no flaky anymore - at least locally (tested 10 times :D ) --- tests/e2e_tests/test_commit_reveal.py | 36 +++++++++++++++++++++------ 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index f8877d0082..ca43ac8f8f 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -90,7 +90,7 @@ def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): current_block = subtensor.chain.get_current_block() upcoming_tempo = subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) logging.console.info( - f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" + f"Checking if window is too low with current block: {current_block}, next tempo: {upcoming_tempo}" ) # Lower than this might mean weights will get revealed before we can check them @@ -276,9 +276,9 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet ) # Fetch current block and calculate next tempo for the subnet - current_block = await async_subtensor.chain.get_current_block() - upcoming_tempo = await async_subtensor.subnets.get_next_epoch_start_block( - alice_sn.netuid + current_block, upcoming_tempo = await asyncio.gather( + async_subtensor.chain.get_current_block(), + async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid), ) logging.console.info( f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" @@ -288,10 +288,11 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet if upcoming_tempo - current_block < 6: await alice_sn.async_wait_next_epoch() - current_block = await async_subtensor.chain.get_current_block() - latest_drand_round = await async_subtensor.chain.last_drand_round() - upcoming_tempo = await async_subtensor.subnets.get_next_epoch_start_block( - alice_sn.netuid + current_block, latest_drand_round = await asyncio.gather( + async_subtensor.chain.get_current_block(), + async_subtensor.subnets.get_next_epoch_start_block( + alice_sn.netuid + ) ) logging.console.info( f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" @@ -333,6 +334,25 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet netuid=alice_sn.netuid, mechid=mechid ) ) + # commits_on_chain = [] + # counter = TEMPO_TO_SET + # while commits_on_chain == []: + # counter -= 1 + # if counter == 0: + # break + # commits_on_chain = ( + # await async_subtensor.commitments.get_timelocked_weight_commits( + # netuid=alice_sn.netuid, mechid=mechid + # ) + # ) + # logging.console.info( + # f"block: {await async_subtensor.block}, commits: {commits_on_chain}, waiting for next round..." + # ) + # await async_subtensor.wait_for_block() + # + # logging.console.info( + # f"block: {await async_subtensor.block}, commits: {commits_on_chain}, waiting for next round..." + # ) address, commit_block, commit, reveal_round = commits_on_chain[0] # Assert correct values are committed on the chain From e80420ecc06ecc7227d7c7dfe4ced37045024bb2 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 15:56:17 -0700 Subject: [PATCH 386/416] additional delay --- tests/e2e_tests/test_commit_reveal.py | 40 ++++++++++++--------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index ca43ac8f8f..a83eebedd1 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -329,30 +329,26 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet # Fetch current commits pending on the chain await async_subtensor.wait_for_block(await async_subtensor.block + 1) - commits_on_chain = ( - await async_subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_sn.netuid, mechid=mechid + # need to wait for the commit to be on the chain bc of the drand delay on fast block node on gh + commits_on_chain = [] + counter = TEMPO_TO_SET + while commits_on_chain == []: + counter -= 1 + if counter == 0: + break + commits_on_chain = ( + await async_subtensor.commitments.get_timelocked_weight_commits( + netuid=alice_sn.netuid, mechid=mechid + ) + ) + logging.console.info( + f"block: {await async_subtensor.block}, commits: {commits_on_chain}, waiting for next round..." ) + await async_subtensor.wait_for_block() + + logging.console.info( + f"block: {await async_subtensor.block}, commits: {commits_on_chain}, waiting for next round..." ) - # commits_on_chain = [] - # counter = TEMPO_TO_SET - # while commits_on_chain == []: - # counter -= 1 - # if counter == 0: - # break - # commits_on_chain = ( - # await async_subtensor.commitments.get_timelocked_weight_commits( - # netuid=alice_sn.netuid, mechid=mechid - # ) - # ) - # logging.console.info( - # f"block: {await async_subtensor.block}, commits: {commits_on_chain}, waiting for next round..." - # ) - # await async_subtensor.wait_for_block() - # - # logging.console.info( - # f"block: {await async_subtensor.block}, commits: {commits_on_chain}, waiting for next round..." - # ) address, commit_block, commit, reveal_round = commits_on_chain[0] # Assert correct values are committed on the chain From 331374394684689ed42c89aa7f41544383e983ae Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 16:13:46 -0700 Subject: [PATCH 387/416] ruff --- tests/e2e_tests/test_commit_reveal.py | 4 +-- tests/e2e_tests/test_stake_fee.py | 12 ++----- tests/e2e_tests/test_subtensor_functions.py | 12 ++++--- tests/e2e_tests/utils/e2e_test_utils.py | 14 ++++++-- .../test_config_does_not_process_cli_args.py | 5 +-- .../extrinsics/asyncex/test_liquidity.py | 4 ++- tests/unit_tests/extrinsics/test_staking.py | 4 ++- tests/unit_tests/test_async_subtensor.py | 31 ++++++++-------- tests/unit_tests/test_subtensor.py | 35 +++++++++---------- 9 files changed, 65 insertions(+), 56 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index a83eebedd1..0cf5be1c93 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -290,9 +290,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet current_block, latest_drand_round = await asyncio.gather( async_subtensor.chain.get_current_block(), - async_subtensor.subnets.get_next_epoch_start_block( - alice_sn.netuid - ) + async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid), ) logging.console.info( f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" diff --git a/tests/e2e_tests/test_stake_fee.py b/tests/e2e_tests/test_stake_fee.py index ee13e34a65..2a045ac355 100644 --- a/tests/e2e_tests/test_stake_fee.py +++ b/tests/e2e_tests/test_stake_fee.py @@ -57,28 +57,24 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): "destination_netuid": sn2.netuid, "stake_fee": min_stake_fee, }, - { "title": "Move between hotkeys on root", "origin_netuid": root_netuid, "destination_netuid": root_netuid, "stake_fee": 0, }, - { "title": "Move between coldkeys on root", "origin_netuid": root_netuid, "destination_netuid": root_netuid, "stake_fee": 0, }, - { "title": "Move between coldkeys on non-root", "origin_netuid": sn2.netuid, "destination_netuid": sn2.netuid, "stake_fee": min_stake_fee, }, - { "title": "Move between different subnets", "origin_netuid": sn2.netuid, @@ -88,7 +84,7 @@ def test_stake_fee_api(subtensor, alice_wallet, bob_wallet): ] for scenario in movement_scenarios: - logging.console.info(f"Scenario: {scenario.get("title")}") + logging.console.info(f"Scenario: {scenario.get('title')}") stake_fee = subtensor.staking.get_stake_movement_fee( origin_netuid=scenario.get("origin_netuid"), destination_netuid=scenario.get("destination_netuid"), @@ -151,28 +147,24 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): "destination_netuid": sn2.netuid, "stake_fee": min_stake_fee, }, - { "title": "Move between hotkeys on root", "origin_netuid": root_netuid, "destination_netuid": root_netuid, "stake_fee": 0, }, - { "title": "Move between coldkeys on root", "origin_netuid": root_netuid, "destination_netuid": root_netuid, "stake_fee": 0, }, - { "title": "Move between coldkeys on non-root", "origin_netuid": sn2.netuid, "destination_netuid": sn2.netuid, "stake_fee": min_stake_fee, }, - { "title": "Move between different subnets", "origin_netuid": sn2.netuid, @@ -182,7 +174,7 @@ async def test_stake_fee_api_async(async_subtensor, alice_wallet, bob_wallet): ] for scenario in movement_scenarios: - logging.console.info(f"Scenario: {scenario.get("title")}") + logging.console.info(f"Scenario: {scenario.get('title')}") stake_fee = await async_subtensor.staking.get_stake_movement_fee( origin_netuid=scenario.get("origin_netuid"), destination_netuid=scenario.get("destination_netuid"), diff --git a/tests/e2e_tests/test_subtensor_functions.py b/tests/e2e_tests/test_subtensor_functions.py index 03e56f57db..15adb2244e 100644 --- a/tests/e2e_tests/test_subtensor_functions.py +++ b/tests/e2e_tests/test_subtensor_functions.py @@ -417,8 +417,8 @@ async def test_blocks_async(subtensor): (None, None, True), (1, None, True), (None, "SOME_HASH", True), - (1, "SOME_HASH", False) - ] + (1, "SOME_HASH", False), + ], ) def test_block_info(subtensor, block, block_hash, result): """Tests sync get_block_info.""" @@ -440,8 +440,8 @@ def test_block_info(subtensor, block, block_hash, result): (None, None, True), (1, None, True), (None, "SOME_HASH", True), - (1, "SOME_HASH", False) - ] + (1, "SOME_HASH", False), + ], ) @pytest.mark.asyncio async def test_block_info(async_subtensor, block, block_hash, result): @@ -452,7 +452,9 @@ async def test_block_info(async_subtensor, block, block_hash, result): await async_subtensor.wait_for_block(2) try: - res = await async_subtensor.chain.get_block_info(block=block, block_hash=block_hash) + res = await async_subtensor.chain.get_block_info( + block=block, block_hash=block_hash + ) assert (res is not None) == result except Exception as e: assert "Either block_hash or block_number should be set" in str(e) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index 47342ac774..76e0916bdf 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -34,9 +34,19 @@ def setup_wallet( name = uri.strip("/") wallet_path = f"/tmp/btcli-e2e-wallet-{name}" wallet = Wallet(name=name, path=wallet_path) - wallet.set_coldkey(keypair=keypair, encrypt=encrypt_coldkey, overwrite=True, coldkey_password=coldkey_password) + wallet.set_coldkey( + keypair=keypair, + encrypt=encrypt_coldkey, + overwrite=True, + coldkey_password=coldkey_password, + ) wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=True) - wallet.set_hotkey(keypair=keypair, encrypt=encrypt_hotkey, overwrite=True, hotkey_password=hotkey_password) + wallet.set_hotkey( + keypair=keypair, + encrypt=encrypt_hotkey, + overwrite=True, + hotkey_password=hotkey_password, + ) wallet.set_hotkeypub(keypair=keypair, encrypt=False, overwrite=True) return keypair, wallet diff --git a/tests/integration_tests/test_config_does_not_process_cli_args.py b/tests/integration_tests/test_config_does_not_process_cli_args.py index 63c67a3224..0c7e846d81 100644 --- a/tests/integration_tests/test_config_does_not_process_cli_args.py +++ b/tests/integration_tests/test_config_does_not_process_cli_args.py @@ -22,9 +22,10 @@ def _config_call(): TEST_ARGS = [ "bittensor", - "--config", "path/to/config.yaml", + "--config", + "path/to/config.yaml", "--strict", - "--no_version_checking" + "--no_version_checking", ] diff --git a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py index acc158fff5..4f41cee97a 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_liquidity.py +++ b/tests/unit_tests/extrinsics/asyncex/test_liquidity.py @@ -15,7 +15,9 @@ async def test_add_liquidity_extrinsic(subtensor, fake_wallet, mocker): mocked_sign_and_send_extrinsic = mocker.patch.object( subtensor, "sign_and_send_extrinsic" ) - mocked_param_add_liquidity = mocker.patch.object(liquidity.LiquidityParams, "add_liquidity") + mocked_param_add_liquidity = mocker.patch.object( + liquidity.LiquidityParams, "add_liquidity" + ) # Call result = await liquidity.add_liquidity_extrinsic( diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index 6f9fc03a81..8696677320 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -14,7 +14,9 @@ def test_add_stake_extrinsic(mocker): "get_balance.return_value": Balance(10), "get_existential_deposit.return_value": Balance(1), "get_hotkey_owner.return_value": "hotkey_owner", - "sign_and_send_extrinsic.return_value": ExtrinsicResponse(True, "Success", extrinsic_fee=fake_extrinsic_fee), + "sign_and_send_extrinsic.return_value": ExtrinsicResponse( + True, "Success", extrinsic_fee=fake_extrinsic_fee + ), } ) fake_wallet_ = mocker.Mock( diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index da34985b80..7961a59780 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -168,7 +168,9 @@ async def test_async_subtensor_aenter_connection_refused_error( async def test_burned_register(subtensor, fake_wallet, mocker): # Preps mocked_compose_call = mocker.patch.object(subtensor, "compose_call") - mocked_sign_and_send_extrinsic = mocker.patch.object(subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "")) + mocked_sign_and_send_extrinsic = mocker.patch.object( + subtensor, "sign_and_send_extrinsic", return_value=ExtrinsicResponse(True, "") + ) mocked_get_neuron_for_pubkey_and_subnet = mocker.patch.object( subtensor, "get_neuron_for_pubkey_and_subnet", @@ -197,7 +199,7 @@ async def test_burned_register(subtensor, fake_wallet, mocker): call_params={ "hotkey": fake_wallet.hotkey.ss58_address, "netuid": fake_netuid, - } + }, ) mocked_sign_and_send_extrinsic.assert_awaited_once_with( call=mocked_compose_call.return_value, @@ -212,9 +214,7 @@ async def test_burned_register(subtensor, fake_wallet, mocker): block_hash=subtensor.substrate.get_chain_head.return_value, netuid=fake_netuid, ) - mocked_get_balance.assert_awaited_with( - address=fake_wallet.coldkeypub.ss58_address - ) + mocked_get_balance.assert_awaited_with(address=fake_wallet.coldkeypub.ss58_address) mocked_recycle.assert_awaited_with( netuid=fake_netuid, block_hash=subtensor.substrate.get_chain_head.return_value, @@ -3796,9 +3796,7 @@ async def test_get_stake_add_fee(subtensor, mocker): # Preps netuid = mocker.Mock() amount = mocker.Mock(spec=Balance) - mocked_sim_swap = mocker.patch.object( - subtensor, "sim_swap" - ) + mocked_sim_swap = mocker.patch.object(subtensor, "sim_swap") # Call result = await subtensor.get_stake_add_fee( @@ -3824,7 +3822,9 @@ async def test_get_unstake_fee(subtensor, mocker): amount = mocker.Mock(spec=Balance) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_sim_swap = mocker.patch.object( - subtensor, "sim_swap", return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()) + subtensor, + "sim_swap", + return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()), ) # Call @@ -3853,7 +3853,9 @@ async def test_get_stake_movement_fee(subtensor, mocker): mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_sim_swap = mocker.patch.object( - subtensor, "sim_swap", return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()) + subtensor, + "sim_swap", + return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()), ) # Call @@ -4195,10 +4197,11 @@ async def test_get_block_info(subtensor, mocker): }, "extrinsics": [ fake_decoded, - ] - + ], } - mocked_get_block = mocker.patch.object(subtensor.substrate, "get_block", return_value=fake_substrate_block) + mocked_get_block = mocker.patch.object( + subtensor.substrate, "get_block", return_value=fake_substrate_block + ) mocked_BlockInfo = mocker.patch.object(async_subtensor, "BlockInfo") # Call @@ -4216,6 +4219,6 @@ async def test_get_block_info(subtensor, mocker): timestamp=fake_timestamp, header=fake_substrate_block.get("header"), extrinsics=fake_substrate_block.get("extrinsics"), - explorer=f"{settings.TAO_APP_BLOCK_EXPLORER}{fake_block}" + explorer=f"{settings.TAO_APP_BLOCK_EXPLORER}{fake_block}", ) assert result == mocked_BlockInfo.return_value diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index ad39ae2054..0333929019 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1652,9 +1652,7 @@ def test_get_last_commitment_bonds_reset_block(subtensor, mocker): fake_uid = 2 fake_hotkey = "hotkey" - mocked_get_last_bonds_reset = mocker.patch.object( - subtensor, "get_last_bonds_reset" - ) + mocked_get_last_bonds_reset = mocker.patch.object(subtensor, "get_last_bonds_reset") mocked_decode_block = mocker.patch.object(subtensor_module, "decode_block") mocked_metagraph = mocker.MagicMock() @@ -1667,13 +1665,11 @@ def test_get_last_commitment_bonds_reset_block(subtensor, mocker): ) # Assertions - mocked_metagraph.assert_called_once_with( - fake_netuid, - block=None) - mocked_get_last_bonds_reset.assert_called_once_with( - fake_netuid, fake_hotkey, None + mocked_metagraph.assert_called_once_with(fake_netuid, block=None) + mocked_get_last_bonds_reset.assert_called_once_with(fake_netuid, fake_hotkey, None) + mocked_decode_block.assert_called_once_with( + mocked_get_last_bonds_reset.return_value ) - mocked_decode_block.assert_called_once_with(mocked_get_last_bonds_reset.return_value) assert result == mocked_decode_block.return_value @@ -3981,9 +3977,7 @@ def test_get_stake_add_fee(subtensor, mocker): # Preps netuid = mocker.Mock() amount = mocker.Mock(spec=Balance) - mocked_sim_swap = mocker.patch.object( - subtensor, "sim_swap" - ) + mocked_sim_swap = mocker.patch.object(subtensor, "sim_swap") # Call result = subtensor.get_stake_add_fee( @@ -4008,7 +4002,9 @@ def test_get_unstake_fee(subtensor, mocker): amount = mocker.Mock(spec=Balance) mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_sim_swap = mocker.patch.object( - subtensor, "sim_swap", return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()) + subtensor, + "sim_swap", + return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()), ) # Call @@ -4036,7 +4032,9 @@ def test_get_stake_movement_fee(subtensor, mocker): mocked_determine_block_hash = mocker.patch.object(subtensor, "determine_block_hash") mocked_sim_swap = mocker.patch.object( - subtensor, "sim_swap", return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()) + subtensor, + "sim_swap", + return_value=mocker.MagicMock(alpha_fee=mocker.MagicMock()), ) # Call @@ -4325,10 +4323,11 @@ def test_get_block_info(subtensor, mocker): }, "extrinsics": [ fake_decoded, - ] - + ], } - mocked_get_block = mocker.patch.object(subtensor.substrate, "get_block", return_value=fake_substrate_block) + mocked_get_block = mocker.patch.object( + subtensor.substrate, "get_block", return_value=fake_substrate_block + ) mocked_BlockInfo = mocker.patch.object(subtensor_module, "BlockInfo") # Call @@ -4346,6 +4345,6 @@ def test_get_block_info(subtensor, mocker): timestamp=fake_timestamp, header=fake_substrate_block.get("header"), extrinsics=fake_substrate_block.get("extrinsics"), - explorer=f"{settings.TAO_APP_BLOCK_EXPLORER}{fake_block}" + explorer=f"{settings.TAO_APP_BLOCK_EXPLORER}{fake_block}", ) assert result == mocked_BlockInfo.return_value From 01e53715198c7d9ddaa72cd6b54db2e5cbd1063f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 16:19:32 -0700 Subject: [PATCH 388/416] update MIGRATION.md --- MIGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 824a05ee93..44e7e72e2d 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -21,7 +21,7 @@ 8. ✅ Should the next functions move to `subtensor` as methods? They have exactly the same behavior as subtensor methods. - `get_metadata` - `get_last_bonds_reset` -9. Apply `SimSwap` logic to calculate any stake operation fees (this is not an extrinsic fee) +9. ✅ Apply `SimSwap` logic to calculate any stake operation fees (this is not an extrinsic fee) ## Metagraph 1. ✅ Remove verbose archival node warnings for blocks older than 300. Some users complained about many messages for them. From 533651396a0e453501d35361635a6b96386a0981 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 17:27:04 -0700 Subject: [PATCH 389/416] test_commit_and_reveal_weights_cr4_async --- tests/e2e_tests/test_commit_reveal.py | 50 +++++++-------------------- 1 file changed, 12 insertions(+), 38 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index 0cf5be1c93..d3e260036a 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -90,7 +90,7 @@ def test_commit_and_reveal_weights_cr4(subtensor, alice_wallet): current_block = subtensor.chain.get_current_block() upcoming_tempo = subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) logging.console.info( - f"Checking if window is too low with current block: {current_block}, next tempo: {upcoming_tempo}" + f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" ) # Lower than this might mean weights will get revealed before we can check them @@ -278,7 +278,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet # Fetch current block and calculate next tempo for the subnet current_block, upcoming_tempo = await asyncio.gather( async_subtensor.chain.get_current_block(), - async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid), + async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) ) logging.console.info( f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" @@ -288,14 +288,6 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet if upcoming_tempo - current_block < 6: await alice_sn.async_wait_next_epoch() - current_block, latest_drand_round = await asyncio.gather( - async_subtensor.chain.get_current_block(), - async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid), - ) - logging.console.info( - f"Post first wait_interval (to ensure window isn't too low): {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" - ) - for mechid in range(TESTED_MECHANISMS): logging.console.info( f"[magenta]Testing subnet mechanism: {alice_sn.netuid}.{mechid}[/magenta]" @@ -309,7 +301,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet uids=weight_uids, weights=weight_vals, wait_for_inclusion=True, - wait_for_finalization=True, + wait_for_finalization=False, block_time=BLOCK_TIME, period=16, ) @@ -321,31 +313,17 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet # Parse expected reveal_round expected_reveal_round = response.data.get("reveal_round") logging.console.success( - f"Successfully set weights: uids {weight_uids}, weights {weight_vals}, reveal_round: {expected_reveal_round}" + f"Successfully set weights: uids {weight_uids}, weights {weight_vals}, expected reveal round: {expected_reveal_round}" ) # Fetch current commits pending on the chain - await async_subtensor.wait_for_block(await async_subtensor.block + 1) - - # need to wait for the commit to be on the chain bc of the drand delay on fast block node on gh - commits_on_chain = [] - counter = TEMPO_TO_SET - while commits_on_chain == []: - counter -= 1 - if counter == 0: - break - commits_on_chain = ( - await async_subtensor.commitments.get_timelocked_weight_commits( - netuid=alice_sn.netuid, mechid=mechid - ) - ) - logging.console.info( - f"block: {await async_subtensor.block}, commits: {commits_on_chain}, waiting for next round..." - ) - await async_subtensor.wait_for_block() + await async_subtensor.wait_for_block() - logging.console.info( - f"block: {await async_subtensor.block}, commits: {commits_on_chain}, waiting for next round..." + commits_on_chain, weights = await asyncio.gather( + async_subtensor.commitments.get_timelocked_weight_commits( + netuid=alice_sn.netuid, mechid=mechid + ), + async_subtensor.subnets.weights(netuid=alice_sn.netuid, mechid=mechid) ) address, commit_block, commit, reveal_round = commits_on_chain[0] @@ -354,10 +332,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet assert address == alice_wallet.hotkey.ss58_address # Ensure no weights are available as of now - assert ( - await async_subtensor.subnets.weights(netuid=alice_sn.netuid, mechid=mechid) - == [] - ) + assert weights == [], "Weights already available." logging.console.success("No weights are available before next epoch.") # 5 is safety drand offset @@ -379,7 +354,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet logging.console.info( f"Latest drand round: {latest_drand_round}, waiting for next round..." ) - # drand round is 2 + # drand round is 3 seconds await asyncio.sleep(3) # Fetch weights on the chain as they should be revealed now @@ -387,7 +362,6 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet netuid=alice_sn.netuid, mechid=mechid ) assert subnet_weights != [], "Weights are not available yet." - logging.console.info(f"Revealed weights: {subnet_weights}") revealed_weights = subnet_weights[0][1] From 6cf3b591c2642d625d675465045a1004cd79bb79 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 18:23:39 -0700 Subject: [PATCH 390/416] signing_keypair --- bittensor/core/extrinsics/serving.py | 5 +++-- bittensor/core/extrinsics/weights.py | 14 ++++++++------ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/bittensor/core/extrinsics/serving.py b/bittensor/core/extrinsics/serving.py index 4adf2fb04b..56b697813e 100644 --- a/bittensor/core/extrinsics/serving.py +++ b/bittensor/core/extrinsics/serving.py @@ -233,9 +233,10 @@ def publish_metadata_extrinsic( failure. """ try: + signing_keypair = "hotkey" if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, unlock_type="both" + wallet, raise_error, signing_keypair ) ).success: return unlocked @@ -253,7 +254,7 @@ def publish_metadata_extrinsic( response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, - sign_with="hotkey", + sign_with=signing_keypair, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, period=period, diff --git a/bittensor/core/extrinsics/weights.py b/bittensor/core/extrinsics/weights.py index d44aa8f88d..a46f028224 100644 --- a/bittensor/core/extrinsics/weights.py +++ b/bittensor/core/extrinsics/weights.py @@ -58,9 +58,10 @@ def commit_timelocked_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, unlock_type="both" + wallet, raise_error, signing_keypair ) ).success: return unlocked @@ -108,8 +109,8 @@ def commit_timelocked_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) @@ -164,9 +165,10 @@ def commit_weights_extrinsic( ExtrinsicResponse: The result object of the extrinsic execution. """ try: + signing_keypair = "hotkey" if not ( unlocked := ExtrinsicResponse.unlock_wallet( - wallet, raise_error, unlock_type="both" + wallet, raise_error, signing_keypair ) ).success: return unlocked @@ -198,8 +200,8 @@ def commit_weights_extrinsic( wait_for_finalization=wait_for_finalization, use_nonce=True, period=period, - sign_with="hotkey", - nonce_key="hotkey", + sign_with=signing_keypair, + nonce_key=signing_keypair, raise_error=raise_error, ) From 7528cbb38e97b065805099445e44d598b4b2b452 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Wed, 8 Oct 2025 19:37:04 -0700 Subject: [PATCH 391/416] fetch sim_swap by block before extrinsic --- bittensor/core/extrinsics/asyncex/move_stake.py | 9 ++++++--- bittensor/core/extrinsics/asyncex/staking.py | 4 +++- bittensor/core/extrinsics/asyncex/unstaking.py | 7 ++----- bittensor/core/extrinsics/move_stake.py | 6 ++++++ bittensor/core/extrinsics/staking.py | 4 +++- bittensor/core/extrinsics/unstaking.py | 2 ++ 6 files changed, 22 insertions(+), 10 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 5a0beb60c4..cea298433b 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -124,7 +124,7 @@ async def move_stake_extrinsic( amount=amount, ), ) - + block_hash_before = await subtensor.get_block_hash() response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -139,6 +139,7 @@ async def move_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + block_hash=block_hash_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) @@ -257,7 +258,7 @@ async def transfer_stake_extrinsic( amount=amount, ), ) - + block_hash_before = await subtensor.get_block_hash() response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -272,6 +273,7 @@ async def transfer_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + block_hash=block_hash_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) @@ -423,7 +425,7 @@ async def swap_stake_extrinsic( call_function=call_function, call_params=call_params, ) - + block_hash_before = await subtensor.get_block_hash() response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -438,6 +440,7 @@ async def swap_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + block_hash=block_hash_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) diff --git a/bittensor/core/extrinsics/asyncex/staking.py b/bittensor/core/extrinsics/asyncex/staking.py index 7849323fd1..7de816e596 100644 --- a/bittensor/core/extrinsics/asyncex/staking.py +++ b/bittensor/core/extrinsics/asyncex/staking.py @@ -134,6 +134,7 @@ async def add_stake_extrinsic( call_function=call_function, call_params=call_params, ) + block_hash_before = await subtensor.get_block_hash() response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -148,7 +149,8 @@ async def add_stake_extrinsic( sim_swap = await subtensor.sim_swap( origin_netuid=0, destination_netuid=netuid, - amount=(amount - response.extrinsic_fee), + amount=amount, + block_hash=block_hash_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(netuid) diff --git a/bittensor/core/extrinsics/asyncex/unstaking.py b/bittensor/core/extrinsics/asyncex/unstaking.py index 9c90a05257..478f95e646 100644 --- a/bittensor/core/extrinsics/asyncex/unstaking.py +++ b/bittensor/core/extrinsics/asyncex/unstaking.py @@ -81,11 +81,6 @@ async def unstake_extrinsic( f"Not enough stake: {old_stake} to unstake: {amount} from hotkey: {hotkey_ss58}", ).with_log() - call_params = { - "hotkey": hotkey_ss58, - "netuid": netuid, - "amount_unstaked": amount.rao, - } if safe_unstaking: pool = await subtensor.subnet(netuid=netuid) @@ -129,6 +124,7 @@ async def unstake_extrinsic( call_params=call_params, ) + block_hash_before = await subtensor.get_block_hash() response = await subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -145,6 +141,7 @@ async def unstake_extrinsic( origin_netuid=netuid, destination_netuid=0, amount=amount, + block_hash=block_hash_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(netuid) diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index a275488956..d62a8c07ca 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -121,6 +121,7 @@ def move_stake_extrinsic( ), ) + block_before = subtensor.block response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -135,6 +136,7 @@ def move_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + block=block_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) @@ -254,6 +256,7 @@ def transfer_stake_extrinsic( ), ) + block_before = subtensor.block response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -268,6 +271,7 @@ def transfer_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + block=block_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) @@ -410,6 +414,7 @@ def swap_stake_extrinsic( call_params=call_params, ) + block_before = subtensor.block response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -424,6 +429,7 @@ def swap_stake_extrinsic( origin_netuid=origin_netuid, destination_netuid=destination_netuid, amount=amount, + block=block_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(origin_netuid) diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index 0350a224d2..f9cf0a6fa9 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -129,6 +129,7 @@ def add_stake_extrinsic( call_params=call_params, ) + block_before = subtensor.block response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -143,7 +144,8 @@ def add_stake_extrinsic( sim_swap = subtensor.sim_swap( origin_netuid=0, destination_netuid=netuid, - amount=(amount - response.extrinsic_fee), + amount=amount, + block=block_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(netuid) diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index 134e34fe25..464ca65e23 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -122,6 +122,7 @@ def unstake_extrinsic( call_params=call_params, ) + block_before = subtensor.block response = subtensor.sign_and_send_extrinsic( call=call, wallet=wallet, @@ -138,6 +139,7 @@ def unstake_extrinsic( origin_netuid=netuid, destination_netuid=0, amount=amount, + block=block_before, ) response.transaction_tao_fee = sim_swap.tao_fee response.transaction_alpha_fee = sim_swap.alpha_fee.set_unit(netuid) From c262638307899caad5b3c7d0f90ae3e31d748f7b Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 10:44:10 -0700 Subject: [PATCH 392/416] TODO in test_root_set_weights.py --- tests/e2e_tests/test_root_set_weights.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_root_set_weights.py b/tests/e2e_tests/test_root_set_weights.py index d33d3c5a81..a67053855c 100644 --- a/tests/e2e_tests/test_root_set_weights.py +++ b/tests/e2e_tests/test_root_set_weights.py @@ -2,6 +2,7 @@ import pytest +from bittensor.extras.dev_framework import REGISTER_NEURON from bittensor.utils.balance import Balance from tests.e2e_tests.utils import ( TestSubnet, @@ -107,7 +108,7 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall assert block_since_update is not None # Use subnetwork_n hyperparam to check sn creation - assert subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 # TODO? + assert subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 assert subtensor.subnets.subnetwork_n(alice_sn.netuid + 1) is None # Ensure correct hyperparams are being fetched regarding weights @@ -138,6 +139,9 @@ async def test_root_reg_hyperparams(subtensor, templates, alice_wallet, bob_wall 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 + alice_sn.execute_one(REGISTER_NEURON(bob_wallet)) + assert subtensor.subnets.subnetwork_n(alice_sn.netuid) == 2 + @pytest.mark.asyncio async def test_root_reg_hyperparams_async( @@ -215,7 +219,7 @@ async def test_root_reg_hyperparams_async( assert block_since_update is not None # Use subnetwork_n hyperparam to check sn creation - assert await async_subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 # TODO? + assert await async_subtensor.subnets.subnetwork_n(alice_sn.netuid) == 1 assert await async_subtensor.subnets.subnetwork_n(alice_sn.netuid + 1) is None # Ensure correct hyperparams are being fetched regarding weights @@ -248,3 +252,6 @@ async def test_root_reg_hyperparams_async( ) 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 + + await alice_sn.async_execute_one(REGISTER_NEURON(bob_wallet)) + assert await async_subtensor.subnets.subnetwork_n(alice_sn.netuid) == 2 From f6014fd580ce1a73e21aec7f77eb89c625a4b714 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 10:44:48 -0700 Subject: [PATCH 393/416] we doing this in e2e tests --- tests/unit_tests/test_metagraph.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/tests/unit_tests/test_metagraph.py b/tests/unit_tests/test_metagraph.py index acf62e22d0..280e885265 100644 --- a/tests/unit_tests/test_metagraph.py +++ b/tests/unit_tests/test_metagraph.py @@ -97,7 +97,6 @@ def test_process_weights_or_bonds(mock_environment): assert weights.shape[1] == len( neurons ) # Number of columns should be equal to number of neurons - # TODO: Add more checks to ensure the weights have been processed correctly # Test bonds processing bonds = metagraph._process_weights_or_bonds( @@ -110,8 +109,6 @@ def test_process_weights_or_bonds(mock_environment): neurons ) # Number of columns should be equal to number of neurons - # TODO: Add more checks to ensure the bonds have been processed correctly - # Mocking the bittensor.Subtensor class for testing purposes @pytest.fixture From 928294a2bcc5aa6d7c630d7b940c186d48454dca Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 10:54:21 -0700 Subject: [PATCH 394/416] maybe, but in our implementation it doesn't make a difference --- bittensor/core/types.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 98ec97922f..e6f8291903 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -58,7 +58,7 @@ def _check_and_log_network_settings(self): "This increases decentralization and resilience of the network." ) - @staticmethod # TODO can this be a class method? + @staticmethod def config() -> "Config": """ Creates and returns a Bittensor configuration object. @@ -72,7 +72,9 @@ def config() -> "Config": return Config(parser) @staticmethod - def setup_config(network: Optional[str], config: "Config"): + def setup_config( + network: Optional[str], config: "Config" + ) -> tuple[Optional[str], Optional[str]]: """ Sets up and returns the configuration for the Subtensor network and endpoint. From e380099d8a3f0364d16d2456b31e3f6c693f6543 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 10:54:26 -0700 Subject: [PATCH 395/416] ruff --- tests/e2e_tests/test_commit_reveal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal.py b/tests/e2e_tests/test_commit_reveal.py index d3e260036a..6d21c8d46f 100644 --- a/tests/e2e_tests/test_commit_reveal.py +++ b/tests/e2e_tests/test_commit_reveal.py @@ -278,7 +278,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet # Fetch current block and calculate next tempo for the subnet current_block, upcoming_tempo = await asyncio.gather( async_subtensor.chain.get_current_block(), - async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid) + async_subtensor.subnets.get_next_epoch_start_block(alice_sn.netuid), ) logging.console.info( f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" @@ -323,7 +323,7 @@ async def test_commit_and_reveal_weights_cr4_async(async_subtensor, alice_wallet async_subtensor.commitments.get_timelocked_weight_commits( netuid=alice_sn.netuid, mechid=mechid ), - async_subtensor.subnets.weights(netuid=alice_sn.netuid, mechid=mechid) + async_subtensor.subnets.weights(netuid=alice_sn.netuid, mechid=mechid), ) address, commit_block, commit, reveal_round = commits_on_chain[0] From 82395d49194a20eeeedfd6b7244143d64704e205 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 11:18:46 -0700 Subject: [PATCH 396/416] DRY --- bittensor/core/async_subtensor.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index beb90c93c7..fcf25729f6 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -275,24 +275,7 @@ async def initialize(self): raise ConnectionError async def __aenter__(self): - logging.info( - f"[magenta]Connecting to Substrate:[/magenta] [blue]{self}[/blue][magenta]...[/magenta]" - ) - try: - await self.substrate.initialize() - return self - except TimeoutError: - logging.error( - f"[red]Error[/red]: Timeout occurred connecting to substrate." - f" Verify your chain and network settings: {self}" - ) - raise ConnectionError - except (ConnectionRefusedError, ssl.SSLError) as error: - logging.error( - f"[red]Error[/red]: Connection refused when connecting to substrate. " - f"Verify your chain and network settings: {self}. Error: {error}" - ) - raise ConnectionError + await self.initialize() async def __aexit__(self, exc_type, exc_val, exc_tb): await self.substrate.close() From 9c332725e24787a8ddc400a1057b7ba052ac31fe Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 11:19:55 -0700 Subject: [PATCH 397/416] consistent call for `sub.metagraph` --- 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 fcf25729f6..638c8fe2ac 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3887,12 +3887,12 @@ async def metagraph( decentralized architecture, particularly in relation to neuron interconnectivity and consensus processes. """ metagraph = AsyncMetagraph( - network=self.chain_endpoint, netuid=netuid, + mechid=mechid, + network=self.chain_endpoint, lite=lite, sync=False, subtensor=self, - mechid=mechid, ) await metagraph.sync(block=block, lite=lite, subtensor=self) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index a6e0087ca9..2a1647d3f2 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2868,12 +2868,12 @@ def metagraph( decentralized architecture, particularly in relation to neuron interconnectivity and consensus processes. """ metagraph = Metagraph( - network=self.chain_endpoint, netuid=netuid, + mechid=mechid, + network=self.chain_endpoint, lite=lite, sync=False, subtensor=self, - mechid=mechid, ) metagraph.sync(block=block, lite=lite, subtensor=self) From 82537ad149a0e2007ccf6e83988b8ad43fefc9a9 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 11:21:48 -0700 Subject: [PATCH 398/416] Metagraphs docstrings --- bittensor/core/metagraph.py | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 15f8080107..bfe74fa680 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -1330,17 +1330,27 @@ def load_from_path(self, dir_path: str) -> "MetagraphMixin": class AsyncMetagraph(NumpyOrTorch): """ - TODO docstring. Advise user to use `async_metagraph` factory fn if they want to sync at init + Asynchronous version of the Metagraph class for non-blocking synchronization with the Bittensor network state. + + This class allows developers to fetch and update metagraph data using async operations, enabling concurrent + execution in event-driven environments. + + Note: + Prefer using the factory function `async_metagraph()` for initialization, which handles async synchronization + automatically. + + Example: + metagraph = await async_metagraph(netuid=1, network="finney") """ def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional["AsyncSubtensor"] = None, - mechid: int = 0, ): super().__init__(netuid, network, lite, sync, subtensor, mechid) @@ -1656,14 +1666,26 @@ async def _apply_extra_info(self, block: int): class Metagraph(NumpyOrTorch): + """ + Synchronous implementation of the Metagraph, representing the current state of a Bittensor subnet. + + The Metagraph encapsulates neuron attributes such as stake, trust, incentive, weights, and connectivity, and + provides methods to synchronize these values directly from the blockchain via a Subtensor instance. + + Example: + from bittensor.core.subtensor import Subtensor + subtensor = Subtensor(network="finney") + metagraph = Metagraph(netuid=1, network="finney", sync=True, subtensor=subtensor) + """ + def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional["Subtensor"] = None, - mechid: int = 0, ): super().__init__(netuid, network, lite, sync, subtensor, mechid) if self.should_sync: From 1d55a7527982ef1f352f41feb210050948b71175 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 11:22:34 -0700 Subject: [PATCH 399/416] async_metagraph --- bittensor/core/metagraph.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index bfe74fa680..bed684fce7 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -1988,6 +1988,7 @@ def _apply_extra_info(self, block: int): async def async_metagraph( netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, @@ -1997,7 +1998,12 @@ async def async_metagraph( Factory function to create an instantiated AsyncMetagraph, mainly for the ability to use sync at instantiation. """ metagraph_ = AsyncMetagraph( - netuid=netuid, network=network, lite=lite, sync=sync, subtensor=subtensor + netuid=netuid, + mechid=mechid, + network=network, + lite=lite, + sync=sync, + subtensor=subtensor, ) if sync: await metagraph_.sync() From e3a17be0d5a803492faa0093dbe42d645a69b95f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 11:23:31 -0700 Subject: [PATCH 400/416] don't kill subtensor after async with done --- bittensor/core/metagraph.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index bed684fce7..7fb9bee224 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -1477,8 +1477,9 @@ async def _initialize_subtensor( # Lazy import due to circular import (subtensor -> metagraph, metagraph -> subtensor) from bittensor.core.async_subtensor import AsyncSubtensor - async with AsyncSubtensor(network=self.chain_endpoint) as subtensor: - self.subtensor = subtensor + self.subtensor = AsyncSubtensor(network=self.chain_endpoint) + await self.subtensor.initialize() + self.subtensor = subtensor return subtensor async def _assign_neurons( From 2f416aae4eb9b1323676d0108707c52e16d548cf Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 12:32:16 -0700 Subject: [PATCH 401/416] bc of logic in `MockSubtensor.neurons_lite` and then --- bittensor/utils/mock/subtensor_mock.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bittensor/utils/mock/subtensor_mock.py b/bittensor/utils/mock/subtensor_mock.py index 69ed181d7e..4fd1277975 100644 --- a/bittensor/utils/mock/subtensor_mock.py +++ b/bittensor/utils/mock/subtensor_mock.py @@ -976,7 +976,6 @@ def neuron_for_uid_lite( neuron_info = self._neuron_subnet_exists(uid, netuid, block) if neuron_info is None: - # TODO Why does this return None here but a null neuron earlier? return None else: From 78626722e42dc05d856948201a6d2b813b8dc5ec Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 13:59:05 -0700 Subject: [PATCH 402/416] query_map is good --- bittensor/core/async_subtensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 638c8fe2ac..45f6f8f96b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -4358,7 +4358,6 @@ async def weights( """ storage_index = get_mechid_storage_index(netuid, mechid) block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - # TODO look into seeing if we can speed this up with storage query w_map_encoded = await self.substrate.query_map( module="SubtensorModule", storage_function="Weights", From 80b8d0fe091114759448dbb51d7ddf3074a6e483 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 14:10:38 -0700 Subject: [PATCH 403/416] removed. validation handled by `convert_*_uids_and_vals_to_tensor()` functions --- bittensor/core/metagraph.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 7fb9bee224..a3d5bf634d 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -706,7 +706,6 @@ def _process_weights_or_bonds(self, data, attribute: str) -> Tensor: data_array.append(np.zeros(len(self.neurons), dtype=np.float32)) else: uids, values = zip(*item) - # TODO: Validate and test the conversion of uids and values to tensor if attribute == "weights": data_array.append( convert_weight_uids_and_vals_to_tensor( @@ -1579,7 +1578,6 @@ async def _process_root_weights( data_array.append(np.zeros(n_subnets, dtype=np.float32)) else: uids, values = zip(*item) - # TODO: Validate and test the conversion of uids and values to tensor data_array.append( convert_root_weight_uids_and_vals_to_tensor( n_subnets, list(uids), list(values), subnets @@ -1900,7 +1898,6 @@ def _process_root_weights( data_array.append(np.zeros(n_subnets, dtype=np.float32)) else: uids, values = zip(*item) - # TODO: Validate and test the conversion of uids and values to tensor data_array.append( convert_root_weight_uids_and_vals_to_tensor( n_subnets, list(uids), list(values), subnets From c438874d8e11ecbbbb2b330ed4afe573abb7c403 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 16:04:41 -0700 Subject: [PATCH 404/416] improve Dev Framework --- bittensor/extras/dev_framework/subnet.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bittensor/extras/dev_framework/subnet.py b/bittensor/extras/dev_framework/subnet.py index c68d48a005..7e248ae39e 100644 --- a/bittensor/extras/dev_framework/subnet.py +++ b/bittensor/extras/dev_framework/subnet.py @@ -47,16 +47,21 @@ def __init__( self.wait_for_finalization = wait_for_finalization self._netuid: Optional[int] = None + self._owner: Optional[Wallet] = None self._calls: list[CALL_RECORD] = [] @property - def calls(self): + def calls(self) -> list[CALL_RECORD]: return self._calls @property - def netuid(self): + def netuid(self) -> int: return self._netuid + @property + def owner(self) -> Wallet: + return self._owner + def execute_steps(self, steps: list[Union[STEPS, tuple]]): """Executes a multiple steps synchronously.""" for step in steps: @@ -214,6 +219,7 @@ def _register_subnet( else: self._netuid = self.s.subnets.get_total_subnets() - 1 if response.success: + self._owner = owner_wallet logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was registered.") self._add_call_record(REGISTER_SUBNET.__name__, response) return response @@ -243,6 +249,7 @@ async def _async_register_subnet( else: self._netuid = self.s.subnets.get_total_subnets() - 1 if response.success: + self._owner = owner_wallet logging.console.info(f"Subnet [blue]{self._netuid}[/blue] was registered.") self._add_call_record(REGISTER_SUBNET.__name__, response) return response From 1d76718911f1d07c56ed758533648ca9b5c62ca6 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 18:04:42 -0700 Subject: [PATCH 405/416] update methods with mechid --- bittensor/core/metagraph.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index a3d5bf634d..e853ba77dd 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -526,11 +526,11 @@ def addresses(self) -> list[str]: def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional[Union["AsyncSubtensor", "Subtensor"]] = None, - mechid: int = 0, ): """ Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the @@ -1032,11 +1032,11 @@ class TorchMetagraph(MetagraphMixin, BaseClass): def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional[Union["AsyncSubtensor", "Subtensor"]] = None, - mechid: int = 0, ): """ Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the @@ -1198,11 +1198,11 @@ class NonTorchMetagraph(MetagraphMixin): def __init__( self, netuid: int, + mechid: int = 0, network: str = settings.DEFAULT_NETWORK, lite: bool = True, sync: bool = True, subtensor: Optional[Union["AsyncSubtensor", "Subtensor"]] = None, - mechid: int = 0, ): """ Initializes a new instance of the metagraph object, setting up the basic structure and parameters based on the @@ -1226,7 +1226,7 @@ def __init__( metagraph = Metagraph(netuid=123, network="finney", lite=True, sync=True) """ - MetagraphMixin.__init__(self, netuid, network, lite, sync, subtensor, mechid) + MetagraphMixin.__init__(self, netuid, mechid, network, lite, sync, subtensor) self.netuid = netuid self.network, self.chain_endpoint = determine_chain_endpoint_and_network( @@ -1351,7 +1351,7 @@ def __init__( sync: bool = True, subtensor: Optional["AsyncSubtensor"] = None, ): - super().__init__(netuid, network, lite, sync, subtensor, mechid) + super().__init__(netuid, mechid, network, lite, sync, subtensor) async def __aenter__(self): if self.should_sync: @@ -1686,7 +1686,7 @@ def __init__( sync: bool = True, subtensor: Optional["Subtensor"] = None, ): - super().__init__(netuid, network, lite, sync, subtensor, mechid) + super().__init__(netuid, mechid, network, lite, sync, subtensor) if self.should_sync: self.sync() From 86e9a3ba2c07efcde4fd416afd05a909ee4f15b1 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 18:05:01 -0700 Subject: [PATCH 406/416] small refactoring --- bittensor/core/async_subtensor.py | 2 +- bittensor/core/subtensor.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 45f6f8f96b..0ac916c5b7 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5625,7 +5625,7 @@ async def _blocks_weight_limit() -> bool: and await _blocks_weight_limit() ): logging.debug( - f"Committing weights for subnet [blue]{netuid}[/blue]. " + f"Committing weights {weights} for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) try: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 2a1647d3f2..af0b7757b6 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1693,11 +1693,11 @@ def get_mechanism_count( def get_metagraph_info( self, netuid: int, + mechid: int = 0, selected_indices: Optional[ Union[list[SelectiveMetagraphIndex], list[int]] ] = None, block: Optional[int] = None, - mechid: int = 0, ) -> Optional[MetagraphInfo]: """ Retrieves full or partial metagraph information for the specified subnet mechanism (netuid, mechid). @@ -4428,7 +4428,7 @@ def _blocks_weight_limit() -> bool: and _blocks_weight_limit() ): logging.debug( - f"Committing weights for subnet [blue]{netuid}[/blue]. " + f"Committing weights {weights} for subnet [blue]{netuid}[/blue]. " f"Attempt [blue]{retries + 1}[blue] of [green]{max_retries}[/green]." ) try: From 6495386a3f5a80484aa5dbc40eeebb4a2da16209 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 18:38:04 -0700 Subject: [PATCH 407/416] TODO fix + added test --- MIGRATION.md | 5 + bittensor/core/metagraph.py | 1 - tests/e2e_tests/test_metagraph.py | 274 ++++++++++++++++++++++++++++-- 3 files changed, 264 insertions(+), 16 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 44e7e72e2d..3f7cf42480 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -274,6 +274,11 @@ Added sub-package `bittensor.core.addons` to host optional extensions and experi - local env variable `BT_CHAIN_ENDPOINT` replaced with `BT_SUBTENSOR_CHAIN_ENDPOINT`. +## Metagraph changes: +- all methods with `mechid` parameter has reordered list of parameters. +- async `_initialize_subtensor` method no longer kill the subtensor instance after use. + + ### Mechid related changes: In the next subtensor methods got updated the parameters order: - `bonds` diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index e853ba77dd..58e85dcdf3 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -671,7 +671,6 @@ def _create_tensor(data, dtype) -> Tensor: self.stake = self._create_tensor(neuron_stakes, dtype=np.float32) """ - # TODO: Check and test the creation of tensor return ( torch.nn.Parameter(torch.tensor(data, dtype=dtype), requires_grad=False) if use_torch() diff --git a/tests/e2e_tests/test_metagraph.py b/tests/e2e_tests/test_metagraph.py index 4a82aa4b4c..e35f629483 100644 --- a/tests/e2e_tests/test_metagraph.py +++ b/tests/e2e_tests/test_metagraph.py @@ -1,24 +1,35 @@ import os.path -import re import shutil import time - +import numpy as np import pytest from bittensor.core.chain_data import SelectiveMetagraphIndex from bittensor.core.chain_data.metagraph_info import MetagraphInfo +from bittensor.extras.dev_framework import ( + SUDO_SET_WEIGHTS_SET_RATE_LIMIT, + SUDO_SET_TEMPO, +) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging +from bittensor.utils.registration.pow import LazyLoadedTorch +from bittensor.utils.weight_utils import convert_and_normalize_weights_and_uids from tests.e2e_tests.utils import ( + AdminUtils, + NETUID, TestSubnet, ACTIVATE_SUBNET, REGISTER_SUBNET, REGISTER_NEURON, + SUDO_SET_ADMIN_FREEZE_WINDOW, ) NULL_KEY = tuple(bytearray(32)) +torch = LazyLoadedTorch() + + def neuron_to_dict(neuron): """ Convert a neuron object to a dictionary, excluding private attributes, methods, and specific fields. @@ -182,8 +193,6 @@ def test_metagraph(subtensor, alice_wallet, bob_wallet, dave_wallet): "Neurons don't match after save and load" ) - logging.console.info("✅ Passed [blue]test_metagraph[/blue]") - @pytest.mark.asyncio async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_wallet): @@ -338,7 +347,252 @@ async def test_metagraph_async(async_subtensor, alice_wallet, bob_wallet, dave_w "Neurons don't match after save and load" ) - logging.console.info("✅ Passed [blue]test_metagraph_async[/blue]") + +def test_metagraph_weights_bonds( + subtensor, alice_wallet, bob_wallet, charlie_wallet, dave_wallet +): + """ + Tests that weights and bonds matrices are computed correctly when the metagraph is initialized with lite=False. + + Test: + - Disable the admin freeze window (set to 0). + - Register a new subnet owned by Bob. + - Update subnet tempo to a custom value. + - Activate the subnet. + - Register Charlie and Dave as new neurons in the subnet. + - Disable weights rate limit (set_weights_rate_limit = 0). + - Set weights for Charlie and Dave (20% / 80%) using Bob. + - Wait for commit-reveal completion and ensure version is 4. + - Initialize the metagraph with lite=False. + - Verify: + - Shape and consistency of weights and bonds tensors. + - Validator (Alice) has non-zero outgoing weights. + - Miners have zero rows (no outgoing weights). + - All bonds are non-negative. + - Validator weight rows are normalized to 1. + """ + logging.set_debug() + TEMPO_TO_SET, BLOCK_TIME = ( + (100, 0.25) if subtensor.chain.is_fast_blocks() else (20, 12) + ) + + bob_sn = TestSubnet(subtensor) + bob_sn.execute_steps( + [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(bob_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(bob_wallet), + REGISTER_NEURON(charlie_wallet), + REGISTER_NEURON(dave_wallet), + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0), + ] + ) + + # wait before CRv4 works in new subnets + bob_sn.wait_next_epoch() + bob_sn.wait_next_epoch() + + cr_version = subtensor.substrate.query( + module="SubtensorModule", storage_function="CommitRevealWeightsVersion" + ) + assert cr_version == 4, "Commit reveal weights version is not 4" + assert subtensor.subnets.weights_rate_limit(netuid=bob_sn.netuid) == 0 + tempo = subtensor.subnets.get_subnet_hyperparameters(netuid=bob_sn.netuid).tempo + assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." + + metagraph = subtensor.metagraphs.metagraph(netuid=bob_sn.netuid, lite=False) + + # Check that the metagraph is instantiated correctly. + assert metagraph.weights.shape == (metagraph.n.item(), metagraph.n.item()) + assert metagraph.bonds.shape == (metagraph.n.item(), metagraph.n.item()) + + uids = [1, 2] + weights = [20, 80] + + response = subtensor.extrinsics.set_weights( + wallet=bob_wallet, + netuid=bob_sn.netuid, + uids=uids, + weights=weights, + block_time=BLOCK_TIME, + ) + logging.console.info(f"Response: {response}") + + assert response.success, response.message + + expected_reveal_round = response.data.get("reveal_round") + last_drand_round = subtensor.chain.last_drand_round() + + while expected_reveal_round > last_drand_round + 24: # drand offset for fast blocks + last_drand_round = subtensor.chain.last_drand_round() + subtensor.wait_for_block() + logging.console.debug( + f"expected_reveal_round: {expected_reveal_round}, last_drand_round: {last_drand_round}" + ) + + counter = TEMPO_TO_SET + while True: + weights = subtensor.subnets.weights(bob_sn.netuid) + counter -= 1 + + if weights or counter == 0: + break + + subtensor.wait_for_block() + logging.console.debug(f"Weights: {weights}, block: {subtensor.block}") + + metagraph.sync() + + # Ensure the validator has at least one non-zero weight + assert metagraph.weights[0].sum().item() > 0.0, "Validator has no outgoing weights." + + # Ensure miner rows are all zeros (miners don't set weights) + if metagraph.n.item() > 1: + assert np.allclose( + metagraph.weights[1], + np.zeros_like(metagraph.weights[1]), + ), "Miner row should be all zeros" + + # Ensure bond matrix contains no negative values + assert (metagraph.bonds >= 0).all(), "Bond matrix contains negative values" + + # Ensure validator weight rows are normalized to 1 + row_sums = metagraph.weights.sum(axis=1) + validator_mask = metagraph.validator_permit + assert np.allclose( + row_sums[validator_mask], + np.ones_like(row_sums[validator_mask]), + atol=1e-6, + ), "Validator weight rows are not normalized to 1" + + +@pytest.mark.asyncio +async def test_metagraph_weights_bonds_async( + async_subtensor, alice_wallet, bob_wallet, charlie_wallet, dave_wallet +): + """ + Tests that weights and bonds matrices are computed correctly when the metagraph is initialized with lite=False. + + Test: + - Disable the admin freeze window (set to 0). + - Register a new subnet owned by Bob. + - Update subnet tempo to a custom value. + - Activate the subnet. + - Register Charlie and Dave as new neurons in the subnet. + - Disable weights rate limit (set_weights_rate_limit = 0). + - Set weights for Charlie and Dave (20% / 80%) using Bob. + - Wait for commit-reveal completion and ensure version is 4. + - Initialize the metagraph with lite=False. + - Verify: + - Shape and consistency of weights and bonds tensors. + - Validator (Alice) has non-zero outgoing weights. + - Miners have zero rows (no outgoing weights). + - All bonds are non-negative. + - Validator weight rows are normalized to 1. + """ + logging.set_debug() + TEMPO_TO_SET, BLOCK_TIME = ( + (100, 0.25) if await async_subtensor.chain.is_fast_blocks() else (20, 12) + ) + + bob_sn = TestSubnet(async_subtensor) + await bob_sn.async_execute_steps( + [ + SUDO_SET_ADMIN_FREEZE_WINDOW(alice_wallet, AdminUtils, True, 0), + REGISTER_SUBNET(bob_wallet), + SUDO_SET_TEMPO(alice_wallet, AdminUtils, True, NETUID, TEMPO_TO_SET), + ACTIVATE_SUBNET(bob_wallet), + REGISTER_NEURON(charlie_wallet), + REGISTER_NEURON(dave_wallet), + SUDO_SET_WEIGHTS_SET_RATE_LIMIT(alice_wallet, AdminUtils, True, NETUID, 0), + ] + ) + + # wait before CRv4 works in new subnets + await bob_sn.async_wait_next_epoch() + await bob_sn.async_wait_next_epoch() + + cr_version = await async_subtensor.substrate.query( + module="SubtensorModule", storage_function="CommitRevealWeightsVersion" + ) + assert cr_version == 4, "Commit reveal weights version is not 4" + assert await async_subtensor.subnets.weights_rate_limit(netuid=bob_sn.netuid) == 0 + tempo = ( + await async_subtensor.subnets.get_subnet_hyperparameters(netuid=bob_sn.netuid) + ).tempo + assert tempo == TEMPO_TO_SET, "SN tempos has not been changed." + + metagraph = await async_subtensor.metagraphs.metagraph( + netuid=bob_sn.netuid, lite=False + ) + + # Check that the metagraph is instantiated correctly. + assert metagraph.weights.shape == (metagraph.n.item(), metagraph.n.item()) + assert metagraph.bonds.shape == (metagraph.n.item(), metagraph.n.item()) + + uids = [1, 2] + weights = [20, 80] + + response = await async_subtensor.extrinsics.set_weights( + wallet=bob_wallet, + netuid=bob_sn.netuid, + uids=uids, + weights=weights, + block_time=BLOCK_TIME, + period=TEMPO_TO_SET, + wait_for_finalization=False, + ) + logging.console.info(f"Response: {response}") + + assert response.success, response.message + + expected_reveal_round = response.data.get("reveal_round") + last_drand_round = await async_subtensor.chain.last_drand_round() + + while expected_reveal_round > last_drand_round + 24: # drand offset for fast blocks + last_drand_round = await async_subtensor.chain.last_drand_round() + await async_subtensor.wait_for_block() + logging.console.debug( + f"expected_reveal_round: {expected_reveal_round}, last_drand_round: {last_drand_round}" + ) + + counter = TEMPO_TO_SET + while True: + weights = await async_subtensor.subnets.weights(bob_sn.netuid) + counter -= 1 + + if weights or counter == 0: + break + + await async_subtensor.wait_for_block() + logging.console.debug( + f"Weights: {weights}, block: {await async_subtensor.block}" + ) + + await metagraph.sync() + + # Ensure the validator has at least one non-zero weight + assert metagraph.weights[0].sum().item() > 0.0, "Validator has no outgoing weights." + + # Ensure miner rows are all zeros (miners don't set weights) + if metagraph.n.item() > 1: + assert np.allclose( + metagraph.weights[1], + np.zeros_like(metagraph.weights[1]), + ), "Miner row should be all zeros" + + # Ensure bond matrix contains no negative values + assert (metagraph.bonds >= 0).all(), "Bond matrix contains negative values" + + # Ensure validator weight rows are normalized to 1 + row_sums = metagraph.weights.sum(axis=1) + validator_mask = metagraph.validator_permit + assert np.allclose( + row_sums[validator_mask], + np.ones_like(row_sums[validator_mask]), + atol=1e-6, + ), "Validator weight rows are not normalized to 1" def test_metagraph_info(subtensor, alice_wallet, bob_wallet): @@ -586,8 +840,6 @@ def test_metagraph_info(subtensor, alice_wallet, bob_wallet): 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): @@ -843,8 +1095,6 @@ async def test_metagraph_info_async(async_subtensor, alice_wallet, bob_wallet): assert metagraph_info is None - logging.console.info("✅ Passed [blue]test_metagraph_info_async[/blue]") - def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): """ @@ -1071,8 +1321,6 @@ def test_metagraph_info_with_indexes(subtensor, alice_wallet, bob_wallet): commitments=None, ) - logging.console.info("✅ Passed [blue]test_metagraph_info_with_indexes[/blue]") - @pytest.mark.asyncio async def test_metagraph_info_with_indexes_async( @@ -1303,7 +1551,3 @@ async def test_metagraph_info_with_indexes_async( validators=None, commitments=None, ) - - logging.console.info( - "✅ Passed [blue]test_metagraph_info_with_indexes_async[/blue]" - ) From 6c40abe6280fb757ad71c9ca0fca6759ef5c9a2f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 18:38:12 -0700 Subject: [PATCH 408/416] test refactor --- tests/e2e_tests/test_set_weights.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/test_set_weights.py b/tests/e2e_tests/test_set_weights.py index 466c278fab..93cb3ed86b 100644 --- a/tests/e2e_tests/test_set_weights.py +++ b/tests/e2e_tests/test_set_weights.py @@ -279,9 +279,9 @@ async def set_weights_(): mechid=mechid_, uids=weight_uids, weights=weight_vals, + period=subnet_tempo, wait_for_inclusion=True, wait_for_finalization=False, - period=subnet_tempo, ) assert success_ is True, message_ From c9d53b9eda757f8076e8e38beb12aabb7c060360 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 18:38:47 -0700 Subject: [PATCH 409/416] close last TODO (e2e test exist) --- bittensor/core/metagraph.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 58e85dcdf3..268659e7e5 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -1527,7 +1527,6 @@ async def _set_weights_and_bonds(self, subtensor: "AsyncSubtensor", block: int): self._set_weights_and_bonds(subtensor=subtensor) """ - # TODO: Check and test the computation of weights and bonds if self.netuid == 0: self.weights = await self._process_root_weights( [neuron.weights for neuron in self.neurons], From b464685bea241c7d271218b4c640a84b2930bb6f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 18:44:28 -0700 Subject: [PATCH 410/416] fix types --- bittensor/core/types.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bittensor/core/types.py b/bittensor/core/types.py index e6f8291903..46ff6585a2 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -72,9 +72,7 @@ def config() -> "Config": return Config(parser) @staticmethod - def setup_config( - network: Optional[str], config: "Config" - ) -> tuple[Optional[str], Optional[str]]: + def setup_config(network: Optional[str], config: "Config") -> tuple[str, str]: """ Sets up and returns the configuration for the Subtensor network and endpoint. From ec0f92f9b5824af88d0b36c7d5702a4a04aeb8aa Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 18:45:03 -0700 Subject: [PATCH 411/416] Update MIGRATION.md --- MIGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 3f7cf42480..05a9ae2618 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -68,7 +68,7 @@ 13. ✅ Remove `Default is` and `Default to` in docstrings bc parameters enough. 14. ✅ `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). `get_commitment_metadata` added. 15. ✅ Resolve an issue where a script using the SDK receives the `--config` or any other CLI parameters used in the SDK. Disable configuration processing. Use default values ​​instead. -16. Find and process all `TODOs` across the entire code base. If in doubt, discuss each one with the team separately. SDK has 29 TODOs. +16. ✅ 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. ✅ Unify extrinsic return values by introducing an ExtrinsicResponse class. Extrinsics currently return either a boolean or a tuple. From 35a51df21a2394e8d45efe46ea0cdfd4a88c9a6f Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 19:12:47 -0700 Subject: [PATCH 412/416] fix subtensor error --- tests/e2e_tests/test_hotkeys.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index ab599a2c65..826c252be2 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -5,7 +5,6 @@ RegistrationNotPermittedOnRootSubnet, SubnetNotExists, InvalidChild, - TooManyChildren, ProportionOverflow, DuplicateChild, TxRateLimitExceeded, @@ -226,7 +225,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): raise_error=True, ) - with pytest.raises(TooManyChildren): + with pytest.raises(DuplicateChild): subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, @@ -532,7 +531,7 @@ async def test_children_async(async_subtensor, alice_wallet, bob_wallet, dave_wa raise_error=True, ) - with pytest.raises(TooManyChildren): + with pytest.raises(DuplicateChild): await async_subtensor.extrinsics.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, From a35314b7edd80b4d7019b955845c1fa4b4fb7e57 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 19:42:03 -0700 Subject: [PATCH 413/416] ops --- bittensor/core/async_subtensor.py | 1 - 1 file changed, 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index ca1764742a..d7372f5556 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2878,7 +2878,6 @@ async def get_stake_add_fee( destination_netuid=netuid, amount=amount, block_hash=block_hash, - reuse_block_hash=reuse_block, ) return sim_swap_result.tao_fee From 23b5fb6d550f3974bfd66b462900a2c730bd7334 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 19:52:25 -0700 Subject: [PATCH 414/416] e2e --- tests/e2e_tests/test_hotkeys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 848627df32..10c2b8d676 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -226,7 +226,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): ) with pytest.raises(DuplicateChild): - subtensor.set_children( + subtensor.wallet.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_sn.netuid, From f15c9f632f5a8d6ba78a726fbfa6c058e86b3f11 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 20:08:12 -0700 Subject: [PATCH 415/416] wallets --- tests/e2e_tests/test_hotkeys.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/test_hotkeys.py b/tests/e2e_tests/test_hotkeys.py index 10c2b8d676..cc006ea8fe 100644 --- a/tests/e2e_tests/test_hotkeys.py +++ b/tests/e2e_tests/test_hotkeys.py @@ -226,7 +226,7 @@ def test_children(subtensor, alice_wallet, bob_wallet, dave_wallet): ) with pytest.raises(DuplicateChild): - subtensor.wallet.set_children( + subtensor.wallets.set_children( wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=dave_sn.netuid, From 7a75d534b829ed39943d23c6e92f05499e5eac31 Mon Sep 17 00:00:00 2001 From: Roman Chkhaidze Date: Thu, 9 Oct 2025 22:46:08 -0700 Subject: [PATCH 416/416] return --- bittensor/core/async_subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index d7372f5556..a02d3475fd 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -279,7 +279,7 @@ async def initialize(self): raise ConnectionError async def __aenter__(self): - await self.initialize() + return await self.initialize() async def __aexit__(self, exc_type, exc_val, exc_tb): await self.substrate.close()