# 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.
import hashlib
from typing import Literal, Union, Optional, TYPE_CHECKING
import scalecodec
from bittensor_wallet import Keypair
from substrateinterface.utils import ss58
from bittensor.core.settings import SS58_FORMAT
from bittensor.utils.btlogging import logging
from .registration import torch, use_torch
from .version import version_checking, check_version, VersionCheckError
if TYPE_CHECKING:
from substrateinterface import SubstrateInterface
RAOPERTAO = 1e9
U16_MAX = 65535
U64_MAX = 18446744073709551615
[docs]
def ss58_to_vec_u8(ss58_address: str) -> list[int]:
ss58_bytes: bytes = ss58_address_to_bytes(ss58_address)
encoded_address: list[int] = [int(byte) for byte in ss58_bytes]
return encoded_address
[docs]
def strtobool(val: str) -> Union[bool, Literal["==SUPRESS=="]]:
"""
Converts a string to a boolean value.
truth-y values are 'y', 'yes', 't', 'true', 'on', and '1';
false-y values are 'n', 'no', 'f', 'false', 'off', and '0'.
Raises ValueError if 'val' is anything else.
"""
val = val.lower()
if val in ("y", "yes", "t", "true", "on", "1"):
return True
elif val in ("n", "no", "f", "false", "off", "0"):
return False
else:
raise ValueError("invalid truth value %r" % (val,))
[docs]
def _get_explorer_root_url_by_network_from_map(
network: str, network_map: dict[str, dict[str, str]]
) -> Optional[dict[str, str]]:
"""
Returns the explorer root url for the given network name from the given network map.
Args:
network(str): The network to get the explorer url for.
network_map(dict[str, str]): The network map to get the explorer url from.
Returns:
The explorer url for the given network.
Or None if the network is not in the network map.
"""
explorer_urls: Optional[dict[str, str]] = {}
for entity_nm, entity_network_map in network_map.items():
if network in entity_network_map:
explorer_urls[entity_nm] = entity_network_map[network]
return explorer_urls
[docs]
def get_explorer_url_for_network(
network: str, block_hash: str, network_map: dict[str, dict[str, str]]
) -> Optional[dict[str, str]]:
"""
Returns the explorer url for the given block hash and network.
Args:
network(str): The network to get the explorer url for.
block_hash(str): The block hash to get the explorer url for.
network_map(dict[str, dict[str, str]]): The network maps to get the explorer urls from.
Returns:
The explorer url for the given block hash and network.
Or None if the network is not known.
"""
explorer_urls: Optional[dict[str, str]] = {}
# Will be None if the network is not known. i.e. not in network_map
explorer_root_urls: Optional[dict[str, str]] = (
_get_explorer_root_url_by_network_from_map(network, network_map)
)
if explorer_root_urls != {}:
# We are on a known network.
explorer_opentensor_url = (
f"{explorer_root_urls.get('opentensor')}/query/{block_hash}"
)
explorer_taostats_url = (
f"{explorer_root_urls.get('taostats')}/extrinsic/{block_hash}"
)
explorer_urls["opentensor"] = explorer_opentensor_url
explorer_urls["taostats"] = explorer_taostats_url
return explorer_urls
[docs]
def ss58_address_to_bytes(ss58_address: str) -> bytes:
"""Converts a ss58 address to a bytes object."""
account_id_hex: str = scalecodec.ss58_decode(ss58_address, SS58_FORMAT)
return bytes.fromhex(account_id_hex)
[docs]
def u16_normalized_float(x: int) -> float:
return float(x) / float(U16_MAX)
[docs]
def u64_normalized_float(x: int) -> float:
return float(x) / float(U64_MAX)
[docs]
def get_hash(content, encoding="utf-8"):
sha3 = hashlib.sha3_256()
# Update the hash object with the concatenated string
sha3.update(content.encode(encoding))
# Produce the hash
return sha3.hexdigest()
# Subnet 24 uses this function
[docs]
def is_valid_ss58_address(address: str) -> bool:
"""
Checks if the given address is a valid ss58 address.
Args:
address(str): The address to check.
Returns:
True if the address is a valid ss58 address for Bittensor, False otherwise.
"""
try:
return ss58.is_valid_ss58_address(
address, valid_ss58_format=SS58_FORMAT
) or ss58.is_valid_ss58_address(
address, valid_ss58_format=42
) # Default substrate ss58 format (legacy)
except IndexError:
return False
[docs]
def _is_valid_ed25519_pubkey(public_key: Union[str, bytes]) -> bool:
"""
Checks if the given public_key is a valid ed25519 key.
Args:
public_key(Union[str, bytes]): The public_key to check.
Returns:
True if the public_key is a valid ed25519 key, False otherwise.
"""
try:
if isinstance(public_key, str):
if len(public_key) != 64 and len(public_key) != 66:
raise ValueError("a public_key should be 64 or 66 characters")
elif isinstance(public_key, bytes):
if len(public_key) != 32:
raise ValueError("a public_key should be 32 bytes")
else:
raise ValueError("public_key must be a string or bytes")
keypair = Keypair(public_key=public_key)
ss58_addr = keypair.ss58_address
return ss58_addr is not None
except (ValueError, IndexError):
return False
[docs]
def is_valid_bittensor_address_or_public_key(address: Union[str, bytes]) -> bool:
"""
Checks if the given address is a valid destination address.
Args:
address(Union[str, bytes]): The address to check.
Returns:
True if the address is a valid destination address, False otherwise.
"""
if isinstance(address, str):
# Check if ed25519
if address.startswith("0x"):
return _is_valid_ed25519_pubkey(address)
else:
# Assume ss58 address
return is_valid_ss58_address(address)
elif isinstance(address, bytes):
# Check if ed25519
return _is_valid_ed25519_pubkey(address)
else:
# Invalid address type
return False