diff --git a/.github/workflows/e2e-subtensor-tests.yaml b/.github/workflows/e2e-subtensor-tests.yaml index 541b283992..cd967da879 100644 --- a/.github/workflows/e2e-subtensor-tests.yaml +++ b/.github/workflows/e2e-subtensor-tests.yaml @@ -52,7 +52,7 @@ jobs: max-parallel: 8 # Set the maximum number of parallel jobs matrix: rust-branch: - - nightly-2024-03-05 + - stable rust-target: - x86_64-unknown-linux-gnu os: diff --git a/CHANGELOG.md b/CHANGELOG.md index 70d29a1875..e1acd20cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,22 @@ # Changelog +## 9.0.2 /2025-02-24 + +## What's Changed +* CI: Upgrade rust compiler for E2E tests by @zyzniewski-reef in https://github.com/opentensor/bittensor/pull/2690 +* Break away cli reqs by @thewhaleking in https://github.com/opentensor/bittensor/pull/2692 +* Updates DelegateInfo chain data by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2683 +* Backmerge main to staging 901 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2689 +* fix: typos in documentation files by @zeevick10 in https://github.com/opentensor/bittensor/pull/2687 +* Removes tx limit in stake_multiple by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2694 + +## New Contributors +* @zeevick10 made their first contribution in https://github.com/opentensor/bittensor/pull/2687 + +**Full Changelog**: https://github.com/opentensor/bittensor/compare/v9.0.1...v9.0.2 + +## 9.0.1 /2025-02-20 + ## What's Changed * Release/9.0.0 by @ibraheem-opentensor in https://github.com/opentensor/bittensor/pull/2671 * fix e2e test by @roman-opentensor in https://github.com/opentensor/bittensor/pull/2673 diff --git a/VERSION b/VERSION index 6768f7e446..f202cd983c 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -9.0.1 \ No newline at end of file +9.0.2 \ No newline at end of file diff --git a/bittensor/core/chain_data/__init__.py b/bittensor/core/chain_data/__init__.py index cdf1eca55d..03b5fbe251 100644 --- a/bittensor/core/chain_data/__init__.py +++ b/bittensor/core/chain_data/__init__.py @@ -7,7 +7,7 @@ from .axon_info import AxonInfo from .chain_identity import ChainIdentity -from .delegate_info import DelegateInfo +from .delegate_info import DelegateInfo, DelegatedInfo from .delegate_info_lite import DelegateInfoLite from .dynamic_info import DynamicInfo from .ip_info import IPInfo @@ -36,6 +36,7 @@ AxonInfo, ChainIdentity, DelegateInfo, + DelegatedInfo, DelegateInfoLite, DynamicInfo, IPInfo, diff --git a/bittensor/core/chain_data/delegate_info.py b/bittensor/core/chain_data/delegate_info.py index 21311e29e0..c0a595bf39 100644 --- a/bittensor/core/chain_data/delegate_info.py +++ b/bittensor/core/chain_data/delegate_info.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, Optional +from typing import Optional from bittensor.core.chain_data.info_base import InfoBase from bittensor.core.chain_data.utils import decode_account_id @@ -8,27 +8,20 @@ @dataclass -class DelegateInfo(InfoBase): - """ - Dataclass for delegate information. For a lighter version of this class, see ``DelegateInfoLite``. +class DelegateInfoBase(InfoBase): + """Base class containing common delegate information fields. - Args: - hotkey_ss58 (str): Hotkey of the delegate for which the information is being fetched. - total_stake (int): Total stake of the delegate. - nominators (list[tuple[str, int]]): List of nominators of the delegate and their stake. + Attributes: + hotkey_ss58 (str): Hotkey of delegate. + owner_ss58 (str): Coldkey of owner. take (float): Take of the delegate as a percentage. - owner_ss58 (str): Coldkey of the owner. - registrations (list[int]): List of subnets that the delegate is registered on. validator_permits (list[int]): List of subnets that the delegate is allowed to validate on. - return_per_1000 (int): Return per 1000 TAO, for the delegate over a day. - total_daily_return (int): Total daily return of the delegate. + registrations (list[int]): List of subnets that the delegate is registered on. + return_per_1000 (Balance): Return per 1000 tao of the delegate over a day. + total_daily_return (Balance): Total daily return of the delegate. """ hotkey_ss58: str # Hotkey of delegate - total_stake: Balance # Total stake of the delegate - nominators: list[ - tuple[str, Balance] - ] # List of nominators of the delegate and their stake owner_ss58: str # Coldkey of owner take: float # Take of the delegate as a percentage validator_permits: list[ @@ -38,35 +31,87 @@ class DelegateInfo(InfoBase): return_per_1000: Balance # Return per 1000 tao of the delegate over a day total_daily_return: Balance # Total daily return of the delegate + +@dataclass +class DelegateInfo(DelegateInfoBase): + """ + Dataclass for delegate information. + + Additional Attributes: + total_stake (dict[int, Balance]): Total stake of the delegate mapped by netuid. + nominators (dict[str, dict[int, Balance]]): Mapping of nominator SS58 addresses to their stakes per subnet. + """ + + total_stake: dict[int, Balance] # Total stake of the delegate by netuid and stake + nominators: dict[ + str, dict[int, Balance] + ] # Mapping of nominator addresses to their stakes per subnet + @classmethod def _from_dict(cls, decoded: dict) -> Optional["DelegateInfo"]: - """Returns a DelegateInfo object from decoded chain data.""" - nominators = [ - (decode_account_id(x), Balance.from_rao(y)) - for x, y in decoded["nominators"] - ] - total_stake = sum((x[1] for x in nominators)) if nominators else Balance(0) - - return DelegateInfo( - hotkey_ss58=decode_account_id(decoded["delegate_ss58"]), + hotkey = decode_account_id(decoded.get("delegate_ss58")) + owner = decode_account_id(decoded.get("owner_ss58")) + + nominators = {} + total_stake_by_netuid = {} + + for raw_nominator, raw_stakes in decoded.get("nominators", []): + nominator_ss58 = decode_account_id(raw_nominator) + stakes = { + int(netuid): Balance.from_rao(stake_amt).set_unit(int(netuid)) + for (netuid, stake_amt) in raw_stakes + } + nominators[nominator_ss58] = stakes + + for netuid, stake in stakes.items(): + if netuid not in total_stake_by_netuid: + total_stake_by_netuid[netuid] = Balance(0).set_unit(netuid) + total_stake_by_netuid[netuid] += stake + + return cls( + hotkey_ss58=hotkey, + total_stake=total_stake_by_netuid, nominators=nominators, - owner_ss58=decode_account_id(decoded["owner_ss58"]), - registrations=decoded["registrations"], - return_per_1000=Balance.from_rao(decoded["return_per_1000"]), - take=u16_normalized_float(decoded["take"]), - total_daily_return=Balance.from_rao(decoded["total_daily_return"]), - total_stake=total_stake, - validator_permits=decoded["validator_permits"], + owner_ss58=owner, + take=u16_normalized_float(decoded.get("take")), + validator_permits=list(decoded.get("validator_permits", [])), + registrations=list(decoded.get("registrations", [])), + return_per_1000=Balance.from_rao(decoded.get("return_per_1000")), + total_daily_return=Balance.from_rao(decoded.get("total_daily_return")), ) + +@dataclass +class DelegatedInfo(DelegateInfoBase): + """ + Dataclass for delegated information. This class represents a delegate's information + specific to a particular subnet. + + Additional Attributes: + netuid (int): Network ID of the subnet. + stake (Balance): Stake amount for this specific delegation. + """ + + netuid: int + stake: Balance + @classmethod - def delegated_list_from_dicts( - cls, delegates: list[Any] - ) -> list[tuple["DelegateInfo", Balance]]: - return [ - ( - DelegateInfo.from_dict(delegate), - Balance.from_rao(balance), - ) - for delegate, balance in delegates - ] + def _from_dict( + cls, decoded: tuple[dict, tuple[int, int]] + ) -> Optional["DelegatedInfo"]: + delegate_info, (netuid, stake) = decoded + hotkey = decode_account_id(delegate_info.get("delegate_ss58")) + owner = decode_account_id(delegate_info.get("owner_ss58")) + return cls( + hotkey_ss58=hotkey, + owner_ss58=owner, + take=u16_normalized_float(delegate_info.get("take")), + validator_permits=list(delegate_info.get("validator_permits", [])), + registrations=list(delegate_info.get("registrations", [])), + return_per_1000=Balance.from_rao(delegate_info.get("return_per_1000")), + total_daily_return=Balance.from_rao( + delegate_info.get("total_daily_return") + ), + netuid=int(netuid), + stake=Balance.from_rao(int(stake)).set_unit(int(netuid)), + ) diff --git a/bittensor/core/extrinsics/staking.py b/bittensor/core/extrinsics/staking.py index a481978155..9872eee0f4 100644 --- a/bittensor/core/extrinsics/staking.py +++ b/bittensor/core/extrinsics/staking.py @@ -1,4 +1,3 @@ -import time from typing import Optional, TYPE_CHECKING, Sequence from bittensor.core.errors import StakeError, NotRegisteredError @@ -233,7 +232,7 @@ def add_stake_multiple_extrinsic( total_staking_rao = sum( [amount.rao if amount is not None else 0 for amount in new_amounts] ) - old_balance = inital_balance = subtensor.get_balance( + old_balance = initial_balance = subtensor.get_balance( wallet.coldkeypub.ss58_address, block=block ) if total_staking_rao == 0: @@ -298,20 +297,6 @@ def add_stake_multiple_extrinsic( if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. - if idx < len(hotkey_ss58s) - 1: - # Wait for tx rate limit. - tx_query = subtensor.substrate.query( - module="SubtensorModule", storage_function="TxRateLimit" - ) - tx_rate_limit_blocks: int = getattr(tx_query, "value", 0) - if tx_rate_limit_blocks > 0: - logging.error( - f":hourglass: [yellow]Waiting for tx rate limit: [white]{tx_rate_limit_blocks}[/white] " - f"blocks[/yellow]" - ) - # 12 seconds per block - time.sleep(tx_rate_limit_blocks * 12) - if not wait_for_finalization and not wait_for_inclusion: old_balance -= staking_balance successful_stakes += 1 @@ -365,7 +350,7 @@ def add_stake_multiple_extrinsic( ) new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) logging.info( - f"Balance: [blue]{inital_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" + f"Balance: [blue]{initial_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) return True diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py index f505f23f56..dcd5fcc8ca 100644 --- a/bittensor/core/settings.py +++ b/bittensor/core/settings.py @@ -1,4 +1,4 @@ -__version__ = "9.0.1" +__version__ = "9.0.2" import os import re diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index e2d9ffda19..7709eba43c 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -24,6 +24,7 @@ WeightCommitInfo, SubnetIdentity, SubnetInfo, + DelegatedInfo, decode_account_id, ) from bittensor.core.chain_data.utils import decode_metadata @@ -925,7 +926,7 @@ def get_delegated( if not result: return [] - return DelegateInfo.delegated_list_from_dicts(result) + return DelegatedInfo.list_from_dicts(result) def get_delegates(self, block: Optional[int] = None) -> list["DelegateInfo"]: """ diff --git a/requirements/cli.txt b/requirements/cli.txt new file mode 100644 index 0000000000..e395b2b9c1 --- /dev/null +++ b/requirements/cli.txt @@ -0,0 +1 @@ +bittensor-cli>=9.0.2 \ No newline at end of file diff --git a/requirements/prod.txt b/requirements/prod.txt index 1e82627dc4..9062893efe 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -22,6 +22,5 @@ scalecodec==1.2.11 uvicorn websockets>=14.1 bittensor-commit-reveal>=0.2.0 -bittensor-wallet>=3.0.3 -async-substrate-interface>=1.0.0 -bittensor-cli>=9.0.0 +bittensor-wallet>=3.0.4 +async-substrate-interface>=1.0.3 diff --git a/setup.py b/setup.py index b6a7c7b1e4..480a3f66bd 100644 --- a/setup.py +++ b/setup.py @@ -8,24 +8,25 @@ from setuptools import setup, find_packages -def read_requirements(path): - requirements = [] +def read_requirements(path_): + requirements_ = [] - with pathlib.Path(path).open() as requirements_txt: + with pathlib.Path(path_).open() as requirements_txt: for line in requirements_txt: if line.startswith("git+"): pkg_name = re.search(r"egg=([a-zA-Z0-9_-]+)", line.strip()).group(1) - requirements.append(pkg_name + " @ " + line.strip()) + requirements_.append(pkg_name + " @ " + line.strip()) else: - requirements.append(line.strip()) + requirements_.append(line.strip()) - return requirements + return requirements_ requirements = read_requirements("requirements/prod.txt") extra_requirements_dev = read_requirements("requirements/dev.txt") extra_requirements_cubit = read_requirements("requirements/cubit.txt") extra_requirements_torch = read_requirements("requirements/torch.txt") +extra_requirements_cli = read_requirements("requirements/cli.txt") here = path.abspath(path.dirname(__file__)) @@ -62,6 +63,7 @@ def read_requirements(path): extras_require={ "dev": extra_requirements_dev + extra_requirements_torch, "torch": extra_requirements_torch, + "cli": extra_requirements_cli, }, classifiers=[ "Development Status :: 3 - Alpha", diff --git a/tests/e2e_tests/test_commit_weights.py b/tests/e2e_tests/test_commit_weights.py index 7e02411ac3..61ac041f20 100644 --- a/tests/e2e_tests/test_commit_weights.py +++ b/tests/e2e_tests/test_commit_weights.py @@ -70,7 +70,7 @@ async def test_commit_and_reveal_weights_legacy(local_chain, subtensor, alice_wa ), "Failed to set weights_rate_limit" assert subtensor.weights_rate_limit(netuid=netuid) == 0 - # Increase subnet tempo so we have enought time to commit and reveal weights + # Increase subnet tempo so we have enough time to commit and reveal weights sudo_set_admin_utils( local_chain, alice_wallet,