# The MIT License (MIT)
# Copyright © 2024 Opentensor Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
# the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
"""
This module defines the :obj:`AxonInfo` class, a data structure used to represent information about an axon endpoint in the Bittensor network.
"""
import json
from dataclasses import asdict, dataclass
from typing import Any, Union
from bittensor.utils import networking
from bittensor.utils.btlogging import logging
from bittensor.utils.registration import torch, use_torch
[docs]
@dataclass
class AxonInfo:
"""
The :obj:`AxonInfo` class represents information about an axon endpoint in the Bittensor network. This includes
properties such as IP address, ports, and relevant keys.
Attributes:
version (int): The version of the axon endpoint.
ip (str): The IP address of the axon endpoint.
port (int): The port number the axon endpoint uses.
ip_type (int): The type of IP protocol (e.g., IPv4 or IPv6).
hotkey (str): The hotkey associated with the axon endpoint.
coldkey (str): The coldkey associated with the axon endpoint.
protocol (int): The protocol version (default is 4).
placeholder1 (int): Reserved field (default is 0).
placeholder2 (int): Reserved field (default is 0).
"""
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 networking.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 f"AxonInfo( {self.ip_str()}, {self.hotkey}, {self.coldkey}, {self.version} )"
[docs]
def __repr__(self):
return self.__str__()
[docs]
def to_string(self) -> str:
"""Converts the :obj:`AxonInfo` object to a string representation using JSON."""
try:
return json.dumps(asdict(self))
except (TypeError, ValueError) as e:
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 :obj:`AxonInfo` object from its string representation using JSON.
Args:
json_string (str): The JSON string representation of the :obj:`AxonInfo` object.
Returns:
AxonInfo: An instance of :obj:`AxonInfo` created from the JSON string. If decoding fails, returns a default :obj:`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 :obj:`AxonInfo` object.
ValueError: If there is a value error when creating the :obj:`AxonInfo` object.
"""
try:
data = json.loads(json_string)
return cls(**data)
except json.JSONDecodeError as e:
logging.error(f"Error decoding JSON: {e}")
except TypeError as e:
logging.error(f"Type error: {e}")
except ValueError as e:
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 :obj:`AxonInfo` object.
Args:
neuron_info (dict): A dictionary containing the neuron information.
Returns:
instance (AxonInfo): An instance of :obj:`AxonInfo` created from the dictionary.
"""
return cls(
version=neuron_info["axon_info"]["version"],
ip=networking.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)