# The MIT License (MIT)
# Copyright © 2023 Opentensor Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
# the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
"""
This module provides data structures and functions for working with the Bittensor network,
including neuron and subnet information, SCALE encoding/decoding, and custom RPC type registry.
"""
import json
from dataclasses import dataclass, asdict
from enum import Enum
from typing import List, Tuple, Dict, Optional, Any, TypedDict, Union
from scalecodec.base import RuntimeConfiguration, ScaleBytes
from scalecodec.type_registry import load_type_registry_preset
from scalecodec.types import GenericCall
from scalecodec.utils.ss58 import ss58_encode
import bittensor
from .utils import networking as net, RAOPERTAO, U16_NORMALIZED_FLOAT
from .utils.balance import Balance
from .utils.registration import torch, use_torch
custom_rpc_type_registry = {
"types": {
"SubnetInfo": {
"type": "struct",
"type_mapping": [
["netuid", "Compact<u16>"],
["rho", "Compact<u16>"],
["kappa", "Compact<u16>"],
["difficulty", "Compact<u64>"],
["immunity_period", "Compact<u16>"],
["max_allowed_validators", "Compact<u16>"],
["min_allowed_weights", "Compact<u16>"],
["max_weights_limit", "Compact<u16>"],
["scaling_law_power", "Compact<u16>"],
["subnetwork_n", "Compact<u16>"],
["max_allowed_uids", "Compact<u16>"],
["blocks_since_last_step", "Compact<u64>"],
["tempo", "Compact<u16>"],
["network_modality", "Compact<u16>"],
["network_connect", "Vec<[u16; 2]>"],
["emission_values", "Compact<u64>"],
["burn", "Compact<u64>"],
["owner", "AccountId"],
],
},
"DelegateInfo": {
"type": "struct",
"type_mapping": [
["delegate_ss58", "AccountId"],
["take", "Compact<u16>"],
["nominators", "Vec<(AccountId, Compact<u64>)>"],
["owner_ss58", "AccountId"],
["registrations", "Vec<Compact<u16>>"],
["validator_permits", "Vec<Compact<u16>>"],
["return_per_1000", "Compact<u64>"],
["total_daily_return", "Compact<u64>"],
],
},
"NeuronInfo": {
"type": "struct",
"type_mapping": [
["hotkey", "AccountId"],
["coldkey", "AccountId"],
["uid", "Compact<u16>"],
["netuid", "Compact<u16>"],
["active", "bool"],
["axon_info", "axon_info"],
["prometheus_info", "PrometheusInfo"],
["stake", "Vec<(AccountId, Compact<u64>)>"],
["rank", "Compact<u16>"],
["emission", "Compact<u64>"],
["incentive", "Compact<u16>"],
["consensus", "Compact<u16>"],
["trust", "Compact<u16>"],
["validator_trust", "Compact<u16>"],
["dividends", "Compact<u16>"],
["last_update", "Compact<u64>"],
["validator_permit", "bool"],
["weights", "Vec<(Compact<u16>, Compact<u16>)>"],
["bonds", "Vec<(Compact<u16>, Compact<u16>)>"],
["pruning_score", "Compact<u16>"],
],
},
"NeuronInfoLite": {
"type": "struct",
"type_mapping": [
["hotkey", "AccountId"],
["coldkey", "AccountId"],
["uid", "Compact<u16>"],
["netuid", "Compact<u16>"],
["active", "bool"],
["axon_info", "axon_info"],
["prometheus_info", "PrometheusInfo"],
["stake", "Vec<(AccountId, Compact<u64>)>"],
["rank", "Compact<u16>"],
["emission", "Compact<u64>"],
["incentive", "Compact<u16>"],
["consensus", "Compact<u16>"],
["trust", "Compact<u16>"],
["validator_trust", "Compact<u16>"],
["dividends", "Compact<u16>"],
["last_update", "Compact<u64>"],
["validator_permit", "bool"],
["pruning_score", "Compact<u16>"],
],
},
"axon_info": {
"type": "struct",
"type_mapping": [
["block", "u64"],
["version", "u32"],
["ip", "u128"],
["port", "u16"],
["ip_type", "u8"],
["protocol", "u8"],
["placeholder1", "u8"],
["placeholder2", "u8"],
],
},
"PrometheusInfo": {
"type": "struct",
"type_mapping": [
["block", "u64"],
["version", "u32"],
["ip", "u128"],
["port", "u16"],
["ip_type", "u8"],
],
},
"IPInfo": {
"type": "struct",
"type_mapping": [
["ip", "Compact<u128>"],
["ip_type_and_protocol", "Compact<u8>"],
],
},
"StakeInfo": {
"type": "struct",
"type_mapping": [
["hotkey", "AccountId"],
["coldkey", "AccountId"],
["stake", "Compact<u64>"],
],
},
"SubnetHyperparameters": {
"type": "struct",
"type_mapping": [
["rho", "Compact<u16>"],
["kappa", "Compact<u16>"],
["immunity_period", "Compact<u16>"],
["min_allowed_weights", "Compact<u16>"],
["max_weights_limit", "Compact<u16>"],
["tempo", "Compact<u16>"],
["min_difficulty", "Compact<u64>"],
["max_difficulty", "Compact<u64>"],
["weights_version", "Compact<u64>"],
["weights_rate_limit", "Compact<u64>"],
["adjustment_interval", "Compact<u16>"],
["activity_cutoff", "Compact<u16>"],
["registration_allowed", "bool"],
["target_regs_per_interval", "Compact<u16>"],
["min_burn", "Compact<u64>"],
["max_burn", "Compact<u64>"],
["bonds_moving_avg", "Compact<u64>"],
["max_regs_per_block", "Compact<u16>"],
["serving_rate_limit", "Compact<u64>"],
["max_validators", "Compact<u16>"],
["adjustment_alpha", "Compact<u64>"],
["difficulty", "Compact<u64>"],
["commit_reveal_weights_interval", "Compact<u64>"],
["commit_reveal_weights_enabled", "bool"],
["alpha_high", "Compact<u16>"],
["alpha_low", "Compact<u16>"],
["liquid_alpha_enabled", "bool"],
],
},
"ScheduledColdkeySwapInfo": {
"type": "struct",
"type_mapping": [
["old_coldkey", "AccountId"],
["new_coldkey", "AccountId"],
["arbitration_block", "Compact<u64>"],
],
},
}
}
[docs]
@dataclass
class AxonInfo:
version: int
ip: str
port: int
ip_type: int
hotkey: str
coldkey: str
protocol: int = 4
placeholder1: int = 0
placeholder2: int = 0
@property
def is_serving(self) -> bool:
"""True if the endpoint is serving."""
return self.ip != "0.0.0.0"
[docs]
def ip_str(self) -> str:
"""Return the whole IP as string"""
return net.ip__str__(self.ip_type, self.ip, self.port)
[docs]
def __eq__(self, other: "AxonInfo"):
if other is None:
return False
if (
self.version == other.version
and self.ip == other.ip
and self.port == other.port
and self.ip_type == other.ip_type
and self.coldkey == other.coldkey
and self.hotkey == other.hotkey
):
return True
return False
[docs]
def __str__(self):
return "AxonInfo( {}, {}, {}, {} )".format(
str(self.ip_str()), str(self.hotkey), str(self.coldkey), self.version
)
[docs]
def __repr__(self):
return self.__str__()
[docs]
def to_string(self) -> str:
"""Converts the AxonInfo object to a string representation using JSON."""
try:
return json.dumps(asdict(self))
except (TypeError, ValueError) as e:
bittensor.logging.error(f"Error converting AxonInfo to string: {e}")
return AxonInfo(0, "", 0, 0, "", "").to_string()
[docs]
@classmethod
def from_string(cls, json_string: str) -> "AxonInfo":
"""
Creates an AxonInfo object from its string representation using JSON.
Args:
json_string (str): The JSON string representation of the AxonInfo object.
Returns:
AxonInfo: An instance of AxonInfo created from the JSON string. If decoding fails, returns a default AxonInfo object with default values.
Raises:
json.JSONDecodeError: If there is an error in decoding the JSON string.
TypeError: If there is a type error when creating the AxonInfo object.
ValueError: If there is a value error when creating the AxonInfo object.
"""
try:
data = json.loads(json_string)
return cls(**data)
except json.JSONDecodeError as e:
bittensor.logging.error(f"Error decoding JSON: {e}")
except TypeError as e:
bittensor.logging.error(f"Type error: {e}")
except ValueError as e:
bittensor.logging.error(f"Value error: {e}")
return AxonInfo(0, "", 0, 0, "", "")
[docs]
@classmethod
def from_neuron_info(cls, neuron_info: dict) -> "AxonInfo":
"""
Converts a dictionary to an AxonInfo object.
Args:
neuron_info (dict): A dictionary containing the neuron information.
Returns:
instance (AxonInfo): An instance of AxonInfo created from the dictionary.
"""
return cls(
version=neuron_info["axon_info"]["version"],
ip=net.int_to_ip(int(neuron_info["axon_info"]["ip"])),
port=neuron_info["axon_info"]["port"],
ip_type=neuron_info["axon_info"]["ip_type"],
hotkey=neuron_info["hotkey"],
coldkey=neuron_info["coldkey"],
)
[docs]
def to_parameter_dict(
self,
) -> Union[dict[str, Union[int, str]], "torch.nn.ParameterDict"]:
"""Returns a torch tensor or dict of the subnet info, depending on the USE_TORCH flag set."""
if use_torch():
return torch.nn.ParameterDict(self.__dict__)
else:
return self.__dict__
[docs]
@classmethod
def from_parameter_dict(
cls, parameter_dict: Union[dict[str, Any], "torch.nn.ParameterDict"]
) -> "AxonInfo":
"""Returns an axon_info object from a torch parameter_dict or a parameter dict."""
if use_torch():
return cls(**dict(parameter_dict))
else:
return cls(**parameter_dict)
[docs]
class ChainDataType(Enum):
NeuronInfo = 1
SubnetInfo = 2
DelegateInfo = 3
NeuronInfoLite = 4
DelegatedInfo = 5
StakeInfo = 6
IPInfo = 7
SubnetHyperparameters = 8
ScheduledColdkeySwapInfo = 9
[docs]
def from_scale_encoding(
input_: Union[List[int], bytes, ScaleBytes],
type_name: ChainDataType,
is_vec: bool = False,
is_option: bool = False,
) -> Optional[Dict]:
"""
Decodes input_ data from SCALE encoding based on the specified type name and modifiers.
Args:
input_ (Union[List[int], bytes, ScaleBytes]): The input_ data to decode.
type_name (ChainDataType): The type of data being decoded.
is_vec (bool, optional): Whether the data is a vector of the specified type. Default is ``False``.
is_option (bool, optional): Whether the data is an optional value of the specified type. Default is ``False``.
Returns:
Optional[Dict]: The decoded data as a dictionary, or ``None`` if the decoding fails.
"""
type_string = type_name.name
if type_name == ChainDataType.DelegatedInfo:
# DelegatedInfo is a tuple of (DelegateInfo, Compact<u64>)
type_string = f"({ChainDataType.DelegateInfo.name}, Compact<u64>)"
if is_option:
type_string = f"Option<{type_string}>"
if is_vec:
type_string = f"Vec<{type_string}>"
return from_scale_encoding_using_type_string(input_, type_string)
[docs]
def from_scale_encoding_using_type_string(
input_: Union[List[int], bytes, ScaleBytes], type_string: str
) -> Optional[Dict]:
if isinstance(input_, ScaleBytes):
as_scale_bytes = input_
else:
if isinstance(input_, list) and all([isinstance(i, int) for i in input_]):
vec_u8 = input_
as_bytes = bytes(vec_u8)
elif isinstance(input_, bytes):
as_bytes = input_
else:
raise TypeError("input_ must be a List[int], bytes, or ScaleBytes")
as_scale_bytes = ScaleBytes(as_bytes)
rpc_runtime_config = RuntimeConfiguration()
rpc_runtime_config.update_type_registry(load_type_registry_preset("legacy"))
rpc_runtime_config.update_type_registry(custom_rpc_type_registry)
obj = rpc_runtime_config.create_scale_object(type_string, data=as_scale_bytes)
return obj.decode()
# Dataclasses for chain data.
[docs]
@dataclass
class NeuronInfo:
"""Dataclass for neuron metadata."""
hotkey: str
coldkey: str
uid: int
netuid: int
active: int
stake: Balance
# mapping of coldkey to amount staked to this Neuron
stake_dict: Dict[str, Balance]
total_stake: Balance
rank: float
emission: float
incentive: float
consensus: float
trust: float
validator_trust: float
dividends: float
last_update: int
validator_permit: bool
weights: List[List[int]]
bonds: List[List[int]]
pruning_score: int
prometheus_info: Optional["PrometheusInfo"] = None
axon_info: Optional[AxonInfo] = None
is_null: bool = False
[docs]
@classmethod
def fix_decoded_values(cls, neuron_info_decoded: Any) -> "NeuronInfo":
"""Fixes the values of the NeuronInfo object."""
neuron_info_decoded["hotkey"] = ss58_encode(
neuron_info_decoded["hotkey"], bittensor.__ss58_format__
)
neuron_info_decoded["coldkey"] = ss58_encode(
neuron_info_decoded["coldkey"], bittensor.__ss58_format__
)
stake_dict = {
ss58_encode(coldkey, bittensor.__ss58_format__): Balance.from_rao(
int(stake)
)
for coldkey, stake in neuron_info_decoded["stake"]
}
neuron_info_decoded["stake_dict"] = stake_dict
neuron_info_decoded["stake"] = sum(stake_dict.values())
neuron_info_decoded["total_stake"] = neuron_info_decoded["stake"]
neuron_info_decoded["weights"] = [
[int(weight[0]), int(weight[1])]
for weight in neuron_info_decoded["weights"]
]
neuron_info_decoded["bonds"] = [
[int(bond[0]), int(bond[1])] for bond in neuron_info_decoded["bonds"]
]
neuron_info_decoded["rank"] = U16_NORMALIZED_FLOAT(neuron_info_decoded["rank"])
neuron_info_decoded["emission"] = neuron_info_decoded["emission"] / RAOPERTAO
neuron_info_decoded["incentive"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["incentive"]
)
neuron_info_decoded["consensus"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["consensus"]
)
neuron_info_decoded["trust"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["trust"]
)
neuron_info_decoded["validator_trust"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["validator_trust"]
)
neuron_info_decoded["dividends"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["dividends"]
)
neuron_info_decoded["prometheus_info"] = PrometheusInfo.fix_decoded_values(
neuron_info_decoded["prometheus_info"]
)
neuron_info_decoded["axon_info"] = AxonInfo.from_neuron_info(
neuron_info_decoded
)
return cls(**neuron_info_decoded)
[docs]
@classmethod
def from_vec_u8(cls, vec_u8: List[int]) -> "NeuronInfo":
"""Returns a NeuronInfo object from a ``vec_u8``."""
if len(vec_u8) == 0:
return NeuronInfo.get_null_neuron()
decoded = from_scale_encoding(vec_u8, ChainDataType.NeuronInfo)
if decoded is None:
return NeuronInfo.get_null_neuron()
return NeuronInfo.fix_decoded_values(decoded)
[docs]
@classmethod
def list_from_vec_u8(cls, vec_u8: List[int]) -> List["NeuronInfo"]:
"""Returns a list of NeuronInfo objects from a ``vec_u8``"""
decoded_list = from_scale_encoding(
vec_u8, ChainDataType.NeuronInfo, is_vec=True
)
if decoded_list is None:
return []
decoded_list = [
NeuronInfo.fix_decoded_values(decoded) for decoded in decoded_list
]
return decoded_list
[docs]
@staticmethod
def get_null_neuron() -> "NeuronInfo":
neuron = NeuronInfo(
uid=0,
netuid=0,
active=0,
stake=Balance.from_rao(0),
stake_dict={},
total_stake=Balance.from_rao(0),
rank=0,
emission=0,
incentive=0,
consensus=0,
trust=0,
validator_trust=0,
dividends=0,
last_update=0,
validator_permit=False,
weights=[],
bonds=[],
prometheus_info=None,
axon_info=None,
is_null=True,
coldkey="000000000000000000000000000000000000000000000000",
hotkey="000000000000000000000000000000000000000000000000",
pruning_score=0,
)
return neuron
[docs]
@classmethod
def from_weights_bonds_and_neuron_lite(
cls,
neuron_lite: "NeuronInfoLite",
weights_as_dict: Dict[int, List[Tuple[int, int]]],
bonds_as_dict: Dict[int, List[Tuple[int, int]]],
) -> "NeuronInfo":
n_dict = neuron_lite.__dict__
n_dict["weights"] = weights_as_dict.get(neuron_lite.uid, [])
n_dict["bonds"] = bonds_as_dict.get(neuron_lite.uid, [])
return cls(**n_dict)
[docs]
@dataclass
class NeuronInfoLite:
"""Dataclass for neuron metadata, but without the weights and bonds."""
hotkey: str
coldkey: str
uid: int
netuid: int
active: int
stake: Balance
# mapping of coldkey to amount staked to this Neuron
stake_dict: Dict[str, Balance]
total_stake: Balance
rank: float
emission: float
incentive: float
consensus: float
trust: float
validator_trust: float
dividends: float
last_update: int
validator_permit: bool
prometheus_info: Optional["PrometheusInfo"]
axon_info: "axon_info"
pruning_score: int
is_null: bool = False
[docs]
@classmethod
def fix_decoded_values(cls, neuron_info_decoded: Any) -> "NeuronInfoLite":
"""Fixes the values of the NeuronInfoLite object."""
neuron_info_decoded["hotkey"] = ss58_encode(
neuron_info_decoded["hotkey"], bittensor.__ss58_format__
)
neuron_info_decoded["coldkey"] = ss58_encode(
neuron_info_decoded["coldkey"], bittensor.__ss58_format__
)
stake_dict = {
ss58_encode(coldkey, bittensor.__ss58_format__): Balance.from_rao(
int(stake)
)
for coldkey, stake in neuron_info_decoded["stake"]
}
neuron_info_decoded["stake_dict"] = stake_dict
neuron_info_decoded["stake"] = sum(stake_dict.values())
neuron_info_decoded["total_stake"] = neuron_info_decoded["stake"]
neuron_info_decoded["rank"] = U16_NORMALIZED_FLOAT(neuron_info_decoded["rank"])
neuron_info_decoded["emission"] = neuron_info_decoded["emission"] / RAOPERTAO
neuron_info_decoded["incentive"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["incentive"]
)
neuron_info_decoded["consensus"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["consensus"]
)
neuron_info_decoded["trust"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["trust"]
)
neuron_info_decoded["validator_trust"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["validator_trust"]
)
neuron_info_decoded["dividends"] = U16_NORMALIZED_FLOAT(
neuron_info_decoded["dividends"]
)
neuron_info_decoded["prometheus_info"] = PrometheusInfo.fix_decoded_values(
neuron_info_decoded["prometheus_info"]
)
neuron_info_decoded["axon_info"] = AxonInfo.from_neuron_info(
neuron_info_decoded
)
return cls(**neuron_info_decoded)
[docs]
@classmethod
def from_vec_u8(cls, vec_u8: List[int]) -> "NeuronInfoLite":
"""Returns a NeuronInfoLite object from a ``vec_u8``."""
if len(vec_u8) == 0:
return NeuronInfoLite.get_null_neuron()
decoded = from_scale_encoding(vec_u8, ChainDataType.NeuronInfoLite)
if decoded is None:
return NeuronInfoLite.get_null_neuron()
return NeuronInfoLite.fix_decoded_values(decoded)
[docs]
@classmethod
def list_from_vec_u8(cls, vec_u8: List[int]) -> List["NeuronInfoLite"]:
"""Returns a list of NeuronInfoLite objects from a ``vec_u8``."""
decoded_list = from_scale_encoding(
vec_u8, ChainDataType.NeuronInfoLite, is_vec=True
)
if decoded_list is None:
return []
decoded_list = [
NeuronInfoLite.fix_decoded_values(decoded) for decoded in decoded_list
]
return decoded_list
[docs]
@staticmethod
def get_null_neuron() -> "NeuronInfoLite":
neuron = NeuronInfoLite(
uid=0,
netuid=0,
active=0,
stake=Balance.from_rao(0),
stake_dict={},
total_stake=Balance.from_rao(0),
rank=0,
emission=0,
incentive=0,
consensus=0,
trust=0,
validator_trust=0,
dividends=0,
last_update=0,
validator_permit=False,
prometheus_info=None,
axon_info=None,
is_null=True,
coldkey="000000000000000000000000000000000000000000000000",
hotkey="000000000000000000000000000000000000000000000000",
pruning_score=0,
)
return neuron
[docs]
@dataclass
class PrometheusInfo:
"""Dataclass for prometheus info."""
block: int
version: int
ip: str
port: int
ip_type: int
[docs]
@classmethod
def fix_decoded_values(cls, prometheus_info_decoded: Dict) -> "PrometheusInfo":
"""Returns a PrometheusInfo object from a prometheus_info_decoded dictionary."""
prometheus_info_decoded["ip"] = net.int_to_ip(
int(prometheus_info_decoded["ip"])
)
return cls(**prometheus_info_decoded)
[docs]
@dataclass
class DelegateInfoLite:
"""
Dataclass for DelegateLiteInfo. This is a lighter version of :func:`DelegateInfo`.
Args:
delegate_ss58 (str): Hotkey of the delegate for which the information is being fetched.
take (float): Take of the delegate as a percentage.
nominators (int): Count of the nominators of the delegate.
owner_ss58 (str): Coldkey of the owner.
registrations (list[int]): List of subnets that the delegate is registered on.
validator_permits (list[int]): List of subnets that the delegate is allowed to validate on.
return_per_1000 (int): Return per 1000 TAO, for the delegate over a day.
total_daily_return (int): Total daily return of the delegate.
"""
delegate_ss58: str # Hotkey of delegate
take: float # Take of the delegate as a percentage
nominators: int # Count of the nominators of the delegate.
owner_ss58: str # Coldkey of 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
[docs]
@dataclass
class DelegateInfo:
"""
Dataclass for delegate information. For a lighter version of this class, see :func:`DelegateInfoLite`.
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.
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.
"""
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[
int
] # List of subnets that the delegate is allowed to validate on
registrations: List[int] # List of subnets that the delegate is registered on
return_per_1000: Balance # Return per 1000 tao of the delegate over a day
total_daily_return: Balance # Total daily return of the delegate
[docs]
@classmethod
def fix_decoded_values(cls, decoded: Any) -> "DelegateInfo":
"""Fixes the decoded values."""
return cls(
hotkey_ss58=ss58_encode(
decoded["delegate_ss58"], bittensor.__ss58_format__
),
owner_ss58=ss58_encode(decoded["owner_ss58"], bittensor.__ss58_format__),
take=U16_NORMALIZED_FLOAT(decoded["take"]),
nominators=[
(
ss58_encode(nom[0], bittensor.__ss58_format__),
Balance.from_rao(nom[1]),
)
for nom in decoded["nominators"]
],
total_stake=Balance.from_rao(
sum([nom[1] for nom in decoded["nominators"]])
),
validator_permits=decoded["validator_permits"],
registrations=decoded["registrations"],
return_per_1000=Balance.from_rao(decoded["return_per_1000"]),
total_daily_return=Balance.from_rao(decoded["total_daily_return"]),
)
[docs]
@classmethod
def from_vec_u8(cls, vec_u8: List[int]) -> Optional["DelegateInfo"]:
"""Returns a DelegateInfo object from a ``vec_u8``."""
if len(vec_u8) == 0:
return None
decoded = from_scale_encoding(vec_u8, ChainDataType.DelegateInfo)
if decoded is None:
return None
return DelegateInfo.fix_decoded_values(decoded)
[docs]
@classmethod
def list_from_vec_u8(cls, vec_u8: List[int]) -> List["DelegateInfo"]:
"""Returns a list of DelegateInfo objects from a ``vec_u8``."""
decoded = from_scale_encoding(vec_u8, ChainDataType.DelegateInfo, is_vec=True)
if decoded is None:
return []
return [DelegateInfo.fix_decoded_values(d) for d in decoded]
[docs]
@classmethod
def delegated_list_from_vec_u8(
cls, vec_u8: List[int]
) -> List[Tuple["DelegateInfo", Balance]]:
"""Returns a list of Tuples of DelegateInfo objects, and Balance, from a ``vec_u8``.
This is the list of delegates that the user has delegated to, and the amount of stake delegated.
"""
decoded = from_scale_encoding(vec_u8, ChainDataType.DelegatedInfo, is_vec=True)
if decoded is None:
return []
return [
(DelegateInfo.fix_decoded_values(d), Balance.from_rao(s))
for d, s in decoded
]
[docs]
@dataclass
class StakeInfo:
"""Dataclass for stake info."""
hotkey_ss58: str # Hotkey address
coldkey_ss58: str # Coldkey address
stake: Balance # Stake for the hotkey-coldkey pair
[docs]
@classmethod
def fix_decoded_values(cls, decoded: Any) -> "StakeInfo":
"""Fixes the decoded values."""
return cls(
hotkey_ss58=ss58_encode(decoded["hotkey"], bittensor.__ss58_format__),
coldkey_ss58=ss58_encode(decoded["coldkey"], bittensor.__ss58_format__),
stake=Balance.from_rao(decoded["stake"]),
)
[docs]
@classmethod
def from_vec_u8(cls, vec_u8: List[int]) -> Optional["StakeInfo"]:
"""Returns a StakeInfo object from a ``vec_u8``."""
if len(vec_u8) == 0:
return None
decoded = from_scale_encoding(vec_u8, ChainDataType.StakeInfo)
if decoded is None:
return None
return StakeInfo.fix_decoded_values(decoded)
[docs]
@classmethod
def list_of_tuple_from_vec_u8(
cls, vec_u8: List[int]
) -> Dict[str, List["StakeInfo"]]:
"""Returns a list of StakeInfo objects from a ``vec_u8``."""
decoded: Optional[list[tuple[str, list[object]]]] = (
from_scale_encoding_using_type_string(
input_=vec_u8, type_string="Vec<(AccountId, Vec<StakeInfo>)>"
)
)
if decoded is None:
return {}
return {
ss58_encode(address=account_id, ss58_format=bittensor.__ss58_format__): [
StakeInfo.fix_decoded_values(d) for d in stake_info
]
for account_id, stake_info in decoded
}
[docs]
@classmethod
def list_from_vec_u8(cls, vec_u8: List[int]) -> 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:
return []
return [StakeInfo.fix_decoded_values(d) for d in decoded]
[docs]
@dataclass
class SubnetInfo:
"""Dataclass for subnet info."""
netuid: int
rho: int
kappa: int
difficulty: int
immunity_period: int
max_allowed_validators: int
min_allowed_weights: int
max_weight_limit: float
scaling_law_power: float
subnetwork_n: int
max_n: int
blocks_since_epoch: int
tempo: int
modality: int
# netuid -> topk percentile prunning score requirement (u16:MAX normalized.)
connection_requirements: Dict[str, float]
emission_value: float
burn: Balance
owner_ss58: str
[docs]
@classmethod
def from_vec_u8(cls, vec_u8: List[int]) -> Optional["SubnetInfo"]:
"""Returns a SubnetInfo object from a ``vec_u8``."""
if len(vec_u8) == 0:
return None
decoded = from_scale_encoding(vec_u8, ChainDataType.SubnetInfo)
if decoded is None:
return None
return SubnetInfo.fix_decoded_values(decoded)
[docs]
@classmethod
def list_from_vec_u8(cls, vec_u8: List[int]) -> List["SubnetInfo"]:
r"""Returns a list of SubnetInfo objects from a ``vec_u8``."""
decoded = from_scale_encoding(
vec_u8, ChainDataType.SubnetInfo, is_vec=True, is_option=True
)
if decoded is None:
return []
return [SubnetInfo.fix_decoded_values(d) for d in decoded]
[docs]
@classmethod
def fix_decoded_values(cls, decoded: Dict) -> "SubnetInfo":
"""Returns a SubnetInfo object from a decoded SubnetInfo dictionary."""
return SubnetInfo(
netuid=decoded["netuid"],
rho=decoded["rho"],
kappa=decoded["kappa"],
difficulty=decoded["difficulty"],
immunity_period=decoded["immunity_period"],
max_allowed_validators=decoded["max_allowed_validators"],
min_allowed_weights=decoded["min_allowed_weights"],
max_weight_limit=decoded["max_weights_limit"],
# adjustment_alpha=decoded["adjustment_alpha"],
# bonds_moving_avg=decoded["bonds_moving_average"],
scaling_law_power=decoded["scaling_law_power"],
subnetwork_n=decoded["subnetwork_n"],
max_n=decoded["max_allowed_uids"],
blocks_since_epoch=decoded["blocks_since_last_step"],
tempo=decoded["tempo"],
modality=decoded["network_modality"],
connection_requirements={
str(int(netuid)): U16_NORMALIZED_FLOAT(int(req))
for netuid, req in decoded["network_connect"]
},
emission_value=decoded["emission_values"],
burn=Balance.from_rao(decoded["burn"]),
owner_ss58=ss58_encode(decoded["owner"], bittensor.__ss58_format__),
)
[docs]
def to_parameter_dict(self) -> Union[dict[str, Any], "torch.nn.ParameterDict"]:
"""Returns a torch tensor or dict of the subnet info."""
if use_torch():
return torch.nn.ParameterDict(self.__dict__)
else:
return self.__dict__
[docs]
@classmethod
def from_parameter_dict(
cls, parameter_dict: Union[dict[str, Any], "torch.nn.ParameterDict"]
) -> "SubnetInfo":
if use_torch():
return cls(**dict(parameter_dict))
else:
return cls(**parameter_dict)
[docs]
@dataclass
class SubnetHyperparameters:
"""Dataclass for subnet hyperparameters."""
rho: int
kappa: int
immunity_period: int
min_allowed_weights: int
max_weight_limit: float
tempo: int
min_difficulty: int
max_difficulty: int
weights_version: int
weights_rate_limit: int
adjustment_interval: int
activity_cutoff: int
registration_allowed: bool
target_regs_per_interval: int
min_burn: int
max_burn: int
bonds_moving_avg: int
max_regs_per_block: int
serving_rate_limit: int
max_validators: int
adjustment_alpha: int
difficulty: int
commit_reveal_weights_interval: int
commit_reveal_weights_enabled: bool
alpha_high: int
alpha_low: int
liquid_alpha_enabled: bool
[docs]
@classmethod
def from_vec_u8(cls, vec_u8: List[int]) -> Optional["SubnetHyperparameters"]:
"""Returns a SubnetHyperparameters object from a ``vec_u8``."""
if len(vec_u8) == 0:
return None
decoded = from_scale_encoding(vec_u8, ChainDataType.SubnetHyperparameters)
if decoded is None:
return None
return SubnetHyperparameters.fix_decoded_values(decoded)
[docs]
@classmethod
def list_from_vec_u8(cls, vec_u8: List[int]) -> List["SubnetHyperparameters"]:
"""Returns a list of SubnetHyperparameters objects from a ``vec_u8``."""
decoded = from_scale_encoding(
vec_u8, ChainDataType.SubnetHyperparameters, is_vec=True, is_option=True
)
if decoded is None:
return []
return [SubnetHyperparameters.fix_decoded_values(d) for d in decoded]
[docs]
@classmethod
def fix_decoded_values(cls, decoded: Dict) -> "SubnetHyperparameters":
"""Returns a SubnetInfo object from a decoded SubnetInfo dictionary."""
return SubnetHyperparameters(
rho=decoded["rho"],
kappa=decoded["kappa"],
immunity_period=decoded["immunity_period"],
min_allowed_weights=decoded["min_allowed_weights"],
max_weight_limit=decoded["max_weights_limit"],
tempo=decoded["tempo"],
min_difficulty=decoded["min_difficulty"],
max_difficulty=decoded["max_difficulty"],
weights_version=decoded["weights_version"],
weights_rate_limit=decoded["weights_rate_limit"],
adjustment_interval=decoded["adjustment_interval"],
activity_cutoff=decoded["activity_cutoff"],
registration_allowed=decoded["registration_allowed"],
target_regs_per_interval=decoded["target_regs_per_interval"],
min_burn=decoded["min_burn"],
max_burn=decoded["max_burn"],
max_regs_per_block=decoded["max_regs_per_block"],
max_validators=decoded["max_validators"],
serving_rate_limit=decoded["serving_rate_limit"],
bonds_moving_avg=decoded["bonds_moving_avg"],
adjustment_alpha=decoded["adjustment_alpha"],
difficulty=decoded["difficulty"],
commit_reveal_weights_interval=decoded["commit_reveal_weights_interval"],
commit_reveal_weights_enabled=decoded["commit_reveal_weights_enabled"],
alpha_high=decoded["alpha_high"],
alpha_low=decoded["alpha_low"],
liquid_alpha_enabled=decoded["liquid_alpha_enabled"],
)
[docs]
def to_parameter_dict(
self,
) -> Union[dict[str, Union[int, float, bool]], "torch.nn.ParameterDict"]:
"""Returns a torch tensor or dict of the subnet hyperparameters."""
if use_torch():
return torch.nn.ParameterDict(self.__dict__)
else:
return self.__dict__
[docs]
@classmethod
def from_parameter_dict(
cls, parameter_dict: Union[dict[str, Any], "torch.nn.ParameterDict"]
) -> "SubnetHyperparameters":
if use_torch():
return cls(**dict(parameter_dict))
else:
return cls(**parameter_dict)
[docs]
@dataclass
class IPInfo:
"""Dataclass for associated IP Info."""
ip: str
ip_type: int
protocol: int
[docs]
def encode(self) -> Dict[str, Any]:
"""Returns a dictionary of the IPInfo object that can be encoded."""
return {
"ip": net.ip_to_int(
self.ip
), # IP type and protocol are encoded together as a u8
"ip_type_and_protocol": ((self.ip_type << 4) + self.protocol) & 0xFF,
}
[docs]
@classmethod
def from_vec_u8(cls, vec_u8: List[int]) -> Optional["IPInfo"]:
"""Returns a IPInfo object from a ``vec_u8``."""
if len(vec_u8) == 0:
return None
decoded = from_scale_encoding(vec_u8, ChainDataType.IPInfo)
if decoded is None:
return None
return IPInfo.fix_decoded_values(decoded)
[docs]
@classmethod
def list_from_vec_u8(cls, vec_u8: List[int]) -> List["IPInfo"]:
r"""Returns a list of IPInfo objects from a ``vec_u8``."""
decoded = from_scale_encoding(vec_u8, ChainDataType.IPInfo, is_vec=True)
if decoded is None:
return []
return [IPInfo.fix_decoded_values(d) for d in decoded]
[docs]
@classmethod
def fix_decoded_values(cls, decoded: Dict) -> "IPInfo":
"""Returns a SubnetInfo object from a decoded IPInfo dictionary."""
return IPInfo(
ip=net.int_to_ip(decoded["ip"]),
ip_type=decoded["ip_type_and_protocol"] >> 4,
protocol=decoded["ip_type_and_protocol"] & 0xF,
)
[docs]
def to_parameter_dict(
self,
) -> Union[dict[str, Union[str, int]], "torch.nn.ParameterDict"]:
"""Returns a torch tensor or dict of the subnet IP info."""
if use_torch():
return torch.nn.ParameterDict(self.__dict__)
else:
return self.__dict__
[docs]
@classmethod
def from_parameter_dict(
cls, parameter_dict: Union[dict[str, Any], "torch.nn.ParameterDict"]
) -> "IPInfo":
if use_torch():
return cls(**dict(parameter_dict))
else:
return cls(**parameter_dict)
# Senate / Proposal data
[docs]
class ProposalVoteData(TypedDict):
index: int
threshold: int
ayes: List[str]
nays: List[str]
end: int
ProposalCallData = GenericCall
[docs]
@dataclass
class ScheduledColdkeySwapInfo:
"""Dataclass for scheduled coldkey swap information."""
old_coldkey: str
new_coldkey: str
arbitration_block: int
[docs]
@classmethod
def fix_decoded_values(cls, decoded: Any) -> "ScheduledColdkeySwapInfo":
"""Fixes the decoded values."""
return cls(
old_coldkey=ss58_encode(decoded["old_coldkey"], bittensor.__ss58_format__),
new_coldkey=ss58_encode(decoded["new_coldkey"], bittensor.__ss58_format__),
arbitration_block=decoded["arbitration_block"],
)
[docs]
@classmethod
def from_vec_u8(cls, vec_u8: List[int]) -> Optional["ScheduledColdkeySwapInfo"]:
"""Returns a ScheduledColdkeySwapInfo object from a ``vec_u8``."""
if len(vec_u8) == 0:
return None
decoded = from_scale_encoding(vec_u8, ChainDataType.ScheduledColdkeySwapInfo)
if decoded is None:
return None
return ScheduledColdkeySwapInfo.fix_decoded_values(decoded)
[docs]
@classmethod
def list_from_vec_u8(cls, vec_u8: List[int]) -> List["ScheduledColdkeySwapInfo"]:
"""Returns a list of ScheduledColdkeySwapInfo objects from a ``vec_u8``."""
decoded = from_scale_encoding(
vec_u8, ChainDataType.ScheduledColdkeySwapInfo, is_vec=True
)
if decoded is None:
return []
return [ScheduledColdkeySwapInfo.fix_decoded_values(d) for d in decoded]
[docs]
@classmethod
def decode_account_id_list(cls, vec_u8: List[int]) -> Optional[List[str]]:
"""Decodes a list of AccountIds from vec_u8."""
decoded = from_scale_encoding(
vec_u8, ChainDataType.ScheduledColdkeySwapInfo.AccountId, is_vec=True
)
if decoded is None:
return None
return [
ss58_encode(account_id, bittensor.__ss58_format__) for account_id in decoded
]