Source code for bittensor.utils.subtensor

# The MIT License (MIT)
# Copyright © 2021 Yuma Rao
# Copyright © 2022 Opentensor Foundation
# Copyright © 2023 Opentensor Technologies Inc

# 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.

"""Module providing common helper functions for working with Subtensor."""

import json
import logging
import os
from typing import Dict, Optional, Union, Any, List, Tuple

from substrateinterface.base import SubstrateInterface

_logger = logging.getLogger("subtensor.errors_handler")

_USER_HOME_DIR = os.path.expanduser("~")
_BT_DIR = os.path.join(_USER_HOME_DIR, ".bittensor")
_ERRORS_FILE_PATH = os.path.join(_BT_DIR, "subtensor_errors_map.json")
_ST_BUILD_ID = "subtensor_build_id"

# Create directory if it doesn't exist
os.makedirs(_BT_DIR, exist_ok=True)


# Pallet's typing class `PalletMetadataV14` is defined only at
# https://github.com/polkascan/py-scale-codec/blob/master/scalecodec/type_registry/core.json#L1024
# A class object is created dynamically at runtime.
# Circleci linter complains about string represented classes like 'PalletMetadataV14'.
[docs] def _get_errors_from_pallet(pallet) -> Optional[Dict[str, Dict[str, str]]]: """Extracts and returns error information from the given pallet metadata. Args: pallet (PalletMetadataV14): The pallet metadata containing error definitions. Returns: dict[str, str]: A dictionary of errors indexed by their IDs. Raises: ValueError: If the pallet does not contain error definitions or the list is empty. """ if not hasattr(pallet, "errors") or not pallet.errors: _logger.warning( "The pallet does not contain any error definitions or the list is empty." ) return None return { str(error["index"]): { "name": error["name"], "description": " ".join(error["docs"]), } for error in pallet.errors }
[docs] def _save_errors_to_cache(uniq_version: str, errors: Dict[str, Dict[str, str]]): """Saves error details and unique version identifier to a JSON file. Args: uniq_version (str): Unique version identifier for the Subtensor build. errors (dict[str, str]): Error information to be cached. """ data = {_ST_BUILD_ID: uniq_version, "errors": errors} try: with open(_ERRORS_FILE_PATH, "w") as json_file: json.dump(data, json_file, indent=4) except IOError as e: _logger.warning(f"Error saving to file: {e}")
[docs] def _get_errors_from_cache() -> Optional[Dict[str, Dict[str, Dict[str, str]]]]: """Retrieves and returns the cached error information from a JSON file, if it exists. Returns: A dictionary containing error information. """ if not os.path.exists(_ERRORS_FILE_PATH): return None try: with open(_ERRORS_FILE_PATH, "r") as json_file: data = json.load(json_file) except IOError as e: _logger.warning(f"Error reading from file: {e}") return None return data
[docs] def get_subtensor_errors( substrate: SubstrateInterface, ) -> Union[Dict[str, Dict[str, str]], Dict[Any, Any]]: """Fetches or retrieves cached Subtensor error definitions using metadata. Args: substrate (SubstrateInterface): Instance of SubstrateInterface to access metadata. Returns: dict[str, str]: A dictionary containing error information. """ if not substrate.metadata: substrate.get_metadata() cached_errors_map = _get_errors_from_cache() # TODO: Talk to the Nucleus team about a unique identification for each assembly (subtensor). Before that, use # the metadata value for `subtensor_build_id` subtensor_build_id = substrate.metadata[0].value if not cached_errors_map or subtensor_build_id != cached_errors_map.get( _ST_BUILD_ID ): pallet = substrate.metadata.get_metadata_pallet("SubtensorModule") subtensor_errors_map = _get_errors_from_pallet(pallet) if not subtensor_errors_map: return {} _save_errors_to_cache( uniq_version=substrate.metadata[0].value, errors=subtensor_errors_map ) _logger.info(f"File {_ERRORS_FILE_PATH} has been updated.") return subtensor_errors_map else: return cached_errors_map.get("errors", {})
[docs] def format_parent(proportion, parent) -> Tuple[str, str]: """ Formats raw parent data into a list of tuples. Args: parent: The raw parent data. proportion: proportion of parent data. Returns: list: List of (proportion, child_address) tuples. """ int_proportion = ( proportion.value if hasattr(proportion, "value") else int(proportion) ) return int_proportion, parent.value
[docs] def format_children(children) -> List[Tuple[int, str]]: """ Formats raw children data into a list of tuples. Args: children: The raw children data. Returns: list: List of (proportion, child_address) tuples. """ formatted_children = [] for proportion, child in children: # Convert U64 to int int_proportion = ( proportion.value if hasattr(proportion, "value") else int(proportion) ) if int_proportion > 0: formatted_children.append((int_proportion, child.value)) return formatted_children