From a7338fc0c6795e5e9cc832310dafbeff1e4943d6 Mon Sep 17 00:00:00 2001 From: socialsister Date: Wed, 16 Jul 2025 00:01:27 +0800 Subject: [PATCH 01/68] chore: fix typo Signed-off-by: socialsister --- bittensor/core/extrinsics/unstaking.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/extrinsics/unstaking.py b/bittensor/core/extrinsics/unstaking.py index fe70c69558..beff7f1993 100644 --- a/bittensor/core/extrinsics/unstaking.py +++ b/bittensor/core/extrinsics/unstaking.py @@ -75,7 +75,7 @@ def unstake_extrinsic( block=block, ) - # Covert to bittensor.Balance + # Convert to bittensor.Balance if amount is None: # Unstake it all. logging.warning( From 0bf124f23e5a331a52a83e11db51ede0e0662a80 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 17 Jul 2025 19:12:32 -0700 Subject: [PATCH 02/68] update `WeightCommitInfo` with `CRV3WeightCommitsV2` (subtensor struct) --- .../core/chain_data/weight_commit_info.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/bittensor/core/chain_data/weight_commit_info.py b/bittensor/core/chain_data/weight_commit_info.py index db253f291c..c4879decb6 100644 --- a/bittensor/core/chain_data/weight_commit_info.py +++ b/bittensor/core/chain_data/weight_commit_info.py @@ -8,30 +8,35 @@ class WeightCommitInfo: Data class representing weight commit information. Attributes: - ss58 (str): The SS58 address of the committer - commit_hex (str): The serialized weight commit data as hex string - reveal_round (int): The round number for reveal + ss58: The SS58 address of the committer + commit_block: The block number of the commitment. + commit_hex: The serialized weight commit data as hex string + reveal_round: The round number for reveal """ ss58: str + commit_block: int commit_hex: str reveal_round: int @classmethod - def from_vec_u8(cls, data: tuple) -> tuple[str, str, int]: + def from_vec_u8(cls, data: tuple) -> tuple[str, int, str, int]: """ Creates a WeightCommitInfo instance Args: - data (tuple): Tuple containing ((AccountId,), (commit_data,), round_number) + data (tuple): Tuple containing ((AccountId,), (commit_block, ) (commit_data,), round_number) Returns: WeightCommitInfo: A new instance with the decoded data """ - account_id, commit_data, round_number = data + account_id, commit_block, commit_data, round_number = data account_id_ = account_id[0] if isinstance(account_id, tuple) else account_id + commit_block = ( + commit_block[0] if isinstance(commit_block, tuple) else commit_block + ) commit_data = commit_data[0] if isinstance(commit_data, tuple) else commit_data commit_hex = "0x" + "".join(format(x, "02x") for x in commit_data) - return decode_account_id(account_id_), commit_hex, round_number + return decode_account_id(account_id_), commit_block, commit_hex, round_number From fff4989f34614326915d497155d7dd675dcf7b5d Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 17 Jul 2025 19:13:05 -0700 Subject: [PATCH 03/68] improve `get_current_weight_commit_info` calls --- 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 058bc10314..9c21513819 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1678,7 +1678,7 @@ async def get_current_weight_commit_info( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> list: + ) -> list[tuple[str, int, str, int]]: """ Retrieves CRV3 weight commit information for a specific subnet. @@ -1695,7 +1695,7 @@ async def get_current_weight_commit_info( block_hash = await self.determine_block_hash(block, block_hash, reuse_block) result = await self.substrate.query_map( module="SubtensorModule", - storage_function="CRV3WeightCommits", + storage_function="CRV3WeightCommitsV2", params=[netuid], block_hash=block_hash, reuse_block_hash=reuse_block, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b64809ee15..ee0ffa8381 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1072,7 +1072,7 @@ def get_all_revealed_commitments( def get_current_weight_commit_info( self, netuid: int, block: Optional[int] = None - ) -> list: + ) -> list[tuple[str, int, str, int]]: """ Retrieves CRV3 weight commit information for a specific subnet. @@ -1086,7 +1086,7 @@ def get_current_weight_commit_info( """ result = self.substrate.query_map( module="SubtensorModule", - storage_function="CRV3WeightCommits", + storage_function="CRV3WeightCommitsV2", params=[netuid], block_hash=self.determine_block_hash(block), ) From 9fa3164aae522df25f9399987d5cf6bfd0220022 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 17 Jul 2025 19:13:14 -0700 Subject: [PATCH 04/68] fix e2e test --- tests/e2e_tests/test_commit_reveal_v3.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/test_commit_reveal_v3.py b/tests/e2e_tests/test_commit_reveal_v3.py index 719dbe5827..96a17bbfa4 100644 --- a/tests/e2e_tests/test_commit_reveal_v3.py +++ b/tests/e2e_tests/test_commit_reveal_v3.py @@ -119,6 +119,7 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle reporting_interval=1, ) current_block = subtensor.get_current_block() + expected_block = current_block latest_drand_round = subtensor.last_drand_round() upcoming_tempo = next_tempo(current_block, tempo) logging.console.info( @@ -161,11 +162,12 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle # Fetch current commits pending on the chain commits_on_chain = subtensor.get_current_weight_commit_info(netuid=netuid) - address, commit, reveal_round = commits_on_chain[0] + address, commit_block, commit, reveal_round = commits_on_chain[0] # Assert correct values are committed on the chain assert expected_reveal_round == reveal_round assert address == alice_wallet.hotkey.ss58_address + assert commit_block == expected_block + 1 # Ensure no weights are available as of now assert subtensor.weights(netuid=netuid) == [] From 0f87b15b91fff0d48492398210a96ddcff79989b Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 17 Jul 2025 19:13:21 -0700 Subject: [PATCH 05/68] fix unit test test --- tests/unit_tests/test_subtensor_extended.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 78851574cf..33080e2795 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -431,6 +431,7 @@ def test_get_current_weight_commit_info(mock_substrate, subtensor, fake_wallet, [ ( bytearray(32), + 100, b"data", 123, ), @@ -445,6 +446,7 @@ def test_get_current_weight_commit_info(mock_substrate, subtensor, fake_wallet, assert result == [ ( "5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM", + 100, "0x64617461", 123, ), @@ -452,7 +454,7 @@ def test_get_current_weight_commit_info(mock_substrate, subtensor, fake_wallet, mock_substrate.query_map.assert_called_once_with( module="SubtensorModule", - storage_function="CRV3WeightCommits", + storage_function="CRV3WeightCommitsV2", params=[1], block_hash=None, ) From ae3b8a339bcc71cb0be5a5b9e07b53d3ae85318f Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 17 Jul 2025 19:13:26 -0700 Subject: [PATCH 06/68] ruff --- tests/e2e_tests/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index c79446be26..c9ed635971 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -18,7 +18,10 @@ setup_wallet, ) -LOCALNET_IMAGE_NAME = os.getenv("LOCALNET_IMAGE_NAME") or "ghcr.io/opentensor/subtensor-localnet:devnet-ready" +LOCALNET_IMAGE_NAME = ( + os.getenv("LOCALNET_IMAGE_NAME") + or "ghcr.io/opentensor/subtensor-localnet:devnet-ready" +) CONTAINER_NAME_PREFIX = "test_local_chain_" From 9801676d83969c620aa16783b0a4e1050754a2d2 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 15:44:03 +0200 Subject: [PATCH 07/68] [WIP] Optimisations --- bittensor/core/subtensor.py | 72 +++++++++++++++++++++---------------- tests/e2e_tests/conftest.py | 5 ++- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 94e9b7dd91..ca7f578626 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -9,6 +9,7 @@ from async_substrate_interface.substrate_addons import RetrySyncSubstrate from async_substrate_interface.sync_substrate import SubstrateInterface from async_substrate_interface.types import ScaleObj +from async_substrate_interface.utils.storage import StorageKey from bittensor_drand import get_encrypted_commitment from numpy.typing import NDArray @@ -429,7 +430,8 @@ def state_call( block_hash = self.determine_block_hash(block) return self.substrate.rpc_request( method="state_call", - params=[method, data, block_hash] if block_hash else [method, data], + params=[method, data], + block_hash=block_hash ) # Common subtensor calls =========================================================================================== @@ -484,20 +486,26 @@ 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. Arguments: netuid (int): The unique identifier of the subnetwork. uid (int): The unique identifier of the neuron. + block (int): The block number for this query. Returns: Optional[int]: 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])) + call = self.get_hyperparameter(param_name="LastUpdate", netuid=netuid, block=block) + if not call: + return None + elif block is not None: + return block - int(call[uid]) + else: + return self.get_current_block() - int(call[uid]) def bonds( self, netuid: int, block: Optional[int] = None @@ -1492,31 +1500,25 @@ def get_liquidity_list( logging.debug(f"Subnet {netuid} is not active.") return None - query = self.substrate.query block_hash = self.determine_block_hash(block) # Fetch global fees and current price - fee_global_tao_query = query( - module="Swap", - storage_function="FeeGlobalTao", - params=[netuid], - block_hash=block_hash, + fee_global_tao_query_sk = self.substrate.create_storage_key( + pallet="Swap", storage_function="FeeGlobalTao", params=[netuid], block_hash=block_hash ) - fee_global_alpha_query = query( - module="Swap", - storage_function="FeeGlobalAlpha", - params=[netuid], - block_hash=block_hash, + fee_global_alpha_query_sk = self.substrate.create_storage_key( + pallet="Swap", storage_function="FeeGlobalAlpha", params=[netuid], block_hash=block_hash ) - sqrt_price_query = query( - module="Swap", - storage_function="AlphaSqrtPrice", - params=[netuid], - block_hash=block_hash, + sqrt_price_query_sk = self.substrate.create_storage_key( + pallet="Swap", storage_function="AlphaSqrtPrice", params=[netuid], block_hash=block_hash ) - fee_global_tao = fixed_to_float(fee_global_tao_query) - fee_global_alpha = fixed_to_float(fee_global_alpha_query) - sqrt_price = fixed_to_float(sqrt_price_query) + fee_global_tao_query, fee_global_alpha_query, sqrt_price_query = self.substrate.query_multi( + [fee_global_tao_query_sk, fee_global_alpha_query_sk, sqrt_price_query_sk], + block_hash=block_hash) + + fee_global_tao = fixed_to_float(fee_global_tao_query[1]) + fee_global_alpha = fixed_to_float(fee_global_alpha_query[1]) + sqrt_price = fixed_to_float(sqrt_price_query[1]) current_tick = price_to_tick(sqrt_price**2) # Fetch positions @@ -1526,26 +1528,36 @@ def get_liquidity_list( block=block, params=[netuid, wallet.coldkeypub.ss58_address], ) - - positions = [] + positions_values: list[tuple[dict, int, int]] = [] + positions_storage_keys: list[StorageKey] = [] for _, p in positions_response: position = p.value tick_low_idx = position["tick_low"][0] tick_high_idx = position["tick_high"][0] - tick_low = query( - module="Swap", + tick_low_sk = self.substrate.create_storage_key( + pallet="Swap", storage_function="Ticks", params=[netuid, tick_low_idx], block_hash=block_hash, ) - tick_high = query( - module="Swap", + tick_high_sk = self.substrate.create_storage_key( + pallet="Swap", storage_function="Ticks", params=[netuid, tick_high_idx], - block_hash=block_hash, + block_hash=block_hash ) + positions_values.append((position, tick_low_idx, tick_high_idx)) + positions_storage_keys.extend([tick_low_sk, tick_high_sk]) + # query all our ticks at once + ticks_query = self.substrate.query_multi(positions_storage_keys, block_hash=block_hash) + # iterator with just the values + ticks = iter([x[1] for x in ticks_query]) + positions = [] + for position, tick_low_idx, tick_high_idx in positions_values: + tick_low = next(ticks) + tick_high = next(ticks) # Calculate fees above/below range for both tokens tao_below = get_fees( diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index c79446be26..c9ed635971 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -18,7 +18,10 @@ setup_wallet, ) -LOCALNET_IMAGE_NAME = os.getenv("LOCALNET_IMAGE_NAME") or "ghcr.io/opentensor/subtensor-localnet:devnet-ready" +LOCALNET_IMAGE_NAME = ( + os.getenv("LOCALNET_IMAGE_NAME") + or "ghcr.io/opentensor/subtensor-localnet:devnet-ready" +) CONTAINER_NAME_PREFIX = "test_local_chain_" From 0761f0da29399084fbe85321f4cf6145b410ea30 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 16:30:44 +0200 Subject: [PATCH 08/68] Fixes liquidity_list abysmal performance --- bittensor/core/async_subtensor.py | 96 ++++++++++++++++++------------- bittensor/core/subtensor.py | 52 ++++++++++------- pyproject.toml | 2 +- 3 files changed, 90 insertions(+), 60 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index bb46e4ddc9..ef2fbf4c71 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -10,6 +10,7 @@ import scalecodec from async_substrate_interface import AsyncSubstrateInterface from async_substrate_interface.substrate_addons import RetryAsyncSubstrate +from async_substrate_interface.utils.storage import StorageKey from bittensor_drand import get_encrypted_commitment from bittensor_wallet.utils import SS58_FORMAT from numpy.typing import NDArray @@ -767,7 +768,8 @@ async def state_call( block_hash = await self.determine_block_hash(block, block_hash, reuse_block) return await self.substrate.rpc_request( method="state_call", - params=[method, data, block_hash] if block_hash else [method, data], + params=[method, data], + block_hash=block_hash, reuse_block_hash=reuse_block, ) @@ -2242,29 +2244,35 @@ async def get_liquidity_list( block=block, block_hash=block_hash, reuse_block=reuse_block ) - query = self.substrate.query + # Fetch global fees and current price + fee_global_tao_query_sk = await self.substrate.create_storage_key( + pallet="Swap", + storage_function="FeeGlobalTao", + params=[netuid], + block_hash=block_hash, + ) + fee_global_alpha_query_sk = await self.substrate.create_storage_key( + pallet="Swap", + storage_function="FeeGlobalAlpha", + params=[netuid], + block_hash=block_hash, + ) + sqrt_price_query_sk = await self.substrate.create_storage_key( + pallet="Swap", + storage_function="AlphaSqrtPrice", + params=[netuid], + block_hash=block_hash, + ) ( - fee_global_tao, - fee_global_alpha, - sqrt_price, + (fee_global_tao_query, fee_global_alpha_query, sqrt_price_query), positions_response, ) = await asyncio.gather( - query( - module="Swap", - storage_function="FeeGlobalTao", - params=[netuid], - block_hash=block_hash, - ), - query( - module="Swap", - storage_function="FeeGlobalAlpha", - params=[netuid], - block_hash=block_hash, - ), - query( - module="Swap", - storage_function="AlphaSqrtPrice", - params=[netuid], + self.substrate.query_multi( + [ + fee_global_tao_query_sk, + fee_global_alpha_query_sk, + sqrt_price_query_sk, + ], block_hash=block_hash, ), self.query_map( @@ -2275,36 +2283,46 @@ async def get_liquidity_list( ), ) # convert to floats - fee_global_tao = fixed_to_float(fee_global_tao) - fee_global_alpha = fixed_to_float(fee_global_alpha) - sqrt_price = fixed_to_float(sqrt_price) + fee_global_tao = fixed_to_float(fee_global_tao_query[1]) + fee_global_alpha = fixed_to_float(fee_global_alpha_query[1]) + sqrt_price = fixed_to_float(sqrt_price_query[1]) # Fetch global fees and current price current_tick = price_to_tick(sqrt_price**2) # Fetch positions - positions = [] + positions_values: list[tuple[dict, int, int]] = [] + positions_storage_keys: list[StorageKey] = [] async for _, p in positions_response: position = p.value tick_low_idx = position.get("tick_low")[0] tick_high_idx = position.get("tick_high")[0] - - tick_low, tick_high = await asyncio.gather( - query( - module="Swap", - storage_function="Ticks", - params=[netuid, tick_low_idx], - block_hash=block_hash, - ), - query( - module="Swap", - storage_function="Ticks", - params=[netuid, tick_high_idx], - block_hash=block_hash, - ), + positions_values.append((position, tick_low_idx, tick_high_idx)) + tick_low_sk = await self.substrate.create_storage_key( + pallet="Swap", + storage_function="Ticks", + params=[netuid, tick_low_idx], + block_hash=block_hash, ) + tick_high_sk = await self.substrate.create_storage_key( + pallet="Swap", + storage_function="Ticks", + params=[netuid, tick_high_idx], + block_hash=block_hash, + ) + positions_storage_keys.extend([tick_low_sk, tick_high_sk]) + # query all our ticks at once + ticks_query = await self.substrate.query_multi( + positions_storage_keys, block_hash=block_hash + ) + # iterator with just the values + ticks = iter([x[1] for x in ticks_query]) + positions = [] + for position, tick_low_idx, tick_high_idx in positions_values: + tick_low = next(ticks) + tick_high = next(ticks) # Calculate fees above/below range for both tokens tao_below = get_fees( current_tick=current_tick, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ca7f578626..2e92a2be22 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -429,9 +429,7 @@ def state_call( """ block_hash = self.determine_block_hash(block) return self.substrate.rpc_request( - method="state_call", - params=[method, data], - block_hash=block_hash + method="state_call", params=[method, data], block_hash=block_hash ) # Common subtensor calls =========================================================================================== @@ -486,26 +484,22 @@ 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, block: Optional[int] = None) -> Optional[int]: + 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. - block (int): The block number for this query. Returns: Optional[int]: 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, block=block) - if not call: - return None - elif block is not None: - return block - int(call[uid]) - else: - return self.get_current_block() - int(call[uid]) + call = self.get_hyperparameter( + param_name="LastUpdate", netuid=netuid, block=block + ) + return None if call is None else self.get_current_block() - int(call[uid]) def bonds( self, netuid: int, block: Optional[int] = None @@ -1504,17 +1498,33 @@ def get_liquidity_list( # Fetch global fees and current price fee_global_tao_query_sk = self.substrate.create_storage_key( - pallet="Swap", storage_function="FeeGlobalTao", params=[netuid], block_hash=block_hash + pallet="Swap", + storage_function="FeeGlobalTao", + params=[netuid], + block_hash=block_hash, ) fee_global_alpha_query_sk = self.substrate.create_storage_key( - pallet="Swap", storage_function="FeeGlobalAlpha", params=[netuid], block_hash=block_hash + pallet="Swap", + storage_function="FeeGlobalAlpha", + params=[netuid], + block_hash=block_hash, ) sqrt_price_query_sk = self.substrate.create_storage_key( - pallet="Swap", storage_function="AlphaSqrtPrice", params=[netuid], block_hash=block_hash + pallet="Swap", + storage_function="AlphaSqrtPrice", + params=[netuid], + block_hash=block_hash, + ) + fee_global_tao_query, fee_global_alpha_query, sqrt_price_query = ( + self.substrate.query_multi( + [ + fee_global_tao_query_sk, + fee_global_alpha_query_sk, + sqrt_price_query_sk, + ], + block_hash=block_hash, + ) ) - fee_global_tao_query, fee_global_alpha_query, sqrt_price_query = self.substrate.query_multi( - [fee_global_tao_query_sk, fee_global_alpha_query_sk, sqrt_price_query_sk], - block_hash=block_hash) fee_global_tao = fixed_to_float(fee_global_tao_query[1]) fee_global_alpha = fixed_to_float(fee_global_alpha_query[1]) @@ -1546,12 +1556,14 @@ def get_liquidity_list( pallet="Swap", storage_function="Ticks", params=[netuid, tick_high_idx], - block_hash=block_hash + block_hash=block_hash, ) positions_values.append((position, tick_low_idx, tick_high_idx)) positions_storage_keys.extend([tick_low_sk, tick_high_sk]) # query all our ticks at once - ticks_query = self.substrate.query_multi(positions_storage_keys, block_hash=block_hash) + ticks_query = self.substrate.query_multi( + positions_storage_keys, block_hash=block_hash + ) # iterator with just the values ticks = iter([x[1] for x in ticks_query]) positions = [] diff --git a/pyproject.toml b/pyproject.toml index 9c32e6ee7a..8b4b70f248 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "uvicorn", "bittensor-drand>=0.5.0", "bittensor-wallet>=3.1.0", - "async-substrate-interface>=1.3.1" + "async-substrate-interface>=1.4.1" ] [project.optional-dependencies] From 295bdde093e353a135540a4fca9e8821480c5aaf Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 16:35:26 +0200 Subject: [PATCH 09/68] Debug --- bittensor/core/subtensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 2e92a2be22..8cab6df590 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -496,9 +496,7 @@ def blocks_since_last_update(self, netuid: int, uid: int) -> Optional[int]: Optional[int]: 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, block=block - ) + call = self.get_hyperparameter(param_name="LastUpdate", netuid=netuid) return None if call is None else self.get_current_block() - int(call[uid]) def bonds( From 8c558d1dfc9a175a9a6c29f89024627682b056ce Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 16:38:09 +0200 Subject: [PATCH 10/68] Flake --- bittensor/core/subtensor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 8cab6df590..bd89046e2a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -655,7 +655,7 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> list["SubnetInfo" for subnet in result: subnet.update({"price": subnets_prices.get(subnet["netuid"], 0)}) - except SubstrateRequestException: + except SubstrateRequestException as e: logging.warning(f"Unable to fetch subnet prices for block {block}: {e}") return SubnetInfo.list_from_dicts(result) From b5ccbef42325a6a68686fb11713562cbcab2c757 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 17:00:48 +0200 Subject: [PATCH 11/68] Fixed unit tests --- tests/unit_tests/test_async_subtensor.py | 109 ++++++++++++++--------- tests/unit_tests/test_subtensor.py | 3 +- 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 24dee30ef7..165905f443 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -3570,52 +3570,75 @@ async def test_get_liquidity_list_happy_path(subtensor, fake_wallet, mocker): return_value=(Balance.from_tao(0.0), Balance.from_tao(0.0, netuid)), ) - mocked_substrate_query = mocker.AsyncMock( + mocked_substrate_query_multi = mocker.AsyncMock( side_effect=[ - # for gather - {"bits": 0}, - {"bits": 0}, - {"bits": 18446744073709551616}, - # for loop - { - "liquidity_net": 1000000000000, - "liquidity_gross": 1000000000000, - "fees_out_tao": {"bits": 0}, - "fees_out_alpha": {"bits": 0}, - }, - { - "liquidity_net": -1000000000000, - "liquidity_gross": 1000000000000, - "fees_out_tao": {"bits": 0}, - "fees_out_alpha": {"bits": 0}, - }, - { - "liquidity_net": 1000000000000, - "liquidity_gross": 1000000000000, - "fees_out_tao": {"bits": 0}, - "fees_out_alpha": {"bits": 0}, - }, - { - "liquidity_net": -1000000000000, - "liquidity_gross": 1000000000000, - "fees_out_tao": {"bits": 0}, - "fees_out_alpha": {"bits": 0}, - }, - { - "liquidity_net": 1000000000000, - "liquidity_gross": 1000000000000, - "fees_out_tao": {"bits": 0}, - "fees_out_alpha": {"bits": 0}, - }, - { - "liquidity_net": -1000000000000, - "liquidity_gross": 1000000000000, - "fees_out_tao": {"bits": 0}, - "fees_out_alpha": {"bits": 0}, - }, + [ + (None, {"bits": 0}), + (None, {"bits": 0}), + (None, {"bits": 18446744073709551616}), + ], + [ + ( + None, + { + "liquidity_net": 1000000000000, + "liquidity_gross": 1000000000000, + "fees_out_tao": {"bits": 0}, + "fees_out_alpha": {"bits": 0}, + }, + ), + ( + None, + { + "liquidity_net": -1000000000000, + "liquidity_gross": 1000000000000, + "fees_out_tao": {"bits": 0}, + "fees_out_alpha": {"bits": 0}, + }, + ), + ( + None, + { + "liquidity_net": 1000000000000, + "liquidity_gross": 1000000000000, + "fees_out_tao": {"bits": 0}, + "fees_out_alpha": {"bits": 0}, + }, + ), + ( + None, + { + "liquidity_net": -1000000000000, + "liquidity_gross": 1000000000000, + "fees_out_tao": {"bits": 0}, + "fees_out_alpha": {"bits": 0}, + }, + ), + ( + None, + { + "liquidity_net": 1000000000000, + "liquidity_gross": 1000000000000, + "fees_out_tao": {"bits": 0}, + "fees_out_alpha": {"bits": 0}, + }, + ), + ( + None, + { + "liquidity_net": -1000000000000, + "liquidity_gross": 1000000000000, + "fees_out_tao": {"bits": 0}, + "fees_out_alpha": {"bits": 0}, + }, + ), + ], ] ) - mocker.patch.object(subtensor.substrate, "query", mocked_substrate_query) + + mocker.patch.object( + subtensor.substrate, "query_multi", mocked_substrate_query_multi + ) fake_positions = [ [ diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 6ee02cea5a..0d22398683 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -954,8 +954,7 @@ def test_state_call(subtensor, mocker): # Asserts subtensor.substrate.rpc_request.assert_called_once_with( - method="state_call", - params=[fake_method, fake_data], + method="state_call", params=[fake_method, fake_data], block_hash=None ) assert result == subtensor.substrate.rpc_request.return_value From 01cc4efb2e2fe0dfd961fa5f76be52785f2e1d68 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 08:00:55 -0700 Subject: [PATCH 12/68] add legacy methods --- bittensor/core/async_subtensor.py | 49 ++++++++++++++++++- .../core/chain_data/weight_commit_info.py | 26 +++++++++- bittensor/core/subtensor.py | 41 +++++++++++++++- 3 files changed, 111 insertions(+), 5 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 9c21513819..4e459bf1da 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1689,8 +1689,13 @@ async def get_current_weight_commit_info( reuse_block: Whether to reuse the last-used block hash. Returns: - list: A list of commit details, where each entry is a dictionary with keys 'who', 'serialized_commit', and - 'reveal_round', or an empty list if no data is found. + 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( @@ -1704,6 +1709,46 @@ 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] + async def get_current_weight_commit_info_legacy( + 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. + + Arguments: + 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. + + 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. + + Note: + This method is used when querying a block or block hash where storage functions `CRV3WeightCommitsV2` does + not exist in Subtensor module. + """ + 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_legacy(commit) for commit in commits] + async def get_delegate_by_hotkey( self, hotkey_ss58: str, diff --git a/bittensor/core/chain_data/weight_commit_info.py b/bittensor/core/chain_data/weight_commit_info.py index c4879decb6..fd289d094d 100644 --- a/bittensor/core/chain_data/weight_commit_info.py +++ b/bittensor/core/chain_data/weight_commit_info.py @@ -1,5 +1,6 @@ from dataclasses import dataclass from bittensor.core.chain_data.utils import decode_account_id +from typing import Optional @dataclass @@ -15,10 +16,33 @@ class WeightCommitInfo: """ ss58: str - commit_block: int + commit_block: Optional[int] commit_hex: str reveal_round: int + @classmethod + def from_vec_u8_legacy(cls, data: tuple) -> tuple[str, str, int]: + """ + Creates a WeightCommitInfo instance + + Args: + data (tuple): Tuple containing ((AccountId,), (commit_data,), round_number) + + Returns: + WeightCommitInfo: A new instance with the decoded data + + Note: + This method is used when querying a block or block hash where storage functions `CRV3WeightCommitsV2` does + not exist in Subtensor module. + """ + account_id, commit_block, commit_data, round_number = data + + account_id_ = account_id[0] if isinstance(account_id, tuple) else account_id + commit_data = commit_data[0] if isinstance(commit_data, tuple) else commit_data + commit_hex = "0x" + "".join(format(x, "02x") for x in commit_data) + + return decode_account_id(account_id_), commit_hex, round_number + @classmethod def from_vec_u8(cls, data: tuple) -> tuple[str, int, str, int]: """ diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index ee0ffa8381..90431ed5a3 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1081,8 +1081,13 @@ def get_current_weight_commit_info( block (Optional[int]): The blockchain block number for the query. Default is ``None``. Returns: - list: A list of commit details, where each entry is a dictionary with keys 'who', 'serialized_commit', and - 'reveal_round', or an empty list if no data is found. + 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", @@ -1094,6 +1099,38 @@ 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] + def get_current_weight_commit_info_legacy( + self, netuid: int, block: Optional[int] = None + ) -> list[tuple[str, str, int]]: + """ + 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``. + + 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. + + Note: + This method is used when querying a block or block hash where storage functions `CRV3WeightCommitsV2` does + not exist in Subtensor module. + """ + 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_legacy(commit) for commit in commits] + def get_delegate_by_hotkey( self, hotkey_ss58: str, block: Optional[int] = None ) -> Optional["DelegateInfo"]: From b5e16de028b357a40266f82e3efed1700ba85525 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 08:01:19 -0700 Subject: [PATCH 13/68] fix SubtensorApi --- bittensor/core/subtensor_api/commitments.py | 3 +++ bittensor/core/subtensor_api/utils.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/bittensor/core/subtensor_api/commitments.py b/bittensor/core/subtensor_api/commitments.py index 6bdbb7f9f3..069b2845e3 100644 --- a/bittensor/core/subtensor_api/commitments.py +++ b/bittensor/core/subtensor_api/commitments.py @@ -12,6 +12,9 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): 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_legacy = ( + subtensor.get_current_weight_commit_info_legacy + ) self.get_last_commitment_bonds_reset_block = ( subtensor.get_last_commitment_bonds_reset_block ) diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index 6aad488979..c1148e4389 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -48,6 +48,9 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_current_weight_commit_info = ( subtensor._subtensor.get_current_weight_commit_info ) + subtensor.get_current_weight_commit_info_legacy = ( + subtensor._subtensor.get_current_weight_commit_info_legacy + ) 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 From 58746d73d2bccb02ebeafa0bab2defdadc8b5490 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 08:42:05 -0700 Subject: [PATCH 14/68] make 2 methods, fix tests --- bittensor/core/async_subtensor.py | 18 +++++++----------- .../core/chain_data/weight_commit_info.py | 4 ++-- bittensor/core/subtensor.py | 17 +++++++---------- bittensor/core/subtensor_api/commitments.py | 4 ++-- bittensor/core/subtensor_api/utils.py | 4 ++-- tests/e2e_tests/test_commit_reveal_v3.py | 4 ++-- tests/unit_tests/test_subtensor_extended.py | 6 ++++-- 7 files changed, 26 insertions(+), 31 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 4e459bf1da..87f6944c1e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -1678,7 +1678,7 @@ async def get_current_weight_commit_info( block: Optional[int] = None, block_hash: Optional[str] = None, reuse_block: bool = False, - ) -> list[tuple[str, int, str, int]]: + ) -> list[tuple[str, str, int]]: """ Retrieves CRV3 weight commit information for a specific subnet. @@ -1691,7 +1691,6 @@ async def get_current_weight_commit_info( 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. @@ -1700,7 +1699,7 @@ async def get_current_weight_commit_info( block_hash = await self.determine_block_hash(block, block_hash, reuse_block) result = await self.substrate.query_map( module="SubtensorModule", - storage_function="CRV3WeightCommitsV2", + storage_function="CRV3WeightCommits", params=[netuid], block_hash=block_hash, reuse_block_hash=reuse_block, @@ -1709,13 +1708,13 @@ 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] - async def get_current_weight_commit_info_legacy( + 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, str, int]]: + ) -> list[tuple[str, int, str, int]]: """ Retrieves CRV3 weight commit information for a specific subnet. @@ -1728,26 +1727,23 @@ async def get_current_weight_commit_info_legacy( 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. - - Note: - This method is used when querying a block or block hash where storage functions `CRV3WeightCommitsV2` does - not exist in Subtensor module. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) result = await self.substrate.query_map( module="SubtensorModule", - storage_function="CRV3WeightCommits", + 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_legacy(commit) for commit in commits] + return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] async def get_delegate_by_hotkey( self, diff --git a/bittensor/core/chain_data/weight_commit_info.py b/bittensor/core/chain_data/weight_commit_info.py index fd289d094d..b8555eed68 100644 --- a/bittensor/core/chain_data/weight_commit_info.py +++ b/bittensor/core/chain_data/weight_commit_info.py @@ -21,7 +21,7 @@ class WeightCommitInfo: reveal_round: int @classmethod - def from_vec_u8_legacy(cls, data: tuple) -> tuple[str, str, int]: + def from_vec_u8(cls, data: tuple) -> tuple[str, str, int]: """ Creates a WeightCommitInfo instance @@ -44,7 +44,7 @@ def from_vec_u8_legacy(cls, data: tuple) -> tuple[str, str, int]: return decode_account_id(account_id_), commit_hex, round_number @classmethod - def from_vec_u8(cls, data: tuple) -> tuple[str, int, str, int]: + def from_vec_u8_v2(cls, data: tuple) -> tuple[str, int, str, int]: """ Creates a WeightCommitInfo instance diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 90431ed5a3..b2c043269f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1072,7 +1072,7 @@ def get_all_revealed_commitments( def get_current_weight_commit_info( self, netuid: int, block: Optional[int] = None - ) -> list[tuple[str, int, str, int]]: + ) -> list[tuple[str, str, int]]: """ Retrieves CRV3 weight commit information for a specific subnet. @@ -1083,15 +1083,15 @@ def get_current_weight_commit_info( 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", + storage_function="CRV3WeightCommits", params=[netuid], block_hash=self.determine_block_hash(block), ) @@ -1099,9 +1099,9 @@ 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] - def get_current_weight_commit_info_legacy( + def get_current_weight_commit_info_v2( self, netuid: int, block: Optional[int] = None - ) -> list[tuple[str, str, int]]: + ) -> list[tuple[str, int, str, int]]: """ Retrieves CRV3 weight commit information for a specific subnet. @@ -1112,14 +1112,11 @@ def get_current_weight_commit_info_legacy( 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. - - Note: - This method is used when querying a block or block hash where storage functions `CRV3WeightCommitsV2` does - not exist in Subtensor module. """ result = self.substrate.query_map( module="SubtensorModule", @@ -1129,7 +1126,7 @@ def get_current_weight_commit_info_legacy( ) commits = result.records[0][1] if result.records else [] - return [WeightCommitInfo.from_vec_u8_legacy(commit) for commit in commits] + return [WeightCommitInfo.from_vec_u8_v2(commit) for commit in commits] def get_delegate_by_hotkey( self, hotkey_ss58: str, block: Optional[int] = None diff --git a/bittensor/core/subtensor_api/commitments.py b/bittensor/core/subtensor_api/commitments.py index 069b2845e3..8a9c49bd30 100644 --- a/bittensor/core/subtensor_api/commitments.py +++ b/bittensor/core/subtensor_api/commitments.py @@ -12,8 +12,8 @@ def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): 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_legacy = ( - subtensor.get_current_weight_commit_info_legacy + 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/utils.py b/bittensor/core/subtensor_api/utils.py index c1148e4389..3f31d51b90 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -48,8 +48,8 @@ def add_legacy_methods(subtensor: "SubtensorApi"): subtensor.get_current_weight_commit_info = ( subtensor._subtensor.get_current_weight_commit_info ) - subtensor.get_current_weight_commit_info_legacy = ( - subtensor._subtensor.get_current_weight_commit_info_legacy + 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 diff --git a/tests/e2e_tests/test_commit_reveal_v3.py b/tests/e2e_tests/test_commit_reveal_v3.py index 96a17bbfa4..1cc2970e3b 100644 --- a/tests/e2e_tests/test_commit_reveal_v3.py +++ b/tests/e2e_tests/test_commit_reveal_v3.py @@ -161,7 +161,7 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle ) # Fetch current commits pending on the chain - commits_on_chain = subtensor.get_current_weight_commit_info(netuid=netuid) + commits_on_chain = subtensor.get_current_weight_commit_info_v2(netuid=netuid) address, commit_block, commit, reveal_round = commits_on_chain[0] # Assert correct values are committed on the chain @@ -201,7 +201,7 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle assert weight_vals[0] == revealed_weights[0][1] # Now that the commit has been revealed, there shouldn't be any pending commits - assert subtensor.get_current_weight_commit_info(netuid=netuid) == [] + assert subtensor.get_current_weight_commit_info_v2(netuid=netuid) == [] # Ensure the drand_round is always in the positive w.r.t expected when revealed assert latest_drand_round - expected_reveal_round >= 0, ( diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 33080e2795..09fcdb6d97 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -424,7 +424,9 @@ def test_get_children_pending(mock_substrate, subtensor): ) -def test_get_current_weight_commit_info(mock_substrate, subtensor, fake_wallet, mocker): +def test_get_current_weight_commit_info_v2( + mock_substrate, subtensor, fake_wallet, mocker +): mock_substrate.query_map.return_value.records = [ ( mocker.ANY, @@ -439,7 +441,7 @@ def test_get_current_weight_commit_info(mock_substrate, subtensor, fake_wallet, ), ] - result = subtensor.get_current_weight_commit_info( + result = subtensor.get_current_weight_commit_info_v2( netuid=1, ) From b16a56ad9e3b840bbddebf12ecdc8220e94fc27d Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 17:51:09 +0200 Subject: [PATCH 15/68] Fixes (relies on https://github.com/opentensor/async-substrate-interface/pull/152) --- bittensor/core/subtensor.py | 4 ++-- bittensor/utils/balance.py | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index bd89046e2a..8c586f4635 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1566,8 +1566,8 @@ def get_liquidity_list( ticks = iter([x[1] for x in ticks_query]) positions = [] for position, tick_low_idx, tick_high_idx in positions_values: - tick_low = next(ticks) - tick_high = next(ticks) + tick_low = next(ticks).value + tick_high = next(ticks).value # Calculate fees above/below range for both tokens tao_below = get_fees( diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index b65fdaec82..34d2e5c787 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -349,8 +349,7 @@ def fixed_to_float( ) -> float: # By default, this is a U64F64 # which is 64 bits of integer and 64 bits of fractional - - data: int = fixed["bits"] + data: int = fb.value if isinstance((fb := fixed["bits"]), ScaleType) else fb # Logical and to get the fractional part; remaining is the integer part fractional_part = data & (2**frac_bits - 1) From 5cb6623a8a2a274b6ca2a9951434d9c1ca00c1cb Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 17:59:10 +0200 Subject: [PATCH 16/68] Apply to async aswell --- 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 ef2fbf4c71..e096ffd7b3 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2321,8 +2321,8 @@ async def get_liquidity_list( ticks = iter([x[1] for x in ticks_query]) positions = [] for position, tick_low_idx, tick_high_idx in positions_values: - tick_low = next(ticks) - tick_high = next(ticks) + tick_low = next(ticks).value + tick_high = next(ticks).value # Calculate fees above/below range for both tokens tao_below = get_fees( current_tick=current_tick, From a3502baa87e28233dcc7e45fdae0b943877b98c4 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 18:08:33 +0200 Subject: [PATCH 17/68] Remove unnecessary .value call --- 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 e096ffd7b3..ef2fbf4c71 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2321,8 +2321,8 @@ async def get_liquidity_list( ticks = iter([x[1] for x in ticks_query]) positions = [] for position, tick_low_idx, tick_high_idx in positions_values: - tick_low = next(ticks).value - tick_high = next(ticks).value + tick_low = next(ticks) + tick_high = next(ticks) # Calculate fees above/below range for both tokens tao_below = get_fees( current_tick=current_tick, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 8c586f4635..bd89046e2a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1566,8 +1566,8 @@ def get_liquidity_list( ticks = iter([x[1] for x in ticks_query]) positions = [] for position, tick_low_idx, tick_high_idx in positions_values: - tick_low = next(ticks).value - tick_high = next(ticks).value + tick_low = next(ticks) + tick_high = next(ticks) # Calculate fees above/below range for both tokens tao_below = get_fees( From 0bb4d1961654d661e0c2cc8ee665527dac40a379 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 09:11:10 -0700 Subject: [PATCH 18/68] add `deprecated_message` to `get_current_weight_commit_info` method --- bittensor/core/async_subtensor.py | 5 +++++ bittensor/core/subtensor.py | 6 +++++- bittensor/utils/__init__.py | 7 +++++++ bittensor/utils/balance.py | 8 +++----- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 87f6944c1e..59269b8e06 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -109,6 +109,7 @@ fixed_to_float, check_and_convert_to_balance, ) +from bittensor.utils import deprecated_message from bittensor.utils.btlogging import logging from bittensor.utils.liquidity import ( calculate_fees, @@ -1696,6 +1697,10 @@ async def get_current_weight_commit_info( 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", diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b2c043269f..eac6061f7a 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -11,7 +11,7 @@ from async_substrate_interface.types import ScaleObj from bittensor_drand import get_encrypted_commitment from numpy.typing import NDArray - +from bittensor.utils import deprecated_message from bittensor.core.async_subtensor import ProposalVoteData from bittensor.core.axon import Axon from bittensor.core.chain_data import ( @@ -1089,6 +1089,10 @@ def get_current_weight_commit_info( 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", diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 14fb383847..26da8b5480 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -1,6 +1,7 @@ import ast import decimal import hashlib +import warnings from collections import namedtuple from typing import Any, Literal, Union, Optional, TYPE_CHECKING from urllib.parse import urlparse @@ -438,3 +439,9 @@ def determine_chain_endpoint_and_network( return result return "unknown", network + + +def deprecated_message(message: str) -> None: + """Shows a deprecation warning message with the given message.""" + warnings.simplefilter("default", DeprecationWarning) + warnings.warn(message=message, category=DeprecationWarning, stacklevel=2) diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index b65fdaec82..07a8431e66 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -4,6 +4,7 @@ from scalecodec import ScaleType from bittensor.core import settings +from bittensor.utils import deprecated_message def _check_currencies(self, other): @@ -830,12 +831,9 @@ def check_and_convert_to_balance( This is used to support backwards compatibility while also providing a deprecation notice. """ if isinstance(amount, (float, int)): - warnings.simplefilter("default", DeprecationWarning) - warnings.warn( + 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 9.0.0.", - category=DeprecationWarning, - stacklevel=2, + "Please update your code to use tao(amount) or Balance.from_tao(amount) for the main release 10.0.0." ) amount = tao(amount) return amount From 8f687b854d5e126f20ac3757a895475b14a61ac8 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 18:14:42 +0200 Subject: [PATCH 19/68] Will rely on changes in 1.4.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 8b4b70f248..ebf263ba57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "uvicorn", "bittensor-drand>=0.5.0", "bittensor-wallet>=3.1.0", - "async-substrate-interface>=1.4.1" + "async-substrate-interface>=1.4.2" ] [project.optional-dependencies] From ccb83cba24f2c22da288ced78a274855fa9576f0 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 11:25:00 -0700 Subject: [PATCH 20/68] add `get_stake_weight` --- bittensor/core/async_subtensor.py | 33 +++++++++++++++++++++++++++++++ bittensor/core/subtensor.py | 20 +++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index bb46e4ddc9..428b301388 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -2878,6 +2878,7 @@ async def get_stake_operations_fee( 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. + Returns: The calculated stake fee as a Balance object. """ @@ -2892,6 +2893,38 @@ async def get_stake_operations_fee( ) return amount * (result.value / U16_MAX) + async def get_stake_weight( + self, + netuid: int, + block: Optional[int] = None, + block_hash: Optional[str] = None, + reuse_block: bool = False, + ) -> list[float]: + """ + Retrieves the stake weight for all hotkeys in a given subnet. + + Arguments: + 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. + + Returns: + A list of stake weights for all hotkeys in the specified subnet. + """ + 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], + block_hash=block_hash, + ) + return [u16_normalized_float(w) for w in result] + async def get_subnet_burn_cost( self, block: Optional[int] = None, diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b4b35d334a..e952421d61 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2050,6 +2050,26 @@ def get_stake_operations_fee( ) return amount * (result.value / U16_MAX) + def get_stake_weight(self, netuid: int, block: Optional[int] = None) -> list[float]: + """ + Retrieves the stake weight for all hotkeys in a given subnet. + + Arguments: + netuid: Netuid of subnet. + block: Block number at which to perform the calculation. + + Returns: + A list of stake weights for all hotkeys in the specified subnet. + """ + block_hash = self.determine_block_hash(block=block) + result = self.substrate.query( + module="SubtensorModule", + storage_function="StakeWeight", + params=[netuid], + block_hash=block_hash, + ) + return [u16_normalized_float(w) for w in result] + 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 From 29fc311170beae79973f8f8553d11dff6d8fa4be Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 11:25:16 -0700 Subject: [PATCH 21/68] add unit tests --- tests/unit_tests/test_async_subtensor.py | 36 ++++++++++++++++++++++++ tests/unit_tests/test_subtensor.py | 31 ++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/tests/unit_tests/test_async_subtensor.py b/tests/unit_tests/test_async_subtensor.py index 24dee30ef7..93573438d2 100644 --- a/tests/unit_tests/test_async_subtensor.py +++ b/tests/unit_tests/test_async_subtensor.py @@ -4060,3 +4060,39 @@ async def test_get_stake_movement_fee(subtensor, mocker): amount=amount, netuid=netuid, block=None ) assert result == mocked_get_stake_operations_fee.return_value + + +@pytest.mark.asyncio +async def test_get_stake_weight(subtensor, mocker): + """Verify that `get_stake_weight` method calls proper methods and returns the correct value.""" + # Preps + netuid = mocker.Mock() + fake_weights = [0, 100, 15000] + expected_result = [0.0, 0.0015259021896696422, 0.22888532845044632] + + mock_determine_block_hash = mocker.patch.object( + subtensor, + "determine_block_hash", + ) + mocked_query = mocker.patch.object( + subtensor.substrate, + "query", + return_value=fake_weights, + ) + + # Call + result = await subtensor.get_stake_weight(netuid=netuid) + + # Asserts + mock_determine_block_hash.assert_awaited_once_with( + block=None, + block_hash=None, + reuse_block=False, + ) + mocked_query.assert_awaited_once_with( + module="SubtensorModule", + storage_function="StakeWeight", + params=[netuid], + block_hash=mock_determine_block_hash.return_value, + ) + assert result == expected_result diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 6ee02cea5a..217caeb9a9 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -4275,3 +4275,34 @@ def test_get_stake_movement_fee(subtensor, mocker): amount=amount, netuid=netuid, block=None ) assert result == mocked_get_stake_operations_fee.return_value + + +def test_get_stake_weight(subtensor, mocker): + """Verify that `get_stake_weight` method calls proper methods and returns the correct value.""" + # Preps + netuid = mocker.Mock() + fake_weights = [0, 100, 15000] + expected_result = [0.0, 0.0015259021896696422, 0.22888532845044632] + + mock_determine_block_hash = mocker.patch.object( + subtensor, + "determine_block_hash", + ) + mocked_query = mocker.patch.object( + subtensor.substrate, + "query", + return_value=fake_weights, + ) + + # Call + result = subtensor.get_stake_weight(netuid=netuid) + + # Asserts + mock_determine_block_hash.assert_called_once_with(block=None) + mocked_query.assert_called_once_with( + module="SubtensorModule", + storage_function="StakeWeight", + params=[netuid], + block_hash=mock_determine_block_hash.return_value, + ) + assert result == expected_result From e75bbe1482121f67a23a2ce5ee6445511530ed4d Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 11:27:34 -0700 Subject: [PATCH 22/68] ruff --- tests/e2e_tests/conftest.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/e2e_tests/conftest.py b/tests/e2e_tests/conftest.py index c79446be26..c9ed635971 100644 --- a/tests/e2e_tests/conftest.py +++ b/tests/e2e_tests/conftest.py @@ -18,7 +18,10 @@ setup_wallet, ) -LOCALNET_IMAGE_NAME = os.getenv("LOCALNET_IMAGE_NAME") or "ghcr.io/opentensor/subtensor-localnet:devnet-ready" +LOCALNET_IMAGE_NAME = ( + os.getenv("LOCALNET_IMAGE_NAME") + or "ghcr.io/opentensor/subtensor-localnet:devnet-ready" +) CONTAINER_NAME_PREFIX = "test_local_chain_" From 5a50b1a530d61e791ec0a8ea2162e6153149777f Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 11:31:18 -0700 Subject: [PATCH 23/68] fix SubtensorApi --- bittensor/core/subtensor_api/staking.py | 1 + bittensor/core/subtensor_api/utils.py | 1 + 2 files changed, 2 insertions(+) diff --git a/bittensor/core/subtensor_api/staking.py b/bittensor/core/subtensor_api/staking.py index a99e201610..979d8a2632 100644 --- a/bittensor/core/subtensor_api/staking.py +++ b/bittensor/core/subtensor_api/staking.py @@ -20,6 +20,7 @@ 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.unstake = subtensor.unstake self.unstake_all = subtensor.unstake_all diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py index 6aad488979..8aec5dd7bb 100644 --- a/bittensor/core/subtensor_api/utils.py +++ b/bittensor/core/subtensor_api/utils.py @@ -87,6 +87,7 @@ def add_legacy_methods(subtensor: "SubtensorApi"): ) 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_subnet_burn_cost = subtensor._subtensor.get_subnet_burn_cost subtensor.get_subnet_hyperparameters = ( subtensor._subtensor.get_subnet_hyperparameters From 2e09cfe04352d6711dc04c48578b9e9e12dcf6e0 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 12:08:57 -0700 Subject: [PATCH 24/68] add trigger --- .github/workflows/e2e-subtensor-tests.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 265b358a84..d9f1514a66 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -170,3 +170,10 @@ jobs: echo "Tests failed after 3 attempts" exit 1 + + - name: Notify Discord on failure + if: failure() + run: | + curl -X POST -H "Content-Type: application/json" \ + -d "{\"content\": \"❌ E2E Test Failed: *${{ matrix.test-file }}* on Python ${{ matrix.python-version }} in job: ${{ github.job }}\n🔗 <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>\"}" \ + "${{ secrets.NIGHTLY_WEBHOOK_URL }}" From 33ac62b608d9871994cf7d2ab2fb12159161fee1 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 12:11:47 -0700 Subject: [PATCH 25/68] make test broken --- tests/e2e_tests/test_axon.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/e2e_tests/test_axon.py b/tests/e2e_tests/test_axon.py index 613027c691..70f71ff2fd 100644 --- a/tests/e2e_tests/test_axon.py +++ b/tests/e2e_tests/test_axon.py @@ -81,3 +81,4 @@ async def test_axon(subtensor, templates, alice_wallet): ) print("✅ Passed test_axon") + assert True is False From 18a8b33ca0d414ccfb98a0e29725f825c9bd82b1 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 12:26:53 -0700 Subject: [PATCH 26/68] add username --- .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 d9f1514a66..829254e887 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -175,5 +175,5 @@ jobs: if: failure() run: | curl -X POST -H "Content-Type: application/json" \ - -d "{\"content\": \"❌ E2E Test Failed: *${{ matrix.test-file }}* on Python ${{ matrix.python-version }} in job: ${{ github.job }}\n🔗 <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>\"}" \ + -d "{\"username\": \"Nightly CI\", \"content\": \"❌ E2E Test Failed: `${{ matrix.test-file }}*` on Python `${{ matrix.python-version }}` in job: `${{ github.job }}`\n🔗 Action: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>\"}" \ "${{ secrets.NIGHTLY_WEBHOOK_URL }}" From 70fa483ca12316f7f258a62ead4446af8839039e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 12:34:51 -0700 Subject: [PATCH 27/68] fix --- .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 829254e887..df21a3bf70 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -175,5 +175,5 @@ jobs: if: failure() run: | curl -X POST -H "Content-Type: application/json" \ - -d "{\"username\": \"Nightly CI\", \"content\": \"❌ E2E Test Failed: `${{ matrix.test-file }}*` on Python `${{ matrix.python-version }}` in job: `${{ github.job }}`\n🔗 Action: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>\"}" \ + -d "{\"username\": \"Nightly CI\", \"content\": \"❌ E2E Test Failed: \`${{ matrix.test-file }}\` on Python \`${{ matrix.python-version }}\` in job: \`${{ github.job }}\`\n🔗 Action: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>\"}" \ "${{ secrets.NIGHTLY_WEBHOOK_URL }}" From 612bb661b4f511db8ae769a73e90054aa63bf921 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 12:43:24 -0700 Subject: [PATCH 28/68] improve nightly workflow --- .github/workflows/e2e-subtensor-tests.yaml | 7 --- .../nightly-e2e-tests-subtensor-main.yml | 50 +++++++++++++++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index df21a3bf70..265b358a84 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -170,10 +170,3 @@ jobs: echo "Tests failed after 3 attempts" exit 1 - - - name: Notify Discord on failure - if: failure() - run: | - curl -X POST -H "Content-Type: application/json" \ - -d "{\"username\": \"Nightly CI\", \"content\": \"❌ E2E Test Failed: \`${{ matrix.test-file }}\` on Python \`${{ matrix.python-version }}\` in job: \`${{ github.job }}\`\n🔗 Action: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>\"}" \ - "${{ secrets.NIGHTLY_WEBHOOK_URL }}" diff --git a/.github/workflows/nightly-e2e-tests-subtensor-main.yml b/.github/workflows/nightly-e2e-tests-subtensor-main.yml index 589141ec1b..cbf10cd4e8 100644 --- a/.github/workflows/nightly-e2e-tests-subtensor-main.yml +++ b/.github/workflows/nightly-e2e-tests-subtensor-main.yml @@ -76,6 +76,7 @@ jobs: with: name: subtensor-localnet-devnet-ready path: subtensor-localnet-devnet-ready.tar + # Determine the day for non-fast-blocks run check-if-saturday: runs-on: ubuntu-latest @@ -102,6 +103,9 @@ jobs: - pull-docker-images runs-on: ubuntu-latest timeout-minutes: 25 + outputs: + failed: ${{ steps.set-fail.outputs.failed }} + strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails max-parallel: 32 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) @@ -159,6 +163,11 @@ jobs: fi done + - name: Set fail flag + if: failure() + id: set-fail + run: echo "failed=true" >> "$GITHUB_OUTPUT" + # Daily run of fast-blocks tests from `bittensor:staging` based on `subtensor:devnet-ready` docker image run-fast-blocks-e2e-test-staging: name: "FB staging: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}" @@ -167,6 +176,9 @@ jobs: - pull-docker-images runs-on: ubuntu-latest timeout-minutes: 25 + outputs: + failed: ${{ steps.set-fail.outputs.failed }} + strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails max-parallel: 32 # Set the maximum number of parallel jobs (same as we have cores in ubuntu-latest runner) @@ -224,6 +236,11 @@ jobs: fi done + - name: Set fail flag + if: failure() + id: set-fail + run: echo "failed=true" >> "$GITHUB_OUTPUT" + # Saturday run of non-fast-blocks tests from `bittensor:master` based on `subtensor:main` docker image run-non-fast-blocks-e2e-test-master: if: needs.check-if-saturday.outputs.is-saturday == 'true' @@ -234,6 +251,8 @@ jobs: - pull-docker-images runs-on: ubuntu-latest timeout-minutes: 1440 + outputs: + failed: ${{ steps.set-fail.outputs.failed }} strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails @@ -293,6 +312,11 @@ jobs: fi done + - name: Set fail flag + if: failure() + id: set-fail + run: echo "failed=true" >> "$GITHUB_OUTPUT" + # Saturday run of non-fast-blocks tests from `bittensor:staging` based on `subtensor:devnet-ready` docker image run-non-fast-blocks-e2e-test-staging: if: needs.check-if-saturday.outputs.is-saturday == 'true' @@ -303,6 +327,8 @@ jobs: - pull-docker-images runs-on: ubuntu-latest timeout-minutes: 1440 + outputs: + failed: ${{ steps.set-fail.outputs.failed }} strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails @@ -361,3 +387,27 @@ jobs: sleep 5 fi done + - name: Set fail flag + if: failure() + id: set-fail + run: echo "failed=true" >> "$GITHUB_OUTPUT" + + # Send centralized Discord failure notification + notify-on-failure: + needs: + - run-fast-blocks-e2e-test-master + - run-fast-blocks-e2e-test-staging + - run-non-fast-blocks-e2e-test-master + - run-non-fast-blocks-e2e-test-staging + if: | + needs.run-fast-blocks-e2e-test-master.outputs.failed == 'true' || + needs.run-fast-blocks-e2e-test-staging.outputs.failed == 'true' || + needs.run-non-fast-blocks-e2e-test-master.outputs.failed == 'true' || + needs.run-non-fast-blocks-e2e-test-staging.outputs.failed == 'true' + runs-on: ubuntu-latest + steps: + - name: Send centralized Discord failure notification + run: | + curl -X POST -H "Content-Type: application/json" \ + -d "{\"username\": \"Nightly CI\", \"content\": \"❌ Nightly E2E tests failed. Check run: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}>\"}" \ + "${{ secrets.NIGHTLY_WEBHOOK_URL }}" From 3e60e801cd2e711139877068d72b327dffc9a0c3 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 12:46:55 -0700 Subject: [PATCH 29/68] =?UTF-8?q?fix=20test=20=E2=9A=92=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/e2e_tests/test_axon.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/e2e_tests/test_axon.py b/tests/e2e_tests/test_axon.py index 70f71ff2fd..613027c691 100644 --- a/tests/e2e_tests/test_axon.py +++ b/tests/e2e_tests/test_axon.py @@ -81,4 +81,3 @@ async def test_axon(subtensor, templates, alice_wallet): ) print("✅ Passed test_axon") - assert True is False From 48edce7edf24ecdf2fa28aa8e93e4325973f2482 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 12:53:35 -0700 Subject: [PATCH 30/68] bumping `tj-actions/changed-files` version --- .github/workflows/changelog-checker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index 8c8de24a0d..aae9580609 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: tj-actions/changed-files@v42 + - uses: tj-actions/changed-files@v46 id: changed - name: Ensure CHANGELOG.md updated if: contains(steps.changed.outputs.all_changed_files, 'CHANGELOG.md') == false From d642ed1b59d1ebd9550a5bd9b09c0c91fd42be80 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 13:45:01 -0700 Subject: [PATCH 31/68] add `workflow_dispatch` checker --- .../nightly-e2e-tests-subtensor-main.yml | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/.github/workflows/nightly-e2e-tests-subtensor-main.yml b/.github/workflows/nightly-e2e-tests-subtensor-main.yml index cbf10cd4e8..b0bc94295c 100644 --- a/.github/workflows/nightly-e2e-tests-subtensor-main.yml +++ b/.github/workflows/nightly-e2e-tests-subtensor-main.yml @@ -78,22 +78,27 @@ jobs: path: subtensor-localnet-devnet-ready.tar # Determine the day for non-fast-blocks run - check-if-saturday: + check-if-non-fast-blocks-run: runs-on: ubuntu-latest outputs: - is-saturday: ${{ steps.check.outputs.is-saturday }} + non-fast-blocks-run: ${{ steps.check.outputs.non-fast-blocks-run }} steps: - id: check run: | - day=$(date -u +%u) - echo "Today is weekday $day" - if [ "$day" -ne 6 ]; then - echo "⏭️ Skipping: not Saturday" - echo "is-saturday=false" >> "$GITHUB_OUTPUT" - exit 0 + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "🔁 Manual trigger detected. Forcing non-fast-blocks-run=true" + echo "non-fast-blocks-run=true" >> "$GITHUB_OUTPUT" + else + day=$(date -u +%u) + echo "Today is weekday $day" + if [ "$day" -ne 6 ]; then + echo "⏭️ Skipping: not Saturday" + echo "non-fast-blocks-run=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + echo "✅ It is Saturday" + echo "non-fast-blocks-run=true" >> "$GITHUB_OUTPUT" fi - echo "is-saturday=true" - echo "is-saturday=true" >> "$GITHUB_OUTPUT" # Daily run of fast-blocks tests from `bittensor:master` based on `subtensor:main docker` image run-fast-blocks-e2e-test-master: @@ -243,10 +248,10 @@ jobs: # Saturday run of non-fast-blocks tests from `bittensor:master` based on `subtensor:main` docker image run-non-fast-blocks-e2e-test-master: - if: needs.check-if-saturday.outputs.is-saturday == 'true' + if: needs.check-if-non-fast-blocks-run.outputs.non-fast-blocks-run == 'true' name: "NFB master: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}" needs: - - check-if-saturday + - check-if-non-fast-blocks-run - find-tests - pull-docker-images runs-on: ubuntu-latest @@ -319,10 +324,10 @@ jobs: # Saturday run of non-fast-blocks tests from `bittensor:staging` based on `subtensor:devnet-ready` docker image run-non-fast-blocks-e2e-test-staging: - if: needs.check-if-saturday.outputs.is-saturday == 'true' + if: needs.check-if-non-fast-blocks-run.outputs.non-fast-blocks-run == 'true' name: "NFB staging: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}" needs: - - check-if-saturday + - check-if-non-fast-blocks-run - find-tests - pull-docker-images runs-on: ubuntu-latest From 32ba782f8f87ad9c244c30e5712e6ea29a0df818 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 17:33:03 -0700 Subject: [PATCH 32/68] add `workflow_dispatch` checker --- tests/e2e_tests/test_liquidity.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/e2e_tests/test_liquidity.py b/tests/e2e_tests/test_liquidity.py index fd9ad09d42..780aa272c8 100644 --- a/tests/e2e_tests/test_liquidity.py +++ b/tests/e2e_tests/test_liquidity.py @@ -73,7 +73,7 @@ async def test_liquidity(local_chain, subtensor, alice_wallet, bob_wallet): wallet=alice_wallet, hotkey_ss58=alice_wallet.hotkey.ss58_address, netuid=alice_subnet_netuid, - amount=Balance.from_tao(1000), + amount=Balance.from_tao(1), wait_for_inclusion=True, wait_for_finalization=True, ), "❌ Cannot cannot add stake to Alice from Alice." From 345342b8e61fa6ea42455b9372ab3f3bbd22e7bf Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 17:54:51 -0700 Subject: [PATCH 33/68] improve logic --- .../nightly-e2e-tests-subtensor-main.yml | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/.github/workflows/nightly-e2e-tests-subtensor-main.yml b/.github/workflows/nightly-e2e-tests-subtensor-main.yml index b0bc94295c..e01443fde8 100644 --- a/.github/workflows/nightly-e2e-tests-subtensor-main.yml +++ b/.github/workflows/nightly-e2e-tests-subtensor-main.yml @@ -156,11 +156,13 @@ jobs: status=$? if [ $status -eq 0 ]; then echo "✅ Tests passed on attempt $i" + echo "failed=false" >> "$GITHUB_OUTPUT" break else echo "❌ Tests failed on attempt $i" if [ $i -eq 3 ]; then echo "Tests failed after 3 attempts" + echo "failed=true" >> "$GITHUB_OUTPUT" exit 1 fi echo "Retrying..." @@ -168,11 +170,6 @@ jobs: fi done - - name: Set fail flag - if: failure() - id: set-fail - run: echo "failed=true" >> "$GITHUB_OUTPUT" - # Daily run of fast-blocks tests from `bittensor:staging` based on `subtensor:devnet-ready` docker image run-fast-blocks-e2e-test-staging: name: "FB staging: ${{ matrix.test-file }} / Python ${{ matrix.python-version }}" @@ -229,11 +226,13 @@ jobs: status=$? if [ $status -eq 0 ]; then echo "✅ Tests passed on attempt $i" + echo "failed=false" >> "$GITHUB_OUTPUT" break else echo "❌ Tests failed on attempt $i" if [ $i -eq 3 ]; then echo "Tests failed after 3 attempts" + echo "failed=true" >> "$GITHUB_OUTPUT" exit 1 fi echo "Retrying..." @@ -241,11 +240,6 @@ jobs: fi done - - name: Set fail flag - if: failure() - id: set-fail - run: echo "failed=true" >> "$GITHUB_OUTPUT" - # Saturday run of non-fast-blocks tests from `bittensor:master` based on `subtensor:main` docker image run-non-fast-blocks-e2e-test-master: if: needs.check-if-non-fast-blocks-run.outputs.non-fast-blocks-run == 'true' @@ -305,11 +299,13 @@ jobs: status=$? if [ $status -eq 0 ]; then echo "✅ Tests passed on attempt $i" + echo "failed=false" >> "$GITHUB_OUTPUT" break else echo "❌ Tests failed on attempt $i" if [ $i -eq 3 ]; then echo "Tests failed after 3 attempts" + echo "failed=true" >> "$GITHUB_OUTPUT" exit 1 fi echo "Retrying..." @@ -317,11 +313,6 @@ jobs: fi done - - name: Set fail flag - if: failure() - id: set-fail - run: echo "failed=true" >> "$GITHUB_OUTPUT" - # Saturday run of non-fast-blocks tests from `bittensor:staging` based on `subtensor:devnet-ready` docker image run-non-fast-blocks-e2e-test-staging: if: needs.check-if-non-fast-blocks-run.outputs.non-fast-blocks-run == 'true' @@ -381,21 +372,19 @@ jobs: status=$? if [ $status -eq 0 ]; then echo "✅ Tests passed on attempt $i" + echo "failed=false" >> "$GITHUB_OUTPUT" break else echo "❌ Tests failed on attempt $i" if [ $i -eq 3 ]; then echo "Tests failed after 3 attempts" + echo "failed=true" >> "$GITHUB_OUTPUT" exit 1 fi echo "Retrying..." sleep 5 fi done - - name: Set fail flag - if: failure() - id: set-fail - run: echo "failed=true" >> "$GITHUB_OUTPUT" # Send centralized Discord failure notification notify-on-failure: From 16fd715d416548bbe185e4ca5b1c57254d6572dc Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 18 Jul 2025 22:22:21 -0700 Subject: [PATCH 34/68] fix id for job step --- .../nightly-e2e-tests-subtensor-main.yml | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/.github/workflows/nightly-e2e-tests-subtensor-main.yml b/.github/workflows/nightly-e2e-tests-subtensor-main.yml index e01443fde8..856bf6347b 100644 --- a/.github/workflows/nightly-e2e-tests-subtensor-main.yml +++ b/.github/workflows/nightly-e2e-tests-subtensor-main.yml @@ -109,7 +109,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 outputs: - failed: ${{ steps.set-fail.outputs.failed }} + failed: ${{ steps.test-failed.outputs.failed }} strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails @@ -145,21 +145,24 @@ jobs: run: docker load -i subtensor-localnet-main.tar - name: Run tests with retry + id: test-failed env: FAST_BLOCKS: "1" LOCALNET_IMAGE_NAME: "ghcr.io/opentensor/subtensor-localnet:main" run: | set +e for i in 1 2 3; do - echo "🔁 Attempt $i: Running tests" + echo "::group::🔁 Test attempt $i" uv run pytest ${{ matrix.test-file }} -s status=$? if [ $status -eq 0 ]; then echo "✅ Tests passed on attempt $i" + echo "::endgroup::" echo "failed=false" >> "$GITHUB_OUTPUT" break else echo "❌ Tests failed on attempt $i" + echo "::endgroup::" if [ $i -eq 3 ]; then echo "Tests failed after 3 attempts" echo "failed=true" >> "$GITHUB_OUTPUT" @@ -179,7 +182,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 25 outputs: - failed: ${{ steps.set-fail.outputs.failed }} + failed: ${{ steps.test-failed.outputs.failed }} strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails @@ -215,21 +218,24 @@ jobs: run: docker load -i subtensor-localnet-devnet-ready.tar - name: Run tests with retry + id: test-failed env: FAST_BLOCKS: "1" LOCALNET_IMAGE_NAME: "ghcr.io/opentensor/subtensor-localnet:devnet-ready" run: | set +e for i in 1 2 3; do - echo "🔁 Attempt $i: Running tests" + echo "::group::🔁 Test attempt $i" uv run pytest ${{ matrix.test-file }} -s status=$? if [ $status -eq 0 ]; then echo "✅ Tests passed on attempt $i" + echo "::endgroup::" echo "failed=false" >> "$GITHUB_OUTPUT" break else echo "❌ Tests failed on attempt $i" + echo "::endgroup::" if [ $i -eq 3 ]; then echo "Tests failed after 3 attempts" echo "failed=true" >> "$GITHUB_OUTPUT" @@ -251,7 +257,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 1440 outputs: - failed: ${{ steps.set-fail.outputs.failed }} + failed: ${{ steps.test-failed.outputs.failed }} strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails @@ -288,21 +294,24 @@ jobs: run: docker load -i subtensor-localnet-main.tar - name: Run patched E2E tests + id: test-failed env: FAST_BLOCKS: "0" LOCALNET_IMAGE_NAME: "ghcr.io/opentensor/subtensor-localnet:main" run: | set +e for i in 1 2 3; do - echo "🔁 Attempt $i: Running tests" + echo "::group::🔁 Test attempt $i" uv run pytest ${{ matrix.test-file }} -s status=$? if [ $status -eq 0 ]; then echo "✅ Tests passed on attempt $i" + echo "::endgroup::" echo "failed=false" >> "$GITHUB_OUTPUT" break else echo "❌ Tests failed on attempt $i" + echo "::endgroup::" if [ $i -eq 3 ]; then echo "Tests failed after 3 attempts" echo "failed=true" >> "$GITHUB_OUTPUT" @@ -324,7 +333,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 1440 outputs: - failed: ${{ steps.set-fail.outputs.failed }} + failed: ${{ steps.test-failed.outputs.failed }} strategy: fail-fast: false # Allow other matrix jobs to run even if this job fails @@ -361,21 +370,24 @@ jobs: run: docker load -i subtensor-localnet-devnet-ready.tar - name: Run patched E2E tests + id: test-failed env: FAST_BLOCKS: "0" LOCALNET_IMAGE_NAME: "ghcr.io/opentensor/subtensor-localnet:devnet-ready" run: | set +e for i in 1 2 3; do - echo "🔁 Attempt $i: Running tests" + echo "::group::🔁 Test attempt $i" uv run pytest ${{ matrix.test-file }} -s status=$? if [ $status -eq 0 ]; then echo "✅ Tests passed on attempt $i" + echo "::endgroup::" echo "failed=false" >> "$GITHUB_OUTPUT" break else echo "❌ Tests failed on attempt $i" + echo "::endgroup::" if [ $i -eq 3 ]; then echo "Tests failed after 3 attempts" echo "failed=true" >> "$GITHUB_OUTPUT" From dda1af5107f41407082b3a32b22cdcb7b59e4117 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 22 Jul 2025 16:35:43 +0200 Subject: [PATCH 35/68] Two errors can actually be raised for the price fetching: SubstrateRequestException and ValueError. This handles both. --- bittensor/core/async_subtensor.py | 6 +++--- bittensor/core/subtensor.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 33bd99f76d..a306f9a4bf 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -821,7 +821,7 @@ async def all_subnets( decoded = query.decode() - if not isinstance(subnet_prices, SubstrateRequestException): + if not isinstance(subnet_prices, (SubstrateRequestException, ValueError)): for sn in decoded: sn.update( {"price": subnet_prices.get(sn["netuid"], Balance.from_tao(0))} @@ -1155,7 +1155,7 @@ async def get_all_subnets_info( if not result: return [] - if not isinstance(prices, SubstrateRequestException): + if not isinstance(prices, (SubstrateRequestException, ValueError)): for subnet in result: subnet.update({"price": prices.get(subnet["netuid"], 0)}) else: @@ -3858,7 +3858,7 @@ async def subnet( ) if isinstance(decoded := query.decode(), dict): - if isinstance(price, SubstrateRequestException): + if isinstance(price, (SubstrateRequestException, ValueError)): price = None return DynamicInfo.from_dict({**decoded, "price": price}) return None diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index fd04dfbc93..4afe9d95e4 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -462,7 +462,7 @@ def all_subnets(self, block: Optional[int] = None) -> Optional[list["DynamicInfo sn.update( {"price": subnet_prices.get(sn["netuid"], Balance.from_tao(0))} ) - except SubstrateRequestException as e: + except (SubstrateRequestException, ValueError) as e: logging.warning(f"Unable to fetch subnet prices for block {block}: {e}") return DynamicInfo.list_from_dicts(decoded) @@ -655,7 +655,7 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> list["SubnetInfo" for subnet in result: subnet.update({"price": subnets_prices.get(subnet["netuid"], 0)}) - except SubstrateRequestException as e: + except (SubstrateRequestException, ValueError) as e: logging.warning(f"Unable to fetch subnet prices for block {block}: {e}") return SubnetInfo.list_from_dicts(result) @@ -2771,7 +2771,7 @@ def subnet(self, netuid: int, block: Optional[int] = None) -> Optional[DynamicIn if isinstance(decoded := query.decode(), dict): try: price = self.get_subnet_price(netuid=netuid, block=block) - except SubstrateRequestException: + except (SubstrateRequestException, ValueError): price = None return DynamicInfo.from_dict({**decoded, "price": price}) return None From 4974adff374644d669c967d3769652be7ef3cfb6 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 23 Jul 2025 19:11:03 +0200 Subject: [PATCH 36/68] Fix test --- bittensor/core/subtensor.py | 1 + .../extrinsics/asyncex/test_registration.py | 1 - .../unit_tests/extrinsics/test_set_weights.py | 3 +- tests/unit_tests/test_subtensor.py | 82 +++++++++---------- tests/unit_tests/utils/test_weight_utils.py | 2 - 5 files changed, 43 insertions(+), 46 deletions(-) diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index c5962d50f1..b28c49533f 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -106,6 +106,7 @@ torch, u16_normalized_float, u64_normalized_float, + deprecated_message, ) from bittensor.utils.balance import ( Balance, diff --git a/tests/unit_tests/extrinsics/asyncex/test_registration.py b/tests/unit_tests/extrinsics/asyncex/test_registration.py index d10c032cd7..4cecae6616 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_registration.py +++ b/tests/unit_tests/extrinsics/asyncex/test_registration.py @@ -1,6 +1,5 @@ import pytest -from bittensor.core import async_subtensor from bittensor.core.extrinsics.asyncex import registration as async_registration diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py index 1046385291..6a47809b43 100644 --- a/tests/unit_tests/extrinsics/test_set_weights.py +++ b/tests/unit_tests/extrinsics/test_set_weights.py @@ -1,9 +1,8 @@ from unittest.mock import MagicMock, patch import pytest -import torch +# import torch -from bittensor.core import subtensor as subtensor_module from bittensor.core.extrinsics.set_weights import ( _do_set_weights, set_weights_extrinsic, diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index cfaeb831ba..aa5121408e 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1496,7 +1496,7 @@ def test_do_serve_axon_is_success( ) assert result[0] is True - assert result[1] is "" + assert result[1] == "" def test_do_serve_axon_is_not_success(subtensor, fake_wallet, mocker, fake_call_params): @@ -3844,25 +3844,22 @@ def test_get_liquidity_list_subnet_is_not_active(subtensor, mocker): def test_get_liquidity_list_happy_path(subtensor, fake_wallet, mocker): """Tests `get_liquidity_list` returns the correct value.""" - # Preps netuid = 2 + # Mock network state mocker.patch.object(subtensor, "subnet_exists", return_value=True) mocker.patch.object(subtensor, "is_subnet_active", return_value=True) - mocker.patch.object(subtensor, "determine_block_hash") + mocker.patch.object(subtensor, "determine_block_hash", return_value="0x1234") - mocker.patch.object( - subtensor_module, "price_to_tick", return_value=Balance.from_tao(1.0, netuid) - ) + # Mock price and fee calculation + mocker.patch.object(subtensor_module, "price_to_tick", return_value=100) mocker.patch.object( subtensor_module, "calculate_fees", return_value=(Balance.from_tao(0.0), Balance.from_tao(0.0, netuid)), ) - mocked_substrate_query = mocker.MagicMock() - mocker.patch.object(subtensor.substrate, "query", mocked_substrate_query) - + # Fake positions to return from query_map fake_positions = [ [ (2,), @@ -3878,55 +3875,58 @@ def test_get_liquidity_list_happy_path(subtensor, fake_wallet, mocker): } ), ], - [ - (2,), - mocker.Mock( - value={ - "id": (2,), - "netuid": 2, - "tick_low": (216189,), - "tick_high": (198196,), - "liquidity": 2000000000000, - "fees_tao": {"bits": 0}, - "fees_alpha": {"bits": 0}, - } - ), - ], - [ - (2,), - mocker.Mock( - value={ - "id": (2,), - "netuid": 2, - "tick_low": (226189,), - "tick_high": (188196,), - "liquidity": 3000000000000, - "fees_tao": {"bits": 0}, - "fees_alpha": {"bits": 0}, - } - ), - ], ] mocked_query_map = mocker.MagicMock(return_value=fake_positions) mocker.patch.object(subtensor, "query_map", new=mocked_query_map) - # Call + # Mock storage key creation + mocker.patch.object( + subtensor.substrate, + "create_storage_key", + side_effect=lambda pallet, + storage_function, + params, + block_hash=None: f"{pallet}:{storage_function}:{params}", + ) + + # Mock query_multi for fee + sqrt_price + tick data + mock_query_multi = mocker.MagicMock( + side_effect=[ + [ + ("key1", {"bits": 0}), # fee_global_tao + ("key2", {"bits": 0}), # fee_global_alpha + ("key3", {"bits": 1072693248}), + ], + [ + ( + "tick_low", + {"fees_out_tao": {"bits": 0}, "fees_out_alpha": {"bits": 0}}, + ), + ( + "tick_high", + {"fees_out_tao": {"bits": 0}, "fees_out_alpha": {"bits": 0}}, + ), + ], + ] + ) + mocker.patch.object(subtensor.substrate, "query_multi", new=mock_query_multi) + # Call result = subtensor.get_liquidity_list(wallet=fake_wallet, netuid=netuid) # Asserts - subtensor.determine_block_hash.assert_called_once_with(None) + assert subtensor.determine_block_hash.call_count == 1 assert subtensor_module.price_to_tick.call_count == 1 assert subtensor_module.calculate_fees.call_count == len(fake_positions) - mocked_query_map.assert_called_once_with( module="Swap", name="Positions", block=None, params=[netuid, fake_wallet.coldkeypub.ss58_address], ) + assert mock_query_multi.call_count == 2 # one for fees, one for ticks assert len(result) == len(fake_positions) - assert all([isinstance(p, subtensor_module.LiquidityPosition) for p in result]) + assert all(isinstance(p, subtensor_module.LiquidityPosition) for p in result) def test_add_liquidity(subtensor, fake_wallet, mocker): diff --git a/tests/unit_tests/utils/test_weight_utils.py b/tests/unit_tests/utils/test_weight_utils.py index e49a814e00..c378173bfa 100644 --- a/tests/unit_tests/utils/test_weight_utils.py +++ b/tests/unit_tests/utils/test_weight_utils.py @@ -1,12 +1,10 @@ import logging import numpy as np -from hypothesis import settings import bittensor.utils.weight_utils as weight_utils import pytest from bittensor.utils import torch -from bittensor.core.settings import version_as_int def test_convert_weight_and_uids(): From aa1cf1132368d27dcca30cd5448a376a8da0b87a Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Jul 2025 22:28:42 +0200 Subject: [PATCH 37/68] Transfers. --- bittensor/core/async_subtensor.py | 7 ++-- bittensor/core/extrinsics/asyncex/transfer.py | 38 ++++++++++++++----- bittensor/core/extrinsics/transfer.py | 38 ++++++++++++++----- bittensor/core/subtensor.py | 5 ++- 4 files changed, 64 insertions(+), 24 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index c78f28f165..caeb837a2a 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -5455,7 +5455,7 @@ async def transfer( self, wallet: "Wallet", dest: str, - amount: Balance, + amount: Optional[Balance], transfer_all: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -5468,7 +5468,7 @@ async def transfer( Arguments: wallet: Source wallet for the transfer. dest: Destination address for the transfer. - amount: Number of tokens to 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`. @@ -5479,7 +5479,8 @@ async def transfer( Returns: `True` if the transferring was successful, otherwise `False`. """ - amount = check_and_convert_to_balance(amount) + if amount is not None: + amount = check_and_convert_to_balance(amount) return await transfer_extrinsic( subtensor=self, wallet=wallet, diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index e98e234307..e78c784678 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -19,7 +19,8 @@ async def _do_transfer( subtensor: "AsyncSubtensor", wallet: "Wallet", destination: str, - amount: "Balance", + amount: Optional[Balance], + keep_alive: bool = True, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, period: Optional[int] = None, @@ -32,6 +33,7 @@ async def _do_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. + keep_alive (bool): If `True`, will keep the existential deposit in the account. 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 @@ -43,10 +45,24 @@ async def _do_transfer( Returns: success, block hash, formatted error message """ + 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["amount"] = amount.rao + if keep_alive: + call_function = "transfer_keep_alive" + else: + call_function = "transfer_allow_death" + call = await subtensor.substrate.compose_call( call_module="Balances", - call_function="transfer_keep_alive", - call_params={"dest": destination, "value": amount.rao}, + call_function=call_function, + call_params=call_params, ) success, message = await subtensor.sign_and_send_extrinsic( @@ -73,7 +89,7 @@ async def transfer_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", dest: str, - amount: "Balance", + amount: Optional[Balance], transfer_all: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -86,7 +102,8 @@ async def transfer_extrinsic( 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. - amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance. + 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. @@ -102,6 +119,11 @@ async def transfer_extrinsic( 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 + # Validate destination address. if not is_valid_bittensor_address_or_public_key(destination): logging.error( @@ -137,12 +159,10 @@ async def transfer_extrinsic( # Check if we have enough balance. if transfer_all is True: - amount = account_balance - fee - existential_deposit - if amount < Balance(0): + if (account_balance - fee) < existential_deposit: logging.error("Not enough balance to transfer") return False - - if account_balance < (amount + fee + existential_deposit): + elif account_balance < (amount + fee + existential_deposit): 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]") diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index 370efaed36..1a0c28a76d 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -18,7 +18,8 @@ def _do_transfer( subtensor: "Subtensor", wallet: "Wallet", destination: str, - amount: Balance, + amount: Optional[Balance], + keep_alive: bool = True, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, period: Optional[int] = None, @@ -30,7 +31,8 @@ def _do_transfer( 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. + amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance. `None` if transferring all. + keep_alive (bool): If `True`, will keep the existential deposit in the account. 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 @@ -42,10 +44,24 @@ def _do_transfer( Returns: success, block hash, formatted error message """ + 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["amount"] = amount.rao + if keep_alive: + call_function = "transfer_keep_alive" + else: + call_function = "transfer_allow_death" + call = subtensor.substrate.compose_call( call_module="Balances", - call_function="transfer_keep_alive", - call_params={"dest": destination, "value": amount.rao}, + call_function=call_function, + call_params=call_params, ) success, message = subtensor.sign_and_send_extrinsic( @@ -72,7 +88,7 @@ def transfer_extrinsic( subtensor: "Subtensor", wallet: "Wallet", dest: str, - amount: Balance, + amount: Optional[Balance], transfer_all: bool = False, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, @@ -85,7 +101,7 @@ def transfer_extrinsic( 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. - amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance. + 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. @@ -100,6 +116,10 @@ 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. """ + if amount is None and not transfer_all: + logging.error("If not transferring all, `amount` must be specified.") + return False + # Validate destination address. if not is_valid_bittensor_address_or_public_key(dest): logging.error( @@ -131,12 +151,10 @@ def transfer_extrinsic( # Check if we have enough balance. if transfer_all is True: - amount = account_balance - fee - existential_deposit - if amount < Balance(0): + if (account_balance - fee) < existential_deposit: logging.error("Not enough balance to transfer") return False - - if account_balance < (amount + fee + existential_deposit): + elif account_balance < (amount + fee + existential_deposit): 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]") diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index b28c49533f..10f0963882 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -4266,7 +4266,7 @@ def transfer( self, wallet: "Wallet", dest: str, - amount: Balance, + amount: Optional[Balance], wait_for_inclusion: bool = True, wait_for_finalization: bool = False, transfer_all: bool = False, @@ -4292,7 +4292,8 @@ def transfer( Returns: `True` if the transferring was successful, otherwise `False`. """ - amount = check_and_convert_to_balance(amount) + if amount is not None: + amount = check_and_convert_to_balance(amount) return transfer_extrinsic( subtensor=self, wallet=wallet, From c27082e0fe1698d32c33583c412f16a44f829f6f Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Jul 2025 22:31:26 +0200 Subject: [PATCH 38/68] Missed kwarg --- bittensor/core/extrinsics/asyncex/transfer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index e78c784678..c5fd8271ce 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -175,6 +175,7 @@ async def transfer_extrinsic( wallet=wallet, destination=destination, amount=amount, + keep_alive=keep_alive, wait_for_finalization=wait_for_finalization, wait_for_inclusion=wait_for_inclusion, period=period, From c60c4861ee45d74d657caceea69059df73777d18 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Jul 2025 22:32:07 +0200 Subject: [PATCH 39/68] Missed kwarg --- bittensor/core/extrinsics/transfer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index 1a0c28a76d..25bf1b554b 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -167,6 +167,7 @@ def transfer_extrinsic( wallet=wallet, destination=dest, amount=amount, + keep_alive=keep_alive, wait_for_finalization=wait_for_finalization, wait_for_inclusion=wait_for_inclusion, period=period, From e6e987f184f7d2c4e81c3523759e678f6484e499 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Jul 2025 22:32:59 +0200 Subject: [PATCH 40/68] Oepsie --- bittensor/core/extrinsics/transfer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index 25bf1b554b..c0f335d95b 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -52,7 +52,7 @@ def _do_transfer( else: call_params["keep_alive"] = False else: - call_params["amount"] = amount.rao + call_params["value"] = amount.rao if keep_alive: call_function = "transfer_keep_alive" else: From c81c0304c3b39ba87171e92bd2f09aa95463fef9 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 24 Jul 2025 22:46:35 +0200 Subject: [PATCH 41/68] Tests --- bittensor/core/extrinsics/asyncex/transfer.py | 2 +- .../extrinsics/asyncex/test_transfer.py | 33 +++++++++++++--- tests/unit_tests/extrinsics/test_transfer.py | 39 ++++++++++++++++--- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index c5fd8271ce..bae1184811 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -53,7 +53,7 @@ async def _do_transfer( else: call_params["keep_alive"] = False else: - call_params["amount"] = amount.rao + call_params["value"] = amount.rao if keep_alive: call_function = "transfer_keep_alive" else: diff --git a/tests/unit_tests/extrinsics/asyncex/test_transfer.py b/tests/unit_tests/extrinsics/asyncex/test_transfer.py index 95c5249b62..299c6df446 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_transfer.py +++ b/tests/unit_tests/extrinsics/asyncex/test_transfer.py @@ -3,12 +3,32 @@ 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): +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 = "destination_address" - fake_amount = mocker.Mock(autospec=Balance, rao=1000) + fake_destination = "SS58PUBLICKEY" fake_block_hash = "fake_block_hash" mocker.patch.object(subtensor.substrate, "compose_call") @@ -24,7 +44,8 @@ async def test_do_transfer_success(subtensor, fake_wallet, mocker): subtensor=subtensor, wallet=fake_wallet, destination=fake_destination, - amount=fake_amount, + amount=amount, + keep_alive=keep_alive, wait_for_inclusion=True, wait_for_finalization=True, ) @@ -32,8 +53,8 @@ async def test_do_transfer_success(subtensor, fake_wallet, mocker): # 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}, + call_function=call_function, + call_params=call_params, ) subtensor.sign_and_send_extrinsic.assert_awaited_once_with( diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index 081a56ffae..458bc9bd46 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -1,12 +1,34 @@ from bittensor.core.extrinsics.transfer import _do_transfer from bittensor.utils.balance import Balance - -def test_do_transfer_is_success_true(subtensor, fake_wallet, mocker): +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_transfer_balance = Balance(1) fake_wait_for_inclusion = True fake_wait_for_finalization = True @@ -18,7 +40,8 @@ def test_do_transfer_is_success_true(subtensor, fake_wallet, mocker): subtensor, fake_wallet, fake_dest, - fake_transfer_balance, + amount, + keep_alive, fake_wait_for_inclusion, fake_wait_for_finalization, ) @@ -26,8 +49,8 @@ def test_do_transfer_is_success_true(subtensor, fake_wallet, mocker): # 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}, + call_function=call_function, + call_params=call_params, ) subtensor.sign_and_send_extrinsic.assert_called_once_with( call=subtensor.substrate.compose_call.return_value, @@ -45,6 +68,7 @@ def test_do_transfer_is_success_false(subtensor, fake_wallet, mocker): # Prep fake_dest = "SS58PUBLICKEY" fake_transfer_balance = Balance(1) + keep_alive = True fake_wait_for_inclusion = True fake_wait_for_finalization = True @@ -57,6 +81,7 @@ def test_do_transfer_is_success_false(subtensor, fake_wallet, mocker): fake_wallet, fake_dest, fake_transfer_balance, + keep_alive, fake_wait_for_inclusion, fake_wait_for_finalization, ) @@ -83,6 +108,7 @@ def test_do_transfer_no_waits(subtensor, fake_wallet, mocker): # Prep fake_dest = "SS58PUBLICKEY" fake_transfer_balance = Balance(1) + keep_alive = True fake_wait_for_inclusion = False fake_wait_for_finalization = False @@ -96,6 +122,7 @@ def test_do_transfer_no_waits(subtensor, fake_wallet, mocker): fake_wallet, fake_dest, fake_transfer_balance, + keep_alive, fake_wait_for_inclusion, fake_wait_for_finalization, ) From b3eba69018b3aa7d2b37da2d71865e8e767dbcda Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Jul 2025 14:41:37 +0200 Subject: [PATCH 42/68] Updated arg order --- bittensor/core/extrinsics/asyncex/transfer.py | 4 ++-- bittensor/core/extrinsics/transfer.py | 4 ++-- tests/unit_tests/extrinsics/test_transfer.py | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index bae1184811..69f23c3675 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -20,10 +20,10 @@ async def _do_transfer( wallet: "Wallet", destination: str, amount: Optional[Balance], - keep_alive: bool = True, 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. @@ -33,7 +33,6 @@ async def _do_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. - keep_alive (bool): If `True`, will keep the existential deposit in the account. 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 @@ -41,6 +40,7 @@ async def _do_transfer( 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 diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index c0f335d95b..6dd8e8ac33 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -19,10 +19,10 @@ def _do_transfer( wallet: "Wallet", destination: str, amount: Optional[Balance], - keep_alive: bool = True, 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. @@ -32,7 +32,6 @@ def _do_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. - keep_alive (bool): If `True`, will keep the existential deposit in the account. 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 @@ -40,6 +39,7 @@ def _do_transfer( 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 diff --git a/tests/unit_tests/extrinsics/test_transfer.py b/tests/unit_tests/extrinsics/test_transfer.py index 458bc9bd46..6aedaea601 100644 --- a/tests/unit_tests/extrinsics/test_transfer.py +++ b/tests/unit_tests/extrinsics/test_transfer.py @@ -41,9 +41,9 @@ def test_do_transfer_is_success_true( fake_wallet, fake_dest, amount, - keep_alive, fake_wait_for_inclusion, fake_wait_for_finalization, + keep_alive=keep_alive, ) # Asserts @@ -81,9 +81,9 @@ def test_do_transfer_is_success_false(subtensor, fake_wallet, mocker): fake_wallet, fake_dest, fake_transfer_balance, - keep_alive, fake_wait_for_inclusion, fake_wait_for_finalization, + keep_alive=keep_alive, ) # Asserts @@ -122,9 +122,9 @@ def test_do_transfer_no_waits(subtensor, fake_wallet, mocker): fake_wallet, fake_dest, fake_transfer_balance, - keep_alive, fake_wait_for_inclusion, fake_wait_for_finalization, + keep_alive=keep_alive, ) # Asserts From 0e93f2ab49441588bb1dc6c0053decf4f3d4e47f Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Jul 2025 15:31:05 +0200 Subject: [PATCH 43/68] Added e2e tests --- bittensor/core/async_subtensor.py | 23 ++++- bittensor/core/extrinsics/asyncex/transfer.py | 2 +- bittensor/core/extrinsics/transfer.py | 4 +- bittensor/core/subtensor.py | 29 +++++- tests/e2e_tests/test_transfer.py | 97 ++++++++++++++++++- 5 files changed, 144 insertions(+), 11 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index caeb837a2a..286d41230e 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3134,7 +3134,7 @@ async def get_total_subnets( return getattr(result, "value", None) async def get_transfer_fee( - self, wallet: "Wallet", dest: str, value: Balance + self, wallet: "Wallet", dest: str, value: Balance, keep_alive: bool = True ) -> Balance: """ Calculates the transaction fee for transferring tokens from a wallet to a specified destination address. This @@ -3146,6 +3146,8 @@ async def get_transfer_fee( 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 (int) 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 @@ -3155,12 +3157,25 @@ 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. """ - value = check_and_convert_to_balance(value) + call_params = {"dest": dest} + if value is None: + call_function = "transfer_all" + if keep_alive: + call_params["keep_alive"] = True + else: + call_params["keep_alive"] = False + else: + value = check_and_convert_to_balance(value) + call_params["value"] = value.rao + if keep_alive: + call_function = "transfer_keep_alive" + else: + call_function = "transfer_allow_death" call = await self.substrate.compose_call( call_module="Balances", - call_function="transfer_keep_alive", - call_params={"dest": dest, "value": value.rao}, + call_function=call_function, + call_params=call_params, ) try: diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index 69f23c3675..f5b0a06b41 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -150,7 +150,7 @@ async def transfer_extrinsic( ) fee = await subtensor.get_transfer_fee( - wallet=wallet, dest=destination, value=amount + wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive ) if not keep_alive: diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index 6dd8e8ac33..fec38b1ffc 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -147,7 +147,9 @@ def transfer_extrinsic( else: existential_deposit = subtensor.get_existential_deposit(block=block) - fee = subtensor.get_transfer_fee(wallet=wallet, dest=dest, value=amount) + fee = subtensor.get_transfer_fee( + wallet=wallet, dest=dest, value=amount, keep_alive=keep_alive + ) # Check if we have enough balance. if transfer_all is True: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 10f0963882..e628f057b2 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2243,7 +2243,13 @@ def get_total_subnets(self, block: Optional[int] = None) -> Optional[int]: ) return getattr(result, "value", None) - def get_transfer_fee(self, wallet: "Wallet", dest: str, value: Balance) -> Balance: + def get_transfer_fee( + self, + wallet: "Wallet", + dest: str, + value: Optional[Balance], + keep_alive: bool = True, + ) -> 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 @@ -2254,6 +2260,8 @@ def get_transfer_fee(self, wallet: "Wallet", dest: str, value: Balance) -> Balan 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. + 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 @@ -2263,11 +2271,24 @@ def get_transfer_fee(self, wallet: "Wallet", dest: str, value: Balance) -> Balan 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. """ - value = check_and_convert_to_balance(value) + call_params = {"dest": dest} + if value is None: + call_function = "transfer_all" + if keep_alive: + call_params["keep_alive"] = True + else: + call_params["keep_alive"] = False + else: + value = check_and_convert_to_balance(value) + call_params["value"] = value.rao + if keep_alive: + call_function = "transfer_keep_alive" + else: + call_function = "transfer_allow_death" call = self.substrate.compose_call( call_module="Balances", - call_function="transfer_keep_alive", - call_params={"dest": dest, "value": value.rao}, + call_function=call_function, + call_params=call_params, ) try: diff --git a/tests/e2e_tests/test_transfer.py b/tests/e2e_tests/test_transfer.py index 7a0728de72..0663b540b2 100644 --- a/tests/e2e_tests/test_transfer.py +++ b/tests/e2e_tests/test_transfer.py @@ -1,10 +1,18 @@ +import typing + +from bittensor_wallet import Wallet +import pytest + from bittensor.utils.balance import Balance from bittensor import logging +if typing.TYPE_CHECKING: + from bittensor.core.subtensor_api import SubtensorApi + logging.set_trace() -def test_transfer(subtensor, alice_wallet): +def test_transfer(subtensor: "SubtensorApi", alice_wallet): """ Test the transfer mechanism on the chain @@ -47,3 +55,90 @@ def test_transfer(subtensor, alice_wallet): ) print("✅ Passed test_transfer") + + +def test_transfer_all(subtensor: "Subtensor", alice_wallet): + # 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") + 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 subtensor.transfer( + alice_wallet, + dest=dummy_account_1.coldkeypub.ss58_address, + amount=Balance.from_tao(2.0), + wait_for_finalization=True, + wait_for_inclusion=True, + ) + # Account details before transfer + existential_deposit = subtensor.get_existential_deposit() + assert subtensor.transfer( + wallet=dummy_account_1, + dest=dummy_account_2.coldkeypub.ss58_address, + amount=None, + transfer_all=True, + wait_for_finalization=True, + wait_for_inclusion=True, + keep_alive=True, + ) + balance_after = subtensor.get_balance(dummy_account_1.coldkeypub.ss58_address) + assert balance_after == existential_deposit + assert subtensor.transfer( + wallet=dummy_account_2, + dest=alice_wallet.coldkeypub.ss58_address, + amount=None, + transfer_all=True, + wait_for_inclusion=True, + wait_for_finalization=True, + keep_alive=False, + ) + balance_after = subtensor.get_balance(dummy_account_2.coldkeypub.ss58_address) + assert balance_after == Balance(0) + + +@pytest.mark.asyncio +async def test_async_transfer(async_subtensor: "SubtensorApi", alice_wallet): + # create two dummy accounts we can drain + 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( + alice_wallet, + dest=dummy_account_1.coldkeypub.ss58_address, + amount=Balance.from_tao(2.0), + wait_for_finalization=True, + wait_for_inclusion=True, + ) + # Account details before transfer + existential_deposit = await async_subtensor.get_existential_deposit() + assert await async_subtensor.transfer( + wallet=dummy_account_1, + dest=dummy_account_2.coldkeypub.ss58_address, + amount=None, + transfer_all=True, + wait_for_finalization=True, + wait_for_inclusion=True, + keep_alive=True, + ) + balance_after = await async_subtensor.get_balance( + dummy_account_1.coldkeypub.ss58_address + ) + assert balance_after == existential_deposit + assert await async_subtensor.transfer( + wallet=dummy_account_2, + dest=alice_wallet.coldkeypub.ss58_address, + amount=None, + transfer_all=True, + wait_for_inclusion=True, + wait_for_finalization=True, + keep_alive=False, + ) + balance_after = await async_subtensor.get_balance( + dummy_account_2.coldkeypub.ss58_address + ) + assert balance_after == Balance(0) From f404ea5780321fd5bfed637dd6ace0dbdaae441c Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Jul 2025 15:36:15 +0200 Subject: [PATCH 44/68] Mypy --- 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 286d41230e..474ca1ac10 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -3157,7 +3157,7 @@ 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. """ - call_params = {"dest": dest} + call_params: dict[str, Union[int, str, bool]] = {"dest": dest} if value is None: call_function = "transfer_all" if keep_alive: diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e628f057b2..e1a4d8b666 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -2271,7 +2271,7 @@ 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. """ - call_params = {"dest": dest} + call_params: dict[str, Union[int, str, bool]] = {"dest": dest} if value is None: call_function = "transfer_all" if keep_alive: From 10ab0d5c8eb8395e22ed371e4dcd5fa0a2fc19a9 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 25 Jul 2025 18:23:41 +0200 Subject: [PATCH 45/68] Helper function --- bittensor/core/async_subtensor.py | 17 +++------- bittensor/core/extrinsics/asyncex/transfer.py | 15 ++------- bittensor/core/extrinsics/transfer.py | 15 ++------- bittensor/core/subtensor.py | 18 +++-------- bittensor/utils/__init__.py | 32 +++++++++++++++++++ 5 files changed, 45 insertions(+), 52 deletions(-) diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index 474ca1ac10..98542c89a6 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -98,6 +98,7 @@ torch, u16_normalized_float, u64_normalized_float, + get_transfer_fn_params, ) from bittensor.core.extrinsics.asyncex.liquidity import ( add_liquidity_extrinsic, @@ -3157,20 +3158,10 @@ 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. """ - call_params: dict[str, Union[int, str, bool]] = {"dest": dest} - if value is None: - call_function = "transfer_all" - if keep_alive: - call_params["keep_alive"] = True - else: - call_params["keep_alive"] = False - else: + if value is not None: value = check_and_convert_to_balance(value) - call_params["value"] = value.rao - if keep_alive: - call_function = "transfer_keep_alive" - else: - call_function = "transfer_allow_death" + call_params: dict[str, Union[int, str, bool]] + call_function, call_params = get_transfer_fn_params(value, dest, keep_alive) call = await self.substrate.compose_call( call_module="Balances", diff --git a/bittensor/core/extrinsics/asyncex/transfer.py b/bittensor/core/extrinsics/asyncex/transfer.py index f5b0a06b41..6b0332cd73 100644 --- a/bittensor/core/extrinsics/asyncex/transfer.py +++ b/bittensor/core/extrinsics/asyncex/transfer.py @@ -6,6 +6,7 @@ get_explorer_url_for_network, 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 @@ -45,19 +46,7 @@ async def _do_transfer( Returns: success, block hash, formatted error message """ - 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" + call_function, call_params = get_transfer_fn_params(amount, destination, keep_alive) call = await subtensor.substrate.compose_call( call_module="Balances", diff --git a/bittensor/core/extrinsics/transfer.py b/bittensor/core/extrinsics/transfer.py index fec38b1ffc..9faecdcea4 100644 --- a/bittensor/core/extrinsics/transfer.py +++ b/bittensor/core/extrinsics/transfer.py @@ -5,6 +5,7 @@ is_valid_bittensor_address_or_public_key, unlock_key, get_explorer_url_for_network, + get_transfer_fn_params, ) from bittensor.utils.balance import Balance from bittensor.utils.btlogging import logging @@ -44,19 +45,7 @@ def _do_transfer( Returns: success, block hash, formatted error message """ - 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" + call_function, call_params = get_transfer_fn_params(amount, destination, keep_alive) call = subtensor.substrate.compose_call( call_module="Balances", diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e1a4d8b666..d1b95521d8 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -107,6 +107,7 @@ u16_normalized_float, u64_normalized_float, deprecated_message, + get_transfer_fn_params, ) from bittensor.utils.balance import ( Balance, @@ -2271,20 +2272,11 @@ 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. """ - call_params: dict[str, Union[int, str, bool]] = {"dest": dest} - if value is None: - call_function = "transfer_all" - if keep_alive: - call_params["keep_alive"] = True - else: - call_params["keep_alive"] = False - else: + if value is not None: value = check_and_convert_to_balance(value) - call_params["value"] = value.rao - if keep_alive: - call_function = "transfer_keep_alive" - else: - call_function = "transfer_allow_death" + call_params: dict[str, Union[int, str, bool]] + call_function, call_params = get_transfer_fn_params(value, dest, keep_alive) + call = self.substrate.compose_call( call_module="Balances", call_function=call_function, diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 26da8b5480..0d808477be 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -22,6 +22,7 @@ if TYPE_CHECKING: from bittensor_wallet import Wallet + from bittensor.utils.balance import Balance BT_DOCS_LINK = "https://docs.bittensor.com" @@ -445,3 +446,34 @@ def deprecated_message(message: str) -> None: """Shows a deprecation warning message with the given message.""" warnings.simplefilter("default", DeprecationWarning) 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 + + Args: + 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 From 3c15308dc92d02f6b5f6ee17bea93a8eac2abfa2 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 28 Jul 2025 15:14:33 -0700 Subject: [PATCH 46/68] remove ownership check --- bittensor/core/extrinsics/asyncex/move_stake.py | 16 ---------------- bittensor/core/extrinsics/move_stake.py | 16 ---------------- 2 files changed, 32 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/move_stake.py b/bittensor/core/extrinsics/asyncex/move_stake.py index 8cfd5a2604..8ed6a29141 100644 --- a/bittensor/core/extrinsics/asyncex/move_stake.py +++ b/bittensor/core/extrinsics/asyncex/move_stake.py @@ -71,14 +71,6 @@ async def transfer_stake_extrinsic( """ amount.set_unit(netuid=origin_netuid) - # Verify ownership - hotkey_owner = await subtensor.get_hotkey_owner(hotkey_ss58) - if hotkey_owner != wallet.coldkeypub.ss58_address: - logging.error( - f":cross_mark: [red]Failed[/red]: Hotkey: {hotkey_ss58} does not belong to the origin coldkey owner: " - f"{wallet.coldkeypub.ss58_address}" - ) - return False # Check sufficient stake stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( @@ -194,14 +186,6 @@ async def swap_stake_extrinsic( bool: True if the swap was successful, False otherwise. """ amount.set_unit(netuid=origin_netuid) - # Verify ownership - hotkey_owner = await subtensor.get_hotkey_owner(hotkey_ss58) - if hotkey_owner != wallet.coldkeypub.ss58_address: - logging.error( - f":cross_mark: [red]Failed[/red]: Hotkey: {hotkey_ss58} does not belong to the origin coldkey owner: " - f"{wallet.coldkeypub.ss58_address}" - ) - return False # Check sufficient stake stake_in_origin, stake_in_destination = await _get_stake_in_origin_and_dest( diff --git a/bittensor/core/extrinsics/move_stake.py b/bittensor/core/extrinsics/move_stake.py index 3e7e49eefd..d3874f1e68 100644 --- a/bittensor/core/extrinsics/move_stake.py +++ b/bittensor/core/extrinsics/move_stake.py @@ -68,14 +68,6 @@ def transfer_stake_extrinsic( """ amount.set_unit(netuid=origin_netuid) - # Verify ownership - hotkey_owner = subtensor.get_hotkey_owner(hotkey_ss58) - if hotkey_owner != wallet.coldkeypub.ss58_address: - logging.error( - f":cross_mark: [red]Failed[/red]: Hotkey: {hotkey_ss58} does not belong to the origin coldkey owner: " - f"{wallet.coldkeypub.ss58_address}" - ) - return False # Check sufficient stake stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( @@ -192,14 +184,6 @@ def swap_stake_extrinsic( """ amount.set_unit(netuid=origin_netuid) - # Verify ownership - hotkey_owner = subtensor.get_hotkey_owner(hotkey_ss58) - if hotkey_owner != wallet.coldkeypub.ss58_address: - logging.error( - f":cross_mark: [red]Failed[/red]: Hotkey: {hotkey_ss58} does not belong to the origin coldkey owner: " - f"{wallet.coldkeypub.ss58_address}" - ) - return False # Check sufficient stake stake_in_origin, stake_in_destination = _get_stake_in_origin_and_dest( From 9a8c06beec4f8bbffa02efe843715688f7e6fc50 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 28 Jul 2025 15:14:44 -0700 Subject: [PATCH 47/68] remove test --- tests/unit_tests/test_subtensor_extended.py | 25 --------------------- 1 file changed, 25 deletions(-) diff --git a/tests/unit_tests/test_subtensor_extended.py b/tests/unit_tests/test_subtensor_extended.py index 09fcdb6d97..ec6015174b 100644 --- a/tests/unit_tests/test_subtensor_extended.py +++ b/tests/unit_tests/test_subtensor_extended.py @@ -1484,31 +1484,6 @@ def test_transfer_stake_error( ) -def test_transfer_stake_non_owner(mock_substrate, subtensor, fake_wallet, mocker): - mocker.patch.object( - subtensor, - "get_hotkey_owner", - autospec=True, - return_value="owner2_ss58", - ) - - success = subtensor.transfer_stake( - fake_wallet, - "dest", - "hotkey_ss58", - origin_netuid=1, - destination_netuid=1, - amount=Balance(1), - ) - - assert success is False - - subtensor.get_hotkey_owner.assert_called_once_with( - "hotkey_ss58", - ) - mock_substrate.submit_extrinsic.assert_not_called() - - def test_transfer_stake_insufficient_stake( mock_substrate, subtensor, fake_wallet, mocker ): From 4d103b38ec19c26bc47e07591224835fae00b01e Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 30 Jul 2025 10:56:21 -0700 Subject: [PATCH 48/68] add hotkey in CRV3 --- bittensor/core/extrinsics/asyncex/commit_reveal.py | 1 + bittensor/core/extrinsics/commit_reveal.py | 1 + tests/e2e_tests/test_commit_reveal_v3.py | 12 ++++++------ .../extrinsics/asyncex/test_commit_reveal.py | 1 + tests/unit_tests/extrinsics/test_commit_reveal.py | 1 + 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/commit_reveal.py b/bittensor/core/extrinsics/asyncex/commit_reveal.py index 9c65ae8ea1..bcb23527bc 100644 --- a/bittensor/core/extrinsics/asyncex/commit_reveal.py +++ b/bittensor/core/extrinsics/asyncex/commit_reveal.py @@ -122,6 +122,7 @@ async def commit_reveal_v3_extrinsic( netuid=netuid, subnet_reveal_period_epochs=subnet_reveal_period_epochs, block_time=block_time, + hotkey=wallet.hotkey.ss58_address.encode(), ) success, message = await _do_commit_reveal_v3( diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py index c531986254..88ca116143 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -123,6 +123,7 @@ def commit_reveal_v3_extrinsic( netuid=netuid, subnet_reveal_period_epochs=subnet_reveal_period_epochs, block_time=block_time, + hotkey=wallet.hotkey.ss58_address.encode(), ) success, message = _do_commit_reveal_v3( diff --git a/tests/e2e_tests/test_commit_reveal_v3.py b/tests/e2e_tests/test_commit_reveal_v3.py index 1cc2970e3b..aa344efa95 100644 --- a/tests/e2e_tests/test_commit_reveal_v3.py +++ b/tests/e2e_tests/test_commit_reveal_v3.py @@ -128,8 +128,8 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle # Commit weights success, message = subtensor.set_weights( - alice_wallet, - netuid, + wallet=alice_wallet, + netuid=netuid, uids=weight_uids, weights=weight_vals, wait_for_inclusion=True, @@ -155,10 +155,10 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle f"After setting weights: Current block: {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" ) - # Ensure the expected drand round is well in the future - assert expected_reveal_round >= latest_drand_round + 1, ( - "Revealed drand pulse is older than the drand pulse right after setting weights" - ) + # # Ensure the expected drand round is well in the future + # assert expected_reveal_round >= latest_drand_round + 1, ( + # "Revealed drand pulse is older than the drand pulse right after setting weights" + # ) # Fetch current commits pending on the chain commits_on_chain = subtensor.get_current_weight_commit_info_v2(netuid=netuid) diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py index e105d07e71..a1612aaa04 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py @@ -220,6 +220,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( netuid=fake_netuid, current_block=mock_block.return_value["header"]["number"], block_time=12.0, + hotkey=fake_wallet.hotkey.ss58_address, ) mock_do_commit_reveal_v3.assert_awaited_once_with( subtensor=subtensor, diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index 49cc92e11f..e4010e4535 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -205,6 +205,7 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( netuid=fake_netuid, current_block=mock_block.return_value, block_time=12.0, + hotkey=fake_wallet.hotkey.ss58_address, ) mock_do_commit_reveal_v3.assert_called_once_with( subtensor=subtensor, From 7ee70b054140210fce836c6518c6f19180d7f51d Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 30 Jul 2025 10:59:16 -0700 Subject: [PATCH 49/68] fix unit tests --- tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py | 2 +- tests/unit_tests/extrinsics/test_commit_reveal.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py index a1612aaa04..263e4d8431 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py @@ -220,7 +220,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( netuid=fake_netuid, current_block=mock_block.return_value["header"]["number"], block_time=12.0, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey=fake_wallet.hotkey.ss58_address.encode(), ) mock_do_commit_reveal_v3.assert_awaited_once_with( subtensor=subtensor, diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index e4010e4535..9e7019db93 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -205,7 +205,7 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( netuid=fake_netuid, current_block=mock_block.return_value, block_time=12.0, - hotkey=fake_wallet.hotkey.ss58_address, + hotkey=fake_wallet.hotkey.ss58_address.encode(), ) mock_do_commit_reveal_v3.assert_called_once_with( subtensor=subtensor, From 3e2f0984df90fdc982486aa0005143567dcab5e8 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 4 Aug 2025 18:56:50 +0200 Subject: [PATCH 50/68] Missed await --- 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 8e5254535f..04b8202c48 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -276,7 +276,7 @@ async def async_wait_to_start_call( bittensor.logging.console.info( f"Waiting for [blue]{in_blocks}[/blue] blocks before [red]start call[/red]. " - f"Current block: [blue]{subtensor.block}[/blue]." + f"Current block: [blue]{await subtensor.block}[/blue]." ) # make sure we passed start_call limit From b028bc4c8d89069b424d3b986a6b0c658a69f476 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 4 Aug 2025 19:00:11 +0200 Subject: [PATCH 51/68] Change order to avoid making a call twice. --- tests/e2e_tests/utils/e2e_test_utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/e2e_tests/utils/e2e_test_utils.py b/tests/e2e_tests/utils/e2e_test_utils.py index 04b8202c48..d41c80982d 100644 --- a/tests/e2e_tests/utils/e2e_test_utils.py +++ b/tests/e2e_tests/utils/e2e_test_utils.py @@ -274,13 +274,14 @@ async def async_wait_to_start_call( if await subtensor.is_fast_blocks() is False: in_blocks = 5 + current_block = await subtensor.block + bittensor.logging.console.info( f"Waiting for [blue]{in_blocks}[/blue] blocks before [red]start call[/red]. " - f"Current block: [blue]{await subtensor.block}[/blue]." + f"Current block: [blue]{current_block}[/blue]." ) # make sure we passed start_call limit - current_block = await subtensor.block await subtensor.wait_for_block(current_block + in_blocks + 1) status, message = await subtensor.start_call( wallet=subnet_owner_wallet, From 63995b1e9256366f4af34251f4ff84922c291341 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Fri, 18 Jul 2025 19:45:48 +0200 Subject: [PATCH 52/68] chore: fix typo chore: fix typo Reapply "Merge pull request #2975 from opentensor/fix/roman/fix-after-devnet-ready-update-fee" This reverts commit a265ead8d48d03dc065b5957cc6c739387298de7. --- contrib/RELEASE_GUIDELINES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/RELEASE_GUIDELINES.md b/contrib/RELEASE_GUIDELINES.md index 74658624d8..f40003bd68 100644 --- a/contrib/RELEASE_GUIDELINES.md +++ b/contrib/RELEASE_GUIDELINES.md @@ -44,7 +44,7 @@ Since you need to use a secret when releasing bittensor (github personal access So you can have: ``` -GITHUB_ACCESS_TOKEN=$(pass github/your_personal_token_with_permisions) +GITHUB_ACCESS_TOKEN=$(pass github/your_personal_token_with_permissions) ``` or From 9cf66f8fb2cb9a5cb4bf0c4282da28484cc20c08 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Tue, 5 Aug 2025 23:53:40 +0200 Subject: [PATCH 53/68] Adds note for installing on macOS --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index e401bea455..de675927d2 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,14 @@ python3 -m pip install --upgrade bittensor ## Install on macOS and Linux +### Note for macOS users +The macOS preinstalled CPython installation is compiled with LibreSSL instead of OpenSSL. There are a number +of issues with LibreSSL, and as such is not fully supported by the libraries used by bittensor. Thus we highly recommend, if +you are using a Mac, to first install Python from [Homebrew](https://brew.sh/). Additionally, the Rust FFI bindings +[if installing from precompiled wheels (default)] require the Homebrew-installed OpenSSL pacakge. If you choose to use +the preinstalled Python version from macOS, things may not work completely. + +### Installation You can install Bittensor SDK on your local machine in either of the following ways. **Make sure you verify your installation after you install**: - [Install using a Bash command](#install-using-a-bash-command). - [Install using `pip3 install`](#install-using-pip3-install) From 58a8161039ba78cc095db381bdadb4cdbd465933 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Wed, 6 Aug 2025 00:04:32 +0200 Subject: [PATCH 54/68] Added bit about bittensor certifi --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index de675927d2..e74a7e1490 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,13 @@ You can install using any of the below options: 1. Install `cubit` first. See the [Install](https://github.com/opentensor/cubit?tab=readme-ov-file#install) section. **Only Python 3.9 and 3.10 versions are supported**. 2. Then install SDK with `pip install bittensor`. + +### Troubleshooting +#### SSL: CERTIFICATE_VERIFY_FAILED + +If you are encountering a `[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate` +error, use the command `python -m bittensor certifi` which will update your local SSL certificates. + --- ## Install on Windows From 701ebff7ceae813958b982865ed5e488476a1a37 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 7 Aug 2025 12:09:55 -0700 Subject: [PATCH 55/68] 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 ac63a7ac28..cfdee525b8 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.commit_reveal_enabled = subtensor.commit_reveal_enabled self.difficulty = subtensor.difficulty self.get_all_subnets_info = subtensor.get_all_subnets_info self.get_parents = subtensor.get_parents From c059e0b3237a02f6401ef1c751850f2de12b8b45 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 7 Aug 2025 12:15:35 -0700 Subject: [PATCH 56/68] update sync extrinsic --- bittensor/core/extrinsics/commit_reveal.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/bittensor/core/extrinsics/commit_reveal.py b/bittensor/core/extrinsics/commit_reveal.py index 88ca116143..7709619d7e 100644 --- a/bittensor/core/extrinsics/commit_reveal.py +++ b/bittensor/core/extrinsics/commit_reveal.py @@ -16,19 +16,20 @@ 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 phase 3 for a given netuid and commit, and optionally waits for extrinsic inclusion or - finalization. + Executes commit-reveal extrinsic for a given netuid, commit, reveal_round, and commit_reveal_version. Arguments: subtensor: An instance of the Subtensor class. @@ -36,6 +37,7 @@ def _do_commit_reveal_v3( 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 @@ -53,11 +55,12 @@ def _do_commit_reveal_v3( call = subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="commit_crv3_weights", + 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( @@ -70,6 +73,7 @@ def _do_commit_reveal_v3( ) +# TODO: rename this extrinsic to `commit_reveal_extrinsic` in SDK.v10 def commit_reveal_v3_extrinsic( subtensor: "Subtensor", wallet: "Wallet", @@ -123,7 +127,7 @@ def commit_reveal_v3_extrinsic( netuid=netuid, subnet_reveal_period_epochs=subnet_reveal_period_epochs, block_time=block_time, - hotkey=wallet.hotkey.ss58_address.encode(), + hotkey=wallet.hotkey.public_key, ) success, message = _do_commit_reveal_v3( @@ -137,7 +141,7 @@ def commit_reveal_v3_extrinsic( period=period, ) - if success is not True: + if not success: logging.error(message) return False, message From 1ab7491c8797d706273d1e3cb90677d14728807b Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 7 Aug 2025 12:15:40 -0700 Subject: [PATCH 57/68] update async extrinsic --- bittensor/core/extrinsics/asyncex/commit_reveal.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/bittensor/core/extrinsics/asyncex/commit_reveal.py b/bittensor/core/extrinsics/asyncex/commit_reveal.py index bcb23527bc..652ceafa50 100644 --- a/bittensor/core/extrinsics/asyncex/commit_reveal.py +++ b/bittensor/core/extrinsics/asyncex/commit_reveal.py @@ -16,12 +16,14 @@ 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, @@ -35,6 +37,7 @@ async def _do_commit_reveal_v3( 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 @@ -52,11 +55,12 @@ async def _do_commit_reveal_v3( call = await subtensor.substrate.compose_call( call_module="SubtensorModule", - call_function="commit_crv3_weights", + 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( @@ -69,6 +73,7 @@ async def _do_commit_reveal_v3( ) +# TODO: rename this extrinsic to `commit_reveal_extrinsic` in SDK.v10 async def commit_reveal_v3_extrinsic( subtensor: "AsyncSubtensor", wallet: "Wallet", @@ -122,7 +127,7 @@ async def commit_reveal_v3_extrinsic( netuid=netuid, subnet_reveal_period_epochs=subnet_reveal_period_epochs, block_time=block_time, - hotkey=wallet.hotkey.ss58_address.encode(), + hotkey=wallet.hotkey.public_key, ) success, message = await _do_commit_reveal_v3( @@ -136,7 +141,7 @@ async def commit_reveal_v3_extrinsic( period=period, ) - if success is not True: + if not success: logging.error(message) return False, message From d3ed2980a7c4a85b4272055332b63c5d7cebbd1b Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 7 Aug 2025 12:16:06 -0700 Subject: [PATCH 58/68] update e2e test --- tests/e2e_tests/test_commit_reveal_v3.py | 111 +++++++++++------------ 1 file changed, 52 insertions(+), 59 deletions(-) diff --git a/tests/e2e_tests/test_commit_reveal_v3.py b/tests/e2e_tests/test_commit_reveal_v3.py index aa344efa95..b729d0a874 100644 --- a/tests/e2e_tests/test_commit_reveal_v3.py +++ b/tests/e2e_tests/test_commit_reveal_v3.py @@ -15,7 +15,7 @@ # @pytest.mark.parametrize("local_chain", [True], indirect=True) @pytest.mark.asyncio -async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_wallet): +async def test_commit_and_reveal_weights_cr4(local_chain, subtensor, alice_wallet): """ Tests the commit/reveal weights mechanism (CR3) @@ -30,56 +30,64 @@ async def test_commit_and_reveal_weights_cr3(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`") + BLOCK_TIME = ( 0.25 if subtensor.is_fast_blocks() else 12.0 ) # 12 for non-fast-block, 0.25 for fast block - netuid = subtensor.get_total_subnets() # 2 - logging.console.info("Testing test_commit_and_reveal_weights") + logging.console.info(f"Using block time: {BLOCK_TIME}") + + netuid = subtensor.get_total_subnets() # 2 # Register root as Alice assert subtensor.register_subnet(alice_wallet), "Unable to register the subnet" # Verify subnet 2 created successfully - assert subtensor.subnet_exists(netuid), ( - f"Subnet {netuid} wasn't created successfully" - ) + assert subtensor.subnet_exists(netuid), f"SN #{netuid} wasn't created successfully" - logging.console.success(f"Subnet {netuid} is registered") + logging.console.success(f"SN #{netuid} is registered.") # Enable commit_reveal on the subnet assert sudo_set_hyperparameter_bool( - local_chain, - alice_wallet, - "sudo_set_commit_reveal_weights_enabled", - True, - netuid, - ), "Unable to enable commit reveal on the subnet" + substrate=local_chain, + wallet=alice_wallet, + call_function="sudo_set_commit_reveal_weights_enabled", + value=True, + netuid=netuid, + ), f"Unable to enable commit reveal on the SN #{netuid}" # Verify commit_reveal was enabled - assert subtensor.commit_reveal_enabled(netuid), "Failed to enable commit/reveal" - logging.console.info("Commit reveal enabled") + assert subtensor.subnets.commit_reveal_enabled(netuid), ( + "Failed to enable commit/reveal" + ) + logging.console.success("Commit reveal enabled") + + 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( - local_chain, - alice_wallet, + substrate=local_chain, + 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 error is None # Verify weights rate limit was changed assert ( subtensor.get_subnet_hyperparameters(netuid=netuid).weights_rate_limit == 0 ), "Failed to set weights_rate_limit" assert subtensor.weights_rate_limit(netuid=netuid) == 0 - logging.console.info("sudo_set_weights_set_rate_limit executed: set to 0") + logging.console.success("sudo_set_weights_set_rate_limit executed: set to 0") # Change the tempo of the subnet - tempo_set = 50 if subtensor.is_fast_blocks() else 10 + tempo_set = 100 if subtensor.is_fast_blocks() else 10 assert ( sudo_set_admin_utils( local_chain, @@ -89,9 +97,10 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle )[0] is True ) + tempo = subtensor.get_subnet_hyperparameters(netuid=netuid).tempo - assert tempo_set == tempo - logging.console.info(f"sudo_set_tempo executed: set to {tempo_set}") + assert tempo_set == tempo, "SN tempos has not been changed." + logging.console.success(f"SN #{netuid} tempo set to {tempo_set}") # Commit-reveal values - setting weights to self uids = np.array([0], dtype=np.int64) @@ -107,11 +116,8 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle f"Checking if window is too low with Current block: {current_block}, next tempo: {upcoming_tempo}" ) - # Wait for 2 tempos to pass as CR3 only reveals weights after 2 tempos + 1 - subtensor.wait_for_block(tempo_set * 2 + 1) - # Lower than this might mean weights will get revealed before we can check them - if upcoming_tempo - current_block < 3: + if upcoming_tempo - current_block < 6: await wait_interval( tempo, subtensor, @@ -119,7 +125,7 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle reporting_interval=1, ) current_block = subtensor.get_current_block() - expected_block = current_block + expected_commit_block = current_block + 1 latest_drand_round = subtensor.last_drand_round() upcoming_tempo = next_tempo(current_block, tempo) logging.console.info( @@ -139,27 +145,15 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle ) # Assert committing was a success - assert success is True + assert success is True, message assert bool(re.match(r"reveal_round:\d+", message)) # Parse expected reveal_round expected_reveal_round = int(message.split(":")[1]) - logging.console.info( + logging.console.success( f"Successfully set weights: uids {weight_uids}, weights {weight_vals}, reveal_round: {expected_reveal_round}" ) - current_block = subtensor.get_current_block() - latest_drand_round = subtensor.last_drand_round() - upcoming_tempo = next_tempo(current_block, tempo) - logging.console.info( - f"After setting weights: Current block: {current_block}, next tempo: {upcoming_tempo}, drand: {latest_drand_round}" - ) - - # # Ensure the expected drand round is well in the future - # assert expected_reveal_round >= latest_drand_round + 1, ( - # "Revealed drand pulse is older than the drand pulse right after setting weights" - # ) - # Fetch current commits pending on the chain commits_on_chain = subtensor.get_current_weight_commit_info_v2(netuid=netuid) address, commit_block, commit, reveal_round = commits_on_chain[0] @@ -167,19 +161,19 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle # Assert correct values are committed on the chain assert expected_reveal_round == reveal_round assert address == alice_wallet.hotkey.ss58_address - assert commit_block == expected_block + 1 + assert commit_block == expected_commit_block + 1 # Ensure no weights are available as of now assert subtensor.weights(netuid=netuid) == [] + logging.console.success("No weights are available before next epoch.") - # Wait for the next tempo so weights can be revealed - await wait_interval( - subtensor.get_subnet_hyperparameters(netuid=netuid).tempo, - subtensor, - netuid=netuid, - reporting_interval=1, - sleep=BLOCK_TIME, + expected_reveal_block = ( + subtensor.subnets.get_next_epoch_start_block(netuid) + 5 + ) # 5 is safety drand offset + logging.console.info( + f"Waiting for the next epoch to ensure weights are revealed: block {expected_reveal_block}" ) + subtensor.wait_for_block(expected_reveal_block) # Fetch the latest drand pulse latest_drand_round = subtensor.last_drand_round() @@ -187,25 +181,24 @@ async def test_commit_and_reveal_weights_cr3(local_chain, subtensor, alice_walle f"Latest drand round after waiting for tempo: {latest_drand_round}" ) - # wait until last_drand_round is the same or greeter than expected_reveal_round with sleep 3 second (as Drand round period) - while expected_reveal_round >= subtensor.last_drand_round(): - time.sleep(3) - # Fetch weights on the chain as they should be revealed now - revealed_weights_ = subtensor.weights(netuid=netuid) + subnet_weights = subtensor.weights(netuid=netuid) - print("revealed weights", revealed_weights_) - revealed_weights = revealed_weights_[0][1] + revealed_weights = subnet_weights[0][1] # Assert correct weights were revealed assert weight_uids[0] == revealed_weights[0][0] assert weight_vals[0] == revealed_weights[0][1] + logging.console.success( + f"Successfully revealed weights: uids {weight_uids}, weights {weight_vals}" + ) + # Now that the commit has been revealed, there shouldn't be any pending commits - assert subtensor.get_current_weight_commit_info_v2(netuid=netuid) == [] + assert subtensor.commitments.get_current_weight_commit_info_v2(netuid=netuid) == [] # Ensure the drand_round is always in the positive w.r.t expected when revealed - assert latest_drand_round - expected_reveal_round >= 0, ( + 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.info("✅ Passed commit_reveal v3") + logging.console.success("✅ Passed `test_commit_and_reveal_weights_cr4`") From e90c412e0b95fd367d34e08a62ac2cf593d11d7c Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 7 Aug 2025 12:22:15 -0700 Subject: [PATCH 59/68] fix unit tests --- tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py | 8 +++++--- tests/unit_tests/extrinsics/test_commit_reveal.py | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py index 263e4d8431..137a5d8d41 100644 --- a/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/asyncex/test_commit_reveal.py @@ -73,11 +73,12 @@ async def test_do_commit_reveal_v3_success(mocker, subtensor, fake_wallet): # Asserts mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="commit_crv3_weights", + 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( @@ -132,11 +133,12 @@ async def test_do_commit_reveal_v3_failure_due_to_error(mocker, subtensor, fake_ # Asserts mocked_compose_call.assert_awaited_once_with( call_module="SubtensorModule", - call_function="commit_crv3_weights", + 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( @@ -220,7 +222,7 @@ async def test_commit_reveal_v3_extrinsic_success_with_torch( netuid=fake_netuid, current_block=mock_block.return_value["header"]["number"], block_time=12.0, - hotkey=fake_wallet.hotkey.ss58_address.encode(), + hotkey=fake_wallet.hotkey.public_key, ) mock_do_commit_reveal_v3.assert_awaited_once_with( subtensor=subtensor, diff --git a/tests/unit_tests/extrinsics/test_commit_reveal.py b/tests/unit_tests/extrinsics/test_commit_reveal.py index 9e7019db93..42ae3e14d9 100644 --- a/tests/unit_tests/extrinsics/test_commit_reveal.py +++ b/tests/unit_tests/extrinsics/test_commit_reveal.py @@ -70,11 +70,12 @@ def test_do_commit_reveal_v3_success(mocker, subtensor, fake_wallet): # Asserts mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", - call_function="commit_crv3_weights", + 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( @@ -123,11 +124,12 @@ def test_do_commit_reveal_v3_failure_due_to_error(mocker, subtensor, fake_wallet # Asserts mocked_compose_call.assert_called_once_with( call_module="SubtensorModule", - call_function="commit_crv3_weights", + 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( @@ -205,7 +207,7 @@ def test_commit_reveal_v3_extrinsic_success_with_torch( netuid=fake_netuid, current_block=mock_block.return_value, block_time=12.0, - hotkey=fake_wallet.hotkey.ss58_address.encode(), + hotkey=fake_wallet.hotkey.public_key, ) mock_do_commit_reveal_v3.assert_called_once_with( subtensor=subtensor, From e30dbc57d918f8c8daa3f21dab0d2e3335f411d1 Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 7 Aug 2025 12:43:34 -0700 Subject: [PATCH 60/68] update requirements --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50aee3d831..42f680a99a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "pydantic>=2.3, <3", "scalecodec==1.2.11", "uvicorn", - "bittensor-drand>=0.5.0", + "bittensor-drand>=1.0.0", "bittensor-wallet>=3.1.0", "async-substrate-interface>=1.4.2" ] From fc47323631492bc005267b5208a88179c068d17e Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 7 Aug 2025 22:29:22 +0200 Subject: [PATCH 61/68] Bump bittensor-wallet version so people are less likely to install the 3.1.0 with its hotkeypub changes. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 50aee3d831..18972bc76a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,7 @@ dependencies = [ "scalecodec==1.2.11", "uvicorn", "bittensor-drand>=0.5.0", - "bittensor-wallet>=3.1.0", + "bittensor-wallet>=4.0.0,<5.0", "async-substrate-interface>=1.4.2" ] From 198d5ab2622900ed354683dcadbd9e32be315d78 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Thu, 7 Aug 2025 23:48:08 +0200 Subject: [PATCH 62/68] Improves the error formatter for instances where the docs are a string (such as is the case with BadOrigin) while retaining the list join for non-strings. --- bittensor/utils/__init__.py | 4 +++- tests/unit_tests/extrinsics/test__init__.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 0d808477be..57d528f76f 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -255,7 +255,9 @@ def format_error_message(error_message: Union[dict, Exception]) -> str: err_type = error_message.get("type", err_type) err_name = error_message.get("name", err_name) err_docs = error_message.get("docs", [err_description]) - err_description = " ".join(err_docs) + err_description = ( + err_docs if isinstance(err_docs, str) else " ".join(err_docs) + ) err_description += ( f" | Please consult {BT_DOCS_LINK}/errors/subtensor#{err_name.lower()}" ) diff --git a/tests/unit_tests/extrinsics/test__init__.py b/tests/unit_tests/extrinsics/test__init__.py index ba17c001be..e0273d9a83 100644 --- a/tests/unit_tests/extrinsics/test__init__.py +++ b/tests/unit_tests/extrinsics/test__init__.py @@ -102,3 +102,22 @@ def test_format_error_message_with_custom_error_message_without_index(): == f"Subtensor returned `SubstrateRequestException({fake_custom_error['message']})` error. This means: " f"`{fake_custom_error['data']}`." ) + + +def test_format_error_with_string_docs(): + fake_error_message = { + "type": "SomeType", + "name": "SomeErrorName", + "docs": "Some error description.", + } + + # Call + result = format_error_message(fake_error_message) + + # Assertions + + assert ( + result == "Subtensor returned `SomeErrorName(SomeType)` error. " + "This means: `Some error description." + f" | Please consult {BT_DOCS_LINK}/errors/subtensor#someerrorname`." + ) From 4a14322d3ee88f1baea7d544bacfd7a02885cb3e Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 8 Aug 2025 15:32:11 -0700 Subject: [PATCH 63/68] `LoggingMachine` initialization updated to explicitly call both parent constructors --- bittensor/utils/btlogging/loggingmachine.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bittensor/utils/btlogging/loggingmachine.py b/bittensor/utils/btlogging/loggingmachine.py index a8803e70fd..32ea7315e0 100644 --- a/bittensor/utils/btlogging/loggingmachine.py +++ b/bittensor/utils/btlogging/loggingmachine.py @@ -137,7 +137,8 @@ class LoggingMachine(StateMachine, Logger): def __init__(self, config: "Config", name: str = BITTENSOR_LOGGER_NAME): # basics - super(LoggingMachine, self).__init__() + StateMachine.__init__(self) + stdlogging.Logger.__init__(self, name) self._queue = mp.Queue(-1) self._primary_loggers = {name} self._config = self._extract_logging_config(config) From 639cb66aacecd1277c7cae2806821126913f0fb3 Mon Sep 17 00:00:00 2001 From: Roman Date: Fri, 8 Aug 2025 15:39:06 -0700 Subject: [PATCH 64/68] fix uv version --- .github/workflows/flake8-and-mypy.yml | 3 ++- .github/workflows/unit-and-integration-tests.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/flake8-and-mypy.yml b/.github/workflows/flake8-and-mypy.yml index 1fbe094728..fe9d10159f 100644 --- a/.github/workflows/flake8-and-mypy.yml +++ b/.github/workflows/flake8-and-mypy.yml @@ -42,7 +42,8 @@ jobs: python -m venv venv source venv/bin/activate python -m pip install --upgrade pip - python -m pip install uv + # uv==0.8.6 is the last version that supports python 3.9 + python -m pip install uv==0.8.6 python -m uv sync --extra dev --active - name: Flake8 diff --git a/.github/workflows/unit-and-integration-tests.yml b/.github/workflows/unit-and-integration-tests.yml index 7bc70ae030..492b1277db 100644 --- a/.github/workflows/unit-and-integration-tests.yml +++ b/.github/workflows/unit-and-integration-tests.yml @@ -39,7 +39,8 @@ jobs: python -m venv venv source venv/bin/activate python -m pip install --upgrade pip - python -m pip install uv + # uv==0.8.6 is the last version that supports python 3.9 + python -m pip install uv==0.8.6 python -m uv sync --extra dev --active - name: Unit tests From ec652767c7541960a855a372e718f159b39dcb37 Mon Sep 17 00:00:00 2001 From: Maciej Kula Date: Sun, 10 Aug 2025 05:13:35 +0200 Subject: [PATCH 65/68] Fixed moving_price conversion from I96F32 to float --- bittensor/core/chain_data/dynamic_info.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bittensor/core/chain_data/dynamic_info.py b/bittensor/core/chain_data/dynamic_info.py index 504185832f..416dfd29c4 100644 --- a/bittensor/core/chain_data/dynamic_info.py +++ b/bittensor/core/chain_data/dynamic_info.py @@ -126,7 +126,7 @@ def _from_dict(cls, decoded: dict) -> "DynamicInfo": network_registered_at=int(decoded["network_registered_at"]), subnet_identity=subnet_identity, subnet_volume=subnet_volume, - moving_price=fixed_to_float(decoded["moving_price"]), + moving_price=fixed_to_float(decoded["moving_price"], 32), ) def tao_to_alpha(self, tao: Union[Balance, float, int]) -> Balance: From 81af4fd5508634c6f19bb468a79572512f8727f2 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 11 Aug 2025 20:06:15 +0200 Subject: [PATCH 66/68] uv is broken for 0.8 < python 3.10 until 0.8.8 (see https://github.com/astral-sh/uv/blob/main/CHANGELOG.md#088) --- .github/workflows/flake8-and-mypy.yml | 4 ++-- .github/workflows/unit-and-integration-tests.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/flake8-and-mypy.yml b/.github/workflows/flake8-and-mypy.yml index fe9d10159f..2e259bdd05 100644 --- a/.github/workflows/flake8-and-mypy.yml +++ b/.github/workflows/flake8-and-mypy.yml @@ -42,8 +42,8 @@ jobs: python -m venv venv source venv/bin/activate python -m pip install --upgrade pip - # uv==0.8.6 is the last version that supports python 3.9 - python -m pip install uv==0.8.6 + # needed for Python 3.9 compatibility + python -m pip install uv>=0.8.8 python -m uv sync --extra dev --active - name: Flake8 diff --git a/.github/workflows/unit-and-integration-tests.yml b/.github/workflows/unit-and-integration-tests.yml index 492b1277db..ec1fc6ded0 100644 --- a/.github/workflows/unit-and-integration-tests.yml +++ b/.github/workflows/unit-and-integration-tests.yml @@ -39,8 +39,8 @@ jobs: python -m venv venv source venv/bin/activate python -m pip install --upgrade pip - # uv==0.8.6 is the last version that supports python 3.9 - python -m pip install uv==0.8.6 + # needed for Python 3.9 compatibility + python -m pip install uv>=0.8.8 python -m uv sync --extra dev --active - name: Unit tests From 437a9b8d8273c37b0b6be68424ef691682dd24e3 Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 11 Aug 2025 20:53:13 +0200 Subject: [PATCH 67/68] Update changelog + version --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++++ pyproject.toml | 2 +- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acc5c17f16..1201b090a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## 9.9.0 /2025-08-11 + +## What's Changed +* Fix crv3 tests after devnet-ready get `CRV3WeightCommitsV2` by @basfroman in https://github.com/opentensor/bittensor/pull/2978 +* Add webhook for failed nightly tests by @basfroman in https://github.com/opentensor/bittensor/pull/2987 +* Fix liquidity test (non-fast-blocks node) by @basfroman in https://github.com/opentensor/bittensor/pull/2988 +* improve nightly logic by @basfroman in https://github.com/opentensor/bittensor/pull/2989 +* improve nightly 2 by @basfroman in https://github.com/opentensor/bittensor/pull/2990 +* Add `get_stake_weight` methods by @basfroman in https://github.com/opentensor/bittensor/pull/2985 +* Handles both exceptions for Swap pallet fetching by @thewhaleking in https://github.com/opentensor/bittensor/pull/2991 +* chore: fix typo by @socialsister in https://github.com/opentensor/bittensor/pull/2969 +* optimisations mostly related to liquidity_list by @thewhaleking in https://github.com/opentensor/bittensor/pull/2980 +* Transfers improvements by @thewhaleking in https://github.com/opentensor/bittensor/pull/2993 +* Remove ownership check in `transfer_stake_extrinsic` and `swap_stake_extrinsic` by @basfroman in https://github.com/opentensor/bittensor/pull/2996 +* Missed await by @thewhaleking in https://github.com/opentensor/bittensor/pull/3002 +* chore: fix typo by @lechpzn in https://github.com/opentensor/bittensor/pull/3001 +* Adds note for installing on macOS by @thewhaleking in https://github.com/opentensor/bittensor/pull/3004 +* Bump bittensor-wallet version by @thewhaleking in https://github.com/opentensor/bittensor/pull/3005 +* Format Error with string docs by @thewhaleking in https://github.com/opentensor/bittensor/pull/3006 +* `LoggingMachine` initialization updated to explicitly call both parent constructors by @basfroman in https://github.com/opentensor/bittensor/pull/3008 +* Fixed `moving_price` conversion from `I96F32` to float by @mcjkula in https://github.com/opentensor/bittensor/pull/3010 +* Add new CRv4 logic by @basfroman in https://github.com/opentensor/bittensor/pull/2999 +* UV Fix by @thewhaleking in https://github.com/opentensor/bittensor/pull/3011 + +## New Contributors +* @socialsister made their first contribution in https://github.com/opentensor/bittensor/pull/2969 +* @lechpzn made their first contribution in https://github.com/opentensor/bittensor/pull/3001 +* @mcjkula made their first contribution in https://github.com/opentensor/bittensor/pull/3010 + +**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.8.3...v9.9.0 + ## 9.8.3 /2025-07-18 * improve make file by @basfroman in https://github.com/opentensor/bittensor/pull/2965 * Move all workflows from `app.circleci.com` to `GH actions` by @basfroman in https://github.com/opentensor/bittensor/pull/2970 diff --git a/pyproject.toml b/pyproject.toml index af84305eb5..957b6ae3fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "bittensor" -version = "9.8.3" +version = "9.9.0" description = "Bittensor" readme = "README.md" authors = [ From 6843a1f87d729a40f73c45d65b2b4e222116ea0b Mon Sep 17 00:00:00 2001 From: Benjamin Himes Date: Mon, 11 Aug 2025 23:31:58 +0200 Subject: [PATCH 68/68] Major version locks drand --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 957b6ae3fa..71f245a935 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,7 @@ dependencies = [ "pydantic>=2.3, <3", "scalecodec==1.2.11", "uvicorn", - "bittensor-drand>=1.0.0", + "bittensor-drand>=1.0.0,<2.0.0", "bittensor-wallet>=4.0.0,<5.0", "async-substrate-interface>=1.4.2" ]