diff --git a/bittensor/core/async_subtensor.py b/bittensor/core/async_subtensor.py index c9b05ceef2..7b8a87b37f 100644 --- a/bittensor/core/async_subtensor.py +++ b/bittensor/core/async_subtensor.py @@ -9,6 +9,7 @@ import numpy as np import scalecodec from async_substrate_interface import AsyncSubstrateInterface +from async_substrate_interface.substrate_addons import RetryAsyncSubstrate from bittensor_drand import get_encrypted_commitment from bittensor_wallet.utils import SS58_FORMAT from numpy.typing import NDArray @@ -114,8 +115,10 @@ def __init__( self, network: Optional[str] = None, config: Optional["Config"] = None, - _mock: bool = False, log_verbose: bool = False, + fallback_chains: Optional[list[str]] = None, + retry_forever: bool = False, + _mock: bool = False, ): """ Initializes an instance of the AsyncSubtensor class. @@ -123,8 +126,10 @@ def __init__( Arguments: network (str): The network name or type to connect to. config (Optional[Config]): Configuration object for the AsyncSubtensor instance. - _mock: Whether this is a mock instance. Mainly just for use in testing. log_verbose (bool): Enables or disables verbose logging. + fallback_chains (list): List of fallback chains endpoints to use if no network is specified. Defaults to `None`. + retry_forever (bool): Whether to retry forever on connection errors. Defaults to `False`. + _mock: Whether this is a mock instance. Mainly just for use in testing. Raises: Any exceptions raised during the setup, configuration, or connection process. @@ -143,13 +148,8 @@ def __init__( f"Connecting to network: [blue]{self.network}[/blue], " f"chain_endpoint: [blue]{self.chain_endpoint}[/blue]..." ) - self.substrate = AsyncSubstrateInterface( - url=self.chain_endpoint, - ss58_format=SS58_FORMAT, - type_registry=TYPE_REGISTRY, - use_remote_preset=True, - chain_name="Bittensor", - _mock=_mock, + self.substrate = self._get_substrate( + fallback_chains=fallback_chains, retry_forever=retry_forever, _mock=_mock ) if self.log_verbose: logging.info( @@ -283,6 +283,42 @@ async def get_hyperparameter( return getattr(result, "value", result) + def _get_substrate( + self, + fallback_chains: Optional[list[str]] = None, + retry_forever: bool = False, + _mock: bool = False, + ) -> Union[AsyncSubstrateInterface, RetryAsyncSubstrate]: + """Creates the Substrate instance based on provided arguments. + + Arguments: + fallback_chains (list): List of fallback chains endpoints to use if no network is specified. Defaults to `None`. + retry_forever (bool): Whether to retry forever on connection errors. Defaults to `False`. + _mock: Whether this is a mock instance. Mainly just for use in testing. + + Returns: + the instance of the SubstrateInterface or RetrySyncSubstrate class. + """ + if fallback_chains or retry_forever: + return RetryAsyncSubstrate( + url=self.chain_endpoint, + fallback_chains=fallback_chains, + ss58_format=SS58_FORMAT, + type_registry=TYPE_REGISTRY, + retry_forever=retry_forever, + use_remote_preset=True, + chain_name="Bittensor", + _mock=_mock, + ) + return AsyncSubstrateInterface( + url=self.chain_endpoint, + ss58_format=SS58_FORMAT, + type_registry=TYPE_REGISTRY, + use_remote_preset=True, + chain_name="Bittensor", + _mock=_mock, + ) + # Subtensor queries =========================================================================================== async def query_constant( diff --git a/bittensor/core/subtensor.py b/bittensor/core/subtensor.py index 800790ea40..47d5741113 100644 --- a/bittensor/core/subtensor.py +++ b/bittensor/core/subtensor.py @@ -6,6 +6,7 @@ import numpy as np import scalecodec from async_substrate_interface.errors import SubstrateRequestException +from async_substrate_interface.substrate_addons import RetrySyncSubstrate from async_substrate_interface.sync_substrate import SubstrateInterface from async_substrate_interface.types import ScaleObj from bittensor_drand import get_encrypted_commitment @@ -116,8 +117,10 @@ def __init__( self, network: Optional[str] = None, config: Optional["Config"] = None, - _mock: bool = False, log_verbose: bool = False, + fallback_chains: Optional[list[str]] = None, + retry_forever: bool = False, + _mock: bool = False, ): """ Initializes an instance of the Subtensor class. @@ -125,8 +128,10 @@ def __init__( Arguments: network (str): The network name or type to connect to. config (Optional[Config]): Configuration object for the AsyncSubtensor instance. - _mock: Whether this is a mock instance. Mainly just for use in testing. log_verbose (bool): Enables or disables verbose logging. + fallback_chains (list): List of fallback chains endpoints to use if no network is specified. Defaults to `None`. + retry_forever (bool): Whether to retry forever on connection errors. Defaults to `False`. + _mock: Whether this is a mock instance. Mainly just for use in testing. Raises: Any exceptions raised during the setup, configuration, or connection process. @@ -143,13 +148,8 @@ def __init__( f"Connecting to network: [blue]{self.network}[/blue], " f"chain_endpoint: [blue]{self.chain_endpoint}[/blue]> ..." ) - self.substrate = SubstrateInterface( - url=self.chain_endpoint, - ss58_format=SS58_FORMAT, - type_registry=TYPE_REGISTRY, - use_remote_preset=True, - chain_name="Bittensor", - _mock=_mock, + self.substrate = self._get_substrate( + fallback_chains=fallback_chains, retry_forever=retry_forever, _mock=_mock ) if self.log_verbose: logging.info( @@ -163,11 +163,45 @@ def __exit__(self, exc_type, exc_val, exc_tb): self.close() def close(self): - """ - Closes the websocket connection - """ + """Closes the websocket connection.""" self.substrate.close() + def _get_substrate( + self, + fallback_chains: Optional[list[str]] = None, + retry_forever: bool = False, + _mock: bool = False, + ) -> Union[SubstrateInterface, RetrySyncSubstrate]: + """Creates the Substrate instance based on provided arguments. + + Arguments: + fallback_chains (list): List of fallback chains endpoints to use if no network is specified. Defaults to `None`. + retry_forever (bool): Whether to retry forever on connection errors. Defaults to `False`. + _mock: Whether this is a mock instance. Mainly just for use in testing. + + Returns: + the instance of the SubstrateInterface or RetrySyncSubstrate class. + """ + if fallback_chains or retry_forever: + return RetrySyncSubstrate( + url=self.chain_endpoint, + ss58_format=SS58_FORMAT, + type_registry=TYPE_REGISTRY, + use_remote_preset=True, + chain_name="Bittensor", + fallback_chains=fallback_chains, + retry_forever=retry_forever, + _mock=_mock, + ) + return SubstrateInterface( + url=self.chain_endpoint, + ss58_format=SS58_FORMAT, + type_registry=TYPE_REGISTRY, + use_remote_preset=True, + chain_name="Bittensor", + _mock=_mock, + ) + # Subtensor queries =========================================================================================== def query_constant( diff --git a/bittensor/core/subtensor_api/__init__.py b/bittensor/core/subtensor_api/__init__.py new file mode 100644 index 0000000000..3c1bacda8a --- /dev/null +++ b/bittensor/core/subtensor_api/__init__.py @@ -0,0 +1,236 @@ +from typing import Optional, Union, TYPE_CHECKING + +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor +from bittensor.core.subtensor import Subtensor as _Subtensor +from .chain import Chain as _Chain +from .commitments import Commitments as _Commitments +from .delegates import Delegates as _Delegates +from .extrinsics import Extrinsics as _Extrinsics +from .metagraphs import Metagraphs as _Metagraphs +from .neurons import Neurons as _Neurons +from .queries import Queries as _Queries +from .stakes import Stakes as _Stakes +from .subnets import Subnets as _Subnets +from .utils import add_legacy_methods as _add_classic_fields +from .wallets import Wallets as _Wallets + +if TYPE_CHECKING: + from bittensor.core.config import Config + + +class SubtensorApi: + """Subtensor API class. + + Arguments: + network: The network to connect to. Defaults to `None` -> "finney". + config: Bittensor configuration object. Defaults to `None`. + legacy_methods: If `True`, all methods from the Subtensor class will be added to the root level of this class. + fallback_chains (list): List of fallback chains to use if no network is specified. Defaults to `None`. + retry_forever (bool): Whether to retry forever on connection errors. Defaults to `False`. + log_verbose (bool): Enables or disables verbose logging. + mock: Whether this is a mock instance. Mainly just for use in testing. + + Example: + # sync version + import bittensor as bt + + subtensor = bt.SubtensorApi() + print(subtensor.block) + print(subtensor.delegates.get_delegate_identities()) + subtensor.chain.tx_rate_limit() + + # async version + import bittensor as bt + + subtensor = bt.SubtensorApi(async_subtensor=True) + async with subtensor: + print(await subtensor.block) + print(await subtensor.delegates.get_delegate_identities()) + print(await subtensor.chain.tx_rate_limit()) + + # using `legacy_methods` + import bittensor as bt + + subtensor = bt.SubtensorApi(legacy_methods=True) + print(subtensor.bonds(0)) + + # using `fallback_chains` or `retry_forever` + import bittensor as bt + + + """ + + def __init__( + self, + network: Optional[str] = None, + config: Optional["Config"] = None, + async_subtensor: bool = False, + legacy_methods: bool = False, + fallback_chains: Optional[list[str]] = None, + retry_forever: bool = False, + log_verbose: bool = False, + mock: bool = False, + ): + self.network = network + self._fallback_chains = fallback_chains + self._retry_forever = retry_forever + self._mock = mock + self.log_verbose = log_verbose + self.is_async = async_subtensor + self._config = config + + # assigned only for async instance + self.initialize = None + self._subtensor = self._get_subtensor() + + # fix naming collision + self._neurons = _Neurons(self._subtensor) + + # define empty fields + self.substrate = self._subtensor.substrate + self.chain_endpoint = self._subtensor.chain_endpoint + self.close = self._subtensor.close + self.config = self._subtensor.config + self.setup_config = self._subtensor.setup_config + self.help = self._subtensor.help + + self.determine_block_hash = self._subtensor.determine_block_hash + self.encode_params = self._subtensor.encode_params + self.sign_and_send_extrinsic = self._subtensor.sign_and_send_extrinsic + self.start_call = self._subtensor.start_call + self.wait_for_block = self._subtensor.wait_for_block + + # adds all Subtensor methods into main level os SubtensorApi class + if legacy_methods: + _add_classic_fields(self) + + def _get_subtensor(self) -> Union["_Subtensor", "_AsyncSubtensor"]: + """Returns the subtensor instance based on the provided config and subtensor type flag.""" + if self.is_async: + _subtensor = _AsyncSubtensor( + network=self.network, + config=self._config, + log_verbose=self.log_verbose, + fallback_chains=self._fallback_chains, + retry_forever=self._retry_forever, + _mock=self._mock, + ) + self.initialize = _subtensor.initialize + return _subtensor + else: + return _Subtensor( + network=self.network, + config=self._config, + log_verbose=self.log_verbose, + fallback_chains=self._fallback_chains, + retry_forever=self._retry_forever, + _mock=self._mock, + ) + + def _determine_chain_endpoint(self) -> str: + """Determines the connection and mock flag.""" + if self._mock: + return "Mock" + return self.substrate.url + + def __str__(self): + return ( + f"" + ) + + def __repr__(self): + return self.__str__() + + def __enter__(self): + if self.is_async: + raise NotImplementedError( + "Async version of SubtensorApi cannot be used with sync context manager." + ) + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + if self.is_async: + raise NotImplementedError( + "Async version of SubtensorApi cannot be used with sync context manager." + ) + self.close() + + async def __aenter__(self): + if not self.is_async: + raise NotImplementedError( + "Sync version of SubtensorApi cannot be used with async context manager." + ) + return await self._subtensor.__aenter__() + + async def __aexit__(self, exc_type, exc_val, exc_tb): + if not self.is_async: + raise NotImplementedError( + "Sync version of SubtensorApi cannot be used with async context manager." + ) + await self.substrate.close() + + @classmethod + def add_args(cls, parser): + _Subtensor.add_args(parser) + + @property + def block(self): + """Returns current chain block number.""" + return self._subtensor.block + + @property + def chain(self): + """Property of interaction with chain methods.""" + return _Chain(self._subtensor) + + @property + def commitments(self): + """Property to access commitments methods.""" + return _Commitments(self._subtensor) + + @property + def delegates(self): + """Property to access delegates methods.""" + return _Delegates(self._subtensor) + + @property + def extrinsics(self): + """Property to access extrinsics methods.""" + return _Extrinsics(self._subtensor) + + @property + def metagraphs(self): + """Property to access metagraphs methods.""" + return _Metagraphs(self._subtensor) + + @property + def neurons(self): + """Property to access neurons methods.""" + return self._neurons + + @neurons.setter + def neurons(self, value): + """Setter for neurons property.""" + self._neurons = value + + @property + def queries(self): + """Property to access subtensor queries methods.""" + return _Queries(self._subtensor) + + @property + def stakes(self): + """Property to access stakes methods.""" + return _Stakes(self._subtensor) + + @property + def subnets(self): + """Property of interaction with subnets methods.""" + return _Subnets(self._subtensor) + + @property + def wallets(self): + """Property of interaction methods with cold/hotkeys, and balances, etc.""" + return _Wallets(self._subtensor) diff --git a/bittensor/core/subtensor_api/chain.py b/bittensor/core/subtensor_api/chain.py new file mode 100644 index 0000000000..fe03aada99 --- /dev/null +++ b/bittensor/core/subtensor_api/chain.py @@ -0,0 +1,19 @@ +from typing import Union +from bittensor.core.subtensor import Subtensor as _Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor + + +class Chain: + """Class for managing chain state operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.get_block_hash = subtensor.get_block_hash + self.get_current_block = subtensor.get_current_block + self.get_delegate_identities = subtensor.get_delegate_identities + self.get_existential_deposit = subtensor.get_existential_deposit + self.get_minimum_required_stake = subtensor.get_minimum_required_stake + self.get_vote_data = subtensor.get_vote_data + self.get_timestamp = subtensor.get_timestamp + self.last_drand_round = subtensor.last_drand_round + self.state_call = subtensor.state_call + self.tx_rate_limit = subtensor.tx_rate_limit diff --git a/bittensor/core/subtensor_api/commitments.py b/bittensor/core/subtensor_api/commitments.py new file mode 100644 index 0000000000..2e594ba6db --- /dev/null +++ b/bittensor/core/subtensor_api/commitments.py @@ -0,0 +1,20 @@ +from typing import Union +from bittensor.core.subtensor import Subtensor as _Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor + + +class Commitments: + """Class for managing any commitment operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.commit_reveal_enabled = subtensor.commit_reveal_enabled + self.get_all_commitments = subtensor.get_all_commitments + self.get_all_revealed_commitments = subtensor.get_all_revealed_commitments + self.get_commitment = subtensor.get_commitment + self.get_current_weight_commit_info = subtensor.get_current_weight_commit_info + self.get_revealed_commitment = subtensor.get_revealed_commitment + self.get_revealed_commitment_by_hotkey = ( + subtensor.get_revealed_commitment_by_hotkey + ) + self.set_commitment = subtensor.set_commitment + self.set_reveal_commitment = subtensor.set_reveal_commitment diff --git a/bittensor/core/subtensor_api/delegates.py b/bittensor/core/subtensor_api/delegates.py new file mode 100644 index 0000000000..1cdbfca08c --- /dev/null +++ b/bittensor/core/subtensor_api/delegates.py @@ -0,0 +1,16 @@ +from typing import Union +from bittensor.core.subtensor import Subtensor as _Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor + + +class Delegates: + """Class for managing delegate operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.is_hotkey_delegate = subtensor.is_hotkey_delegate + self.get_delegate_by_hotkey = subtensor.get_delegate_by_hotkey + self.set_delegate_take = subtensor.set_delegate_take + self.get_delegate_identities = subtensor.get_delegate_identities + self.get_delegate_take = subtensor.get_delegate_take + self.get_delegated = subtensor.get_delegated + self.get_delegates = subtensor.get_delegates diff --git a/bittensor/core/subtensor_api/extrinsics.py b/bittensor/core/subtensor_api/extrinsics.py new file mode 100644 index 0000000000..0ff4439201 --- /dev/null +++ b/bittensor/core/subtensor_api/extrinsics.py @@ -0,0 +1,29 @@ +from typing import Union +from bittensor.core.subtensor import Subtensor as _Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor + + +class Extrinsics: + """Class for managing extrinsic operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.add_stake = subtensor.add_stake + self.add_stake_multiple = subtensor.add_stake_multiple + self.burned_register = subtensor.burned_register + self.commit_weights = subtensor.commit_weights + self.move_stake = subtensor.move_stake + self.register = subtensor.register + self.register_subnet = subtensor.register_subnet + self.reveal_weights = subtensor.reveal_weights + self.root_register = subtensor.root_register + self.root_set_weights = subtensor.root_set_weights + self.set_children = subtensor.set_children + self.set_subnet_identity = subtensor.set_subnet_identity + self.set_weights = subtensor.set_weights + self.serve_axon = subtensor.serve_axon + self.start_call = subtensor.start_call + self.swap_stake = subtensor.swap_stake + self.transfer = subtensor.transfer + self.transfer_stake = subtensor.transfer_stake + self.unstake = subtensor.unstake + self.unstake_multiple = subtensor.unstake_multiple diff --git a/bittensor/core/subtensor_api/metagraphs.py b/bittensor/core/subtensor_api/metagraphs.py new file mode 100644 index 0000000000..af143a1620 --- /dev/null +++ b/bittensor/core/subtensor_api/metagraphs.py @@ -0,0 +1,12 @@ +from typing import Union +from bittensor.core.subtensor import Subtensor as _Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor + + +class Metagraphs: + """Class for managing metagraph operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.get_metagraph_info = subtensor.get_metagraph_info + self.get_all_metagraphs_info = subtensor.get_all_metagraphs_info + self.metagraph = subtensor.metagraph diff --git a/bittensor/core/subtensor_api/neurons.py b/bittensor/core/subtensor_api/neurons.py new file mode 100644 index 0000000000..c1fd40066f --- /dev/null +++ b/bittensor/core/subtensor_api/neurons.py @@ -0,0 +1,15 @@ +from typing import Union +from bittensor.core.subtensor import Subtensor as _Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor + + +class Neurons: + """Class for managing neuron operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.get_all_neuron_certificates = subtensor.get_all_neuron_certificates + self.get_neuron_certificate = subtensor.get_neuron_certificate + self.neuron_for_uid = subtensor.neuron_for_uid + self.neurons = subtensor.neurons + self.neurons_lite = subtensor.neurons_lite + self.query_identity = subtensor.query_identity diff --git a/bittensor/core/subtensor_api/queries.py b/bittensor/core/subtensor_api/queries.py new file mode 100644 index 0000000000..7209ffb7ae --- /dev/null +++ b/bittensor/core/subtensor_api/queries.py @@ -0,0 +1,15 @@ +from typing import Union +from bittensor.core.subtensor import Subtensor as _Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor + + +class Queries: + """Class for managing subtensor query operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.query_constant = subtensor.query_constant + self.query_map = subtensor.query_map + self.query_map_subtensor = subtensor.query_map_subtensor + self.query_module = subtensor.query_module + self.query_runtime_api = subtensor.query_runtime_api + self.query_subtensor = subtensor.query_subtensor diff --git a/bittensor/core/subtensor_api/stakes.py b/bittensor/core/subtensor_api/stakes.py new file mode 100644 index 0000000000..78e497d901 --- /dev/null +++ b/bittensor/core/subtensor_api/stakes.py @@ -0,0 +1,24 @@ +from typing import Union +from bittensor.core.subtensor import Subtensor as _Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor + + +class Stakes: + """Class for managing stake operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.add_stake = subtensor.add_stake + self.add_stake_multiple = subtensor.add_stake_multiple + self.get_hotkey_stake = subtensor.get_hotkey_stake + self.get_minimum_required_stake = subtensor.get_minimum_required_stake + self.get_stake = subtensor.get_stake + self.get_stake_add_fee = subtensor.get_stake_add_fee + self.get_stake_for_coldkey = subtensor.get_stake_for_coldkey + self.get_stake_for_coldkey_and_hotkey = ( + subtensor.get_stake_for_coldkey_and_hotkey + ) + self.get_stake_info_for_coldkey = subtensor.get_stake_info_for_coldkey + self.get_stake_movement_fee = subtensor.get_stake_movement_fee + self.get_unstake_fee = subtensor.get_unstake_fee + self.unstake = subtensor.unstake + self.unstake_multiple = subtensor.unstake_multiple diff --git a/bittensor/core/subtensor_api/subnets.py b/bittensor/core/subtensor_api/subnets.py new file mode 100644 index 0000000000..c3333daf30 --- /dev/null +++ b/bittensor/core/subtensor_api/subnets.py @@ -0,0 +1,45 @@ +from typing import Union + +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor +from bittensor.core.subtensor import Subtensor as _Subtensor + + +class Subnets: + """Class for managing subnet operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.all_subnets = subtensor.all_subnets + self.blocks_since_last_step = subtensor.blocks_since_last_step + self.blocks_since_last_update = subtensor.blocks_since_last_update + self.bonds = subtensor.bonds + self.difficulty = subtensor.difficulty + self.get_all_subnets_info = subtensor.get_all_subnets_info + self.get_children = subtensor.get_children + self.get_children_pending = subtensor.get_children_pending + self.get_current_weight_commit_info = subtensor.get_current_weight_commit_info + self.get_hyperparameter = subtensor.get_hyperparameter + self.get_neuron_for_pubkey_and_subnet = ( + subtensor.get_neuron_for_pubkey_and_subnet + ) + self.get_next_epoch_start_block = subtensor.get_next_epoch_start_block + self.get_subnet_burn_cost = subtensor.get_subnet_burn_cost + self.get_subnet_hyperparameters = subtensor.get_subnet_hyperparameters + self.get_subnet_owner_hotkey = subtensor.get_subnet_owner_hotkey + self.get_subnet_reveal_period_epochs = subtensor.get_subnet_reveal_period_epochs + self.get_subnet_validator_permits = subtensor.get_subnet_validator_permits + self.get_subnets = subtensor.get_subnets + self.get_total_subnets = subtensor.get_total_subnets + self.get_uid_for_hotkey_on_subnet = subtensor.get_uid_for_hotkey_on_subnet + self.immunity_period = subtensor.immunity_period + self.is_hotkey_registered_on_subnet = subtensor.is_hotkey_registered_on_subnet + self.max_weight_limit = subtensor.max_weight_limit + self.min_allowed_weights = subtensor.min_allowed_weights + self.recycle = subtensor.recycle + self.register_subnet = subtensor.register_subnet + self.set_subnet_identity = subtensor.set_subnet_identity + self.subnet = subtensor.subnet + self.subnet_exists = subtensor.subnet_exists + self.subnetwork_n = subtensor.subnetwork_n + self.tempo = subtensor.tempo + self.weights_rate_limit = subtensor.weights_rate_limit + self.weights = subtensor.weights diff --git a/bittensor/core/subtensor_api/utils.py b/bittensor/core/subtensor_api/utils.py new file mode 100644 index 0000000000..5d8783f6e6 --- /dev/null +++ b/bittensor/core/subtensor_api/utils.py @@ -0,0 +1,156 @@ +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from bittensor.core.subtensor_api import SubtensorApi + + +def add_legacy_methods(subtensor: "SubtensorApi"): + """If SubtensorApi get `subtensor_fields=True` arguments, then all classic Subtensor fields added to root level.""" + subtensor.add_stake = subtensor._subtensor.add_stake + subtensor.add_stake_multiple = subtensor._subtensor.add_stake_multiple + subtensor.all_subnets = subtensor._subtensor.all_subnets + subtensor.blocks_since_last_step = subtensor._subtensor.blocks_since_last_step + subtensor.blocks_since_last_update = subtensor._subtensor.blocks_since_last_update + subtensor.bonds = subtensor._subtensor.bonds + subtensor.burned_register = subtensor._subtensor.burned_register + subtensor.chain_endpoint = subtensor._subtensor.chain_endpoint + subtensor.commit = subtensor._subtensor.commit + subtensor.commit_reveal_enabled = subtensor._subtensor.commit_reveal_enabled + subtensor.commit_weights = subtensor._subtensor.commit_weights + subtensor.determine_block_hash = subtensor._subtensor.determine_block_hash + subtensor.difficulty = subtensor._subtensor.difficulty + subtensor.does_hotkey_exist = subtensor._subtensor.does_hotkey_exist + subtensor.encode_params = subtensor._subtensor.encode_params + subtensor.filter_netuids_by_registered_hotkeys = ( + subtensor._subtensor.filter_netuids_by_registered_hotkeys + ) + subtensor.get_all_commitments = subtensor._subtensor.get_all_commitments + subtensor.get_all_metagraphs_info = subtensor._subtensor.get_all_metagraphs_info + subtensor.get_all_neuron_certificates = ( + subtensor._subtensor.get_all_neuron_certificates + ) + subtensor.get_all_revealed_commitments = ( + subtensor._subtensor.get_all_revealed_commitments + ) + subtensor.get_all_subnets_info = subtensor._subtensor.get_all_subnets_info + subtensor.get_balance = subtensor._subtensor.get_balance + subtensor.get_balances = subtensor._subtensor.get_balances + subtensor.get_block_hash = subtensor._subtensor.get_block_hash + subtensor.get_children = subtensor._subtensor.get_children + subtensor.get_children_pending = subtensor._subtensor.get_children_pending + subtensor.get_commitment = subtensor._subtensor.get_commitment + subtensor.get_current_block = subtensor._subtensor.get_current_block + subtensor.get_current_weight_commit_info = ( + subtensor._subtensor.get_current_weight_commit_info + ) + subtensor.get_delegate_by_hotkey = subtensor._subtensor.get_delegate_by_hotkey + subtensor.get_delegate_identities = subtensor._subtensor.get_delegate_identities + subtensor.get_delegate_take = subtensor._subtensor.get_delegate_take + subtensor.get_delegated = subtensor._subtensor.get_delegated + subtensor.get_delegates = subtensor._subtensor.get_delegates + subtensor.get_existential_deposit = subtensor._subtensor.get_existential_deposit + subtensor.get_hotkey_owner = subtensor._subtensor.get_hotkey_owner + subtensor.get_hotkey_stake = subtensor._subtensor.get_hotkey_stake + subtensor.get_hyperparameter = subtensor._subtensor.get_hyperparameter + subtensor.get_metagraph_info = subtensor._subtensor.get_metagraph_info + subtensor.get_minimum_required_stake = ( + subtensor._subtensor.get_minimum_required_stake + ) + subtensor.get_netuids_for_hotkey = subtensor._subtensor.get_netuids_for_hotkey + subtensor.get_neuron_certificate = subtensor._subtensor.get_neuron_certificate + subtensor.get_neuron_for_pubkey_and_subnet = ( + subtensor._subtensor.get_neuron_for_pubkey_and_subnet + ) + subtensor.get_next_epoch_start_block = ( + subtensor._subtensor.get_next_epoch_start_block + ) + subtensor.get_owned_hotkeys = subtensor._subtensor.get_owned_hotkeys + subtensor.get_revealed_commitment = subtensor._subtensor.get_revealed_commitment + subtensor.get_revealed_commitment_by_hotkey = ( + subtensor._subtensor.get_revealed_commitment_by_hotkey + ) + subtensor.get_stake = subtensor._subtensor.get_stake + subtensor.get_stake_add_fee = subtensor._subtensor.get_stake_add_fee + subtensor.get_stake_for_coldkey = subtensor._subtensor.get_stake_for_coldkey + subtensor.get_stake_for_coldkey_and_hotkey = ( + subtensor._subtensor.get_stake_for_coldkey_and_hotkey + ) + subtensor.get_stake_for_hotkey = subtensor._subtensor.get_stake_for_hotkey + subtensor.get_stake_info_for_coldkey = ( + subtensor._subtensor.get_stake_info_for_coldkey + ) + subtensor.get_stake_movement_fee = subtensor._subtensor.get_stake_movement_fee + subtensor.get_subnet_burn_cost = subtensor._subtensor.get_subnet_burn_cost + subtensor.get_subnet_hyperparameters = ( + subtensor._subtensor.get_subnet_hyperparameters + ) + subtensor.get_subnet_owner_hotkey = subtensor._subtensor.get_subnet_owner_hotkey + subtensor.get_subnet_reveal_period_epochs = ( + subtensor._subtensor.get_subnet_reveal_period_epochs + ) + subtensor.get_subnet_validator_permits = ( + subtensor._subtensor.get_subnet_validator_permits + ) + subtensor.get_subnets = subtensor._subtensor.get_subnets + subtensor.get_timestamp = subtensor._subtensor.get_timestamp + subtensor.get_total_subnets = subtensor._subtensor.get_total_subnets + subtensor.get_transfer_fee = subtensor._subtensor.get_transfer_fee + subtensor.get_uid_for_hotkey_on_subnet = ( + subtensor._subtensor.get_uid_for_hotkey_on_subnet + ) + subtensor.get_unstake_fee = subtensor._subtensor.get_unstake_fee + subtensor.get_vote_data = subtensor._subtensor.get_vote_data + subtensor.immunity_period = subtensor._subtensor.immunity_period + subtensor.is_hotkey_delegate = subtensor._subtensor.is_hotkey_delegate + subtensor.is_hotkey_registered = subtensor._subtensor.is_hotkey_registered + subtensor.is_hotkey_registered_any = subtensor._subtensor.is_hotkey_registered_any + subtensor.is_hotkey_registered_on_subnet = ( + subtensor._subtensor.is_hotkey_registered_on_subnet + ) + subtensor.last_drand_round = subtensor._subtensor.last_drand_round + subtensor.log_verbose = subtensor._subtensor.log_verbose + subtensor.max_weight_limit = subtensor._subtensor.max_weight_limit + subtensor.metagraph = subtensor._subtensor.metagraph + subtensor.min_allowed_weights = subtensor._subtensor.min_allowed_weights + subtensor.move_stake = subtensor._subtensor.move_stake + subtensor.network = subtensor._subtensor.network + subtensor.neuron_for_uid = subtensor._subtensor.neuron_for_uid + subtensor.neurons_lite = subtensor._subtensor.neurons_lite + subtensor.query_constant = subtensor._subtensor.query_constant + subtensor.query_identity = subtensor._subtensor.query_identity + subtensor.query_map = subtensor._subtensor.query_map + subtensor.query_map_subtensor = subtensor._subtensor.query_map_subtensor + subtensor.query_module = subtensor._subtensor.query_module + subtensor.query_runtime_api = subtensor._subtensor.query_runtime_api + subtensor.query_subtensor = subtensor._subtensor.query_subtensor + subtensor.recycle = subtensor._subtensor.recycle + subtensor.register = subtensor._subtensor.register + subtensor.register_subnet = subtensor._subtensor.register_subnet + subtensor.reveal_weights = subtensor._subtensor.reveal_weights + subtensor.root_register = subtensor._subtensor.root_register + subtensor.root_set_weights = subtensor._subtensor.root_set_weights + subtensor.serve_axon = subtensor._subtensor.serve_axon + subtensor.set_children = subtensor._subtensor.set_children + subtensor.set_commitment = subtensor._subtensor.set_commitment + subtensor.set_delegate_take = subtensor._subtensor.set_delegate_take + subtensor.set_reveal_commitment = subtensor._subtensor.set_reveal_commitment + subtensor.set_subnet_identity = subtensor._subtensor.set_subnet_identity + subtensor.set_weights = subtensor._subtensor.set_weights + subtensor.setup_config = subtensor._subtensor.setup_config + subtensor.sign_and_send_extrinsic = subtensor._subtensor.sign_and_send_extrinsic + subtensor.start_call = subtensor._subtensor.start_call + subtensor.state_call = subtensor._subtensor.state_call + subtensor.subnet = subtensor._subtensor.subnet + subtensor.subnet_exists = subtensor._subtensor.subnet_exists + subtensor.subnetwork_n = subtensor._subtensor.subnetwork_n + subtensor.substrate = subtensor._subtensor.substrate + subtensor.swap_stake = subtensor._subtensor.swap_stake + subtensor.tempo = subtensor._subtensor.tempo + subtensor.transfer = subtensor._subtensor.transfer + subtensor.transfer_stake = subtensor._subtensor.transfer_stake + subtensor.tx_rate_limit = subtensor._subtensor.tx_rate_limit + subtensor.unstake = subtensor._subtensor.unstake + subtensor.unstake_multiple = subtensor._subtensor.unstake_multiple + subtensor.wait_for_block = subtensor._subtensor.wait_for_block + subtensor.weights = subtensor._subtensor.weights + subtensor.weights_rate_limit = subtensor._subtensor.weights_rate_limit diff --git a/bittensor/core/subtensor_api/wallets.py b/bittensor/core/subtensor_api/wallets.py new file mode 100644 index 0000000000..7314a4fe39 --- /dev/null +++ b/bittensor/core/subtensor_api/wallets.py @@ -0,0 +1,39 @@ +from typing import Union +from bittensor.core.subtensor import Subtensor as _Subtensor +from bittensor.core.async_subtensor import AsyncSubtensor as _AsyncSubtensor + + +class Wallets: + """Class for managing coldkey, hotkey, wallet operations.""" + + def __init__(self, subtensor: Union["_Subtensor", "_AsyncSubtensor"]): + self.does_hotkey_exist = subtensor.does_hotkey_exist + self.filter_netuids_by_registered_hotkeys = ( + subtensor.filter_netuids_by_registered_hotkeys + ) + self.is_hotkey_registered_any = subtensor.is_hotkey_registered_any + self.is_hotkey_registered = subtensor.is_hotkey_registered + self.is_hotkey_delegate = subtensor.is_hotkey_delegate + self.get_balance = subtensor.get_balance + self.get_balances = subtensor.get_balances + self.get_children = subtensor.get_children + self.get_children_pending = subtensor.get_children_pending + self.get_delegate_by_hotkey = subtensor.get_delegate_by_hotkey + self.get_delegate_take = subtensor.get_delegate_take + self.get_delegated = subtensor.get_delegated + self.get_hotkey_owner = subtensor.get_hotkey_owner + self.get_hotkey_stake = subtensor.get_hotkey_stake + self.get_minimum_required_stake = subtensor.get_minimum_required_stake + self.get_netuids_for_hotkey = subtensor.get_netuids_for_hotkey + self.get_owned_hotkeys = subtensor.get_owned_hotkeys + self.get_stake = subtensor.get_stake + self.get_stake_add_fee = subtensor.get_stake_add_fee + self.get_stake_for_coldkey = subtensor.get_stake_for_coldkey + self.get_stake_for_coldkey_and_hotkey = ( + subtensor.get_stake_for_coldkey_and_hotkey + ) + self.get_stake_for_hotkey = subtensor.get_stake_for_hotkey + self.get_stake_info_for_coldkey = subtensor.get_stake_info_for_coldkey + self.get_stake_movement_fee = subtensor.get_stake_movement_fee + self.get_transfer_fee = subtensor.get_transfer_fee + self.get_unstake_fee = subtensor.get_unstake_fee diff --git a/bittensor/utils/easy_imports.py b/bittensor/utils/easy_imports.py index ea0e72bce9..8fa826d4d9 100644 --- a/bittensor/utils/easy_imports.py +++ b/bittensor/utils/easy_imports.py @@ -104,6 +104,7 @@ from bittensor.core.settings import BLOCKTIME from bittensor.core.stream import StreamingSynapse # noqa: F401 from bittensor.core.subtensor import Subtensor +from bittensor.core.subtensor_api import SubtensorApi # noqa: F401 from bittensor.core.synapse import TerminalInfo, Synapse # noqa: F401 from bittensor.core.tensor import Tensor # noqa: F401 from bittensor.core.threadpool import ( # noqa: F401 diff --git a/pyproject.toml b/pyproject.toml index 7ef6313fcc..fe5d089278 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "uvicorn", "bittensor-drand>=0.5.0", "bittensor-wallet>=3.0.8", - "async-substrate-interface>=1.1.0" + "async-substrate-interface>=1.2.0" ] [project.optional-dependencies] diff --git a/tests/unit_tests/test_subtensor_api.py b/tests/unit_tests/test_subtensor_api.py new file mode 100644 index 0000000000..4078cb5a96 --- /dev/null +++ b/tests/unit_tests/test_subtensor_api.py @@ -0,0 +1,94 @@ +from bittensor.core.subtensor import Subtensor +from bittensor.core.subtensor_api import SubtensorApi +import pytest + + +def test_properties_methods_comparable(other_class: "Subtensor" = None): + """Verifies that methods in SubtensorApi and its properties contains all Subtensors methods.""" + # Preps + subtensor = other_class(_mock=True) if other_class else Subtensor(_mock=True) + subtensor_api = SubtensorApi(mock=True) + + subtensor_methods = [m for m in dir(subtensor) if not m.startswith("_")] + + excluded_subtensor_methods = ["commit"] + + subtensor_api_methods = [m for m in dir(subtensor_api) if not m.startswith("_")] + chain_methods = [m for m in dir(subtensor_api.chain) if not m.startswith("_")] + commitments_methods = [ + m for m in dir(subtensor_api.commitments) if not m.startswith("_") + ] + delegates_methods = [ + m for m in dir(subtensor_api.delegates) if not m.startswith("_") + ] + extrinsics_methods = [ + m for m in dir(subtensor_api.extrinsics) if not m.startswith("_") + ] + metagraphs_methods = [ + m for m in dir(subtensor_api.metagraphs) if not m.startswith("_") + ] + neurons_methods = [m for m in dir(subtensor_api.neurons) if not m.startswith("_")] + queries_methods = [m for m in dir(subtensor_api.queries) if not m.startswith("_")] + stakes_methods = [m for m in dir(subtensor_api.stakes) if not m.startswith("_")] + subnets_methods = [m for m in dir(subtensor_api.subnets) if not m.startswith("_")] + wallets_methods = [m for m in dir(subtensor_api.wallets) if not m.startswith("_")] + + all_subtensor_api_methods = ( + subtensor_api_methods + + chain_methods + + commitments_methods + + delegates_methods + + extrinsics_methods + + metagraphs_methods + + neurons_methods + + queries_methods + + stakes_methods + + subnets_methods + + wallets_methods + ) + + # Assertions + for method in subtensor_methods: + # skipp excluded methods + if method in excluded_subtensor_methods: + continue + assert method in all_subtensor_api_methods, ( + f"`Subtensor.{method}`is not present in class `SubtensorApi`." + ) + + +def test__methods_comparable_with_passed_legacy_methods( + other_class: "Subtensor" = None, +): + """Verifies that methods in SubtensorApi contains all Subtensors methods if `legacy_methods=True` is passed.""" + # Preps + subtensor = other_class(mock=True) if other_class else Subtensor(_mock=True) + subtensor_api = SubtensorApi(mock=True, legacy_methods=True) + + subtensor_methods = [m for m in dir(subtensor) if not m.startswith("_")] + subtensor_api_methods = [m for m in dir(subtensor_api) if not m.startswith("_")] + + excluded_subtensor_methods = ["commit"] + + # Assertions + for method in subtensor_methods: + # skipp excluded methods + if method in excluded_subtensor_methods: + continue + assert method in subtensor_api_methods, ( + f"`Subtensor.{method}`is not present in class `SubtensorApi`." + ) + + +def test_failed_if_subtensor_has_new_method(): + """Verifies that SubtensorApi fails if Subtensor has a new method.""" + # Preps + + class SubtensorWithNewMethod(Subtensor): + def return_my_id(self): + return id(self) + + # Call and assert + + with pytest.raises(AssertionError): + test_properties_methods_comparable(other_class=SubtensorWithNewMethod)