Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions bittensor/core/async_subtensor.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import argparse
import asyncio
import copy
from itertools import chain
import ssl
from itertools import chain
from typing import Optional, Any, Union, Iterable, TYPE_CHECKING

import aiohttp
import asyncstdlib as a
import numpy as np
import scalecodec
from async_substrate_interface import AsyncSubstrateInterface
from bittensor_wallet.utils import SS58_FORMAT
from numpy.typing import NDArray
from scalecodec import GenericCall, ScaleType
Expand All @@ -28,7 +29,6 @@
custom_rpc_type_registry,
decode_account_id,
)

from bittensor.core.config import Config
from bittensor.core.errors import SubstrateRequestException
from bittensor.core.extrinsics.asyncex.commit_reveal import commit_reveal_v3_extrinsic
Expand Down Expand Up @@ -60,8 +60,8 @@
reveal_weights_extrinsic,
)
from bittensor.core.metagraph import AsyncMetagraph
from bittensor.core.types import ParamWithTypes
from bittensor.core.settings import version_as_int, TYPE_REGISTRY, DELEGATES_DETAILS_URL
from bittensor.core.types import ParamWithTypes
from bittensor.utils import (
decode_hex_identity_dict,
format_error_message,
Expand All @@ -72,19 +72,17 @@
execute_coroutine,
)
from bittensor.utils import networking
from bittensor.utils.substrate_interface import AsyncSubstrateInterface
from bittensor.utils.balance import Balance
from bittensor.utils.btlogging import logging
from bittensor.utils.delegates_details import DelegatesDetails
from bittensor.utils.weight_utils import generate_weight_hash


if TYPE_CHECKING:
from scalecodec import ScaleType
from bittensor_wallet import Wallet
from bittensor.core.axon import Axon
from bittensor.utils import Certificate
from bittensor.utils.substrate_interface import QueryMapResult
from async_substrate_interface import QueryMapResult


def _decode_hex_identity_dict(info_dictionary: dict[str, Any]) -> dict[str, Any]:
Expand Down
46 changes: 12 additions & 34 deletions bittensor/core/errors.py
Original file line number Diff line number Diff line change
@@ -1,42 +1,20 @@
# 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.

from __future__ import annotations

from typing import Optional, TYPE_CHECKING

from async_substrate_interface.errors import (
SubstrateRequestException,
StorageFunctionNotFound,
BlockNotFound,
ExtrinsicNotFound,
)

if TYPE_CHECKING:
from bittensor.core.synapse import Synapse


class SubstrateRequestException(Exception):
pass


class StorageFunctionNotFound(ValueError):
pass


class BlockNotFound(Exception):
pass


class ExtrinsicNotFound(Exception):
pass
# redundant aliases
SubstrateRequestException = SubstrateRequestException
StorageFunctionNotFound = StorageFunctionNotFound
BlockNotFound = BlockNotFound
ExtrinsicNotFound = ExtrinsicNotFound


class ChainError(SubstrateRequestException):
Expand Down
7 changes: 4 additions & 3 deletions bittensor/core/extrinsics/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

from typing import TYPE_CHECKING

from bittensor.utils.btlogging import logging
from async_substrate_interface.errors import SubstrateRequestException

from bittensor.utils import format_error_message
from bittensor.utils.substrate_interface import SubstrateRequestException
from bittensor.utils.btlogging import logging

if TYPE_CHECKING:
from bittensor.core.subtensor import Subtensor
from bittensor.core.async_subtensor import AsyncSubtensor
from bittensor.utils.substrate_interface import (
from async_substrate_interface import (
AsyncExtrinsicReceipt,
ExtrinsicReceipt,
)
Expand Down
4 changes: 2 additions & 2 deletions bittensor/core/subtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
from typing import TYPE_CHECKING, Any, Iterable, Optional, Union

import numpy as np
from async_substrate_interface import SubstrateInterface
from numpy.typing import NDArray

from bittensor.core.async_subtensor import AsyncSubtensor
from bittensor.core.metagraph import Metagraph
from bittensor.core.settings import version_as_int
from bittensor.utils.substrate_interface import SubstrateInterface
from bittensor.utils import execute_coroutine, torch, get_event_loop

if TYPE_CHECKING:
Expand All @@ -23,7 +23,7 @@
from bittensor.core.chain_data.subnet_info import SubnetInfo
from bittensor.utils.balance import Balance
from bittensor.utils import Certificate
from bittensor.utils.substrate_interface import QueryMapResult
from async_substrate_interface import QueryMapResult
from bittensor.utils.delegates_details import DelegatesDetails
from scalecodec.types import ScaleType

Expand Down
195 changes: 15 additions & 180 deletions bittensor/utils/__init__.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,22 @@
# 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 asyncio
import ast
import base58
from collections import namedtuple
import hashlib
from hashlib import blake2b
from typing import Any, Literal, Union, Optional, TYPE_CHECKING, Coroutine
from collections import namedtuple
from typing import Any, Literal, Union, Optional, TYPE_CHECKING
from urllib.parse import urlparse

import scalecodec
from async_substrate_interface.utils import (
event_loop_is_running,
hex_to_bytes,
get_event_loop,
execute_coroutine,
)
from bittensor_wallet import Keypair
from bittensor_wallet.errors import KeyFileError, PasswordError
from scalecodec import ss58_decode, is_valid_ss58_address as _is_valid_ss58_address

from bittensor.core.settings import SS58_FORMAT
from bittensor.utils.btlogging import logging
from bittensor_wallet.errors import KeyFileError, PasswordError
from .registration import torch, use_torch
from .version import version_checking, check_version, VersionCheckError

Expand All @@ -44,6 +31,11 @@
version_checking = version_checking
check_version = check_version
VersionCheckError = VersionCheckError
ss58_decode = ss58_decode
event_loop_is_running = event_loop_is_running
hex_to_bytes = hex_to_bytes
get_event_loop = get_event_loop
execute_coroutine = execute_coroutine


RAOPERTAO = 1e9
Expand All @@ -54,118 +46,6 @@
UnlockStatus = namedtuple("UnlockStatus", ["success", "message"])


def ss58_decode(address: str, valid_ss58_format: Optional[int] = None) -> str:
"""
Decodes given SS58 encoded address to an account ID

Args:
address: e.g. EaG2CRhJWPb7qmdcJvy3LiWdh26Jreu9Dx6R1rXxPmYXoDk
valid_ss58_format: the format for what is considered valid

Returns:
Decoded string AccountId
"""

# Check if address is already decoded
if address.startswith("0x"):
return address

if address == "":
raise ValueError("Empty address provided")

checksum_prefix = b"SS58PRE"

address_decoded = base58.b58decode(address)

if address_decoded[0] & 0b0100_0000:
ss58_format_length = 2
ss58_format = (
((address_decoded[0] & 0b0011_1111) << 2)
| (address_decoded[1] >> 6)
| ((address_decoded[1] & 0b0011_1111) << 8)
)
else:
ss58_format_length = 1
ss58_format = address_decoded[0]

if ss58_format in [46, 47]:
raise ValueError(f"{ss58_format} is a reserved SS58 format")

if valid_ss58_format is not None and ss58_format != valid_ss58_format:
raise ValueError("Invalid SS58 format")

# Determine checksum length according to length of address string
if len(address_decoded) in [3, 4, 6, 10]:
checksum_length = 1
elif len(address_decoded) in [
5,
7,
11,
34 + ss58_format_length,
35 + ss58_format_length,
]:
checksum_length = 2
elif len(address_decoded) in [8, 12]:
checksum_length = 3
elif len(address_decoded) in [9, 13]:
checksum_length = 4
elif len(address_decoded) in [14]:
checksum_length = 5
elif len(address_decoded) in [15]:
checksum_length = 6
elif len(address_decoded) in [16]:
checksum_length = 7
elif len(address_decoded) in [17]:
checksum_length = 8
else:
raise ValueError("Invalid address length")

checksum = blake2b(checksum_prefix + address_decoded[0:-checksum_length]).digest()

if checksum[0:checksum_length] != address_decoded[-checksum_length:]:
raise ValueError("Invalid checksum")

return address_decoded[
ss58_format_length : len(address_decoded) - checksum_length
].hex()


def _is_valid_ss58_address(value: str, valid_ss58_format: Optional[int] = None) -> bool:
"""
Checks if given value is a valid SS58 formatted address, optionally check if address is valid for specified
ss58_format

Args:
value: value to checked
valid_ss58_format: if valid_ss58_format is provided the address must be valid for specified ss58_format
(network) as well

Returns:
bool result
"""

# Return False in case a public key is provided
if value.startswith("0x"):
return False

try:
ss58_decode(value, valid_ss58_format=valid_ss58_format)
except ValueError:
return False

return True


def event_loop_is_running() -> Optional[asyncio.AbstractEventLoop]:
"""
Simple function to check if event loop is running. Returns the loop if it is, otherwise None.
"""
try:
return asyncio.get_running_loop()
except RuntimeError:
return None


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]
Expand Down Expand Up @@ -508,48 +388,3 @@ def unlock_key(wallet: "Wallet", unlock_type="coldkey") -> "UnlockStatus":
except KeyFileError:
err_msg = f"{unlock_type.capitalize()} keyfile is corrupt, non-writable, or non-readable, or non-existent."
return UnlockStatus(False, err_msg)


def hex_to_bytes(hex_str: str) -> bytes:
"""
Converts a hex-encoded string into bytes. Handles 0x-prefixed and non-prefixed hex-encoded strings.
"""
if hex_str.startswith("0x"):
bytes_result = bytes.fromhex(hex_str[2:])
else:
bytes_result = bytes.fromhex(hex_str)
return bytes_result


def get_event_loop() -> asyncio.AbstractEventLoop:
"""
If an event loop is already running, returns that. Otherwise, creates a new event loop,
and sets it as the main event loop for this thread, returning the newly-created event loop.
"""
if loop := event_loop_is_running():
event_loop = loop
else:
event_loop = asyncio.get_event_loop()
asyncio.set_event_loop(event_loop)
return event_loop


def execute_coroutine(
coroutine: "Coroutine", event_loop: asyncio.AbstractEventLoop = None
):
"""
Helper function to run an asyncio coroutine synchronously.

Args:
coroutine (Coroutine): The coroutine to run.
event_loop (AbstractEventLoop): The event loop to use. If `None`, attempts to fetch the already-running
loop. If one is not running, a new loop is created.

Returns:
The result of the coroutine execution.
"""
if event_loop:
event_loop = event_loop
else:
event_loop = get_event_loop()
return event_loop.run_until_complete(asyncio.wait_for(coroutine, timeout=None))
Loading