diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index e7f6629a2e..1c0ea4f27b 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -89,7 +89,7 @@ from bittensor_wallet import Wallet from bittensor.core.axon import Axon from bittensor.utils import Certificate - from async_substrate_interface import QueryMapResult + from async_substrate_interface import AsyncQueryMapResult class AsyncSubtensor(SubtensorMixin): @@ -316,7 +316,7 @@ async def query_map( block_hash: Optional[str] = None, reuse_block: bool = False, params: Optional[list] = None, - ) -> "QueryMapResult": + ) -> "AsyncQueryMapResult": """ Queries map storage from any module on the Bittensor blockchain. This function retrieves data structures that represent key-value mappings, essential for accessing complex and structured data within the blockchain @@ -354,7 +354,7 @@ async def query_map_subtensor( block_hash: Optional[str] = None, reuse_block: bool = False, params: Optional[list] = None, - ) -> "QueryMapResult": + ) -> "AsyncQueryMapResult": """ Queries map storage from the Subtensor module on the Bittensor blockchain. This function is designed to retrieve a map-like data structure, which can include various neuron-specific details or network-wide attributes. @@ -1380,7 +1380,7 @@ async def get_all_metagraphs_info( Returns: MetagraphInfo dataclass """ - block_hash = await self.determine_block_hash(block, block_hash.reuse_block) + block_hash = await self.determine_block_hash(block, block_hash, reuse_block) if not block_hash and reuse_block: block_hash = self.substrate.last_block_hash query = await self.substrate.runtime_call( @@ -2019,7 +2019,7 @@ async def get_vote_data( network, particularly how proposals are received and acted upon by the governing body. """ block_hash = await self.determine_block_hash(block, block_hash, reuse_block) - vote_data = await self.substrate.query( + vote_data: dict[str, Any] = await self.substrate.query( module="Triumvirate", storage_function="Voting", params=[proposal_hash], @@ -2598,7 +2598,7 @@ async def subnet( params=[netuid], block_hash=block_hash, ) - subnet = DynamicInfo.from_vec_u8(bytes.fromhex(query.decode()[2:])) + subnet = DynamicInfo.from_vec_u8(hex_to_bytes(query.decode())) return subnet async def subnet_exists( diff --git a/bittensor/core/chain_data/dynamic_info.py b/bittensor/core/chain_data/dynamic_info.py index eee481d3fd..7f3ccb90de 100644 --- a/bittensor/core/chain_data/dynamic_info.py +++ b/bittensor/core/chain_data/dynamic_info.py @@ -43,7 +43,7 @@ class DynamicInfo: subnet_identity: Optional[SubnetIdentity] @classmethod - def from_vec_u8(cls, vec_u8: list[int]) -> Optional["DynamicInfo"]: + def from_vec_u8(cls, vec_u8: Union[list[int], bytes]) -> Optional["DynamicInfo"]: if len(vec_u8) == 0: return None decoded = from_scale_encoding(vec_u8, ChainDataType.DynamicInfo) @@ -154,8 +154,11 @@ def tao_to_alpha_with_slippage( ) -> Union[tuple[Balance, Balance], float]: """ Returns an estimate of how much Alpha would a staker receive if they stake their tao using the current pool state. - Args: + + Arguments: tao: Amount of TAO to stake. + percentage: percentage + Returns: If percentage is False, a tuple of balances where the first part is the amount of Alpha received, and the second part (slippage) is the difference between the estimated amount and ideal @@ -206,8 +209,11 @@ def alpha_to_tao_with_slippage( ) -> Union[tuple[Balance, Balance], float]: """ Returns an estimate of how much TAO would a staker receive if they unstake their alpha using the current pool state. + Args: alpha: Amount of Alpha to stake. + percentage: percentage + Returns: If percentage is False, a tuple of balances where the first part is the amount of TAO received, and the second part (slippage) is the difference between the estimated amount and ideal diff --git a/bittensor/core/chain_data/stake_info.py b/bittensor/core/chain_data/stake_info.py index 42588021c4..e4a439f56c 100644 --- a/bittensor/core/chain_data/stake_info.py +++ b/bittensor/core/chain_data/stake_info.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Optional, Any +from typing import Any, Optional, Union from scalecodec.utils.ss58 import ss58_encode @@ -81,7 +81,7 @@ def list_of_tuple_from_vec_u8( } @classmethod - def list_from_vec_u8(cls, vec_u8: list[int]) -> list["StakeInfo"]: + def list_from_vec_u8(cls, vec_u8: Union[list[int], bytes]) -> list["StakeInfo"]: """Returns a list of StakeInfo objects from a ``vec_u8``.""" decoded = from_scale_encoding(vec_u8, ChainDataType.StakeInfo, is_vec=True) if decoded is None: diff --git a/bittensor/core/metagraph.py b/bittensor/core/metagraph.py index 9874d02a08..732e5d229d 100644 --- a/bittensor/core/metagraph.py +++ b/bittensor/core/metagraph.py @@ -1,5 +1,4 @@ import copy -import importlib import os import pickle import typing @@ -764,7 +763,7 @@ def _set_metagraph_attributes(self, block: int): ) self.axons = [n.axon_info for n in self.neurons] - def save(self, root_dir: Optional[list[str]] = None) -> "AsyncMetagraph": + def save(self, root_dir: Optional[list[str]] = None) -> "MetagraphMixin": """ Saves the current state of the metagraph to a file on disk. This function is crucial for persisting the current state of the network's metagraph, which can later be reloaded or analyzed. The save operation includes all @@ -1022,7 +1021,7 @@ def __init__( self.axons: list["AxonInfo"] = [] self.total_stake: list["Balance"] = [] - def load_from_path(self, dir_path: str) -> "AsyncMetagraph": + def load_from_path(self, dir_path: str) -> "MetagraphMixin": """ Loads the metagraph state from a specified directory path. @@ -1152,7 +1151,7 @@ def __init__( self.axons: list["AxonInfo"] = [] self.total_stake: list["Balance"] = [] - def load_from_path(self, dir_path: str) -> "AsyncMetagraph": + def load_from_path(self, dir_path: str) -> "MetagraphMixin": """ Loads the state of the Metagraph from a specified directory path. @@ -1356,10 +1355,7 @@ async def _initialize_subtensor( subtensor = self.subtensor if not subtensor: # Lazy import due to circular import (subtensor -> metagraph, metagraph -> subtensor) - AsyncSubtensor = getattr( - importlib.import_module("bittensor.core.async_subtensor"), - "AsyncSubtensor", - ) + from bittensor.core.async_subtensor import AsyncSubtensor async with AsyncSubtensor(network=self.chain_endpoint) as subtensor: self.subtensor = subtensor @@ -1592,7 +1588,7 @@ def sync( """ # Initialize subtensor - subtensor = self._initialize_subtensor(subtensor) + subtensor = self._initialize_subtensor(subtensor=subtensor) if ( subtensor.chain_endpoint != settings.ARCHIVE_ENTRYPOINT @@ -1632,11 +1628,11 @@ def _initialize_subtensor(self, subtensor: "Subtensor") -> "Subtensor": according to the current network settings. Args: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The subtensor instance provided for + subtensor (bittensor.core.subtensor.Subtensor): The subtensor instance provided for initialization. If ``None``, a new subtensor instance is created using the current network configuration. Returns: - subtensor (bittensor.core.async_subtensor.AsyncSubtensor): The initialized subtensor instance, ready to be + subtensor (bittensor.core.subtensor.Subtensor): The initialized subtensor instance, ready to be used for syncing the metagraph. Internal Usage: @@ -1650,9 +1646,8 @@ def _initialize_subtensor(self, subtensor: "Subtensor") -> "Subtensor": subtensor = self.subtensor if not subtensor: # Lazy import due to circular import (subtensor -> metagraph, metagraph -> subtensor) - Subtensor = getattr( - importlib.import_module("bittensor.core.subtensor"), "Subtensor" - ) + from bittensor.core.subtensor import Subtensor + subtensor = Subtensor(network=self.chain_endpoint) self.subtensor = subtensor diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 036e40e5f1..a03e811f98 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1181,7 +1181,7 @@ def get_stake( block=block, params=[hotkey_ss58, coldkey_ss58, netuid], ) - hotkey_alpha_obj: ScaleType = self.query_module( + hotkey_alpha_obj: ScaleObj = self.query_module( module="SubtensorModule", name="TotalHotkeyAlpha", block=block, @@ -1541,7 +1541,7 @@ def get_vote_data( This function is important for tracking and understanding the decision-making processes within the Bittensor network, particularly how proposals are received and acted upon by the governing body. """ - vote_data = self.substrate.query( + vote_data: dict[str, Any] = self.substrate.query( module="Triumvirate", storage_function="Voting", params=[proposal_hash], @@ -2180,6 +2180,7 @@ def add_stake( Args: wallet (bittensor_wallet.Wallet): The wallet to be used for staking. hotkey_ss58 (Optional[str]): The ``SS58`` address of the hotkey associated with the neuron. + netuid (Optional[int]): The unique identifier of the subnet to which the neuron belongs. amount (Balance): The amount of TAO to stake. wait_for_inclusion (bool): Waits for the transaction to be included in a block. wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. @@ -2217,6 +2218,7 @@ def add_stake_multiple( Args: wallet (bittensor_wallet.Wallet): The wallet used for staking. hotkey_ss58s (list[str]): List of ``SS58`` addresses of hotkeys to stake to. + netuids (list[int]): List of network UIDs to stake to. amounts (list[Balance]): Corresponding amounts of TAO to stake for each hotkey. wait_for_inclusion (bool): Waits for the transaction to be included in a block. wait_for_finalization (bool): Waits for the transaction to be finalized on the blockchain. @@ -2637,6 +2639,7 @@ def _blocks_weight_limit() -> bool: retries = 0 success = False + message = "No attempt made. Perhaps it is too soon to commit weights!" if ( uid := self.get_uid_for_hotkey_on_subnet(wallet.hotkey.ss58_address, netuid) ) is None: @@ -2647,7 +2650,7 @@ def _blocks_weight_limit() -> bool: if self.commit_reveal_enabled(netuid=netuid) is True: # go with `commit reveal v3` extrinsic - message = "No attempt made. Perhaps it is too soon to commit weights!" + while retries < max_retries and success is False and _blocks_weight_limit(): logging.info( f"Committing weights for subnet #{netuid}. Attempt {retries + 1} of {max_retries}." @@ -2666,7 +2669,7 @@ def _blocks_weight_limit() -> bool: return success, message else: # go with classic `set weights extrinsic` - message = "No attempt made. Perhaps it is too soon to set weights!" + while retries < max_retries and success is False and _blocks_weight_limit(): try: logging.info( diff --git a/bittensor/core/types.py b/bittensor/core/types.py index 2269ea3f9a..9563b00328 100644 --- a/bittensor/core/types.py +++ b/bittensor/core/types.py @@ -202,17 +202,6 @@ def determine_chain_endpoint_and_network( return "unknown", network -class AxonServeCallParams_(TypedDict): - """Axon serve chain call parameters.""" - - version: int - ip: int - port: int - ip_type: int - netuid: int - certificate: Optional[Certificate] - - class AxonServeCallParams: def __init__( self, diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 07c3125879..75993b543b 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -239,7 +239,6 @@ def format_error_message(error_message: Union[dict, Exception]) -> str: return f"Subtensor returned `{err_name}({err_type})` error. This means: `{err_description}`." -# Subnet 24 uses this function def is_valid_ss58_address(address: str) -> bool: """ Checks if the given address is a valid ss58 address. @@ -339,7 +338,7 @@ def decode_hex_identity_dict(info_dictionary) -> dict[str, Any]: {'name': 'john', 'additional': [('data', 'data')]} """ - def get_decoded(data: str) -> str: + def get_decoded(data: str) -> Optional[str]: """Decodes a hex-encoded string.""" try: return bytes.fromhex(data[2:]).decode() diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 680c765c2f..c49be249ed 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -1,5 +1,4 @@ import warnings - from typing import Union, TypedDict, Optional from scalecodec import ScaleType diff --git a/bittensor/utils/delegates_details.py b/bittensor/utils/delegates_details.py index eeb8d24c77..92f3872dd2 100644 --- a/bittensor/utils/delegates_details.py +++ b/bittensor/utils/delegates_details.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Any, Optional +from typing import Any, Optional, Union # TODO: consider move it to `bittensor.core.chain_data` @@ -17,7 +17,7 @@ class DelegatesDetails: @classmethod def from_chain_data(cls, data: dict[str, Any]) -> "DelegatesDetails": - def decode(key: str, default: Optional[str] = ""): + def decode(key: str, default: Union[Optional[str], list] = ""): try: if isinstance(data.get(key), dict): value = next(data.get(key).values()) diff --git a/bittensor/utils/networking.py b/bittensor/utils/networking.py index 84b2749e87..e01012d8b7 100644 --- a/bittensor/utils/networking.py +++ b/bittensor/utils/networking.py @@ -1,60 +1,50 @@ """Utils for handling local network with ip and ports.""" import os -import urllib from typing import Optional +from urllib import request as urllib_request -from async_substrate_interface.utils import json import netaddr import requests +from async_substrate_interface.utils import json + + +class ExternalIPNotFound(Exception): + """Raised if we cannot attain your external ip from CURL/URLLIB/IPIFY/AWS""" def int_to_ip(int_val: int) -> str: """Maps an integer to a unique ip-string - Args: - int_val (int): - The integer representation of an ip. Must be in the range (0, 3.4028237e+38). - Returns: - str_val (str): - The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 + Arguments: + int_val (int): The integer representation of an ip. Must be in the range (0, 3.4028237e+38). - Raises: - netaddr.core.AddrFormatError (Exception): Raised when the passed int_vals is not a valid ip int value. + Returns: + str_val (str): The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 """ return str(netaddr.IPAddress(int_val)) def ip_to_int(str_val: str) -> int: """Maps an ip-string to a unique integer. - arg: - str_val (:tyep:`str`, `required): - The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 - Returns: - int_val (:type:`int128`, `required`): - The integer representation of an ip. Must be in the range (0, 3.4028237e+38). + Arguments: + str_val (str): The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 - Raises: - netaddr.core.AddrFormatError (Exception): - Raised when the passed str_val is not a valid ip string value. + Returns: + int_val (int): The integer representation of an ip. Must be in the range (0, 3.4028237e+38). """ return int(netaddr.IPAddress(str_val)) def ip_version(str_val: str) -> int: """Returns the ip version (IPV4 or IPV6). - arg: - str_val (:tyep:`str`, `required): - The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 - Returns: - int_val (:type:`int128`, `required`): - The ip version (Either 4 or 6 for IPv4/IPv6) + Arguments: + str_val (str): The string representation of an ip. Of form *.*.*.* for ipv4 or *::*:*:*:* for ipv6 - Raises: - netaddr.core.AddrFormatError (Exception): - Raised when the passed str_val is not a valid ip string value. + Returns: + int_val (int): The ip version (Either 4 or 6 for IPv4/IPv6) """ return int(netaddr.IPAddress(str_val).version) @@ -64,26 +54,21 @@ def ip__str__(ip_type: int, ip_str: str, port: int): return "/ipv%i/%s:%i" % (ip_type, ip_str, port) -class ExternalIPNotFound(Exception): - """Raised if we cannot attain your external ip from CURL/URLLIB/IPIFY/AWS""" - - def get_external_ip() -> str: """Checks CURL/URLLIB/IPIFY/AWS for your external ip. + Returns: - external_ip (:obj:`str` `required`): - Your routers external facing ip as a string. + external_ip (str): Your routers external facing ip as a string. Raises: - ExternalIPNotFound (Exception): - Raised if all external ip attempts fail. + ExternalIPNotFound(Exception): Raised if all external ip attempts fail. """ # --- Try AWS try: external_ip = requests.get("https://checkip.amazonaws.com").text.strip() assert isinstance(ip_to_int(external_ip), int) return str(external_ip) - except Exception: + except ExternalIPNotFound: pass # --- Try ipconfig. @@ -93,7 +78,7 @@ def get_external_ip() -> str: process.close() assert isinstance(ip_to_int(external_ip), int) return str(external_ip) - except Exception: + except ExternalIPNotFound: pass # --- Try ipinfo. @@ -103,7 +88,7 @@ def get_external_ip() -> str: process.close() assert isinstance(ip_to_int(external_ip), int) return str(external_ip) - except Exception: + except ExternalIPNotFound: pass # --- Try myip.dnsomatic @@ -113,15 +98,15 @@ def get_external_ip() -> str: process.close() assert isinstance(ip_to_int(external_ip), int) return str(external_ip) - except Exception: + except ExternalIPNotFound: pass # --- Try urllib ipv6 try: - external_ip = urllib.request.urlopen("https://ident.me").read().decode("utf8") + external_ip = urllib_request.urlopen("https://ident.me").read().decode("utf8") assert isinstance(ip_to_int(external_ip), int) return str(external_ip) - except Exception: + except ExternalIPNotFound: pass # --- Try Wikipedia @@ -129,7 +114,7 @@ def get_external_ip() -> str: external_ip = requests.get("https://www.wikipedia.org").headers["X-Client-IP"] assert isinstance(ip_to_int(external_ip), int) return str(external_ip) - except Exception: + except ExternalIPNotFound: pass raise ExternalIPNotFound @@ -138,13 +123,15 @@ def get_external_ip() -> str: def get_formatted_ws_endpoint_url(endpoint_url: Optional[str]) -> Optional[str]: """ Returns a formatted websocket endpoint url. - Note: The port (or lack thereof) is left unchanged - Args: - endpoint_url (Optional[str]): - The endpoint url to format. + + Arguments: + endpoint_url (Optional[str]): The endpoint url to format. + Returns: formatted_endpoint_url (Optional[str]): The formatted endpoint url. In the form of ws:// or wss:// + + Note: The port (or lack thereof) is left unchanged. """ if endpoint_url is None: return None diff --git a/bittensor/utils/substrate_utils/storage.py b/bittensor/utils/substrate_utils/storage.py index b5e95296e5..315f7e44c8 100644 --- a/bittensor/utils/substrate_utils/storage.py +++ b/bittensor/utils/substrate_utils/storage.py @@ -25,11 +25,11 @@ class StorageKey: def __init__( self, - pallet: str, - storage_function: str, - params: list, - data: bytes, - value_scale_type: str, + pallet: Optional[str], + storage_function: Optional[str], + params: Optional[list], + data: Optional[bytes], + value_scale_type: Optional[str], metadata: GenericMetadataVersioned, runtime_config: RuntimeConfigurationObject, ): @@ -141,7 +141,7 @@ def convert_storage_parameter(self, scale_type: str, value: Any): return value - def to_hex(self) -> str: + def to_hex(self) -> Optional[str]: """ Returns a Hex-string representation of current StorageKey data