diff --git a/bin/btcli b/bin/btcli deleted file mode 100755 index fa98536a09..0000000000 --- a/bin/btcli +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env python - -import websocket - -import sys -import shtab -from bittensor import cli as btcli -from bittensor import logging as bt_logging - - -def main(): - # Create the parser with shtab support - parser = btcli.__create_parser__() - args, unknown = parser.parse_known_args() - - if args.print_completion: # Check for print-completion argument - print(shtab.complete(parser, args.print_completion)) - return - - try: - cli_instance = btcli(args=sys.argv[1:]) - cli_instance.run() - except KeyboardInterrupt: - print('KeyboardInterrupt') - except RuntimeError as e: - bt_logging.error(f'RuntimeError: {e}') - except websocket.WebSocketConnectionClosedException as e: - bt_logging.error(f'Subtensor related error. WebSocketConnectionClosedException: {e}') - - -if __name__ == '__main__': - main() - -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 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. diff --git a/bittensor/__init__.py b/bittensor/__init__.py index 6f6bbc1c70..ceb7979438 100644 --- a/bittensor/__init__.py +++ b/bittensor/__init__.py @@ -18,46 +18,9 @@ import os import warnings -from rich.console import Console -from rich.traceback import install - - -if (NEST_ASYNCIO_ENV := os.getenv("NEST_ASYNCIO")) in ("1", None): - if NEST_ASYNCIO_ENV is None: - warnings.warn( - "NEST_ASYNCIO implicitly set to '1'. In the future, the default value will be '0'." - "If you use `nest_asyncio` make sure to add it explicitly to your project dependencies," - "as it will be removed from `bittensor` package dependencies in the future." - "To silence this warning, explicitly set the environment variable, e.g. `export NEST_ASYNCIO=0`.", - DeprecationWarning, - ) - # Install and apply nest asyncio to allow the async functions - # to run in a .ipynb - import nest_asyncio - - nest_asyncio.apply() - - -# Bittensor code and protocol version. -__version__ = "7.3.0" - -_version_split = __version__.split(".") -__version_info__ = tuple(int(part) for part in _version_split) -_version_int_base = 1000 -assert max(__version_info__) < _version_int_base - -__version_as_int__: int = sum( - e * (_version_int_base**i) for i, e in enumerate(reversed(__version_info__)) -) -assert __version_as_int__ < 2**31 # fits in int32 -__new_signature_version__ = 360 - -# Rich console. -__console__ = Console() -__use_console__ = True - -# Remove overdue locals in debug training. -install(show_locals=False) +from .core.settings import __version__, version_split, defaults +from .utils.backwards_compatibility import * +from .utils.btlogging import logging def __getattr__(name): @@ -66,29 +29,10 @@ def __getattr__(name): "version_split is deprecated and will be removed in future versions. Use __version__ instead.", DeprecationWarning, ) - return _version_split + return version_split raise AttributeError(f"module {__name__} has no attribute {name}") -def turn_console_off(): - global __use_console__ - global __console__ - from io import StringIO - - __use_console__ = False - __console__ = Console(file=StringIO(), stderr=False) - - -def turn_console_on(): - global __use_console__ - global __console__ - __use_console__ = True - __console__ = Console() - - -turn_console_off() - - # Logging helpers. def trace(on: bool = True): logging.set_trace(on) @@ -98,290 +42,26 @@ def debug(on: bool = True): logging.set_debug(on) -# Substrate chain block time (seconds). -__blocktime__ = 12 - -# Pip address for versioning -__pipaddress__ = "https://pypi.org/pypi/bittensor/json" - -# Raw GitHub url for delegates registry file -__delegates_details_url__: str = "https://raw.githubusercontent.com/opentensor/bittensor-delegates/main/public/delegates.json" - -# Substrate ss58_format -__ss58_format__ = 42 - -# Wallet ss58 address length -__ss58_address_length__ = 48 - -__networks__ = ["local", "finney", "test", "archive"] - -__finney_entrypoint__ = "wss://entrypoint-finney.opentensor.ai:443" - -__finney_test_entrypoint__ = "wss://test.finney.opentensor.ai:443/" - -__archive_entrypoint__ = "wss://archive.chain.opentensor.ai:443/" - -# Needs to use wss:// -__bellagene_entrypoint__ = "wss://parachain.opentensor.ai:443" - -if ( - BT_SUBTENSOR_CHAIN_ENDPOINT := os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT") -) is not None: - __local_entrypoint__ = BT_SUBTENSOR_CHAIN_ENDPOINT -else: - __local_entrypoint__ = "ws://127.0.0.1:9944" - -__tao_symbol__: str = chr(0x03C4) - -__rao_symbol__: str = chr(0x03C1) - -# Block Explorers map network to explorer url -# Must all be polkadotjs explorer urls -__network_explorer_map__ = { - "opentensor": { - "local": "https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fentrypoint-finney.opentensor.ai%3A443#/explorer", - "endpoint": "https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fentrypoint-finney.opentensor.ai%3A443#/explorer", - "finney": "https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fentrypoint-finney.opentensor.ai%3A443#/explorer", - }, - "taostats": { - "local": "https://x.taostats.io", - "endpoint": "https://x.taostats.io", - "finney": "https://x.taostats.io", - }, -} - -# --- Type Registry --- -__type_registry__ = { - "types": { - "Balance": "u64", # Need to override default u128 - }, - "runtime_api": { - "NeuronInfoRuntimeApi": { - "methods": { - "get_neuron_lite": { - "params": [ - { - "name": "netuid", - "type": "u16", - }, - { - "name": "uid", - "type": "u16", - }, - ], - "type": "Vec", - }, - "get_neurons_lite": { - "params": [ - { - "name": "netuid", - "type": "u16", - }, - ], - "type": "Vec", - }, - } - }, - "StakeInfoRuntimeApi": { - "methods": { - "get_stake_info_for_coldkey": { - "params": [ - { - "name": "coldkey_account_vec", - "type": "Vec", - }, - ], - "type": "Vec", - }, - "get_stake_info_for_coldkeys": { - "params": [ - { - "name": "coldkey_account_vecs", - "type": "Vec>", - }, - ], - "type": "Vec", - }, - }, - }, - "ValidatorIPRuntimeApi": { - "methods": { - "get_associated_validator_ip_info_for_subnet": { - "params": [ - { - "name": "netuid", - "type": "u16", - }, - ], - "type": "Vec", - }, - }, - }, - "SubnetInfoRuntimeApi": { - "methods": { - "get_subnet_hyperparams": { - "params": [ - { - "name": "netuid", - "type": "u16", - }, - ], - "type": "Vec", - } - } - }, - "SubnetRegistrationRuntimeApi": { - "methods": {"get_network_registration_cost": {"params": [], "type": "u64"}} - }, - "ColdkeySwapRuntimeApi": { - "methods": { - "get_scheduled_coldkey_swap": { - "params": [ - { - "name": "coldkey_account_vec", - "type": "Vec", - }, - ], - "type": "Vec", - }, - "get_remaining_arbitration_period": { - "params": [ - { - "name": "coldkey_account_vec", - "type": "Vec", - }, - ], - "type": "Vec", - }, - "get_coldkey_swap_destinations": { - "params": [ - { - "name": "coldkey_account_vec", - "type": "Vec", - }, - ], - "type": "Vec", - }, - } - }, - }, -} - -from .errors import ( - BlacklistedException, - ChainConnectionError, - ChainError, - ChainQueryError, - ChainTransactionError, - IdentityError, - InternalServerError, - InvalidRequestNameError, - MetadataError, - NominationError, - NotDelegateError, - NotRegisteredError, - NotVerifiedException, - PostProcessException, - PriorityException, - RegistrationError, - RunException, - StakeError, - SynapseDendriteNoneException, - SynapseParsingError, - TransferError, - UnknownSynapseError, - UnstakeError, -) - -from bittensor_wallet.errors import KeyFileError - -from substrateinterface import Keypair # noqa: F401 -from bittensor_wallet.config import ( - InvalidConfigFile, - DefaultConfig, - Config as config, - T, -) -from bittensor_wallet.keyfile import ( - serialized_keypair_to_keyfile_data, - deserialize_keypair_from_keyfile_data, - validate_password, - ask_password_to_encrypt, - keyfile_data_is_encrypted_nacl, - keyfile_data_is_encrypted_ansible, - keyfile_data_is_encrypted_legacy, - keyfile_data_is_encrypted, - keyfile_data_encryption_method, - legacy_encrypt_keyfile_data, - encrypt_keyfile_data, - get_coldkey_password_from_environment, - decrypt_keyfile_data, - Keyfile as keyfile, -) -from bittensor_wallet.wallet import display_mnemonic_msg, Wallet as wallet - -from .utils import ( - ss58_to_vec_u8, - unbiased_topk, - version_checking, - strtobool, - strtobool_with_default, - get_explorer_root_url_by_network_from_map, - get_explorer_root_url_by_network_from_map, - get_explorer_url_for_network, - ss58_address_to_bytes, - U16_NORMALIZED_FLOAT, - U64_NORMALIZED_FLOAT, - u8_key_to_ss58, - hash, - wallet_utils, -) - -from .utils.balance import Balance as Balance -from .chain_data import ( - AxonInfo, - NeuronInfo, - NeuronInfoLite, - PrometheusInfo, - DelegateInfo, - StakeInfo, - SubnetInfo, - SubnetHyperparameters, - IPInfo, - ProposalCallData, - ProposalVoteData, -) - -# Allows avoiding name spacing conflicts and continue access to the `subtensor` module with `subtensor_module` name -from . import subtensor as subtensor_module - -# Double import allows using class `Subtensor` by referencing `bittensor.Subtensor` and `bittensor.subtensor`. -# This will be available for a while until we remove reference `bittensor.subtensor` -from .subtensor import Subtensor -from .subtensor import Subtensor as subtensor - -from .cli import cli as cli, COMMANDS as ALL_COMMANDS -from .btlogging import logging -from .metagraph import metagraph as metagraph -from .threadpool import PriorityThreadPoolExecutor as PriorityThreadPoolExecutor +def __apply_nest_asyncio(): + """ + Apply nest_asyncio if the environment variable NEST_ASYNCIO is set to "1" or not set. + If not set, warn the user that the default will change in the future. + """ + nest_asyncio_env = os.getenv("NEST_ASYNCIO") -from .synapse import TerminalInfo, Synapse -from .stream import StreamingSynapse -from .tensor import tensor, Tensor -from .axon import axon as axon -from .dendrite import dendrite as dendrite + if nest_asyncio_env == "1" or nest_asyncio_env is None: + if nest_asyncio_env is None: + warnings.warn( + """NEST_ASYNCIO implicitly set to '1'. In the future, the default value will be '0'. + If you use `nest_asyncio`, make sure to add it explicitly to your project dependencies, + as it will be removed from `bittensor` package dependencies in the future. + To silence this warning, explicitly set the environment variable, e.g. `export NEST_ASYNCIO=0`.""", + DeprecationWarning, + ) + # Install and apply nest asyncio to allow the async functions to run in a .ipynb + import nest_asyncio -from .mock.keyfile_mock import MockKeyfile as MockKeyfile -from .mock.subtensor_mock import MockSubtensor as MockSubtensor -from .mock.wallet_mock import MockWallet as MockWallet + nest_asyncio.apply() -from .subnets import SubnetsAPI as SubnetsAPI -configs = [ - axon.config(), - subtensor.config(), - PriorityThreadPoolExecutor.config(), - wallet.config(), - logging.get_config(), -] -defaults = config.merge_all(configs) +__apply_nest_asyncio() diff --git a/bittensor/api/__init__.py b/bittensor/api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bittensor/extrinsics/__init__.py b/bittensor/api/extrinsics/__init__.py similarity index 95% rename from bittensor/extrinsics/__init__.py rename to bittensor/api/extrinsics/__init__.py index 5780b2ee82..640a132503 100644 --- a/bittensor/extrinsics/__init__.py +++ b/bittensor/api/extrinsics/__init__.py @@ -1,14 +1,14 @@ # The MIT License (MIT) -# Copyright © 2023 Opentensor Foundation - +# 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 diff --git a/bittensor/extrinsics/commit_weights.py b/bittensor/api/extrinsics/commit_weights.py similarity index 82% rename from bittensor/extrinsics/commit_weights.py rename to bittensor/api/extrinsics/commit_weights.py index a9192952ef..e9f545d592 100644 --- a/bittensor/extrinsics/commit_weights.py +++ b/bittensor/api/extrinsics/commit_weights.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 @@ -18,18 +17,22 @@ """Module commit weights and reveal weights extrinsic.""" -from typing import Tuple, List +from typing import Tuple, List, TYPE_CHECKING +from bittensor_wallet import Wallet from rich.prompt import Confirm -import bittensor - from bittensor.utils import format_error_message +from bittensor.utils.btlogging import logging + +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor def commit_weights_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", netuid: int, commit_hash: str, wait_for_inclusion: bool = False, @@ -39,6 +42,7 @@ def commit_weights_extrinsic( """ Commits a hash of the neuron's weights to the Bittensor blockchain using the provided wallet. This function is a wrapper around the `_do_commit_weights` method, handling user prompts and error messages. + Args: subtensor (bittensor.subtensor): The subtensor instance used for blockchain interaction. wallet (bittensor.wallet): The wallet associated with the neuron committing the weights. @@ -47,11 +51,11 @@ def commit_weights_extrinsic( wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. + Returns: - Tuple[bool, str]: ``True`` if the weight commitment is successful, False otherwise. And `msg`, a string - value describing the success or potential error. - This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper - error handling and user interaction when required. + Tuple[bool, str]: ``True`` if the weight commitment is successful, False otherwise. And `msg`, a string value describing the success or potential error. + + This function provides a user-friendly interface for committing weights to the Bittensor blockchain, ensuring proper error handling and user interaction when required. """ if prompt and not Confirm.ask(f"Would you like to commit weights?"): return False, "User cancelled the operation." @@ -65,16 +69,16 @@ def commit_weights_extrinsic( ) if success: - bittensor.logging.info("Successfully committed weights.") + logging.info("Successfully committed weights.") return True, "Successfully committed weights." else: - bittensor.logging.error(f"Failed to commit weights: {error_message}") + logging.error(f"Failed to commit weights: {error_message}") return False, format_error_message(error_message) def reveal_weights_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", netuid: int, uids: List[int], weights: List[int], @@ -87,6 +91,7 @@ def reveal_weights_extrinsic( """ Reveals the weights for a specific subnet on the Bittensor blockchain using the provided wallet. This function is a wrapper around the `_do_reveal_weights` method, handling user prompts and error messages. + Args: subtensor (bittensor.subtensor): The subtensor instance used for blockchain interaction. wallet (bittensor.wallet): The wallet associated with the neuron revealing the weights. @@ -98,11 +103,11 @@ def reveal_weights_extrinsic( wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. + Returns: - Tuple[bool, str]: ``True`` if the weight revelation is successful, False otherwise. And `msg`, a string - value describing the success or potential error. - This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper - error handling and user interaction when required. + Tuple[bool, str]: ``True`` if the weight revelation is successful, False otherwise. And `msg`, a string value describing the success or potential error. + + This function provides a user-friendly interface for revealing weights on the Bittensor blockchain, ensuring proper error handling and user interaction when required. """ if prompt and not Confirm.ask(f"Would you like to reveal weights?"): @@ -120,8 +125,8 @@ def reveal_weights_extrinsic( ) if success: - bittensor.logging.info("Successfully revealed weights.") + logging.info("Successfully revealed weights.") return True, "Successfully revealed weights." else: - bittensor.logging.error(f"Failed to reveal weights: {error_message}") + logging.error(f"Failed to reveal weights: {error_message}") return False, error_message diff --git a/bittensor/extrinsics/delegation.py b/bittensor/api/extrinsics/delegation.py similarity index 71% rename from bittensor/extrinsics/delegation.py rename to bittensor/api/extrinsics/delegation.py index 54bdb5273c..8b0aeb3610 100644 --- a/bittensor/extrinsics/delegation.py +++ b/bittensor/api/extrinsics/delegation.py @@ -1,48 +1,55 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 logging -import bittensor -from ..errors import ( +from typing import Union, Optional, TYPE_CHECKING + +from bittensor_wallet import Wallet +from rich.prompt import Confirm + +from bittensor.core.errors import ( NominationError, NotDelegateError, NotRegisteredError, StakeError, TakeError, ) -from rich.prompt import Confirm -from typing import Union, Optional +from bittensor.core.settings import bt_console from bittensor.utils.balance import Balance -from bittensor.btlogging.defines import BITTENSOR_LOGGER_NAME +from bittensor.utils.btlogging import logging -logger = logging.getLogger(BITTENSOR_LOGGER_NAME) +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor def nominate_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", wait_for_finalization: bool = False, wait_for_inclusion: bool = True, ) -> bool: - r"""Becomes a delegate for the hotkey. + """Becomes a delegate for the hotkey. Args: - wallet (bittensor.wallet): The wallet to become a delegate for. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): The wallet to become a delegate for. + wait_for_inclusion: + wait_for_finalization: + Returns: success (bool): ``True`` if the transaction was successful. """ @@ -52,12 +59,20 @@ def nominate_extrinsic( # Check if the hotkey is already a delegate. if subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address): - logger.error( + logging.error( "Hotkey {} is already a delegate.".format(wallet.hotkey.ss58_address) ) return False - with bittensor.__console__.status( + if not subtensor.is_hotkey_registered_any(wallet.hotkey.ss58_address): + logging.error( + "Hotkey {} is not registered to any network".format( + wallet.hotkey.ss58_address + ) + ) + return False + + with bt_console.status( ":satellite: Sending nominate call on [white]{}[/white] ...".format( subtensor.network ) @@ -70,10 +85,8 @@ def nominate_extrinsic( ) if success is True: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - bittensor.logging.success( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + logging.success( prefix="Become Delegate", suffix="Finalized: " + str(success), ) @@ -82,41 +95,35 @@ def nominate_extrinsic( return success except Exception as e: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error:{}".format(e) - ) - bittensor.logging.warning( - prefix="Set weights", suffix="Failed: " + str(e) - ) + bt_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(e)) + logging.warning(prefix="Set weights", suffix="Failed: " + str(e)) except NominationError as e: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error:{}".format(e) - ) - bittensor.logging.warning( - prefix="Set weights", suffix="Failed: " + str(e) - ) + bt_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(e)) + logging.warning(prefix="Set weights", suffix="Failed: " + str(e)) return False def delegate_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", delegate_ss58: Optional[str] = None, amount: Optional[Union[Balance, float]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, prompt: bool = False, ) -> bool: - r"""Delegates the specified amount of stake to the passed delegate. + """Delegates the specified amount of stake to the passed delegate. Args: - wallet (bittensor.wallet): Bittensor wallet object. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. delegate_ss58 (Optional[str]): The ``ss58`` address of the delegate. amount (Union[Balance, float]): Amount to stake as bittensor balance, or ``float`` interpreted as Tao. wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. @@ -137,24 +144,24 @@ def delegate_extrinsic( coldkey_ss58=wallet.coldkeypub.ss58_address, hotkey_ss58=delegate_ss58 ) - # Convert to bittensor.Balance + # Convert to Balance if amount is None: # Stake it all. - staking_balance = bittensor.Balance.from_tao(my_prev_coldkey_balance.tao) - elif not isinstance(amount, bittensor.Balance): - staking_balance = bittensor.Balance.from_tao(amount) + staking_balance = Balance.from_tao(my_prev_coldkey_balance.tao) + elif not isinstance(amount, Balance): + staking_balance = Balance.from_tao(amount) else: staking_balance = amount # Remove existential balance to keep key alive. - if staking_balance > bittensor.Balance.from_rao(1000): - staking_balance = staking_balance - bittensor.Balance.from_rao(1000) + if staking_balance > Balance.from_rao(1000): + staking_balance = staking_balance - Balance.from_rao(1000) else: staking_balance = staking_balance # Check enough balance to stake. if staking_balance > my_prev_coldkey_balance: - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Not enough balance[/red]:[bold white]\n balance:{}\n amount: {}\n coldkey: {}[/bold white]".format( my_prev_coldkey_balance, staking_balance, wallet.name ) @@ -171,7 +178,7 @@ def delegate_extrinsic( return False try: - with bittensor.__console__.status( + with bt_console.status( ":satellite: Staking to: [bold white]{}[/bold white] ...".format( subtensor.network ) @@ -189,10 +196,8 @@ def delegate_extrinsic( if not wait_for_finalization and not wait_for_inclusion: return True - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - with bittensor.__console__.status( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + with bt_console.status( ":satellite: Checking Balance on: [white]{}[/white] ...".format( subtensor.network ) @@ -205,53 +210,53 @@ def delegate_extrinsic( block=block, ) # Get current stake - bittensor.__console__.print( + bt_console.print( "Balance:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( my_prev_coldkey_balance, new_balance ) ) - bittensor.__console__.print( + bt_console.print( "Stake:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( my_prev_delegated_stake, new_delegate_stake ) ) return True else: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: Error unknown." - ) + bt_console.print(":cross_mark: [red]Failed[/red]: Error unknown.") return False - except NotRegisteredError as e: - bittensor.__console__.print( + except NotRegisteredError: + bt_console.print( ":cross_mark: [red]Hotkey: {} is not registered.[/red]".format( wallet.hotkey_str ) ) return False except StakeError as e: - bittensor.__console__.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) + bt_console.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) return False def undelegate_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", delegate_ss58: Optional[str] = None, amount: Optional[Union[Balance, float]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, prompt: bool = False, ) -> bool: - r"""Un-delegates stake from the passed delegate. + """Un-delegates stake from the passed delegate. Args: - wallet (bittensor.wallet): Bittensor wallet object. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. delegate_ss58 (Optional[str]): The ``ss58`` address of the delegate. amount (Union[Balance, float]): Amount to unstake as bittensor balance, or ``float`` interpreted as Tao. wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. @@ -272,20 +277,20 @@ def undelegate_extrinsic( coldkey_ss58=wallet.coldkeypub.ss58_address, hotkey_ss58=delegate_ss58 ) - # Convert to bittensor.Balance + # Convert to Balance if amount is None: # Stake it all. - unstaking_balance = bittensor.Balance.from_tao(my_prev_delegated_stake.tao) + unstaking_balance = Balance.from_tao(my_prev_delegated_stake.tao) - elif not isinstance(amount, bittensor.Balance): - unstaking_balance = bittensor.Balance.from_tao(amount) + elif not isinstance(amount, Balance): + unstaking_balance = Balance.from_tao(amount) else: unstaking_balance = amount # Check enough stake to unstake. if unstaking_balance > my_prev_delegated_stake: - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Not enough delegated stake[/red]:[bold white]\n stake:{}\n amount: {}\n coldkey: {}[/bold white]".format( my_prev_delegated_stake, unstaking_balance, wallet.name ) @@ -302,7 +307,7 @@ def undelegate_extrinsic( return False try: - with bittensor.__console__.status( + with bt_console.status( ":satellite: Unstaking from: [bold white]{}[/bold white] ...".format( subtensor.network ) @@ -320,10 +325,8 @@ def undelegate_extrinsic( if not wait_for_finalization and not wait_for_inclusion: return True - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - with bittensor.__console__.status( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + with bt_console.status( ":satellite: Checking Balance on: [white]{}[/white] ...".format( subtensor.network ) @@ -336,52 +339,51 @@ def undelegate_extrinsic( block=block, ) # Get current stake - bittensor.__console__.print( + bt_console.print( "Balance:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( my_prev_coldkey_balance, new_balance ) ) - bittensor.__console__.print( + bt_console.print( "Stake:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( my_prev_delegated_stake, new_delegate_stake ) ) return True else: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: Error unknown." - ) + bt_console.print(":cross_mark: [red]Failed[/red]: Error unknown.") return False - except NotRegisteredError as e: - bittensor.__console__.print( + except NotRegisteredError: + bt_console.print( ":cross_mark: [red]Hotkey: {} is not registered.[/red]".format( wallet.hotkey_str ) ) return False except StakeError as e: - bittensor.__console__.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) + bt_console.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) return False def decrease_take_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", hotkey_ss58: Optional[str] = None, take: int = 0, wait_for_finalization: bool = False, wait_for_inclusion: bool = True, ) -> bool: - r"""Decrease delegate take for the hotkey. + """Decrease delegate take for the hotkey. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - hotkey_ss58 (Optional[str]): - The ``ss58`` address of the hotkey account to stake to defaults to the wallet's hotkey. - take (float): - The ``take`` of the hotkey. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + hotkey_ss58 (Optional[str]): The ``ss58`` address of the hotkey account to stake to defaults to the wallet's hotkey. + take (float): The ``take`` of the hotkey. + wait_for_inclusion: + wait_for_finalization: + Returns: success (bool): ``True`` if the transaction was successful. """ @@ -389,7 +391,7 @@ def decrease_take_extrinsic( wallet.coldkey wallet.hotkey - with bittensor.__console__.status( + with bt_console.status( ":satellite: Sending decrease_take_extrinsic call on [white]{}[/white] ...".format( subtensor.network ) @@ -404,10 +406,8 @@ def decrease_take_extrinsic( ) if success is True: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - bittensor.logging.success( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + logging.success( prefix="Decrease Delegate Take", suffix="Finalized: " + str(success), ) @@ -415,33 +415,30 @@ def decrease_take_extrinsic( return success except (TakeError, Exception) as e: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error:{}".format(e) - ) - bittensor.logging.warning( - prefix="Set weights", suffix="Failed: " + str(e) - ) + bt_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(e)) + logging.warning(prefix="Set weights", suffix="Failed: " + str(e)) return False def increase_take_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", hotkey_ss58: Optional[str] = None, take: int = 0, wait_for_finalization: bool = False, wait_for_inclusion: bool = True, ) -> bool: - r"""Increase delegate take for the hotkey. + """Increase delegate take for the hotkey. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - hotkey_ss58 (Optional[str]): - The ``ss58`` address of the hotkey account to stake to defaults to the wallet's hotkey. - take (float): - The ``take`` of the hotkey. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + hotkey_ss58 (Optional[str]): The ``ss58`` address of the hotkey account to stake to defaults to the wallet's hotkey. + take (float): The ``take`` of the hotkey. + wait_for_inclusion: + wait_for_finalization: + Returns: success (bool): ``True`` if the transaction was successful. """ @@ -449,7 +446,7 @@ def increase_take_extrinsic( wallet.coldkey wallet.hotkey - with bittensor.__console__.status( + with bt_console.status( ":satellite: Sending increase_take_extrinsic call on [white]{}[/white] ...".format( subtensor.network ) @@ -464,10 +461,8 @@ def increase_take_extrinsic( ) if success is True: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - bittensor.logging.success( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + logging.success( prefix="Increase Delegate Take", suffix="Finalized: " + str(success), ) @@ -475,18 +470,10 @@ def increase_take_extrinsic( return success except Exception as e: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error:{}".format(e) - ) - bittensor.logging.warning( - prefix="Set weights", suffix="Failed: " + str(e) - ) + bt_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(e)) + logging.warning(prefix="Set weights", suffix="Failed: " + str(e)) except TakeError as e: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error:{}".format(e) - ) - bittensor.logging.warning( - prefix="Set weights", suffix="Failed: " + str(e) - ) + bt_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(e)) + logging.warning(prefix="Set weights", suffix="Failed: " + str(e)) return False diff --git a/bittensor/extrinsics/network.py b/bittensor/api/extrinsics/network.py similarity index 75% rename from bittensor/extrinsics/network.py rename to bittensor/api/extrinsics/network.py index 16cbc0ed26..4703cb285e 100644 --- a/bittensor/extrinsics/network.py +++ b/bittensor/api/extrinsics/network.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 @@ -17,13 +16,20 @@ # DEALINGS IN THE SOFTWARE. import time +from typing import TYPE_CHECKING import substrateinterface +from bittensor_wallet import Wallet from rich.prompt import Confirm -import bittensor +from bittensor.api.extrinsics.utils import HYPERPARAMS +from bittensor.core.settings import bt_console from bittensor.utils import format_error_message -from ..commands.network import HYPERPARAMS +from bittensor.utils.balance import Balance + +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor def _find_event_attributes_in_extrinsic_receipt( @@ -50,38 +56,36 @@ def _find_event_attributes_in_extrinsic_receipt( def register_subnetwork_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: - r"""Registers a new subnetwork. + """Registers a new subnetwork. Args: - wallet (bittensor.wallet): - bittensor wallet object. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If true, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (bittensor.wallet): bittensor wallet object. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If true, the call waits for confirmation from the user before proceeding. + Returns: success (bool): Flag is ``true`` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ your_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - burn_cost = bittensor.utils.balance.Balance(subtensor.get_subnet_burn_cost()) + burn_cost = Balance(subtensor.get_subnet_burn_cost()) if burn_cost > your_balance: - bittensor.__console__.print( + bt_console.print( f"Your balance of: [green]{your_balance}[/green] is not enough to pay the subnet lock cost of: [green]{burn_cost}[/green]" ) return False if prompt: - bittensor.__console__.print(f"Your balance is: [green]{your_balance}[/green]") + bt_console.print(f"Your balance is: [green]{your_balance}[/green]") if not Confirm.ask( f"Do you want to register a subnet for [green]{ burn_cost }[/green]?" ): @@ -89,7 +93,7 @@ def register_subnetwork_extrinsic( wallet.coldkey # unlock coldkey - with bittensor.__console__.status(":satellite: Registering subnet..."): + with bt_console.status(":satellite: Registering subnet..."): with subtensor.substrate as substrate: # create extrinsic call call = substrate.compose_call( @@ -113,7 +117,7 @@ def register_subnetwork_extrinsic( # process if registration successful response.process_events() if not response.is_success: - bittensor.__console__.print( + bt_console.print( f":cross_mark: [red]Failed[/red]: {format_error_message(response.error_message)}" ) time.sleep(0.5) @@ -123,15 +127,15 @@ def register_subnetwork_extrinsic( attributes = _find_event_attributes_in_extrinsic_receipt( response, "NetworkAdded" ) - bittensor.__console__.print( + bt_console.print( f":white_heavy_check_mark: [green]Registered subnetwork with netuid: {attributes[0]}[/green]" ) return True def set_hyperparameter_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", netuid: int, parameter: str, value, @@ -139,30 +143,25 @@ def set_hyperparameter_extrinsic( wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: - r"""Sets a hyperparameter for a specific subnetwork. + """Sets a hyperparameter for a specific subnetwork. Args: - wallet (bittensor.wallet): - bittensor wallet object. - netuid (int): - Subnetwork ``uid``. - parameter (str): - Hyperparameter name. - value (any): - New hyperparameter value. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (bittensor.wallet): bittensor wallet object. + netuid (int): Subnetwork ``uid``. + parameter (str): Hyperparameter name. + value (any): New hyperparameter value. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: success (bool): Flag is ``true`` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ if subtensor.get_subnet_owner(netuid) != wallet.coldkeypub.ss58_address: - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]This wallet doesn't own the specified subnet.[/red]" ) return False @@ -171,12 +170,10 @@ def set_hyperparameter_extrinsic( extrinsic = HYPERPARAMS.get(parameter) if extrinsic is None: - bittensor.__console__.print( - ":cross_mark: [red]Invalid hyperparameter specified.[/red]" - ) + bt_console.print(":cross_mark: [red]Invalid hyperparameter specified.[/red]") return False - with bittensor.__console__.status( + with bt_console.status( f":satellite: Setting hyperparameter {parameter} to {value} on subnet: {netuid} ..." ): with subtensor.substrate as substrate: @@ -231,14 +228,14 @@ def set_hyperparameter_extrinsic( # process if registration successful response.process_events() if not response.is_success: - bittensor.__console__.print( + bt_console.print( f":cross_mark: [red]Failed[/red]: {format_error_message(response.error_message)}" ) time.sleep(0.5) # Successful registration, final check for membership else: - bittensor.__console__.print( + bt_console.print( f":white_heavy_check_mark: [green]Hyper parameter {parameter} changed to {value}[/green]" ) return True diff --git a/bittensor/extrinsics/prometheus.py b/bittensor/api/extrinsics/prometheus.py similarity index 69% rename from bittensor/extrinsics/prometheus.py rename to bittensor/api/extrinsics/prometheus.py index 97f7c17714..6bf58a8821 100644 --- a/bittensor/extrinsics/prometheus.py +++ b/bittensor/api/extrinsics/prometheus.py @@ -1,69 +1,69 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 bittensor - import json -import bittensor.utils.networking as net +from typing import TYPE_CHECKING + +from bittensor_wallet import Wallet + +from bittensor.core.settings import version_as_int, bt_console +from bittensor.core.types import PrometheusServeCallParams +from bittensor.utils import networking as net +from bittensor.utils.btlogging import logging + +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor def prometheus_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", port: int, netuid: int, ip: int = None, wait_for_inclusion: bool = False, wait_for_finalization=True, ) -> bool: - r"""Subscribes an Bittensor endpoint to the substensor chain. + """Subscribes a Bittensor endpoint to the substensor chain. Args: - subtensor (bittensor.subtensor): - Bittensor subtensor object. - wallet (bittensor.wallet): - Bittensor wallet object. - ip (str): - Endpoint host port i.e., ``192.122.31.4``. - port (int): - Endpoint port number i.e., `9221`. - netuid (int): - Network `uid` to serve on. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + subtensor (bittensor.subtensor): Bittensor subtensor object. + wallet (bittensor.wallet): Bittensor wallet object. + ip (str): Endpoint host port i.e., ``192.122.31.4``. + port (int): Endpoint port number i.e., `9221`. + netuid (int): Network `uid` to serve on. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. - If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ - # ---- Get external ip ---- + # Get external ip if ip is None: try: external_ip = net.get_external_ip() - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [green]Found external ip: {}[/green]".format( external_ip ) ) - bittensor.logging.success( + logging.success( prefix="External IP", suffix="{}".format(external_ip) ) except Exception as E: @@ -75,14 +75,14 @@ def prometheus_extrinsic( else: external_ip = ip - call_params: "bittensor.PrometheusServeCallParams" = { - "version": bittensor.__version_as_int__, + call_params: "PrometheusServeCallParams" = { + "version": version_as_int, "ip": net.ip_to_int(external_ip), "port": port, "ip_type": net.ip_version(external_ip), } - with bittensor.__console__.status(":satellite: Checking Prometheus..."): + with bt_console.status(":satellite: Checking Prometheus..."): neuron = subtensor.get_neuron_for_pubkey_and_subnet( wallet.hotkey.ss58_address, netuid=netuid ) @@ -94,7 +94,7 @@ def prometheus_extrinsic( } if neuron_up_to_date: - bittensor.__console__.print( + bt_console.print( f":white_heavy_check_mark: [green]Prometheus already Served[/green]\n" f"[green not bold]- Status: [/green not bold] |" f"[green not bold] ip: [/green not bold][white not bold]{net.int_to_ip(neuron.prometheus_info.ip)}[/white not bold] |" @@ -103,7 +103,7 @@ def prometheus_extrinsic( f"[green not bold] version: [/green not bold][white not bold]{neuron.prometheus_info.version}[/white not bold] |" ) - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [white]Prometheus already served.[/white]".format( external_ip ) @@ -113,7 +113,7 @@ def prometheus_extrinsic( # Add netuid, not in prometheus_info call_params["netuid"] = netuid - with bittensor.__console__.status( + with bt_console.status( ":satellite: Serving prometheus on: [white]{}:{}[/white] ...".format( subtensor.network, netuid ) @@ -127,14 +127,14 @@ def prometheus_extrinsic( if wait_for_inclusion or wait_for_finalization: if success is True: - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [green]Served prometheus[/green]\n [bold white]{}[/bold white]".format( json.dumps(call_params, indent=4, sort_keys=True) ) ) return True else: - bittensor.__console__.print(f":cross_mark: [red]Failed[/red]: {err}") + bt_console.print(f":cross_mark: [red]Failed[/red]: {err}") return False else: return True diff --git a/bittensor/extrinsics/registration.py b/bittensor/api/extrinsics/registration.py similarity index 70% rename from bittensor/extrinsics/registration.py rename to bittensor/api/extrinsics/registration.py index e82add8383..d616eda2dc 100644 --- a/bittensor/extrinsics/registration.py +++ b/bittensor/api/extrinsics/registration.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 @@ -17,13 +16,14 @@ # DEALINGS IN THE SOFTWARE. import time -from typing import List, Union, Optional, Tuple +from typing import List, Union, Optional, Tuple, TYPE_CHECKING +from bittensor_wallet import Wallet from rich.prompt import Confirm -import bittensor +from bittensor.core.settings import bt_console from bittensor.utils import format_error_message - +from bittensor.utils.btlogging import logging from bittensor.utils.registration import ( POWSolution, create_pow, @@ -31,10 +31,14 @@ log_no_torch_error, ) +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor + def register_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -48,53 +52,43 @@ def register_extrinsic( update_interval: Optional[int] = None, log_verbose: bool = False, ) -> bool: - r"""Registers the wallet to the chain. + """Registers the wallet to the chain. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - netuid (int): - The ``netuid`` of the subnet to register on. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. - max_allowed_attempts (int): - Maximum number of attempts to register the wallet. - cuda (bool): - If ``true``, the wallet should be registered using CUDA device(s). - dev_id (Union[List[int], int]): - The CUDA device id to use, or a list of device ids. - tpb (int): - The number of threads per block (CUDA). - num_processes (int): - The number of processes to use to register. - update_interval (int): - The number of nonces to solve between updates. - log_verbose (bool): - If ``true``, the registration process will log more information. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + netuid (int): The ``netuid`` of the subnet to register on. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + max_allowed_attempts (int): Maximum number of attempts to register the wallet. + output_in_place: + cuda (bool): If ``true``, the wallet should be registered using CUDA device(s). + dev_id (Union[List[int], int]): The CUDA device id to use, or a list of device ids. + tpb (int): The number of threads per block (CUDA). + num_processes (int): The number of processes to use to register. + update_interval (int): The number of nonces to solve between updates. + log_verbose (bool): If ``true``, the registration process will log more information. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ if not subtensor.subnet_exists(netuid): - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Failed[/red]: error: [bold white]subnet:{}[/bold white] does not exist.".format( netuid ) ) return False - with bittensor.__console__.status( + with bt_console.status( f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]..." ): neuron = subtensor.get_neuron_for_pubkey_and_subnet( wallet.hotkey.ss58_address, netuid=netuid ) if not neuron.is_null: - bittensor.logging.debug( + logging.debug( f"Wallet {wallet} is already registered on {neuron.netuid} with {neuron.uid}" ) return True @@ -116,14 +110,14 @@ def register_extrinsic( # Attempt rolling registration. attempts = 1 while True: - bittensor.__console__.print( + bt_console.print( ":satellite: Registering...({}/{})".format(attempts, max_allowed_attempts) ) # Solve latest POW. if cuda: if not torch.cuda.is_available(): if prompt: - bittensor.__console__.print("CUDA is not available.") + bt_console.print("CUDA is not available.") return False pow_result: Optional[POWSolution] = create_pow( subtensor, @@ -156,14 +150,14 @@ def register_extrinsic( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - bittensor.__console__.print( + bt_console.print( f":white_heavy_check_mark: [green]Already registered on netuid:{netuid}[/green]" ) return True # pow successful, proceed to submit pow to chain for registration else: - with bittensor.__console__.status(":satellite: Submitting POW..."): + with bt_console.status(":satellite: Submitting POW..."): # check if pow result is still valid while not pow_result.is_stale(subtensor=subtensor): result: Tuple[bool, Optional[str]] = subtensor._do_pow_register( @@ -179,80 +173,75 @@ def register_extrinsic( # Look error here # https://github.com/opentensor/subtensor/blob/development/pallets/subtensor/src/errors.rs if "HotKeyAlreadyRegisteredInSubNet" in err_msg: - bittensor.__console__.print( + bt_console.print( f":white_heavy_check_mark: [green]Already Registered on [bold]subnet:{netuid}[/bold][/green]" ) return True - bittensor.__console__.print( - f":cross_mark: [red]Failed[/red]: {err_msg}" - ) + bt_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") time.sleep(0.5) # Successful registration, final check for neuron and pubkey else: - bittensor.__console__.print(":satellite: Checking Balance...") + bt_console.print(":satellite: Checking Balance...") is_registered = subtensor.is_hotkey_registered( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [green]Registered[/green]" ) return True else: # neuron not found, try again - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Unknown error. Neuron not found.[/red]" ) continue else: # Exited loop because pow is no longer valid. - bittensor.__console__.print("[red]POW is stale.[/red]") + bt_console.print("[red]POW is stale.[/red]") # Try again. continue if attempts < max_allowed_attempts: # Failed registration, retry pow attempts += 1 - bittensor.__console__.print( + bt_console.print( ":satellite: Failed registration, retrying pow ...({}/{})".format( attempts, max_allowed_attempts ) ) else: # Failed to register after max attempts. - bittensor.__console__.print("[red]No more attempts.[/red]") + bt_console.print("[red]No more attempts.[/red]") return False def burned_register_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: - r"""Registers the wallet to chain by recycling TAO. + """Registers the wallet to chain by recycling TAO. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - netuid (int): - The ``netuid`` of the subnet to register on. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + netuid (int): The ``netuid`` of the subnet to register on. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ if not subtensor.subnet_exists(netuid): - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Failed[/red]: error: [bold white]subnet:{}[/bold white] does not exist.".format( netuid ) @@ -260,7 +249,7 @@ def burned_register_extrinsic( return False wallet.coldkey # unlock coldkey - with bittensor.__console__.status( + with bt_console.status( f":satellite: Checking Account on [bold]subnet:{netuid}[/bold]..." ): neuron = subtensor.get_neuron_for_pubkey_and_subnet( @@ -271,7 +260,7 @@ def burned_register_extrinsic( recycle_amount = subtensor.recycle(netuid=netuid) if not neuron.is_null: - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [green]Already Registered[/green]:\n" "uid: [bold white]{}[/bold white]\n" "netuid: [bold white]{}[/bold white]\n" @@ -287,7 +276,7 @@ def burned_register_extrinsic( if not Confirm.ask(f"Recycle {recycle_amount} to register on subnet:{netuid}?"): return False - with bittensor.__console__.status(":satellite: Recycling TAO for Registration..."): + with bt_console.status(":satellite: Recycling TAO for Registration..."): success, err_msg = subtensor._do_burned_register( netuid=netuid, wallet=wallet, @@ -296,18 +285,18 @@ def burned_register_extrinsic( ) if not success: - bittensor.__console__.print(f":cross_mark: [red]Failed[/red]: {err_msg}") + bt_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") time.sleep(0.5) return False # Successful registration, final check for neuron and pubkey else: - bittensor.__console__.print(":satellite: Checking Balance...") + bt_console.print(":satellite: Checking Balance...") block = subtensor.get_current_block() new_balance = subtensor.get_balance( wallet.coldkeypub.ss58_address, block=block ) - bittensor.__console__.print( + bt_console.print( "Balance:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance ) @@ -316,13 +305,11 @@ def burned_register_extrinsic( netuid=netuid, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Registered[/green]" - ) + bt_console.print(":white_heavy_check_mark: [green]Registered[/green]") return True else: # neuron not found, try again - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Unknown error. Neuron not found.[/red]" ) return False @@ -337,8 +324,8 @@ class MaxAttemptsException(Exception): def run_faucet_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, @@ -351,34 +338,25 @@ def run_faucet_extrinsic( update_interval: Optional[int] = None, log_verbose: bool = False, ) -> Tuple[bool, str]: - r"""Runs a continual POW to get a faucet of TAO on the test net. + """Runs a continual POW to get a faucet of TAO on the test net. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - max_allowed_attempts (int): - Maximum number of attempts to register the wallet. - cuda (bool): - If ``true``, the wallet should be registered using CUDA device(s). - dev_id (Union[List[int], int]): - The CUDA device id to use, or a list of device ids. - tpb (int): - The number of threads per block (CUDA). - num_processes (int): - The number of processes to use to register. - update_interval (int): - The number of nonces to solve between updates. - log_verbose (bool): - If ``true``, the registration process will log more information. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + max_allowed_attempts (int): Maximum number of attempts to register the wallet. + cuda (bool): If ``true``, the wallet should be registered using CUDA device(s). + dev_id (Union[List[int], int]): The CUDA device id to use, or a list of device ids. + tpb (int): The number of threads per block (CUDA). + num_processes (int): The number of processes to use to register. + update_interval (int): The number of nonces to solve between updates. + log_verbose (bool): If ``true``, the registration process will log more information. + output_in_place: + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ if prompt: if not Confirm.ask( @@ -410,7 +388,7 @@ def run_faucet_extrinsic( if cuda: if not torch.cuda.is_available(): if prompt: - bittensor.__console__.print("CUDA is not available.") + bt_console.print("CUDA is not available.") return False, "CUDA is not available." pow_result: Optional[POWSolution] = create_pow( subtensor, @@ -456,7 +434,7 @@ def run_faucet_extrinsic( # process if registration successful, try again if pow is still valid response.process_events() if not response.is_success: - bittensor.__console__.print( + bt_console.print( f":cross_mark: [red]Failed[/red]: {format_error_message(response.error_message)}" ) if attempts == max_allowed_attempts: @@ -468,7 +446,7 @@ def run_faucet_extrinsic( # Successful registration else: new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - bittensor.__console__.print( + bt_console.print( f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) old_balance = new_balance @@ -490,9 +468,9 @@ def run_faucet_extrinsic( def swap_hotkey_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", - new_wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", + new_wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, @@ -505,7 +483,7 @@ def swap_hotkey_extrinsic( ): return False - with bittensor.__console__.status(":satellite: Swapping hotkeys..."): + with bt_console.status(":satellite: Swapping hotkeys..."): success, err_msg = subtensor._do_swap_hotkey( wallet=wallet, new_wallet=new_wallet, @@ -514,12 +492,12 @@ def swap_hotkey_extrinsic( ) if not success: - bittensor.__console__.print(f":cross_mark: [red]Failed[/red]: {err_msg}") + bt_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") time.sleep(0.5) return False else: - bittensor.__console__.print( + bt_console.print( f"Hotkey {wallet.hotkey} swapped for new hotkey: {new_wallet.hotkey}" ) return True diff --git a/bittensor/extrinsics/root.py b/bittensor/api/extrinsics/root.py similarity index 60% rename from bittensor/extrinsics/root.py rename to bittensor/api/extrinsics/root.py index 8a7e9e3863..3bd950c557 100644 --- a/bittensor/extrinsics/root.py +++ b/bittensor/api/extrinsics/root.py @@ -1,57 +1,57 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 bittensor - -import time import logging +import time +from typing import Union, List, TYPE_CHECKING + import numpy as np +from bittensor_wallet import Wallet from numpy.typing import NDArray from rich.prompt import Confirm -from typing import Union, List -import bittensor.utils.weight_utils as weight_utils -from bittensor.btlogging.defines import BITTENSOR_LOGGER_NAME + +from bittensor.core.settings import bt_console +from bittensor.utils import weight_utils +from bittensor.utils.btlogging import logging from bittensor.utils.registration import torch, legacy_torch_api_compat -logger = logging.getLogger(BITTENSOR_LOGGER_NAME) +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor def root_register_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: - r"""Registers the wallet to root network. + """Registers the wallet to root network. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (bittensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ wallet.coldkey # unlock coldkey @@ -60,7 +60,7 @@ def root_register_extrinsic( netuid=0, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [green]Already registered on root network.[/green]" ) return True @@ -70,7 +70,7 @@ def root_register_extrinsic( if not Confirm.ask("Register to root network?"): return False - with bittensor.__console__.status(":satellite: Registering to root network..."): + with bt_console.status(":satellite: Registering to root network..."): success, err_msg = subtensor._do_root_register( wallet=wallet, wait_for_inclusion=wait_for_inclusion, @@ -78,7 +78,7 @@ def root_register_extrinsic( ) if not success: - bittensor.__console__.print(f":cross_mark: [red]Failed[/red]: {err_msg}") + bt_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") time.sleep(0.5) # Successful registration, final check for neuron and pubkey @@ -87,21 +87,19 @@ def root_register_extrinsic( netuid=0, hotkey_ss58=wallet.hotkey.ss58_address ) if is_registered: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Registered[/green]" - ) + bt_console.print(":white_heavy_check_mark: [green]Registered[/green]") return True else: # neuron not found, try again - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Unknown error. Neuron not found.[/red]" ) @legacy_torch_api_compat def set_root_weights_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", netuids: Union[NDArray[np.int64], "torch.LongTensor", List[int]], weights: Union[NDArray[np.float32], "torch.FloatTensor", List[float]], version_key: int = 0, @@ -109,26 +107,20 @@ def set_root_weights_extrinsic( wait_for_finalization: bool = False, prompt: bool = False, ) -> bool: - r"""Sets the given weights and values on chain for wallet hotkey account. + """Sets the given weights and values on chain for wallet hotkey account. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - netuids (Union[NDArray[np.int64], torch.LongTensor, List[int]]): - The ``netuid`` of the subnet to set weights for. - weights (Union[NDArray[np.float32], torch.FloatTensor, list]): - Weights to set. These must be ``float`` s and must correspond to the passed ``netuid`` s. - version_key (int): - The version key of the validator. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (bittensor.core.subtensor.Subtensor: The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + netuids (Union[NDArray[np.int64], torch.LongTensor, List[int]]): The ``netuid`` of the subnet to set weights for. + weights (Union[NDArray[np.float32], torch.FloatTensor, list]): Weights to set. These must be ``float`` s and must correspond to the passed ``netuid`` s. + version_key (int): The version key of the validator. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ wallet.coldkey # unlock coldkey @@ -155,10 +147,10 @@ def set_root_weights_extrinsic( ) # Normalize the weights to max value. - formatted_weights = bittensor.utils.weight_utils.normalize_max_weight( + formatted_weights = weight_utils.normalize_max_weight( x=weights, limit=max_weight_limit ) - bittensor.__console__.print( + bt_console.print( f"\nRaw Weights -> Normalized weights: \n\t{weights} -> \n\t{formatted_weights}\n" ) @@ -171,7 +163,7 @@ def set_root_weights_extrinsic( ): return False - with bittensor.__console__.status( + with bt_console.status( ":satellite: Setting root weights on [white]{}[/white] ...".format( subtensor.network ) @@ -190,36 +182,28 @@ def set_root_weights_extrinsic( wait_for_inclusion=wait_for_inclusion, ) - bittensor.__console__.print(success, error_message) + bt_console.print(success, error_message) if not wait_for_finalization and not wait_for_inclusion: return True if success is True: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - bittensor.logging.success( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + logging.success( prefix="Set weights", suffix="Finalized: " + str(success), ) return True else: - bittensor.__console__.print( - f":cross_mark: [red]Failed[/red]: {error_message}" - ) - bittensor.logging.warning( + bt_console.print(f":cross_mark: [red]Failed[/red]: {error_message}") + logging.warning( prefix="Set weights", suffix="Failed: " + str(error_message), ) return False except Exception as e: - # TODO( devs ): lets remove all of the bittensor.__console__ calls and replace with the bittensor logger. - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error:{}".format(e) - ) - bittensor.logging.warning( - prefix="Set weights", suffix="Failed: " + str(e) - ) + # TODO( devs ): lets remove all of the bt_console calls and replace with the bittensor logger. + bt_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(e)) + logging.warning(prefix="Set weights", suffix="Failed: " + str(e)) return False diff --git a/bittensor/extrinsics/senate.py b/bittensor/api/extrinsics/senate.py similarity index 67% rename from bittensor/extrinsics/senate.py rename to bittensor/api/extrinsics/senate.py index 043233996c..9ceebf9ec4 100644 --- a/bittensor/extrinsics/senate.py +++ b/bittensor/api/extrinsics/senate.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 @@ -17,35 +16,39 @@ # DEALINGS IN THE SOFTWARE. import time +from typing import TYPE_CHECKING +from bittensor_wallet import Wallet from rich.prompt import Confirm -import bittensor +from bittensor.core.settings import bt_console from bittensor.utils import format_error_message +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor + def register_senate_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: - r"""Registers the wallet to chain for senate voting. + """Registers the wallet to chain for senate voting. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (bittensor.wallet): Bittensor wallet object. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ + wallet.coldkey # unlock coldkey wallet.hotkey # unlock hotkey @@ -54,7 +57,7 @@ def register_senate_extrinsic( if not Confirm.ask(f"Register delegate hotkey to senate?"): return False - with bittensor.__console__.status(":satellite: Registering with senate..."): + with bt_console.status(":satellite: Registering with senate..."): with subtensor.substrate as substrate: # create extrinsic call call = substrate.compose_call( @@ -78,48 +81,45 @@ def register_senate_extrinsic( # process if registration successful response.process_events() if not response.is_success: - bittensor.__console__.print( + bt_console.print( f":cross_mark: [red]Failed[/red]:{format_error_message(response.error_message)}" ) time.sleep(0.5) # Successful registration, final check for membership else: - is_registered = wallet.is_senate_member(subtensor) + is_registered = subtensor.is_senate_member(wallet.hotkey.ss58_address) if is_registered: - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [green]Registered[/green]" ) return True else: # neuron not found, try again - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Unknown error. Senate membership not found.[/red]" ) def leave_senate_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: - r"""Removes the wallet from chain for senate voting. + """Removes the wallet from chain for senate voting. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (bittensor.wallet): Bittensor wallet object. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ wallet.coldkey # unlock coldkey wallet.hotkey # unlock hotkey @@ -129,7 +129,7 @@ def leave_senate_extrinsic( if not Confirm.ask(f"Remove delegate hotkey from senate?"): return False - with bittensor.__console__.status(":satellite: Leaving senate..."): + with bt_console.status(":satellite: Leaving senate..."): with subtensor.substrate as substrate: # create extrinsic call call = substrate.compose_call( @@ -153,30 +153,30 @@ def leave_senate_extrinsic( # process if registration successful response.process_events() if not response.is_success: - bittensor.__console__.print( + bt_console.print( f":cross_mark: [red]Failed[/red]: {format_error_message(response.error_message)}" ) time.sleep(0.5) # Successful registration, final check for membership else: - is_registered = wallet.is_senate_member(subtensor) + is_registered = subtensor.is_senate_member(wallet.hotkey.ss58_address) if not is_registered: - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [green]Left senate[/green]" ) return True else: # neuron not found, try again - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Unknown error. Senate membership still found.[/red]" ) def vote_senate_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", proposal_hash: str, proposal_idx: int, vote: bool, @@ -184,17 +184,19 @@ def vote_senate_extrinsic( wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: - r"""Votes ayes or nays on proposals. + """ + Votes ayes or nays on proposals. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (bittensor.wallet): Bittensor wallet object. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + vote: + proposal_idx: + proposal_hash: + Returns: success (bool): Flag is ``true`` if extrinsic was finalized or included in the block. If we did not wait for finalization / inclusion, the response is ``true``. @@ -207,7 +209,7 @@ def vote_senate_extrinsic( if not Confirm.ask("Cast a vote of {}?".format(vote)): return False - with bittensor.__console__.status(":satellite: Casting vote.."): + with bt_console.status(":satellite: Casting vote.."): with subtensor.substrate as substrate: # create extrinsic call call = substrate.compose_call( @@ -236,7 +238,7 @@ def vote_senate_extrinsic( # process if vote successful response.process_events() if not response.is_success: - bittensor.__console__.print( + bt_console.print( f":cross_mark: [red]Failed[/red]: {format_error_message(response.error_message)}" ) time.sleep(0.5) @@ -250,12 +252,12 @@ def vote_senate_extrinsic( ) if has_voted: - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [green]Vote cast.[/green]" ) return True else: # hotkey not found in ayes/nays - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Unknown error. Couldn't find vote.[/red]" ) diff --git a/bittensor/extrinsics/serving.py b/bittensor/api/extrinsics/serving.py similarity index 61% rename from bittensor/extrinsics/serving.py rename to bittensor/api/extrinsics/serving.py index bba5367de1..9790c25ede 100644 --- a/bittensor/extrinsics/serving.py +++ b/bittensor/api/extrinsics/serving.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 @@ -17,20 +16,27 @@ # DEALINGS IN THE SOFTWARE. import json -from typing import Optional +from typing import Optional, TYPE_CHECKING +from bittensor_wallet import Wallet from retry import retry from rich.prompt import Confirm -import bittensor -import bittensor.utils.networking as net -from bittensor.utils import format_error_message -from ..errors import MetadataError +from bittensor.core.axon import Axon +from bittensor.core.errors import MetadataError +from bittensor.core.settings import version_as_int, bt_console +from bittensor.core.types import AxonServeCallParams +from bittensor.utils import format_error_message, networking as net +from bittensor.utils.btlogging import logging + +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor def serve_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", ip: str, port: int, protocol: int, @@ -41,37 +47,28 @@ def serve_extrinsic( wait_for_finalization=True, prompt: bool = False, ) -> bool: - r"""Subscribes a Bittensor endpoint to the subtensor chain. + """Subscribes a Bittensor endpoint to the subtensor chain. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - ip (str): - Endpoint host port i.e., ``192.122.31.4``. - port (int): - Endpoint port number i.e., ``9221``. - protocol (int): - An ``int`` representation of the protocol. - netuid (int): - The network uid to serve on. - placeholder1 (int): - A placeholder for future use. - placeholder2 (int): - A placeholder for future use. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance object. + wallet (bittensor.wallet): Bittensor wallet object. + ip (str): Endpoint host port i.e., ``192.122.31.4``. + port (int): Endpoint port number i.e., ``9221``. + protocol (int): An ``int`` representation of the protocol. + netuid (int): The network uid to serve on. + placeholder1 (int): A placeholder for future use. + placeholder2 (int): A placeholder for future use. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ # Decrypt hotkey wallet.hotkey - params: "bittensor.AxonServeCallParams" = { - "version": bittensor.__version_as_int__, + params: "AxonServeCallParams" = { + "version": version_as_int, "ip": net.ip_to_int(ip), "port": port, "ip_type": net.ip_version(ip), @@ -82,7 +79,7 @@ def serve_extrinsic( "placeholder1": placeholder1, "placeholder2": placeholder2, } - bittensor.logging.debug("Checking axon ...") + logging.debug("Checking axon ...") neuron = subtensor.get_neuron_for_pubkey_and_subnet( wallet.hotkey.ss58_address, netuid=netuid ) @@ -102,7 +99,7 @@ def serve_extrinsic( output["coldkey"] = wallet.coldkeypub.ss58_address output["hotkey"] = wallet.hotkey.ss58_address if neuron_up_to_date: - bittensor.logging.debug( + logging.debug( f"Axon already served on: AxonInfo({wallet.hotkey.ss58_address},{ip}:{port}) " ) return True @@ -118,7 +115,7 @@ def serve_extrinsic( ): return False - bittensor.logging.debug( + logging.debug( f"Serving axon with: AxonInfo({wallet.hotkey.ss58_address},{ip}:{port}) -> {subtensor.network}:{netuid}" ) success, error_message = subtensor._do_serve_axon( @@ -130,41 +127,37 @@ def serve_extrinsic( if wait_for_inclusion or wait_for_finalization: if success is True: - bittensor.logging.debug( + logging.debug( f"Axon served with: AxonInfo({wallet.hotkey.ss58_address},{ip}:{port}) on {subtensor.network}:{netuid} " ) return True else: - bittensor.logging.error(f"Failed: {error_message}") + logging.error(f"Failed: {error_message}") return False else: return True def serve_axon_extrinsic( - subtensor: "bittensor.subtensor", + subtensor: "Subtensor", netuid: int, - axon: "bittensor.Axon", + axon: "Axon", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: - r"""Serves the axon to the network. + """Serves the axon to the network. Args: - netuid ( int ): - The ``netuid`` being served on. - axon (bittensor.Axon): - Axon to serve. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (bittensor.core.subtensor.Subtensor): Subtensor instance object. + netuid (int): The ``netuid`` being served on. + axon (bittensor.core.axon.Axon): Axon to serve. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ axon.wallet.hotkey axon.wallet.coldkeypub @@ -174,12 +167,12 @@ def serve_axon_extrinsic( if axon.external_ip is None: try: external_ip = net.get_external_ip() - bittensor.__console__.print( + bt_console.print( ":white_heavy_check_mark: [green]Found external ip: {}[/green]".format( external_ip ) ) - bittensor.logging.success( + logging.success( prefix="External IP", suffix="{}".format(external_ip) ) except Exception as E: @@ -205,8 +198,8 @@ def serve_axon_extrinsic( def publish_metadata( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor, + wallet: "Wallet", netuid: int, data_type: str, data: bytes, @@ -217,28 +210,19 @@ def publish_metadata( Publishes metadata on the Bittensor network using the specified wallet and network identifier. Args: - subtensor (bittensor.subtensor): - The subtensor instance representing the Bittensor blockchain connection. - wallet (bittensor.wallet): - The wallet object used for authentication in the transaction. - netuid (int): - Network UID on which the metadata is to be published. - data_type (str): - The data type of the information being submitted. It should be one of the following: ``'Sha256'``, ``'Blake256'``, ``'Keccak256'``, or ``'Raw0-128'``. This specifies the format or hashing algorithm used for the data. - data (str): - The actual metadata content to be published. This should be formatted or hashed according to the ``type`` specified. (Note: max ``str`` length is 128 bytes) - wait_for_inclusion (bool, optional): - If ``True``, the function will wait for the extrinsic to be included in a block before returning. Defaults to ``False``. - wait_for_finalization (bool, optional): - If ``True``, the function will wait for the extrinsic to be finalized on the chain before returning. Defaults to ``True``. + subtensor (bittensor.subtensor): The subtensor instance representing the Bittensor blockchain connection. + wallet (bittensor.wallet): The wallet object used for authentication in the transaction. + netuid (int): Network UID on which the metadata is to be published. + data_type (str): The data type of the information being submitted. It should be one of the following: ``'Sha256'``, ``'Blake256'``, ``'Keccak256'``, or ``'Raw0-128'``. This specifies the format or hashing algorithm used for the data. + data (str): The actual metadata content to be published. This should be formatted or hashed according to the ``type`` specified. (Note: max ``str`` length is 128 bytes) + wait_for_inclusion (bool, optional): If ``True``, the function will wait for the extrinsic to be included in a block before returning. Defaults to ``False``. + wait_for_finalization (bool, optional): If ``True``, the function will wait for the extrinsic to be finalized on the chain before returning. Defaults to ``True``. Returns: - bool: - ``True`` if the metadata was successfully published (and finalized if specified). ``False`` otherwise. + bool: ``True`` if the metadata was successfully published (and finalized if specified). ``False`` otherwise. Raises: - MetadataError: - If there is an error in submitting the extrinsic or if the response from the blockchain indicates failure. + MetadataError: If there is an error in submitting the extrinsic or if the response from the blockchain indicates failure. """ wallet.hotkey diff --git a/bittensor/extrinsics/set_weights.py b/bittensor/api/extrinsics/set_weights.py similarity index 63% rename from bittensor/extrinsics/set_weights.py rename to bittensor/api/extrinsics/set_weights.py index dc3052d0a0..80a51f763e 100644 --- a/bittensor/extrinsics/set_weights.py +++ b/bittensor/api/extrinsics/set_weights.py @@ -1,38 +1,41 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 bittensor - import logging +from typing import Union, Tuple, TYPE_CHECKING + import numpy as np +from bittensor_wallet import Wallet from numpy.typing import NDArray from rich.prompt import Confirm -from typing import Union, Tuple -import bittensor.utils.weight_utils as weight_utils -from bittensor.btlogging.defines import BITTENSOR_LOGGER_NAME + +from bittensor.utils import weight_utils +from bittensor.core.settings import bt_console +from bittensor.utils.btlogging import logging from bittensor.utils.registration import torch, use_torch -logger = logging.getLogger(BITTENSOR_LOGGER_NAME) +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor def set_weights_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", netuid: int, uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], @@ -41,30 +44,21 @@ def set_weights_extrinsic( wait_for_finalization: bool = False, prompt: bool = False, ) -> Tuple[bool, str]: - r"""Sets the given weights and values on chain for wallet hotkey account. + """Sets the given weights and values on chain for wallet hotkey account. Args: - subtensor (bittensor.subtensor): - Subtensor endpoint to use. - wallet (bittensor.wallet): - Bittensor wallet object. - netuid (int): - The ``netuid`` of the subnet to set weights for. - uids (Union[NDArray[np.int64], torch.LongTensor, list]): - The ``uint64`` uids of destination neurons. - weights (Union[NDArray[np.float32], torch.FloatTensor, list]): - The weights to set. These must be ``float`` s and correspond to the passed ``uid`` s. - version_key (int): - The version key of the validator. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (bittensor.subtensor): Subtensor endpoint to use. + wallet (bittensor.wallet): Bittensor wallet object. + netuid (int): The ``netuid`` of the subnet to set weights for. + uids (Union[NDArray[np.int64], torch.LongTensor, list]): The ``uint64`` uids of destination neurons. + weights (Union[NDArray[np.float32], torch.FloatTensor, list]): The weights to set. These must be ``float`` s and correspond to the passed ``uid`` s. + version_key (int): The version key of the validator. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ # First convert types. if use_torch(): @@ -92,7 +86,7 @@ def set_weights_extrinsic( ): return False, "Prompt refused." - with bittensor.__console__.status( + with bt_console.status( ":satellite: Setting weights on [white]{}[/white] ...".format(subtensor.network) ): try: @@ -110,16 +104,14 @@ def set_weights_extrinsic( return True, "Not waiting for finalization or inclusion." if success is True: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - bittensor.logging.success( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + logging.success( prefix="Set weights", suffix="Finalized: " + str(success), ) return True, "Successfully set weights and Finalized." else: - bittensor.logging.error( + logging.error( msg=error_message, prefix="Set weights", suffix="Failed: ", @@ -127,10 +119,6 @@ def set_weights_extrinsic( return False, error_message except Exception as e: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: error:{}".format(e) - ) - bittensor.logging.warning( - prefix="Set weights", suffix="Failed: " + str(e) - ) + bt_console.print(":cross_mark: [red]Failed[/red]: error:{}".format(e)) + logging.warning(prefix="Set weights", suffix="Failed: " + str(e)) return False, str(e) diff --git a/bittensor/extrinsics/staking.py b/bittensor/api/extrinsics/staking.py similarity index 64% rename from bittensor/extrinsics/staking.py rename to bittensor/api/extrinsics/staking.py index 298bb1f0d3..556a8cd406 100644 --- a/bittensor/extrinsics/staking.py +++ b/bittensor/api/extrinsics/staking.py @@ -1,45 +1,47 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 bittensor from rich.prompt import Confirm from time import sleep -from typing import List, Union, Optional, Tuple +from typing import List, Union, Optional, Tuple, TYPE_CHECKING +from bittensor.core.errors import NotDelegateError, StakeError, NotRegisteredError +from bittensor.core.settings import __console__ as bt_console from bittensor.utils.balance import Balance +from bittensor_wallet import Wallet + +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor def _check_threshold_amount( - subtensor: "bittensor.subtensor", stake_balance: Balance -) -> Tuple[bool, Balance]: + subtensor: "Subtensor", stake_balance: "Balance" +) -> Tuple[bool, "Balance"]: """ Checks if the new stake balance will be above the minimum required stake threshold. Args: - stake_balance (Balance): - the balance to check for threshold limits. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + stake_balance (Balance): the balance to check for threshold limits. Returns: - success, threshold (bool, Balance): - ``true`` if the staking balance is above the threshold, or ``false`` if the - staking balance is below the threshold. - The threshold balance required to stake. + success, threshold (bool, Balance): ``true`` if the staking balance is above the threshold, or ``false`` if the staking balance is below the threshold. The threshold balance required to stake. """ - min_req_stake: Balance = subtensor.get_minimum_required_stake() + min_req_stake: "Balance" = subtensor.get_minimum_required_stake() if min_req_stake > stake_balance: return False, min_req_stake @@ -48,38 +50,31 @@ def _check_threshold_amount( def add_stake_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", hotkey_ss58: Optional[str] = None, - amount: Optional[Union[Balance, float]] = None, + amount: Optional[Union["Balance", float]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, prompt: bool = False, ) -> bool: - r"""Adds the specified amount of stake to passed hotkey ``uid``. + """Adds the specified amount of stake to passed hotkey ``uid``. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - hotkey_ss58 (Optional[str]): - The ``ss58`` address of the hotkey account to stake to defaults to the wallet's hotkey. - amount (Union[Balance, float]): - Amount to stake as Bittensor balance, or ``float`` interpreted as Tao. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + hotkey_ss58 (Optional[str]): The ``ss58`` address of the hotkey account to stake to defaults to the wallet's hotkey. + amount (Union[Balance, float]): Amount to stake as Bittensor balance, or ``float`` interpreted as Tao. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. Raises: - bittensor.errors.NotRegisteredError: - If the wallet is not registered on the chain. - bittensor.errors.NotDelegateError: - If the hotkey is not a delegate on the chain. + bittensor.core.errors.NotRegisteredError: If the wallet is not registered on the chain. + bittensor.core.errors.NotDelegateError: If the hotkey is not a delegate on the chain. """ # Decrypt keys, wallet.coldkey @@ -91,7 +86,7 @@ def add_stake_extrinsic( # Flag to indicate if we are using the wallet's own hotkey. own_hotkey: bool - with bittensor.__console__.status( + with bt_console.status( ":satellite: Syncing with chain: [white]{}[/white] ...".format( subtensor.network ) @@ -101,9 +96,9 @@ def add_stake_extrinsic( hotkey_owner = subtensor.get_hotkey_owner(hotkey_ss58) own_hotkey = wallet.coldkeypub.ss58_address == hotkey_owner if not own_hotkey: - # This is not the wallet's own hotkey so we are delegating. + # This is not the wallet's own hotkey, so we are delegating. if not subtensor.is_hotkey_delegate(hotkey_ss58): - raise bittensor.errors.NotDelegateError( + raise NotDelegateError( "Hotkey: {} is not a delegate.".format(hotkey_ss58) ) @@ -118,12 +113,12 @@ def add_stake_extrinsic( # Grab the existential deposit. existential_deposit = subtensor.get_existential_deposit() - # Convert to bittensor.Balance + # Convert to Balance if amount is None: # Stake it all. - staking_balance = bittensor.Balance.from_tao(old_balance.tao) - elif not isinstance(amount, bittensor.Balance): - staking_balance = bittensor.Balance.from_tao(amount) + staking_balance = Balance.from_tao(old_balance.tao) + elif not isinstance(amount, Balance): + staking_balance = Balance.from_tao(amount) else: staking_balance = amount @@ -136,7 +131,7 @@ def add_stake_extrinsic( # Check enough to stake. if staking_balance > old_balance: - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Not enough stake[/red]:[bold white]\n balance:{}\n amount: {}\n coldkey: {}[/bold white]".format( old_balance, staking_balance, wallet.name ) @@ -150,7 +145,7 @@ def add_stake_extrinsic( subtensor, new_stake_balance ) if not is_above_threshold: - bittensor.__console__.print( + bt_console.print( f":cross_mark: [red]New stake balance of {new_stake_balance} is below the minimum required nomination stake threshold {threshold}.[/red]" ) return False @@ -174,7 +169,7 @@ def add_stake_extrinsic( return False try: - with bittensor.__console__.status( + with bt_console.status( ":satellite: Staking to: [bold white]{}[/bold white] ...".format( subtensor.network ) @@ -193,10 +188,8 @@ def add_stake_extrinsic( if not wait_for_finalization and not wait_for_inclusion: return True - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - with bittensor.__console__.status( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + with bt_console.status( ":satellite: Checking Balance on: [white]{}[/white] ...".format( subtensor.network ) @@ -211,62 +204,55 @@ def add_stake_extrinsic( block=block, ) # Get current stake - bittensor.__console__.print( + bt_console.print( "Balance:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance ) ) - bittensor.__console__.print( + bt_console.print( "Stake:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_stake, new_stake ) ) return True else: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: Error unknown." - ) + bt_console.print(":cross_mark: [red]Failed[/red]: Error unknown.") return False - except bittensor.errors.NotRegisteredError as e: - bittensor.__console__.print( + except NotRegisteredError: + bt_console.print( ":cross_mark: [red]Hotkey: {} is not registered.[/red]".format( wallet.hotkey_str ) ) return False - except bittensor.errors.StakeError as e: - bittensor.__console__.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) + except StakeError as e: + bt_console.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) return False def add_stake_multiple_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", hotkey_ss58s: List[str], - amounts: Optional[List[Union[Balance, float]]] = None, + amounts: Optional[List[Union["Balance", float]]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, prompt: bool = False, ) -> bool: - r"""Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey. + """Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey. Args: - wallet (bittensor.wallet): - Bittensor wallet object for the coldkey. - hotkey_ss58s (List[str]): - List of hotkeys to stake to. - amounts (List[Union[Balance, float]]): - List of amounts to stake. If ``None``, stake all to the first hotkey. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (bittensor_wallet.Wallet): Bittensor wallet object for the coldkey. + hotkey_ss58s (List[str]): List of hotkeys to stake to. + amounts (List[Union[Balance, float]]): List of amounts to stake. If ``None``, stake all to the first hotkey. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or included in the block. Flag is ``true`` if any wallet was staked. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or included in the block. Flag is ``true`` if any wallet was staked. If we did not wait for finalization / inclusion, the response is ``true``. """ if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s @@ -282,16 +268,14 @@ def add_stake_multiple_extrinsic( if amounts is not None and not all( isinstance(amount, (Balance, float)) for amount in amounts ): - raise TypeError( - "amounts must be a [list of bittensor.Balance or float] or None" - ) + raise TypeError("amounts must be a [list of Balance or float] or None") if amounts is None: amounts = [None] * len(hotkey_ss58s) else: # Convert to Balance amounts = [ - bittensor.Balance.from_tao(amount) if isinstance(amount, float) else amount + Balance.from_tao(amount) if isinstance(amount, float) else amount for amount in amounts ] @@ -303,7 +287,7 @@ def add_stake_multiple_extrinsic( wallet.coldkey old_stakes = [] - with bittensor.__console__.status( + with bt_console.status( ":satellite: Syncing with chain: [white]{}[/white] ...".format( subtensor.network ) @@ -319,21 +303,21 @@ def add_stake_multiple_extrinsic( ) # Remove existential balance to keep key alive. - ## Keys must maintain a balance of at least 1000 rao to stay alive. + # Keys must maintain a balance of at least 1000 rao to stay alive. total_staking_rao = sum( [amount.rao if amount is not None else 0 for amount in amounts] ) if total_staking_rao == 0: # Staking all to the first wallet. if old_balance.rao > 1000: - old_balance -= bittensor.Balance.from_rao(1000) + old_balance -= Balance.from_rao(1000) elif total_staking_rao < 1000: # Staking less than 1000 rao to the wallets. pass else: # Staking more than 1000 rao to the wallets. - ## Reduce the amount to stake to each wallet to keep the balance above 1000 rao. + # Reduce the amount to stake to each wallet to keep the balance above 1000 rao. percent_reduction = 1 - (1000 / total_staking_rao) amounts = [ Balance.from_tao(amount.tao * percent_reduction) for amount in amounts @@ -344,19 +328,19 @@ def add_stake_multiple_extrinsic( zip(hotkey_ss58s, amounts, old_stakes) ): staking_all = False - # Convert to bittensor.Balance - if amount == None: + # Convert to Balance + if amount is None: # Stake it all. - staking_balance = bittensor.Balance.from_tao(old_balance.tao) + staking_balance = Balance.from_tao(old_balance.tao) staking_all = True else: # Amounts are cast to balance earlier in the function - assert isinstance(amount, bittensor.Balance) + assert isinstance(amount, Balance) staking_balance = amount # Check enough to stake if staking_balance > old_balance: - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Not enough balance[/red]: [green]{}[/green] to stake: [blue]{}[/blue] from coldkey: [white]{}[/white]".format( old_balance, staking_balance, wallet.name ) @@ -382,14 +366,14 @@ def add_stake_multiple_extrinsic( wait_for_finalization=wait_for_finalization, ) - if staking_response == True: # If we successfully staked. + if staking_response is True: # If we successfully staked. # We only wait here if we expect finalization. if idx < len(hotkey_ss58s) - 1: # Wait for tx rate limit. tx_rate_limit_blocks = subtensor.tx_rate_limit() if tx_rate_limit_blocks > 0: - bittensor.__console__.print( + bt_console.print( ":hourglass: [yellow]Waiting for tx rate limit: [white]{}[/white] blocks[/yellow]".format( tx_rate_limit_blocks ) @@ -405,9 +389,7 @@ def add_stake_multiple_extrinsic( continue - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") block = subtensor.get_current_block() new_stake = subtensor.get_stake_for_coldkey_and_hotkey( @@ -418,7 +400,7 @@ def add_stake_multiple_extrinsic( new_balance = subtensor.get_balance( wallet.coldkeypub.ss58_address, block=block ) - bittensor.__console__.print( + bt_console.print( "Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( hotkey_ss58, old_stake, new_stake ) @@ -430,32 +412,28 @@ def add_stake_multiple_extrinsic( break else: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: Error unknown." - ) + bt_console.print(":cross_mark: [red]Failed[/red]: Error unknown.") continue - except bittensor.errors.NotRegisteredError as e: - bittensor.__console__.print( + except NotRegisteredError: + bt_console.print( ":cross_mark: [red]Hotkey: {} is not registered.[/red]".format( hotkey_ss58 ) ) continue - except bittensor.errors.StakeError as e: - bittensor.__console__.print( - ":cross_mark: [red]Stake Error: {}[/red]".format(e) - ) + except StakeError as e: + bt_console.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) continue if successful_stakes != 0: - with bittensor.__console__.status( + with bt_console.status( ":satellite: Checking Balance on: ([white]{}[/white] ...".format( subtensor.network ) ): new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - bittensor.__console__.print( + bt_console.print( "Balance: [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance ) @@ -466,40 +444,31 @@ def add_stake_multiple_extrinsic( def __do_add_stake_single( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", hotkey_ss58: str, - amount: "bittensor.Balance", + amount: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: - r""" + """ Executes a stake call to the chain using the wallet and the amount specified. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - hotkey_ss58 (str): - Hotkey to stake to. - amount (bittensor.Balance): - Amount to stake as Bittensor balance object. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (bittensor_wallet.Wallet): Bittensor wallet object. + hotkey_ss58 (str): Hotkey to stake to. + amount (Balance): Amount to stake as Bittensor balance object. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. - Raises: - bittensor.errors.StakeError: - If the extrinsic fails to be finalized or included in the block. - bittensor.errors.NotDelegateError: - If the hotkey is not a delegate. - bittensor.errors.NotRegisteredError: - If the hotkey is not registered in any subnets. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + Raises: + bittensor.core.errors.StakeError: If the extrinsic fails to be finalized or included in the block. + bittensor.core.errors.NotDelegateError: If the hotkey is not a delegate. + bittensor.core.errors.NotRegisteredError: If the hotkey is not registered in any subnets. """ # Decrypt keys, wallet.coldkey @@ -510,9 +479,7 @@ def __do_add_stake_single( # We are delegating. # Verify that the hotkey is a delegate. if not subtensor.is_hotkey_delegate(hotkey_ss58=hotkey_ss58): - raise bittensor.errors.NotDelegateError( - "Hotkey: {} is not a delegate.".format(hotkey_ss58) - ) + raise NotDelegateError("Hotkey: {} is not a delegate.".format(hotkey_ss58)) success = subtensor._do_stake( wallet=wallet, diff --git a/bittensor/extrinsics/transfer.py b/bittensor/api/extrinsics/transfer.py similarity index 59% rename from bittensor/extrinsics/transfer.py rename to bittensor/api/extrinsics/transfer.py index 91ef3237eb..f7362443b9 100644 --- a/bittensor/extrinsics/transfer.py +++ b/bittensor/api/extrinsics/transfer.py @@ -1,32 +1,38 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 bittensor +from typing import Union, TYPE_CHECKING from rich.prompt import Confirm -from typing import Union -from ..utils.balance import Balance -from ..utils import is_valid_bittensor_address_or_public_key + +from bittensor.core.settings import bt_console, network_explorer_map +from bittensor.utils import get_explorer_url_for_network +from bittensor.utils import is_valid_bittensor_address_or_public_key +from bittensor.utils.balance import Balance +from bittensor_wallet import Wallet + +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor def transfer_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", dest: str, amount: Union[Balance, float], wait_for_inclusion: bool = True, @@ -34,30 +40,24 @@ def transfer_extrinsic( keep_alive: bool = True, prompt: bool = False, ) -> bool: - r"""Transfers funds from this wallet to the destination public key address. + """Transfers funds from this wallet to the destination public key address. Args: - wallet (bittensor.wallet): - Bittensor wallet object to make transfer from. - dest (str, ss58_address or ed25519): - Destination public key address of reciever. - amount (Union[Balance, int]): - Amount to stake as Bittensor balance, or ``float`` interpreted as Tao. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - keep_alive (bool): - If set, keeps the account alive by keeping the balance above the existential deposit. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (bittensor.wallet): Bittensor wallet object to make transfer from. + dest (str, ss58_address or ed25519): Destination public key address of receiver. + amount (Union[Balance, int]): Amount to stake as Bittensor balance, or ``float`` interpreted as Tao. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + keep_alive (bool): If set, keeps the account alive by keeping the balance above the existential deposit. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ # Validate destination address. if not is_valid_bittensor_address_or_public_key(dest): - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Invalid destination address[/red]:[bold white]\n {}[/bold white]".format( dest ) @@ -72,29 +72,29 @@ def transfer_extrinsic( wallet.coldkey # Convert to bittensor.Balance - if not isinstance(amount, bittensor.Balance): - transfer_balance = bittensor.Balance.from_tao(amount) + if not isinstance(amount, Balance): + transfer_balance = Balance.from_tao(amount) else: transfer_balance = amount # Check balance. - with bittensor.__console__.status(":satellite: Checking Balance..."): + with bt_console.status(":satellite: Checking Balance..."): account_balance = subtensor.get_balance(wallet.coldkey.ss58_address) # check existential deposit. existential_deposit = subtensor.get_existential_deposit() - with bittensor.__console__.status(":satellite: Transferring..."): + with bt_console.status(":satellite: Transferring..."): fee = subtensor.get_transfer_fee( wallet=wallet, dest=dest, value=transfer_balance.rao ) if not keep_alive: # Check if the transfer should keep_alive the account - existential_deposit = bittensor.Balance(0) + existential_deposit = Balance(0) # Check if we have enough balance. if account_balance < (transfer_balance + fee + existential_deposit): - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Not enough balance[/red]:[bold white]\n balance: {}\n amount: {}\n for fee: {}[/bold white]".format( account_balance, transfer_balance, fee ) @@ -110,7 +110,7 @@ def transfer_extrinsic( ): return False - with bittensor.__console__.status(":satellite: Transferring..."): + with bt_console.status(":satellite: Transferring..."): success, block_hash, err_msg = subtensor._do_transfer( wallet, dest, @@ -120,34 +120,30 @@ def transfer_extrinsic( ) if success: - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - bittensor.__console__.print( - "[green]Block Hash: {}[/green]".format(block_hash) - ) + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + bt_console.print("[green]Block Hash: {}[/green]".format(block_hash)) - explorer_urls = bittensor.utils.get_explorer_url_for_network( - subtensor.network, block_hash, bittensor.__network_explorer_map__ + explorer_urls = get_explorer_url_for_network( + subtensor.network, block_hash, network_explorer_map ) if explorer_urls != {} and explorer_urls: - bittensor.__console__.print( + bt_console.print( "[green]Opentensor Explorer Link: {}[/green]".format( explorer_urls.get("opentensor") ) ) - bittensor.__console__.print( + bt_console.print( "[green]Taostats Explorer Link: {}[/green]".format( explorer_urls.get("taostats") ) ) else: - bittensor.__console__.print(f":cross_mark: [red]Failed[/red]: {err_msg}") + bt_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}") if success: - with bittensor.__console__.status(":satellite: Checking Balance..."): + with bt_console.status(":satellite: Checking Balance..."): new_balance = subtensor.get_balance(wallet.coldkey.ss58_address) - bittensor.__console__.print( + bt_console.print( "Balance:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( account_balance, new_balance ) diff --git a/bittensor/extrinsics/unstaking.py b/bittensor/api/extrinsics/unstaking.py similarity index 65% rename from bittensor/extrinsics/unstaking.py rename to bittensor/api/extrinsics/unstaking.py index 105bb145b9..a16e4660fa 100644 --- a/bittensor/extrinsics/unstaking.py +++ b/bittensor/api/extrinsics/unstaking.py @@ -1,60 +1,60 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 bittensor -from rich.prompt import Confirm from time import sleep -from typing import List, Union, Optional +from typing import List, Union, Optional, TYPE_CHECKING + +from bittensor_wallet import Wallet +from rich.prompt import Confirm + +from bittensor.core.errors import NotRegisteredError, StakeError +from bittensor.core.settings import bt_console from bittensor.utils.balance import Balance +# For annotation purposes +if TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor + def __do_remove_stake_single( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", hotkey_ss58: str, - amount: "bittensor.Balance", + amount: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: - r""" + """ Executes an unstake call to the chain using the wallet and the amount specified. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - hotkey_ss58 (str): - Hotkey address to unstake from. - amount (bittensor.Balance): - Amount to unstake as Bittensor balance object. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + hotkey_ss58 (str): Hotkey address to unstake from. + amount (bittensor.Balance): Amount to unstake as Bittensor balance object. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. Raises: - bittensor.errors.StakeError: - If the extrinsic fails to be finalized or included in the block. - bittensor.errors.NotRegisteredError: - If the hotkey is not registered in any subnets. + bittensor.core.errors.StakeError: If the extrinsic fails to be finalized or included in the block. + bittensor.core.errors.NotRegisteredError: If the hotkey is not registered in any subnets. """ # Decrypt keys, @@ -71,25 +71,21 @@ def __do_remove_stake_single( return success -def check_threshold_amount( - subtensor: "bittensor.subtensor", stake_balance: Balance -) -> bool: +def check_threshold_amount(subtensor: "Subtensor", stake_balance: Balance) -> bool: """ Checks if the remaining stake balance is above the minimum required stake threshold. Args: - stake_balance (Balance): - the balance to check for threshold limits. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + stake_balance (Balance): the balance to check for threshold limits. Returns: - success (bool): - ``true`` if the unstaking is above the threshold or 0, or ``false`` if the - unstaking is below the threshold, but not 0. + success (bool): ``true`` if the unstaking is above the threshold or 0, or ``false`` if the unstaking is below the threshold, but not 0. """ min_req_stake: Balance = subtensor.get_minimum_required_stake() if min_req_stake > stake_balance > 0: - bittensor.__console__.print( + bt_console.print( f":cross_mark: [yellow]Remaining stake balance of {stake_balance} less than minimum of {min_req_stake} TAO[/yellow]" ) return False @@ -98,32 +94,27 @@ def check_threshold_amount( def unstake_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", hotkey_ss58: Optional[str] = None, amount: Optional[Union[Balance, float]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, prompt: bool = False, ) -> bool: - r"""Removes stake into the wallet coldkey from the specified hotkey ``uid``. + """Removes stake into the wallet coldkey from the specified hotkey ``uid``. Args: - wallet (bittensor.wallet): - Bittensor wallet object. - hotkey_ss58 (Optional[str]): - The ``ss58`` address of the hotkey to unstake from. By default, the wallet hotkey is used. - amount (Union[Balance, float]): - Amount to stake as Bittensor balance, or ``float`` interpreted as Tao. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): Bittensor wallet object. + hotkey_ss58 (Optional[str]): The ``ss58`` address of the hotkey to unstake from. By default, the wallet hotkey is used. + amount (Union[Balance, float]): Amount to stake as Bittensor balance, or ``float`` interpreted as Tao. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ # Decrypt keys, wallet.coldkey @@ -131,7 +122,7 @@ def unstake_extrinsic( if hotkey_ss58 is None: hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey. - with bittensor.__console__.status( + with bt_console.status( ":satellite: Syncing with chain: [white]{}[/white] ...".format( subtensor.network ) @@ -148,15 +139,15 @@ def unstake_extrinsic( if amount is None: # Unstake it all. unstaking_balance = old_stake - elif not isinstance(amount, bittensor.Balance): - unstaking_balance = bittensor.Balance.from_tao(amount) + elif not isinstance(amount, Balance): + unstaking_balance = Balance.from_tao(amount) else: unstaking_balance = amount # Check enough to unstake. stake_on_uid = old_stake if unstaking_balance > stake_on_uid: - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Not enough stake[/red]: [green]{}[/green] to unstake: [blue]{}[/blue] from hotkey: [white]{}[/white]".format( stake_on_uid, unstaking_balance, wallet.hotkey_str ) @@ -167,7 +158,7 @@ def unstake_extrinsic( if not own_hotkey and not check_threshold_amount( subtensor=subtensor, stake_balance=(stake_on_uid - unstaking_balance) ): - bittensor.__console__.print( + bt_console.print( f":warning: [yellow]This action will unstake the entire staked balance![/yellow]" ) unstaking_balance = stake_on_uid @@ -182,7 +173,7 @@ def unstake_extrinsic( return False try: - with bittensor.__console__.status( + with bt_console.status( ":satellite: Unstaking from chain: [white]{}[/white] ...".format( subtensor.network ) @@ -201,10 +192,8 @@ def unstake_extrinsic( if not wait_for_finalization and not wait_for_inclusion: return True - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - with bittensor.__console__.status( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + with bt_console.status( ":satellite: Checking Balance on: [white]{}[/white] ...".format( subtensor.network ) @@ -215,62 +204,55 @@ def unstake_extrinsic( new_stake = subtensor.get_stake_for_coldkey_and_hotkey( coldkey_ss58=wallet.coldkeypub.ss58_address, hotkey_ss58=hotkey_ss58 ) # Get stake on hotkey. - bittensor.__console__.print( + bt_console.print( "Balance:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance ) ) - bittensor.__console__.print( + bt_console.print( "Stake:\n [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_stake, new_stake ) ) return True else: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: Unknown Error." - ) + bt_console.print(":cross_mark: [red]Failed[/red]: Unknown Error.") return False - except bittensor.errors.NotRegisteredError as e: - bittensor.__console__.print( + except NotRegisteredError as e: + bt_console.print( ":cross_mark: [red]Hotkey: {} is not registered.[/red]".format( wallet.hotkey_str ) ) return False - except bittensor.errors.StakeError as e: - bittensor.__console__.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) + except StakeError as e: + bt_console.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) return False def unstake_multiple_extrinsic( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", hotkey_ss58s: List[str], amounts: Optional[List[Union[Balance, float]]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, prompt: bool = False, ) -> bool: - r"""Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. + """Removes stake from each ``hotkey_ss58`` in the list, using each amount, to a common coldkey. Args: - wallet (bittensor.wallet): - The wallet with the coldkey to unstake to. - hotkey_ss58s (List[str]): - List of hotkeys to unstake from. - amounts (List[Union[Balance, float]]): - List of amounts to unstake. If ``None``, unstake all. - wait_for_inclusion (bool): - If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. - wait_for_finalization (bool): - If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. - prompt (bool): - If ``true``, the call waits for confirmation from the user before proceeding. + subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. + wallet (Wallet): The wallet with the coldkey to unstake to. + hotkey_ss58s (List[str]): List of hotkeys to unstake from. + amounts (List[Union[Balance, float]]): List of amounts to unstake. If ``None``, unstake all. + wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning ``true``, or returns ``false`` if the extrinsic fails to enter the block within the timeout. + wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning ``true``, or returns ``false`` if the extrinsic fails to be finalized within the timeout. + prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. + Returns: - success (bool): - Flag is ``true`` if extrinsic was finalized or included in the block. Flag is ``true`` if any wallet was unstaked. If we did not wait for finalization / inclusion, the response is ``true``. + success (bool): Flag is ``true`` if extrinsic was finalized or included in the block. Flag is ``true`` if any wallet was unstaked. If we did not wait for finalization / inclusion, the response is ``true``. """ if not isinstance(hotkey_ss58s, list) or not all( isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s @@ -286,16 +268,14 @@ def unstake_multiple_extrinsic( if amounts is not None and not all( isinstance(amount, (Balance, float)) for amount in amounts ): - raise TypeError( - "amounts must be a [list of bittensor.Balance or float] or None" - ) + raise TypeError("amounts must be a [list of Balance or float] or None") if amounts is None: amounts = [None] * len(hotkey_ss58s) else: # Convert to Balance amounts = [ - bittensor.Balance.from_tao(amount) if isinstance(amount, float) else amount + Balance.from_tao(amount) if isinstance(amount, float) else amount for amount in amounts ] @@ -308,7 +288,7 @@ def unstake_multiple_extrinsic( old_stakes = [] own_hotkeys = [] - with bittensor.__console__.status( + with bt_console.status( ":satellite: Syncing with chain: [white]{}[/white] ...".format( subtensor.network ) @@ -332,15 +312,15 @@ def unstake_multiple_extrinsic( if amount is None: # Unstake it all. unstaking_balance = old_stake - elif not isinstance(amount, bittensor.Balance): - unstaking_balance = bittensor.Balance.from_tao(amount) + elif not isinstance(amount, Balance): + unstaking_balance = Balance.from_tao(amount) else: unstaking_balance = amount # Check enough to unstake. stake_on_uid = old_stake if unstaking_balance > stake_on_uid: - bittensor.__console__.print( + bt_console.print( ":cross_mark: [red]Not enough stake[/red]: [green]{}[/green] to unstake: [blue]{}[/blue] from hotkey: [white]{}[/white]".format( stake_on_uid, unstaking_balance, wallet.hotkey_str ) @@ -351,7 +331,7 @@ def unstake_multiple_extrinsic( if not own_hotkey and not check_threshold_amount( subtensor=subtensor, stake_balance=(stake_on_uid - unstaking_balance) ): - bittensor.__console__.print( + bt_console.print( f":warning: [yellow]This action will unstake the entire staked balance![/yellow]" ) unstaking_balance = stake_on_uid @@ -366,7 +346,7 @@ def unstake_multiple_extrinsic( continue try: - with bittensor.__console__.status( + with bt_console.status( ":satellite: Unstaking from chain: [white]{}[/white] ...".format( subtensor.network ) @@ -387,7 +367,7 @@ def unstake_multiple_extrinsic( # Wait for tx rate limit. tx_rate_limit_blocks = subtensor.tx_rate_limit() if tx_rate_limit_blocks > 0: - bittensor.__console__.print( + bt_console.print( ":hourglass: [yellow]Waiting for tx rate limit: [white]{}[/white] blocks[/yellow]".format( tx_rate_limit_blocks ) @@ -398,10 +378,8 @@ def unstake_multiple_extrinsic( successful_unstakes += 1 continue - bittensor.__console__.print( - ":white_heavy_check_mark: [green]Finalized[/green]" - ) - with bittensor.__console__.status( + bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") + with bt_console.status( ":satellite: Checking Balance on: [white]{}[/white] ...".format( subtensor.network ) @@ -412,37 +390,33 @@ def unstake_multiple_extrinsic( hotkey_ss58=hotkey_ss58, block=block, ) - bittensor.__console__.print( + bt_console.print( "Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( hotkey_ss58, stake_on_uid, new_stake ) ) successful_unstakes += 1 else: - bittensor.__console__.print( - ":cross_mark: [red]Failed[/red]: Unknown Error." - ) + bt_console.print(":cross_mark: [red]Failed[/red]: Unknown Error.") continue - except bittensor.errors.NotRegisteredError as e: - bittensor.__console__.print( + except NotRegisteredError as e: + bt_console.print( ":cross_mark: [red]{} is not registered.[/red]".format(hotkey_ss58) ) continue - except bittensor.errors.StakeError as e: - bittensor.__console__.print( - ":cross_mark: [red]Stake Error: {}[/red]".format(e) - ) + except StakeError as e: + bt_console.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) continue if successful_unstakes != 0: - with bittensor.__console__.status( + with bt_console.status( ":satellite: Checking Balance on: ([white]{}[/white] ...".format( subtensor.network ) ): new_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - bittensor.__console__.print( + bt_console.print( "Balance: [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( old_balance, new_balance ) diff --git a/bittensor/api/extrinsics/utils.py b/bittensor/api/extrinsics/utils.py new file mode 100644 index 0000000000..16466e9c6f --- /dev/null +++ b/bittensor/api/extrinsics/utils.py @@ -0,0 +1,41 @@ +# 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. + +HYPERPARAMS = { + "serving_rate_limit": "sudo_set_serving_rate_limit", + "min_difficulty": "sudo_set_min_difficulty", + "max_difficulty": "sudo_set_max_difficulty", + "weights_version": "sudo_set_weights_version_key", + "weights_rate_limit": "sudo_set_weights_set_rate_limit", + "max_weight_limit": "sudo_set_max_weight_limit", + "immunity_period": "sudo_set_immunity_period", + "min_allowed_weights": "sudo_set_min_allowed_weights", + "activity_cutoff": "sudo_set_activity_cutoff", + "network_registration_allowed": "sudo_set_network_registration_allowed", + "network_pow_registration_allowed": "sudo_set_network_pow_registration_allowed", + "min_burn": "sudo_set_min_burn", + "max_burn": "sudo_set_max_burn", + "adjustment_alpha": "sudo_set_adjustment_alpha", + "rho": "sudo_set_rho", + "kappa": "sudo_set_kappa", + "difficulty": "sudo_set_difficulty", + "bonds_moving_avg": "sudo_set_bonds_moving_average", + "commit_reveal_weights_interval": "sudo_set_commit_reveal_weights_interval", + "commit_reveal_weights_enabled": "sudo_set_commit_reveal_weights_enabled", + "alpha_values": "sudo_set_alpha_values", + "liquid_alpha_enabled": "sudo_set_liquid_alpha_enabled", +} diff --git a/bittensor/cli.py b/bittensor/cli.py deleted file mode 100644 index 4a7a47775e..0000000000 --- a/bittensor/cli.py +++ /dev/null @@ -1,388 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 sys -import shtab -import argparse -import bittensor -from typing import List, Optional -from .commands import ( - AutocompleteCommand, - DelegateStakeCommand, - DelegateUnstakeCommand, - GetIdentityCommand, - GetWalletHistoryCommand, - InspectCommand, - ListCommand, - ListDelegatesCommand, - MetagraphCommand, - MyDelegatesCommand, - NewColdkeyCommand, - NewHotkeyCommand, - NominateCommand, - OverviewCommand, - PowRegisterCommand, - ProposalsCommand, - RegenColdkeyCommand, - RegenColdkeypubCommand, - RegenHotkeyCommand, - RegisterCommand, - RegisterSubnetworkCommand, - RootGetWeightsCommand, - RootList, - RootRegisterCommand, - RootSetBoostCommand, - RootSetSlashCommand, - RootSetWeightsCommand, - RunFaucetCommand, - SenateCommand, - SetIdentityCommand, - SetTakeCommand, - StakeCommand, - StakeShow, - SubnetGetHyperparamsCommand, - SubnetHyperparamsCommand, - SubnetListCommand, - SubnetLockCostCommand, - SubnetSudoCommand, - SwapHotkeyCommand, - TransferCommand, - UnStakeCommand, - UpdateCommand, - UpdateWalletCommand, - VoteCommand, - WalletBalanceCommand, - WalletCreateCommand, - CommitWeightCommand, - RevealWeightCommand, - CheckColdKeySwapCommand, -) - -# Create a console instance for CLI display. -console = bittensor.__console__ - -ALIAS_TO_COMMAND = { - "subnets": "subnets", - "root": "root", - "wallet": "wallet", - "stake": "stake", - "sudo": "sudo", - "legacy": "legacy", - "s": "subnets", - "r": "root", - "w": "wallet", - "st": "stake", - "su": "sudo", - "l": "legacy", - "subnet": "subnets", - "roots": "root", - "wallets": "wallet", - "stakes": "stake", - "sudos": "sudo", - "i": "info", - "info": "info", - "weights": "weights", - "wt": "weights", - "weight": "weights", -} -COMMANDS = { - "subnets": { - "name": "subnets", - "aliases": ["s", "subnet"], - "help": "Commands for managing and viewing subnetworks.", - "commands": { - "list": SubnetListCommand, - "metagraph": MetagraphCommand, - "lock_cost": SubnetLockCostCommand, - "create": RegisterSubnetworkCommand, - "pow_register": PowRegisterCommand, - "register": RegisterCommand, - "hyperparameters": SubnetHyperparamsCommand, - }, - }, - "root": { - "name": "root", - "aliases": ["r", "roots"], - "help": "Commands for managing and viewing the root network.", - "commands": { - "list": RootList, - "weights": RootSetWeightsCommand, - "get_weights": RootGetWeightsCommand, - "boost": RootSetBoostCommand, - "slash": RootSetSlashCommand, - "senate_vote": VoteCommand, - "senate": SenateCommand, - "register": RootRegisterCommand, - "proposals": ProposalsCommand, - "set_take": SetTakeCommand, - "delegate": DelegateStakeCommand, - "undelegate": DelegateUnstakeCommand, - "my_delegates": MyDelegatesCommand, - "list_delegates": ListDelegatesCommand, - "nominate": NominateCommand, - }, - }, - "wallet": { - "name": "wallet", - "aliases": ["w", "wallets"], - "help": "Commands for managing and viewing wallets.", - "commands": { - "list": ListCommand, - "overview": OverviewCommand, - "transfer": TransferCommand, - "inspect": InspectCommand, - "balance": WalletBalanceCommand, - "create": WalletCreateCommand, - "new_hotkey": NewHotkeyCommand, - "new_coldkey": NewColdkeyCommand, - "regen_coldkey": RegenColdkeyCommand, - "regen_coldkeypub": RegenColdkeypubCommand, - "regen_hotkey": RegenHotkeyCommand, - "faucet": RunFaucetCommand, - "update": UpdateWalletCommand, - "swap_hotkey": SwapHotkeyCommand, - "set_identity": SetIdentityCommand, - "get_identity": GetIdentityCommand, - "history": GetWalletHistoryCommand, - "check_coldkey_swap": CheckColdKeySwapCommand, - }, - }, - "stake": { - "name": "stake", - "aliases": ["st", "stakes"], - "help": "Commands for staking and removing stake from hotkey accounts.", - "commands": { - "show": StakeShow, - "add": StakeCommand, - "remove": UnStakeCommand, - }, - }, - "weights": { - "name": "weights", - "aliases": ["wt", "weight"], - "help": "Commands for managing weight for subnets.", - "commands": { - "commit": CommitWeightCommand, - "reveal": RevealWeightCommand, - }, - }, - "sudo": { - "name": "sudo", - "aliases": ["su", "sudos"], - "help": "Commands for subnet management", - "commands": { - # "dissolve": None, - "set": SubnetSudoCommand, - "get": SubnetGetHyperparamsCommand, - }, - }, - "legacy": { - "name": "legacy", - "aliases": ["l"], - "help": "Miscellaneous commands.", - "commands": { - "update": UpdateCommand, - "faucet": RunFaucetCommand, - }, - }, - "info": { - "name": "info", - "aliases": ["i"], - "help": "Instructions for enabling autocompletion for the CLI.", - "commands": { - "autocomplete": AutocompleteCommand, - }, - }, -} - - -class CLIErrorParser(argparse.ArgumentParser): - """ - Custom ArgumentParser for better error messages. - """ - - def error(self, message): - """ - This method is called when an error occurs. It prints a custom error message. - """ - sys.stderr.write(f"Error: {message}\n") - self.print_help() - sys.exit(2) - - -class cli: - """ - Implementation of the Command Line Interface (CLI) class for the Bittensor protocol. - This class handles operations like key management (hotkey and coldkey) and token transfer. - """ - - def __init__( - self, - config: Optional["bittensor.config"] = None, - args: Optional[List[str]] = None, - ): - """ - Initializes a bittensor.CLI object. - - Args: - config (bittensor.config, optional): The configuration settings for the CLI. - args (List[str], optional): List of command line arguments. - """ - # Turns on console for cli. - bittensor.turn_console_on() - - # If no config is provided, create a new one from args. - if config is None: - config = cli.create_config(args) - - self.config = config - if self.config.command in ALIAS_TO_COMMAND: - self.config.command = ALIAS_TO_COMMAND[self.config.command] - else: - console.print( - f":cross_mark:[red]Unknown command: {self.config.command}[/red]" - ) - sys.exit() - - # Check if the config is valid. - cli.check_config(self.config) - - # If no_version_checking is not set or set as False in the config, version checking is done. - if not self.config.get("no_version_checking", d=True): - try: - bittensor.utils.check_version() - except bittensor.utils.VersionCheckError: - # If version checking fails, inform user with an exception. - raise RuntimeError( - "To avoid internet-based version checking, pass --no_version_checking while running the CLI." - ) - - @staticmethod - def __create_parser__() -> "argparse.ArgumentParser": - """ - Creates the argument parser for the Bittensor CLI. - - Returns: - argparse.ArgumentParser: An argument parser object for Bittensor CLI. - """ - # Define the basic argument parser. - parser = CLIErrorParser( - description=f"bittensor cli v{bittensor.__version__}", - usage="btcli ", - add_help=True, - ) - # Add shtab completion - parser.add_argument( - "--print-completion", - choices=shtab.SUPPORTED_SHELLS, - help="Print shell tab completion script", - ) - # Add arguments for each sub-command. - cmd_parsers = parser.add_subparsers(dest="command") - # Add argument parsers for all available commands. - for command in COMMANDS.values(): - if isinstance(command, dict): - subcmd_parser = cmd_parsers.add_parser( - name=command["name"], - aliases=command["aliases"], - help=command["help"], - ) - subparser = subcmd_parser.add_subparsers( - help=command["help"], dest="subcommand", required=True - ) - - for subcommand in command["commands"].values(): - subcommand.add_args(subparser) - else: - command.add_args(cmd_parsers) - - return parser - - @staticmethod - def create_config(args: List[str]) -> "bittensor.config": - """ - From the argument parser, add config to bittensor.executor and local config - - Args: - args (List[str]): List of command line arguments. - - Returns: - bittensor.config: The configuration object for Bittensor CLI. - """ - parser = cli.__create_parser__() - - # If no arguments are passed, print help text and exit the program. - if len(args) == 0: - parser.print_help() - sys.exit() - - return bittensor.config(parser, args=args) - - @staticmethod - def check_config(config: "bittensor.config"): - """ - Checks if the essential configuration exists under different command - - Args: - config (bittensor.config): The configuration settings for the CLI. - """ - # Check if command exists, if so, run the corresponding check_config. - # If command doesn't exist, inform user and exit the program. - if config.command in COMMANDS: - command = config.command - command_data = COMMANDS[command] - - if isinstance(command_data, dict): - if config["subcommand"] is not None: - command_data["commands"][config["subcommand"]].check_config(config) - else: - console.print( - f":cross_mark:[red]Missing subcommand for: {config.command}[/red]" - ) - sys.exit(1) - else: - command_data.check_config(config) - else: - console.print(f":cross_mark:[red]Unknown command: {config.command}[/red]") - sys.exit(1) - - def run(self): - """ - Executes the command from the configuration. - """ - # Check for print-completion argument - if self.config.print_completion: - parser = cli.__create_parser__() - shell = self.config.print_completion - print(shtab.complete(parser, shell)) - return - - # Check if command exists, if so, run the corresponding method. - # If command doesn't exist, inform user and exit the program. - command = self.config.command - if command in COMMANDS: - command_data = COMMANDS[command] - - if isinstance(command_data, dict): - command_data["commands"][self.config["subcommand"]].run(self) - else: - command_data.run(self) - else: - console.print( - f":cross_mark:[red]Unknown command: {self.config.command}[/red]" - ) - sys.exit() diff --git a/bittensor/commands/__init__.py b/bittensor/commands/__init__.py deleted file mode 100644 index 514a081c41..0000000000 --- a/bittensor/commands/__init__.py +++ /dev/null @@ -1,124 +0,0 @@ -# The MIT License (MIT) -# 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. - -from munch import Munch, munchify - -defaults: Munch = munchify( - { - "netuid": 1, - "subtensor": {"network": "finney", "chain_endpoint": None, "_mock": False}, - "pow_register": { - "num_processes": None, - "update_interval": 50000, - "output_in_place": True, - "verbose": False, - "cuda": {"dev_id": [0], "use_cuda": False, "tpb": 256}, - }, - "axon": { - "port": 8091, - "ip": "[::]", - "external_port": None, - "external_ip": None, - "max_workers": 10, - "maximum_concurrent_rpcs": 400, - }, - "priority": {"max_workers": 5, "maxsize": 10}, - "prometheus": {"port": 7091, "level": "INFO"}, - "wallet": { - "name": "default", - "hotkey": "default", - "path": "~/.bittensor/wallets/", - }, - "dataset": { - "batch_size": 10, - "block_size": 20, - "num_workers": 0, - "dataset_names": "default", - "data_dir": "~/.bittensor/data/", - "save_dataset": False, - "max_datasets": 3, - "num_batches": 100, - }, - "logging": { - "debug": False, - "trace": False, - "record_log": False, - "logging_dir": "~/.bittensor/miners", - }, - } -) - -from .stake import StakeCommand, StakeShow -from .unstake import UnStakeCommand -from .overview import OverviewCommand -from .register import ( - PowRegisterCommand, - RegisterCommand, - RunFaucetCommand, - SwapHotkeyCommand, -) -from .delegates import ( - NominateCommand, - ListDelegatesCommand, - DelegateStakeCommand, - DelegateUnstakeCommand, - MyDelegatesCommand, - SetTakeCommand, -) -from .wallets import ( - NewColdkeyCommand, - NewHotkeyCommand, - RegenColdkeyCommand, - RegenColdkeypubCommand, - RegenHotkeyCommand, - UpdateWalletCommand, - WalletCreateCommand, - WalletBalanceCommand, - GetWalletHistoryCommand, -) -from .weights import CommitWeightCommand, RevealWeightCommand -from .transfer import TransferCommand -from .inspect import InspectCommand -from .metagraph import MetagraphCommand -from .list import ListCommand -from .misc import UpdateCommand, AutocompleteCommand -from .senate import ( - SenateCommand, - ProposalsCommand, - ShowVotesCommand, - SenateRegisterCommand, - SenateLeaveCommand, - VoteCommand, -) -from .network import ( - RegisterSubnetworkCommand, - SubnetLockCostCommand, - SubnetListCommand, - SubnetSudoCommand, - SubnetHyperparamsCommand, - SubnetGetHyperparamsCommand, -) -from .root import ( - RootRegisterCommand, - RootList, - RootSetWeightsCommand, - RootGetWeightsCommand, - RootSetBoostCommand, - RootSetSlashCommand, -) -from .identity import GetIdentityCommand, SetIdentityCommand -from .check_coldkey_swap import CheckColdKeySwapCommand diff --git a/bittensor/commands/check_coldkey_swap.py b/bittensor/commands/check_coldkey_swap.py deleted file mode 100644 index 2b003e8289..0000000000 --- a/bittensor/commands/check_coldkey_swap.py +++ /dev/null @@ -1,128 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# -# 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 argparse - -from rich.prompt import Prompt - -import bittensor -from bittensor.utils.formatting import convert_blocks_to_time -from . import defaults - -console = bittensor.__console__ - - -def fetch_arbitration_stats(subtensor, wallet): - """ - Performs a check of the current arbitration data (if any), and displays it through the bittensor console. - """ - arbitration_check = len( - subtensor.check_in_arbitration(wallet.coldkeypub.ss58_address) - ) - if arbitration_check == 0: - bittensor.__console__.print( - "[green]There has been no previous key swap initiated for your coldkey.[/green]" - ) - if arbitration_check == 1: - arbitration_remaining = subtensor.get_remaining_arbitration_period( - wallet.coldkeypub.ss58_address - ) - hours, minutes, seconds = convert_blocks_to_time(arbitration_remaining) - bittensor.__console__.print( - "[yellow]There has been 1 swap request made for this coldkey already." - " By adding another swap request, the key will enter arbitration." - f" Your key swap is scheduled for {hours} hours, {minutes} minutes, {seconds} seconds" - " from now.[/yellow]" - ) - if arbitration_check > 1: - bittensor.__console__.print( - f"[red]This coldkey is currently in arbitration with a total swaps of {arbitration_check}.[/red]" - ) - - -class CheckColdKeySwapCommand: - """ - Executes the ``check_coldkey_swap`` command to check swap status of a coldkey in the Bittensor network. - Usage: - Users need to specify the wallet they want to check the swap status of. - Example usage:: - btcli wallet check_coldkey_swap - Note: - This command is important for users who wish check if swap requests were made against their coldkey. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - """ - Runs the check coldkey swap command. - Args: - cli (bittensor.cli): The CLI object containing configuration and command-line interface utilities. - """ - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - CheckColdKeySwapCommand._run(cli, subtensor) - except Exception as e: - bittensor.logging.warning(f"Failed to get swap status: {e}") - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - """ - Internal method to check coldkey swap status. - Args: - cli (bittensor.cli): The CLI object containing configuration and command-line interface utilities. - subtensor (bittensor.subtensor): The subtensor object for blockchain interactions. - """ - config = cli.config.copy() - wallet = bittensor.wallet(config=config) - - fetch_arbitration_stats(subtensor, wallet) - - @classmethod - def check_config(cls, config: "bittensor.config"): - """ - Checks and prompts for necessary configuration settings. - Args: - config (bittensor.config): The configuration object. - Prompts the user for wallet name if not set in the config. - """ - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name: str = Prompt.ask( - "Enter wallet name", default=defaults.wallet.name - ) - config.wallet.name = str(wallet_name) - - @staticmethod - def add_args(command_parser: argparse.ArgumentParser): - """ - Adds arguments to the command parser. - Args: - command_parser (argparse.ArgumentParser): The command parser to add arguments to. - """ - swap_parser = command_parser.add_parser( - "check_coldkey_swap", - help="""Check the status of swap requests for a coldkey on the Bittensor network. - Adding more than one swap request will make the key go into arbitration mode.""", - ) - bittensor.wallet.add_args(swap_parser) - bittensor.subtensor.add_args(swap_parser) diff --git a/bittensor/commands/delegates.py b/bittensor/commands/delegates.py deleted file mode 100644 index 4d03b289e4..0000000000 --- a/bittensor/commands/delegates.py +++ /dev/null @@ -1,1147 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 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 argparse -import os -import sys -from typing import List, Dict, Optional - -from rich.console import Text -from rich.prompt import Prompt, FloatPrompt, Confirm -from rich.table import Table -from substrateinterface.exceptions import SubstrateRequestException -from tqdm import tqdm - -import bittensor -from . import defaults -from .identity import SetIdentityCommand -from .utils import get_delegates_details, DelegatesDetails - - -def _get_coldkey_wallets_for_path(path: str) -> List["bittensor.wallet"]: - try: - wallet_names = next(os.walk(os.path.expanduser(path)))[1] - return [bittensor.wallet(path=path, name=name) for name in wallet_names] - except StopIteration: - # No wallet files found. - wallets = [] - return wallets - - -console = bittensor.__console__ - - -def show_delegates_lite( - delegates_lite: List["bittensor.DelegateInfoLite"], width: Optional[int] = None -): - """ - This method is a lite version of the :func:`show_delegates`. This method displays a formatted table of Bittensor network delegates with detailed statistics to the console. - - The table is sorted by total stake in descending order and provides - a snapshot of delegate performance and status, helping users make informed decisions for staking or nominating. - - This helper function is not intended to be used directly in user code unless specifically required. - - Args: - delegates_lite (List[bittensor.DelegateInfoLite]): A list of delegate information objects to be displayed. - width (Optional[int]): The width of the console output table. Defaults to ``None``, which will make the table expand to the maximum width of the console. - - The output table contains the following columns. To display more columns, use the :func:`show_delegates` function. - - - INDEX: The numerical index of the delegate. - - DELEGATE: The name of the delegate. - - SS58: The truncated SS58 address of the delegate. - - NOMINATORS: The number of nominators supporting the delegate. - - VPERMIT: Validator permits held by the delegate for the subnets. - - TAKE: The percentage of the delegate's earnings taken by the network. - - DELEGATE/(24h): The earnings of the delegate in the last 24 hours. - - Desc: A brief description provided by the delegate. - - Usage: - This function is typically used within the Bittensor CLI to show current delegate options to users who are considering where to stake their tokens. - - Example usage:: - - show_delegates_lite(delegates_lite, width=80) - - Note: - This function is primarily for display purposes within a command-line interface and does not return any values. It relies on the `rich `_ Python library to render - the table in the console. - """ - - registered_delegate_info: Optional[Dict[str, DelegatesDetails]] = ( - get_delegates_details(url=bittensor.__delegates_details_url__) - ) - if registered_delegate_info is None: - bittensor.__console__.print( - ":warning:[yellow]Could not get delegate info from chain.[/yellow]" - ) - registered_delegate_info = {} - - table = Table(show_footer=True, width=width, pad_edge=False, box=None, expand=True) - table.add_column( - "[overline white]INDEX", - str(len(delegates_lite)), - footer_style="overline white", - style="bold white", - ) - table.add_column( - "[overline white]DELEGATE", - style="rgb(50,163,219)", - no_wrap=True, - justify="left", - ) - table.add_column( - "[overline white]SS58", - str(len(delegates_lite)), - footer_style="overline white", - style="bold yellow", - ) - table.add_column( - "[overline white]NOMINATORS", justify="center", style="green", no_wrap=True - ) - table.add_column("[overline white]VPERMIT", justify="right", no_wrap=False) - table.add_column("[overline white]TAKE", style="white", no_wrap=True) - table.add_column("[overline white]DELEGATE/(24h)", style="green", justify="center") - table.add_column("[overline white]Desc", style="rgb(50,163,219)") - - for i, d in enumerate(delegates_lite): - if d.delegate_ss58 in registered_delegate_info: - delegate_name = registered_delegate_info[d.delegate_ss58].name - delegate_url = registered_delegate_info[d.delegate_ss58].url - delegate_description = registered_delegate_info[d.delegate_ss58].description - else: - delegate_name = "" - delegate_url = "" - delegate_description = "" - - table.add_row( - # `INDEX` column - str(i), - # `DELEGATE` column - Text(delegate_name, style=f"link {delegate_url}"), - # `SS58` column - f"{d.delegate_ss58:8.8}...", - # `NOMINATORS` column - str(d.nominators), - # `VPERMIT` column - str(d.registrations), - # `TAKE` column - f"{d.take * 100:.1f}%", - # `DELEGATE/(24h)` column - f"τ{bittensor.Balance.from_tao(d.total_daily_return * 0.18) !s:6.6}", - # `Desc` column - str(delegate_description), - end_section=True, - ) - bittensor.__console__.print(table) - - -# Uses rich console to pretty print a table of delegates. -def show_delegates( - delegates: List["bittensor.DelegateInfo"], - prev_delegates: Optional[List["bittensor.DelegateInfo"]], - width: Optional[int] = None, -): - """ - Displays a formatted table of Bittensor network delegates with detailed statistics to the console. - - The table is sorted by total stake in descending order and provides - a snapshot of delegate performance and status, helping users make informed decisions for staking or nominating. - - This is a helper function that is called by the :func:`list_delegates` and :func:`my_delegates`, and is not intended - to be used directly in user code unless specifically required. - - Args: - delegates (List[bittensor.DelegateInfo]): A list of delegate information objects to be displayed. - prev_delegates (Optional[List[bittensor.DelegateInfo]]): A list of delegate information objects from a previous state, used to calculate changes in stake. Defaults to ``None``. - width (Optional[int]): The width of the console output table. Defaults to ``None``, which will make the table expand to the maximum width of the console. - - The output table contains the following columns: - - - INDEX: The numerical index of the delegate. - - DELEGATE: The name of the delegate. - - SS58: The truncated SS58 address of the delegate. - - NOMINATORS: The number of nominators supporting the delegate. - - DELEGATE STAKE(τ): The stake that is directly delegated to the delegate. - - TOTAL STAKE(τ): The total stake held by the delegate, including nominators' stake. - - CHANGE/(4h): The percentage change in the delegate's stake over the past 4 hours. - - VPERMIT: Validator permits held by the delegate for the subnets. - - TAKE: The percentage of the delegate's earnings taken by the network. - - NOMINATOR/(24h)/kτ: The earnings per 1000 τ staked by nominators in the last 24 hours. - - DELEGATE/(24h): The earnings of the delegate in the last 24 hours. - - Desc: A brief description provided by the delegate. - - Usage: - This function is typically used within the Bittensor CLI to show current delegate options to users who are considering where to stake their tokens. - - Example usage:: - - show_delegates(current_delegates, previous_delegates, width=80) - - Note: - This function is primarily for display purposes within a command-line interface and does - not return any values. It relies on the `rich `_ Python library to render - the table in the - console. - """ - - delegates.sort(key=lambda delegate: delegate.total_stake, reverse=True) - prev_delegates_dict = {} - if prev_delegates is not None: - for prev_delegate in prev_delegates: - prev_delegates_dict[prev_delegate.hotkey_ss58] = prev_delegate - - registered_delegate_info: Optional[Dict[str, DelegatesDetails]] = ( - get_delegates_details(url=bittensor.__delegates_details_url__) - ) - if registered_delegate_info is None: - bittensor.__console__.print( - ":warning:[yellow]Could not get delegate info from chain.[/yellow]" - ) - registered_delegate_info = {} - - table = Table(show_footer=True, width=width, pad_edge=False, box=None, expand=True) - table.add_column( - "[overline white]INDEX", - str(len(delegates)), - footer_style="overline white", - style="bold white", - ) - table.add_column( - "[overline white]DELEGATE", - style="rgb(50,163,219)", - no_wrap=True, - justify="left", - ) - table.add_column( - "[overline white]SS58", - str(len(delegates)), - footer_style="overline white", - style="bold yellow", - ) - table.add_column( - "[overline white]NOMINATORS", justify="center", style="green", no_wrap=True - ) - table.add_column( - "[overline white]DELEGATE STAKE(\u03c4)", justify="right", no_wrap=True - ) - table.add_column( - "[overline white]TOTAL STAKE(\u03c4)", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column("[overline white]CHANGE/(4h)", style="grey0", justify="center") - table.add_column("[overline white]VPERMIT", justify="right", no_wrap=False) - table.add_column("[overline white]TAKE", style="white", no_wrap=True) - table.add_column( - "[overline white]NOMINATOR/(24h)/k\u03c4", style="green", justify="center" - ) - table.add_column("[overline white]DELEGATE/(24h)", style="green", justify="center") - table.add_column("[overline white]Desc", style="rgb(50,163,219)") - - for i, delegate in enumerate(delegates): - owner_stake = next( - map( - lambda x: x[1], # get stake - filter( - lambda x: x[0] == delegate.owner_ss58, delegate.nominators - ), # filter for owner - ), - bittensor.Balance.from_rao(0), # default to 0 if no owner stake. - ) - if delegate.hotkey_ss58 in registered_delegate_info: - delegate_name = registered_delegate_info[delegate.hotkey_ss58].name - delegate_url = registered_delegate_info[delegate.hotkey_ss58].url - delegate_description = registered_delegate_info[ - delegate.hotkey_ss58 - ].description - else: - delegate_name = "" - delegate_url = "" - delegate_description = "" - - if delegate.hotkey_ss58 in prev_delegates_dict: - prev_stake = prev_delegates_dict[delegate.hotkey_ss58].total_stake - if prev_stake == 0: - rate_change_in_stake_str = "[green]100%[/green]" - else: - rate_change_in_stake = ( - 100 - * (float(delegate.total_stake) - float(prev_stake)) - / float(prev_stake) - ) - if rate_change_in_stake > 0: - rate_change_in_stake_str = "[green]{:.2f}%[/green]".format( - rate_change_in_stake - ) - elif rate_change_in_stake < 0: - rate_change_in_stake_str = "[red]{:.2f}%[/red]".format( - rate_change_in_stake - ) - else: - rate_change_in_stake_str = "[grey0]0%[/grey0]" - else: - rate_change_in_stake_str = "[grey0]NA[/grey0]" - - table.add_row( - # INDEX - str(i), - # DELEGATE - Text(delegate_name, style=f"link {delegate_url}"), - # SS58 - f"{delegate.hotkey_ss58:8.8}...", - # NOMINATORS - str(len([nom for nom in delegate.nominators if nom[1].rao > 0])), - # DELEGATE STAKE - f"{owner_stake!s:13.13}", - # TOTAL STAKE - f"{delegate.total_stake!s:13.13}", - # CHANGE/(4h) - rate_change_in_stake_str, - # VPERMIT - str(delegate.registrations), - # TAKE - f"{delegate.take * 100:.1f}%", - # NOMINATOR/(24h)/k - f"{bittensor.Balance.from_tao( delegate.total_daily_return.tao * (1000/ (0.001 + delegate.total_stake.tao)))!s:6.6}", - # DELEGATE/(24h) - f"{bittensor.Balance.from_tao(delegate.total_daily_return.tao * 0.18) !s:6.6}", - # Desc - str(delegate_description), - end_section=True, - ) - bittensor.__console__.print(table) - - -class DelegateStakeCommand: - """ - Executes the ``delegate`` command, which stakes Tao to a specified delegate on the Bittensor network. - - This action allocates the user's Tao to support a delegate, potentially earning staking rewards in return. - - Optional Arguments: - - ``wallet.name``: The name of the wallet to use for the command. - - ``delegate_ss58key``: The ``SS58`` address of the delegate to stake to. - - ``amount``: The amount of Tao to stake. - - ``all``: If specified, the command stakes all available Tao. - - The command interacts with the user to determine the delegate and the amount of Tao to be staked. If the ``--all`` - flag is used, it delegates the entire available balance. - - Usage: - The user must specify the delegate's SS58 address and the amount of Tao to stake. The function sends a - transaction to the subtensor network to delegate the specified amount to the chosen delegate. These values are - prompted if not provided. - - Example usage:: - - btcli delegate --delegate_ss58key --amount - btcli delegate --delegate_ss58key --all - - Note: - This command modifies the blockchain state and may incur transaction fees. It requires user confirmation and - interaction, and is designed to be used within the Bittensor CLI environment. The user should ensure the - delegate's address and the amount to be staked are correct before executing the command. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - """Delegates stake to a chain delegate.""" - try: - config = cli.config.copy() - wallet = bittensor.wallet(config=config) - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - subtensor.delegate( - wallet=wallet, - delegate_ss58=config.get("delegate_ss58key"), - amount=config.get("amount"), - wait_for_inclusion=True, - prompt=not config.no_prompt, - ) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - delegate_stake_parser = parser.add_parser( - "delegate", help="""Delegate Stake to an account.""" - ) - delegate_stake_parser.add_argument( - "--delegate_ss58key", - "--delegate_ss58", - dest="delegate_ss58key", - type=str, - required=False, - help="""The ss58 address of the choosen delegate""", - ) - delegate_stake_parser.add_argument( - "--all", dest="stake_all", action="store_true" - ) - delegate_stake_parser.add_argument( - "--amount", dest="amount", type=float, required=False - ) - bittensor.wallet.add_args(delegate_stake_parser) - bittensor.subtensor.add_args(delegate_stake_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.get("delegate_ss58key"): - # Check for delegates. - with bittensor.__console__.status(":satellite: Loading delegates..."): - subtensor = bittensor.subtensor(config=config, log_verbose=False) - delegates: List[bittensor.DelegateInfo] = subtensor.get_delegates() - try: - prev_delegates = subtensor.get_delegates( - max(0, subtensor.block - 1200) - ) - except SubstrateRequestException: - prev_delegates = None - - if prev_delegates is None: - bittensor.__console__.print( - ":warning: [yellow]Could not fetch delegates history[/yellow]" - ) - - if len(delegates) == 0: - console.print( - ":cross_mark: [red]There are no delegates on {}[/red]".format( - subtensor.network - ) - ) - sys.exit(1) - - delegates.sort(key=lambda delegate: delegate.total_stake, reverse=True) - show_delegates(delegates, prev_delegates=prev_delegates) - delegate_index = Prompt.ask("Enter delegate index") - config.delegate_ss58key = str(delegates[int(delegate_index)].hotkey_ss58) - console.print( - "Selected: [yellow]{}[/yellow]".format(config.delegate_ss58key) - ) - - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - # Get amount. - if not config.get("amount") and not config.get("stake_all"): - if not Confirm.ask( - "Stake all Tao from account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", defaults.wallet.name) - ) - ): - amount = Prompt.ask("Enter Tao amount to stake") - try: - config.amount = float(amount) - except ValueError: - console.print( - ":cross_mark: [red]Invalid Tao amount[/red] [bold white]{}[/bold white]".format( - amount - ) - ) - sys.exit() - else: - config.stake_all = True - - -class DelegateUnstakeCommand: - """ - Executes the ``undelegate`` command, allowing users to withdraw their staked Tao from a delegate on the Bittensor - network. - - This process is known as "undelegating" and it reverses the delegation process, freeing up the staked tokens. - - Optional Arguments: - - ``wallet.name``: The name of the wallet to use for the command. - - ``delegate_ss58key``: The ``SS58`` address of the delegate to undelegate from. - - ``amount``: The amount of Tao to undelegate. - - ``all``: If specified, the command undelegates all staked Tao from the delegate. - - The command prompts the user for the amount of Tao to undelegate and the ``SS58`` address of the delegate from which - to undelegate. If the ``--all`` flag is used, it will attempt to undelegate the entire staked amount from the - specified delegate. - - Usage: - The user must provide the delegate's SS58 address and the amount of Tao to undelegate. The function will then - send a transaction to the Bittensor network to process the undelegation. - - Example usage:: - - btcli undelegate --delegate_ss58key --amount - btcli undelegate --delegate_ss58key --all - - Note: - This command can result in a change to the blockchain state and may incur transaction fees. It is interactive - and requires confirmation from the user before proceeding. It should be used with care as undelegating can - affect the delegate's total stake and - potentially the user's staking rewards. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - """Undelegates stake from a chain delegate.""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - DelegateUnstakeCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - def _run(self: "bittensor.cli", subtensor: "bittensor.subtensor"): - """Undelegates stake from a chain delegate.""" - config = self.config.copy() - wallet = bittensor.wallet(config=config) - subtensor.undelegate( - wallet=wallet, - delegate_ss58=config.get("delegate_ss58key"), - amount=config.get("amount"), - wait_for_inclusion=True, - prompt=not config.no_prompt, - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - undelegate_stake_parser = parser.add_parser( - "undelegate", help="""Undelegate Stake from an account.""" - ) - undelegate_stake_parser.add_argument( - "--delegate_ss58key", - "--delegate_ss58", - dest="delegate_ss58key", - type=str, - required=False, - help="""The ss58 address of the choosen delegate""", - ) - undelegate_stake_parser.add_argument( - "--all", dest="unstake_all", action="store_true" - ) - undelegate_stake_parser.add_argument( - "--amount", dest="amount", type=float, required=False - ) - bittensor.wallet.add_args(undelegate_stake_parser) - bittensor.subtensor.add_args(undelegate_stake_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.get("delegate_ss58key"): - # Check for delegates. - with bittensor.__console__.status(":satellite: Loading delegates..."): - subtensor = bittensor.subtensor(config=config, log_verbose=False) - delegates: List[bittensor.DelegateInfo] = subtensor.get_delegates() - try: - prev_delegates = subtensor.get_delegates( - max(0, subtensor.block - 1200) - ) - except SubstrateRequestException: - prev_delegates = None - - if prev_delegates is None: - bittensor.__console__.print( - ":warning: [yellow]Could not fetch delegates history[/yellow]" - ) - - if len(delegates) == 0: - console.print( - ":cross_mark: [red]There are no delegates on {}[/red]".format( - subtensor.network - ) - ) - sys.exit(1) - - delegates.sort(key=lambda delegate: delegate.total_stake, reverse=True) - show_delegates(delegates, prev_delegates=prev_delegates) - delegate_index = Prompt.ask("Enter delegate index") - config.delegate_ss58key = str(delegates[int(delegate_index)].hotkey_ss58) - console.print( - "Selected: [yellow]{}[/yellow]".format(config.delegate_ss58key) - ) - - # Get amount. - if not config.get("amount") and not config.get("unstake_all"): - if not Confirm.ask( - "Unstake all Tao to account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", defaults.wallet.name) - ) - ): - amount = Prompt.ask("Enter Tao amount to unstake") - try: - config.amount = float(amount) - except ValueError: - console.print( - ":cross_mark: [red]Invalid Tao amount[/red] [bold white]{}[/bold white]".format( - amount - ) - ) - sys.exit() - else: - config.unstake_all = True - - -class ListDelegatesCommand: - """ - Displays a formatted table of Bittensor network delegates, providing a comprehensive overview of delegate statistics and information. - - This table helps users make informed decisions on which delegates to allocate their TAO stake. - - Optional Arguments: - - ``wallet.name``: The name of the wallet to use for the command. - - ``subtensor.network``: The name of the network to use for the command. - - The table columns include: - - - INDEX: The delegate's index in the sorted list. - - DELEGATE: The name of the delegate. - - SS58: The delegate's unique SS58 address (truncated for display). - - NOMINATORS: The count of nominators backing the delegate. - - DELEGATE STAKE(τ): The amount of delegate's own stake (not the TAO delegated from any nominators). - - TOTAL STAKE(τ): The delegate's cumulative stake, including self-staked and nominators' stakes. - - CHANGE/(4h): The percentage change in the delegate's stake over the last four hours. - - SUBNETS: The subnets to which the delegate is registered. - - VPERMIT: Indicates the subnets for which the delegate has validator permits. - - NOMINATOR/(24h)/kτ: The earnings per 1000 τ staked by nominators in the last 24 hours. - - DELEGATE/(24h): The total earnings of the delegate in the last 24 hours. - - DESCRIPTION: A brief description of the delegate's purpose and operations. - - Sorting is done based on the ``TOTAL STAKE`` column in descending order. Changes in stake are highlighted: - increases in green and decreases in red. Entries with no previous data are marked with ``NA``. Each delegate's name - is a hyperlink to their respective URL, if available. - - Example usage:: - - btcli root list_delegates - btcli root list_delegates --wallet.name my_wallet - btcli root list_delegates --subtensor.network finney # can also be `test` or `local` - - Note: - This function is part of the Bittensor CLI tools and is intended for use within a console application. It prints - directly to the console and does not return any value. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r""" - List all delegates on the network. - """ - try: - cli.config.subtensor.network = "archive" - cli.config.subtensor.chain_endpoint = ( - "wss://archive.chain.opentensor.ai:443" - ) - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - ListDelegatesCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r""" - List all delegates on the network. - """ - with bittensor.__console__.status(":satellite: Loading delegates..."): - delegates: list[bittensor.DelegateInfo] = subtensor.get_delegates() - - try: - prev_delegates = subtensor.get_delegates(max(0, subtensor.block - 1200)) - except SubstrateRequestException: - prev_delegates = None - - if prev_delegates is None: - bittensor.__console__.print( - ":warning: [yellow]Could not fetch delegates history[/yellow]" - ) - - show_delegates( - delegates, - prev_delegates=prev_delegates, - width=cli.config.get("width", None), - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - list_delegates_parser = parser.add_parser( - "list_delegates", help="""List all delegates on the network""" - ) - bittensor.subtensor.add_args(list_delegates_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - pass - - -class NominateCommand: - """ - Executes the ``nominate`` command, which facilitates a wallet to become a delegate on the Bittensor network. - - This command handles the nomination process, including wallet unlocking and verification of the hotkey's current - delegate status. - - The command performs several checks: - - - Verifies that the hotkey is not already a delegate to prevent redundant nominations. - - Tries to nominate the wallet and reports success or failure. - - Upon success, the wallet's hotkey is registered as a delegate on the network. - - Optional Arguments: - - ``wallet.name``: The name of the wallet to use for the command. - - ``wallet.hotkey``: The name of the hotkey to use for the command. - - Usage: - To run the command, the user must have a configured wallet with both hotkey and coldkey. If the wallet is not - already nominated, this command will initiate the process. - - Example usage:: - - btcli root nominate - btcli root nominate --wallet.name my_wallet --wallet.hotkey my_hotkey - - Note: - This function is intended to be used as a CLI command. It prints the outcome directly to the console and does - not return any value. It should not be called programmatically in user code due to its interactive nature and - side effects on the network state. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Nominate wallet.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - NominateCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Nominate wallet.""" - wallet = bittensor.wallet(config=cli.config) - - # Unlock the wallet. - wallet.hotkey - wallet.coldkey - - # Check if the hotkey is already a delegate. - if subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address): - bittensor.__console__.print( - "Aborting: Hotkey {} is already a delegate.".format( - wallet.hotkey.ss58_address - ) - ) - return - - result: bool = subtensor.nominate(wallet) - if not result: - bittensor.__console__.print( - "Could not became a delegate on [white]{}[/white]".format( - subtensor.network - ) - ) - else: - # Check if we are a delegate. - is_delegate: bool = subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address) - if not is_delegate: - bittensor.__console__.print( - "Could not became a delegate on [white]{}[/white]".format( - subtensor.network - ) - ) - return - bittensor.__console__.print( - "Successfully became a delegate on [white]{}[/white]".format( - subtensor.network - ) - ) - - # Prompt use to set identity on chain. - if not cli.config.no_prompt: - do_set_identity = Prompt.ask( - f"Subnetwork registered successfully. Would you like to set your identity? [y/n]", - choices=["y", "n"], - ) - - if do_set_identity.lower() == "y": - subtensor.close() - config = cli.config.copy() - SetIdentityCommand.check_config(config) - cli.config = config - SetIdentityCommand.run(cli) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - nominate_parser = parser.add_parser( - "nominate", help="""Become a delegate on the network""" - ) - bittensor.wallet.add_args(nominate_parser) - bittensor.subtensor.add_args(nominate_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - -class MyDelegatesCommand: - """ - Executes the ``my_delegates`` command within the Bittensor CLI, which retrieves and displays a table of delegated - stakes from a user's wallet(s) to various delegates on the Bittensor network. - - The command provides detailed insights into the user's - staking activities and the performance of their chosen delegates. - - Optional Arguments: - - ``wallet.name``: The name of the wallet to use for the command. - - ``all``: If specified, the command aggregates information across all wallets. - - The table output includes the following columns: - - - Wallet: The name of the user's wallet. - - OWNER: The name of the delegate's owner. - - SS58: The truncated SS58 address of the delegate. - - Delegation: The amount of Tao staked by the user to the delegate. - - τ/24h: The earnings from the delegate to the user over the past 24 hours. - - NOMS: The number of nominators for the delegate. - - OWNER STAKE(τ): The stake amount owned by the delegate. - - TOTAL STAKE(τ): The total stake amount held by the delegate. - - SUBNETS: The list of subnets the delegate is a part of. - - VPERMIT: Validator permits held by the delegate for various subnets. - - 24h/kτ: Earnings per 1000 Tao staked over the last 24 hours. - - Desc: A description of the delegate. - - The command also sums and prints the total amount of Tao delegated across all wallets. - - Usage: - The command can be run as part of the Bittensor CLI suite of tools and requires no parameters if a single wallet - is used. If multiple wallets are present, the ``--all`` flag can be specified to aggregate information across - all wallets. - - Example usage:: - - btcli my_delegates - btcli my_delegates --all - btcli my_delegates --wallet.name my_wallet - - Note: - This function is typically called by the CLI parser and is not intended to be used directly in user code. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - """Delegates stake to a chain delegate.""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - MyDelegatesCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - """Delegates stake to a chain delegate.""" - config = cli.config.copy() - if config.get("all", d=None): - wallets = _get_coldkey_wallets_for_path(config.wallet.path) - else: - wallets = [bittensor.wallet(config=config)] - - table = Table(show_footer=True, pad_edge=False, box=None, expand=True) - table.add_column( - "[overline white]Wallet", footer_style="overline white", style="bold white" - ) - table.add_column( - "[overline white]OWNER", - style="rgb(50,163,219)", - no_wrap=True, - justify="left", - ) - table.add_column( - "[overline white]SS58", footer_style="overline white", style="bold yellow" - ) - table.add_column( - "[overline green]Delegation", - footer_style="overline green", - style="bold green", - ) - table.add_column( - "[overline green]\u03c4/24h", - footer_style="overline green", - style="bold green", - ) - table.add_column( - "[overline white]NOMS", justify="center", style="green", no_wrap=True - ) - table.add_column( - "[overline white]OWNER STAKE(\u03c4)", justify="right", no_wrap=True - ) - table.add_column( - "[overline white]TOTAL STAKE(\u03c4)", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]SUBNETS", justify="right", style="white", no_wrap=True - ) - table.add_column("[overline white]VPERMIT", justify="right", no_wrap=True) - table.add_column("[overline white]24h/k\u03c4", style="green", justify="center") - table.add_column("[overline white]Desc", style="rgb(50,163,219)") - total_delegated = 0 - - for wallet in tqdm(wallets): - if not wallet.coldkeypub_file.exists_on_device(): - continue - delegates = subtensor.get_delegated( - coldkey_ss58=wallet.coldkeypub.ss58_address - ) - - my_delegates = {} # hotkey, amount - for delegate in delegates: - for coldkey_addr, staked in delegate[0].nominators: - if ( - coldkey_addr == wallet.coldkeypub.ss58_address - and staked.tao > 0 - ): - my_delegates[delegate[0].hotkey_ss58] = staked - - delegates.sort(key=lambda delegate: delegate[0].total_stake, reverse=True) - total_delegated += sum(my_delegates.values()) - - registered_delegate_info: Optional[DelegatesDetails] = ( - get_delegates_details(url=bittensor.__delegates_details_url__) - ) - if registered_delegate_info is None: - bittensor.__console__.print( - ":warning:[yellow]Could not get delegate info from chain.[/yellow]" - ) - registered_delegate_info = {} - - for i, delegate in enumerate(delegates): - owner_stake = next( - map( - lambda x: x[1], # get stake - filter( - lambda x: x[0] == delegate[0].owner_ss58, - delegate[0].nominators, - ), # filter for owner - ), - bittensor.Balance.from_rao(0), # default to 0 if no owner stake. - ) - if delegate[0].hotkey_ss58 in registered_delegate_info: - delegate_name = registered_delegate_info[ - delegate[0].hotkey_ss58 - ].name - delegate_url = registered_delegate_info[delegate[0].hotkey_ss58].url - delegate_description = registered_delegate_info[ - delegate[0].hotkey_ss58 - ].description - else: - delegate_name = "" - delegate_url = "" - delegate_description = "" - - if delegate[0].hotkey_ss58 in my_delegates: - table.add_row( - wallet.name, - Text(delegate_name, style=f"link {delegate_url}"), - f"{delegate[0].hotkey_ss58:8.8}...", - f"{my_delegates[delegate[0].hotkey_ss58]!s:13.13}", - f"{delegate[0].total_daily_return.tao * (my_delegates[delegate[0].hotkey_ss58]/delegate[0].total_stake.tao)!s:6.6}", - str(len(delegate[0].nominators)), - f"{owner_stake!s:13.13}", - f"{delegate[0].total_stake!s:13.13}", - str(delegate[0].registrations), - str( - [ - "*" if subnet in delegate[0].validator_permits else "" - for subnet in delegate[0].registrations - ] - ), - # f'{delegate.take * 100:.1f}%',s - f"{ delegate[0].total_daily_return.tao * ( 1000 / ( 0.001 + delegate[0].total_stake.tao ) )!s:6.6}", - str(delegate_description), - # f'{delegate_profile.description:140.140}', - ) - - bittensor.__console__.print(table) - bittensor.__console__.print("Total delegated Tao: {}".format(total_delegated)) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - delegate_stake_parser = parser.add_parser( - "my_delegates", - help="""Show all delegates where I am delegating a positive amount of stake""", - ) - delegate_stake_parser.add_argument( - "--all", - action="store_true", - help="""Check all coldkey wallets.""", - default=False, - ) - bittensor.wallet.add_args(delegate_stake_parser) - bittensor.subtensor.add_args(delegate_stake_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if ( - not config.get("all", d=None) - and not config.is_set("wallet.name") - and not config.no_prompt - ): - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - -class SetTakeCommand: - """ - Executes the ``set_take`` command, which sets the delegate take. - - The command performs several checks: - - 1. Hotkey is already a delegate - 2. New take value is within 0-18% range - - Optional Arguments: - - ``take``: The new take value - - ``wallet.name``: The name of the wallet to use for the command. - - ``wallet.hotkey``: The name of the hotkey to use for the command. - - Usage: - To run the command, the user must have a configured wallet with both hotkey and coldkey. Also, the hotkey should already be a delegate. - - Example usage:: - btcli root set_take --wallet.name my_wallet --wallet.hotkey my_hotkey - - Note: - This function can be used to update the takes individually for every subnet - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Set delegate take.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - SetTakeCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Set delegate take.""" - config = cli.config.copy() - wallet = bittensor.wallet(config=cli.config) - - # Unlock the wallet. - wallet.hotkey - wallet.coldkey - - # Check if the hotkey is not a delegate. - if not subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address): - bittensor.__console__.print( - "Aborting: Hotkey {} is NOT a delegate.".format( - wallet.hotkey.ss58_address - ) - ) - return - - # Prompt user for take value. - new_take_str = config.get("take") - if new_take_str == None: - new_take = FloatPrompt.ask(f"Enter take value (0.18 for 18%)") - else: - new_take = float(new_take_str) - - if new_take > 0.18: - bittensor.__console__.print("ERROR: Take value should not exceed 18%") - return - - result: bool = subtensor.set_take( - wallet=wallet, - delegate_ss58=wallet.hotkey.ss58_address, - take=new_take, - ) - if not result: - bittensor.__console__.print("Could not set the take") - else: - # Check if we are a delegate. - is_delegate: bool = subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address) - if not is_delegate: - bittensor.__console__.print( - "Could not set the take [white]{}[/white]".format(subtensor.network) - ) - return - bittensor.__console__.print( - "Successfully set the take on [white]{}[/white]".format( - subtensor.network - ) - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - set_take_parser = parser.add_parser( - "set_take", help="""Set take for delegate""" - ) - set_take_parser.add_argument( - "--take", - dest="take", - type=float, - required=False, - help="""Take as a float number""", - ) - bittensor.wallet.add_args(set_take_parser) - bittensor.subtensor.add_args(set_take_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) diff --git a/bittensor/commands/identity.py b/bittensor/commands/identity.py deleted file mode 100644 index 15232c4440..0000000000 --- a/bittensor/commands/identity.py +++ /dev/null @@ -1,337 +0,0 @@ -import argparse -from rich.table import Table -from rich.prompt import Prompt -from sys import getsizeof - -import bittensor - - -class SetIdentityCommand: - """ - Executes the :func:`set_identity` command within the Bittensor network, which allows for the creation or update of a delegate's on-chain identity. - - This identity includes various - attributes such as display name, legal name, web URL, PGP fingerprint, and contact - information, among others. - - Optional Arguments: - - ``display``: The display name for the identity. - - ``legal``: The legal name for the identity. - - ``web``: The web URL for the identity. - - ``riot``: The riot handle for the identity. - - ``email``: The email address for the identity. - - ``pgp_fingerprint``: The PGP fingerprint for the identity. - - ``image``: The image URL for the identity. - - ``info``: The info for the identity. - - ``twitter``: The X (twitter) URL for the identity. - - The command prompts the user for the different identity attributes and validates the - input size for each attribute. It provides an option to update an existing validator - hotkey identity. If the user consents to the transaction cost, the identity is updated - on the blockchain. - - Each field has a maximum size of 64 bytes. The PGP fingerprint field is an exception - and has a maximum size of 20 bytes. The user is prompted to enter the PGP fingerprint - as a hex string, which is then converted to bytes. The user is also prompted to enter - the coldkey or hotkey ``ss58`` address for the identity to be updated. If the user does - not have a hotkey, the coldkey address is used by default. - - If setting a validator identity, the hotkey will be used by default. If the user is - setting an identity for a subnet, the coldkey will be used by default. - - Usage: - The user should call this command from the command line and follow the interactive - prompts to enter or update the identity information. The command will display the - updated identity details in a table format upon successful execution. - - Example usage:: - - btcli wallet set_identity - - Note: - This command should only be used if the user is willing to incur the 1 TAO transaction - fee associated with setting an identity on the blockchain. It is a high-level command - that makes changes to the blockchain state and should not be used programmatically as - part of other scripts or applications. - """ - - def run(cli: "bittensor.cli"): - r"""Create a new or update existing identity on-chain.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - SetIdentityCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Create a new or update existing identity on-chain.""" - console = bittensor.__console__ - - wallet = bittensor.wallet(config=cli.config) - - id_dict = { - "display": cli.config.display, - "legal": cli.config.legal, - "web": cli.config.web, - "pgp_fingerprint": cli.config.pgp_fingerprint, - "riot": cli.config.riot, - "email": cli.config.email, - "image": cli.config.image, - "twitter": cli.config.twitter, - "info": cli.config.info, - } - - for field, string in id_dict.items(): - if getsizeof(string) > 113: # 64 + 49 overhead bytes for string - raise ValueError(f"Identity value `{field}` must be <= 64 raw bytes") - - identified = ( - wallet.hotkey.ss58_address - if str( - Prompt.ask( - "Are you updating a validator hotkey identity?", - default="y", - choices=["y", "n"], - ) - ).lower() - == "y" - else None - ) - - if ( - str( - Prompt.ask( - "Cost to register an Identity is [bold white italic]0.1 Tao[/bold white italic], are you sure you wish to continue?", - default="n", - choices=["y", "n"], - ) - ).lower() - == "n" - ): - console.print(":cross_mark: Aborted!") - exit(0) - - wallet.coldkey # unlock coldkey - with console.status(":satellite: [bold green]Updating identity on-chain..."): - try: - subtensor.update_identity( - identified=identified, - wallet=wallet, - params=id_dict, - ) - except Exception as e: - console.print(f"[red]:cross_mark: Failed![/red] {e}") - exit(1) - - console.print(":white_heavy_check_mark: Success!") - - identity = subtensor.query_identity(identified or wallet.coldkey.ss58_address) - - table = Table(title="[bold white italic]Updated On-Chain Identity") - table.add_column("Key", justify="right", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("Address", identified or wallet.coldkey.ss58_address) - for key, value in identity.items(): - table.add_row(key, str(value) if value is not None else "None") - - console.print(table) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - config.wallet.name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) - if not config.is_set("wallet.hotkey") and not config.no_prompt: - config.wallet.hotkey = Prompt.ask( - "Enter wallet hotkey", default=bittensor.defaults.wallet.hotkey - ) - if not config.is_set("subtensor.network") and not config.no_prompt: - config.subtensor.network = Prompt.ask( - "Enter subtensor network", - default=bittensor.defaults.subtensor.network, - choices=bittensor.__networks__, - ) - ( - _, - config.subtensor.chain_endpoint, - ) = bittensor.subtensor.determine_chain_endpoint_and_network( - config.subtensor.network - ) - if not config.is_set("display") and not config.no_prompt: - config.display = Prompt.ask("Enter display name", default="") - if not config.is_set("legal") and not config.no_prompt: - config.legal = Prompt.ask("Enter legal string", default="") - if not config.is_set("web") and not config.no_prompt: - config.web = Prompt.ask("Enter web url", default="") - if not config.is_set("pgp_fingerprint") and not config.no_prompt: - config.pgp_fingerprint = Prompt.ask( - "Enter pgp fingerprint (must be 20 bytes)", default=None - ) - if not config.is_set("riot") and not config.no_prompt: - config.riot = Prompt.ask("Enter riot", default="") - if not config.is_set("email") and not config.no_prompt: - config.email = Prompt.ask("Enter email address", default="") - if not config.is_set("image") and not config.no_prompt: - config.image = Prompt.ask("Enter image url", default="") - if not config.is_set("twitter") and not config.no_prompt: - config.twitter = Prompt.ask("Enter twitter url", default="") - if not config.is_set("info") and not config.no_prompt: - config.info = Prompt.ask("Enter info", default="") - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - new_coldkey_parser = parser.add_parser( - "set_identity", - help="""Create or update identity on-chain for a given cold wallet. Must be a subnet owner.""", - ) - new_coldkey_parser.add_argument( - "--display", - type=str, - help="""The display name for the identity.""", - ) - new_coldkey_parser.add_argument( - "--legal", - type=str, - help="""The legal name for the identity.""", - ) - new_coldkey_parser.add_argument( - "--web", - type=str, - help="""The web url for the identity.""", - ) - new_coldkey_parser.add_argument( - "--riot", - type=str, - help="""The riot handle for the identity.""", - ) - new_coldkey_parser.add_argument( - "--email", - type=str, - help="""The email address for the identity.""", - ) - new_coldkey_parser.add_argument( - "--pgp_fingerprint", - type=str, - help="""The pgp fingerprint for the identity.""", - ) - new_coldkey_parser.add_argument( - "--image", - type=str, - help="""The image url for the identity.""", - ) - new_coldkey_parser.add_argument( - "--info", - type=str, - help="""The info for the identity.""", - ) - new_coldkey_parser.add_argument( - "--twitter", - type=str, - help="""The twitter url for the identity.""", - ) - bittensor.wallet.add_args(new_coldkey_parser) - bittensor.subtensor.add_args(new_coldkey_parser) - - -class GetIdentityCommand: - """ - Executes the :func:`get_identity` command, which retrieves and displays the identity details of a user's coldkey or hotkey associated with the Bittensor network. This function - queries the subtensor chain for information such as the stake, rank, and trust associated - with the provided key. - - Optional Arguments: - - ``key``: The ``ss58`` address of the coldkey or hotkey to query. - - The command performs the following actions: - - - Connects to the subtensor network and retrieves the identity information. - - Displays the information in a structured table format. - - The displayed table includes: - - - **Address**: The ``ss58`` address of the queried key. - - **Item**: Various attributes of the identity such as stake, rank, and trust. - - **Value**: The corresponding values of the attributes. - - Usage: - The user must provide an ``ss58`` address as input to the command. If the address is not - provided in the configuration, the user is prompted to enter one. - - Example usage:: - - btcli wallet get_identity --key - - Note: - This function is designed for CLI use and should be executed in a terminal. It is - primarily used for informational purposes and has no side effects on the network state. - """ - - def run(cli: "bittensor.cli"): - r"""Queries the subtensor chain for user identity.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - GetIdentityCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - console = bittensor.__console__ - - with console.status(":satellite: [bold green]Querying chain identity..."): - identity = subtensor.query_identity(cli.config.key) - - table = Table(title="[bold white italic]On-Chain Identity") - table.add_column("Item", justify="right", style="cyan", no_wrap=True) - table.add_column("Value", style="magenta") - - table.add_row("Address", cli.config.key) - for key, value in identity.items(): - table.add_row(key, str(value) if value is not None else "None") - - console.print(table) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("key") and not config.no_prompt: - config.key = Prompt.ask( - "Enter coldkey or hotkey ss58 address", default=None - ) - if config.key is None: - raise ValueError("key must be set") - if not config.is_set("subtensor.network") and not config.no_prompt: - config.subtensor.network = Prompt.ask( - "Enter subtensor network", - default=bittensor.defaults.subtensor.network, - choices=bittensor.__networks__, - ) - ( - _, - config.subtensor.chain_endpoint, - ) = bittensor.subtensor.determine_chain_endpoint_and_network( - config.subtensor.network - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - new_coldkey_parser = parser.add_parser( - "get_identity", - help="""Creates a new coldkey (for containing balance) under the specified path. """, - ) - new_coldkey_parser.add_argument( - "--key", - type=str, - default=None, - help="""The coldkey or hotkey ss58 address to query.""", - ) - bittensor.wallet.add_args(new_coldkey_parser) - bittensor.subtensor.add_args(new_coldkey_parser) diff --git a/bittensor/commands/inspect.py b/bittensor/commands/inspect.py deleted file mode 100644 index 4ef0e84c4e..0000000000 --- a/bittensor/commands/inspect.py +++ /dev/null @@ -1,279 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 argparse -import bittensor -from tqdm import tqdm -from rich.table import Table -from rich.prompt import Prompt -from .utils import ( - get_delegates_details, - DelegatesDetails, - get_hotkey_wallets_for_wallet, - get_all_wallets_for_path, - filter_netuids_by_registered_hotkeys, -) -from . import defaults - -console = bittensor.__console__ - -import os -import bittensor -from typing import List, Tuple, Optional, Dict - - -def _get_coldkey_wallets_for_path(path: str) -> List["bittensor.wallet"]: - try: - wallet_names = next(os.walk(os.path.expanduser(path)))[1] - return [bittensor.wallet(path=path, name=name) for name in wallet_names] - except StopIteration: - # No wallet files found. - wallets = [] - return wallets - - -def _get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: - hotkey_wallets = [] - hotkeys_path = wallet.path + "/" + wallet.name + "/hotkeys" - try: - hotkey_files = next(os.walk(os.path.expanduser(hotkeys_path)))[2] - except StopIteration: - hotkey_files = [] - for hotkey_file_name in hotkey_files: - try: - hotkey_for_name = bittensor.wallet( - path=wallet.path, name=wallet.name, hotkey=hotkey_file_name - ) - if ( - hotkey_for_name.hotkey_file.exists_on_device() - and not hotkey_for_name.hotkey_file.is_encrypted() - ): - hotkey_wallets.append(hotkey_for_name) - except Exception: - pass - return hotkey_wallets - - -class InspectCommand: - """ - Executes the ``inspect`` command, which compiles and displays a detailed report of a user's wallet pairs (coldkey, hotkey) on the Bittensor network. - - This report includes balance and - staking information for both the coldkey and hotkey associated with the wallet. - - Optional arguments: - - ``all``: If set to ``True``, the command will inspect all wallets located within the specified path. If set to ``False``, the command will inspect only the wallet specified by the user. - - The command gathers data on: - - - Coldkey balance and delegated stakes. - - Hotkey stake and emissions per neuron on the network. - - Delegate names and details fetched from the network. - - The resulting table includes columns for: - - - **Coldkey**: The coldkey associated with the user's wallet. - - **Balance**: The balance of the coldkey. - - **Delegate**: The name of the delegate to which the coldkey has staked funds. - - **Stake**: The amount of stake held by both the coldkey and hotkey. - - **Emission**: The emission or rewards earned from staking. - - **Netuid**: The network unique identifier of the subnet where the hotkey is active. - - **Hotkey**: The hotkey associated with the neuron on the network. - - Usage: - This command can be used to inspect a single wallet or all wallets located within a - specified path. It is useful for a comprehensive overview of a user's participation - and performance in the Bittensor network. - - Example usage:: - - btcli wallet inspect - btcli wallet inspect --all - - Note: - The ``inspect`` command is for displaying information only and does not perform any - transactions or state changes on the Bittensor network. It is intended to be used as - part of the Bittensor CLI and not as a standalone function within user code. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Inspect a cold, hot pair.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - InspectCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - if cli.config.get("all", d=False) == True: - wallets = _get_coldkey_wallets_for_path(cli.config.wallet.path) - all_hotkeys = get_all_wallets_for_path(cli.config.wallet.path) - else: - wallets = [bittensor.wallet(config=cli.config)] - all_hotkeys = get_hotkey_wallets_for_wallet(wallets[0]) - - netuids = subtensor.get_all_subnet_netuids() - netuids = filter_netuids_by_registered_hotkeys( - cli, subtensor, netuids, all_hotkeys - ) - bittensor.logging.debug(f"Netuids to check: {netuids}") - - registered_delegate_info: Optional[Dict[str, DelegatesDetails]] = ( - get_delegates_details(url=bittensor.__delegates_details_url__) - ) - if registered_delegate_info is None: - bittensor.__console__.print( - ":warning:[yellow]Could not get delegate info from chain.[/yellow]" - ) - registered_delegate_info = {} - - neuron_state_dict = {} - for netuid in tqdm(netuids): - neurons = subtensor.neurons_lite(netuid) - neuron_state_dict[netuid] = neurons if neurons != None else [] - - table = Table(show_footer=True, pad_edge=False, box=None, expand=True) - table.add_column( - "[overline white]Coldkey", footer_style="overline white", style="bold white" - ) - table.add_column( - "[overline white]Balance", footer_style="overline white", style="green" - ) - table.add_column( - "[overline white]Delegate", footer_style="overline white", style="blue" - ) - table.add_column( - "[overline white]Stake", footer_style="overline white", style="green" - ) - table.add_column( - "[overline white]Emission", footer_style="overline white", style="green" - ) - table.add_column( - "[overline white]Netuid", footer_style="overline white", style="bold white" - ) - table.add_column( - "[overline white]Hotkey", footer_style="overline white", style="yellow" - ) - table.add_column( - "[overline white]Stake", footer_style="overline white", style="green" - ) - table.add_column( - "[overline white]Emission", footer_style="overline white", style="green" - ) - for wallet in tqdm(wallets): - delegates: List[Tuple[bittensor.DelegateInfo, bittensor.Balance]] = ( - subtensor.get_delegated(coldkey_ss58=wallet.coldkeypub.ss58_address) - ) - if not wallet.coldkeypub_file.exists_on_device(): - continue - cold_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - table.add_row(wallet.name, str(cold_balance), "", "", "", "", "", "", "") - for dele, staked in delegates: - if dele.hotkey_ss58 in registered_delegate_info: - delegate_name = registered_delegate_info[dele.hotkey_ss58].name - else: - delegate_name = dele.hotkey_ss58 - table.add_row( - "", - "", - str(delegate_name), - str(staked), - str( - dele.total_daily_return.tao - * (staked.tao / dele.total_stake.tao) - ), - "", - "", - "", - "", - ) - - hotkeys = _get_hotkey_wallets_for_wallet(wallet) - for netuid in netuids: - for neuron in neuron_state_dict[netuid]: - if neuron.coldkey == wallet.coldkeypub.ss58_address: - hotkey_name: str = "" - - hotkey_names: List[str] = [ - wallet.hotkey_str - for wallet in filter( - lambda hotkey: hotkey.hotkey.ss58_address - == neuron.hotkey, - hotkeys, - ) - ] - if len(hotkey_names) > 0: - hotkey_name = f"{hotkey_names[0]}-" - - table.add_row( - "", - "", - "", - "", - "", - str(netuid), - f"{hotkey_name}{neuron.hotkey}", - str(neuron.stake), - str(bittensor.Balance.from_tao(neuron.emission)), - ) - - bittensor.__console__.print(table) - - @staticmethod - def check_config(config: "bittensor.config"): - if ( - not config.is_set("wallet.name") - and not config.no_prompt - and not config.get("all", d=None) - ): - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if config.netuids != [] and config.netuids != None: - if not isinstance(config.netuids, list): - config.netuids = [int(config.netuids)] - else: - config.netuids = [int(netuid) for netuid in config.netuids] - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - inspect_parser = parser.add_parser( - "inspect", help="""Inspect a wallet (cold, hot) pair""" - ) - inspect_parser.add_argument( - "--all", - action="store_true", - help="""Check all coldkey wallets.""", - default=False, - ) - inspect_parser.add_argument( - "--netuids", - dest="netuids", - type=int, - nargs="*", - help="""Set the netuid(s) to filter by.""", - default=None, - ) - - bittensor.wallet.add_args(inspect_parser) - bittensor.subtensor.add_args(inspect_parser) diff --git a/bittensor/commands/list.py b/bittensor/commands/list.py deleted file mode 100644 index b2946efffb..0000000000 --- a/bittensor/commands/list.py +++ /dev/null @@ -1,128 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 os -import argparse -import bittensor -from rich import print -from rich.tree import Tree - -console = bittensor.__console__ - - -class ListCommand: - """ - Executes the ``list`` command which enumerates all wallets and their respective hotkeys present in the user's Bittensor configuration directory. - - The command organizes the information in a tree structure, displaying each wallet along with the ``ss58`` addresses for the coldkey public key and any hotkeys associated with it. - - Optional arguments: - - ``-p``, ``--path``: The path to the Bittensor configuration directory. Defaults to '~/.bittensor'. - - The output is presented in a hierarchical tree format, with each wallet as a root node, - and any associated hotkeys as child nodes. The ``ss58`` address is displayed for each - coldkey and hotkey that is not encrypted and exists on the device. - - Usage: - Upon invocation, the command scans the wallet directory and prints a list of all wallets, indicating whether the public keys are available (``?`` denotes unavailable or encrypted keys). - - Example usage:: - - btcli wallet list --path ~/.bittensor - - Note: - This command is read-only and does not modify the filesystem or the network state. It is intended for use within the Bittensor CLI to provide a quick overview of the user's wallets. - """ - - @staticmethod - def run(cli): - r"""Lists wallets.""" - try: - wallets = next(os.walk(os.path.expanduser(cli.config.wallet.path)))[1] - except StopIteration: - # No wallet files found. - wallets = [] - ListCommand._run(cli, wallets) - - @staticmethod - def _run(cli: "bittensor.cli", wallets, return_value=False): - root = Tree("Wallets") - for w_name in wallets: - wallet_for_name = bittensor.wallet(path=cli.config.wallet.path, name=w_name) - try: - if ( - wallet_for_name.coldkeypub_file.exists_on_device() - and not wallet_for_name.coldkeypub_file.is_encrypted() - ): - coldkeypub_str = wallet_for_name.coldkeypub.ss58_address - else: - coldkeypub_str = "?" - except: - coldkeypub_str = "?" - - wallet_tree = root.add( - "\n[bold white]{} ({})".format(w_name, coldkeypub_str) - ) - hotkeys_path = os.path.join(cli.config.wallet.path, w_name, "hotkeys") - try: - hotkeys = next(os.walk(os.path.expanduser(hotkeys_path))) - if len(hotkeys) > 1: - for h_name in hotkeys[2]: - hotkey_for_name = bittensor.wallet( - path=cli.config.wallet.path, name=w_name, hotkey=h_name - ) - try: - if ( - hotkey_for_name.hotkey_file.exists_on_device() - and not hotkey_for_name.hotkey_file.is_encrypted() - ): - hotkey_str = hotkey_for_name.hotkey.ss58_address - else: - hotkey_str = "?" - except: - hotkey_str = "?" - wallet_tree.add("[bold grey]{} ({})".format(h_name, hotkey_str)) - except: - continue - - if len(wallets) == 0: - root.add("[bold red]No wallets found.") - - # Uses rich print to display the tree. - if not return_value: - print(root) - else: - return root - - @staticmethod - def check_config(config: "bittensor.config"): - pass - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - list_parser = parser.add_parser("list", help="""List wallets""") - bittensor.wallet.add_args(list_parser) - bittensor.subtensor.add_args(list_parser) - - @staticmethod - def get_tree(cli): - try: - wallets = next(os.walk(os.path.expanduser(cli.config.wallet.path)))[1] - except StopIteration: - # No wallet files found. - wallets = [] - return ListCommand._run(cli=cli, wallets=wallets, return_value=True) diff --git a/bittensor/commands/metagraph.py b/bittensor/commands/metagraph.py deleted file mode 100644 index 79fa48b786..0000000000 --- a/bittensor/commands/metagraph.py +++ /dev/null @@ -1,268 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 argparse - -from rich.table import Table - -import bittensor - -from .utils import check_netuid_set - -console = bittensor.__console__ # type: ignore - - -class MetagraphCommand: - """ - Executes the ``metagraph`` command to retrieve and display the entire metagraph for a specified network. - - This metagraph contains detailed information about - all the neurons (nodes) participating in the network, including their stakes, - trust scores, and more. - - Optional arguments: - - ``--netuid``: The netuid of the network to query. Defaults to the default network UID. - - ``--subtensor.network``: The name of the network to query. Defaults to the default network name. - - The table displayed includes the following columns for each neuron: - - - UID: Unique identifier of the neuron. - - STAKE(τ): Total stake of the neuron in Tau (τ). - - RANK: Rank score of the neuron. - - TRUST: Trust score assigned to the neuron by other neurons. - - CONSENSUS: Consensus score of the neuron. - - INCENTIVE: Incentive score representing the neuron's incentive alignment. - - DIVIDENDS: Dividends earned by the neuron. - - EMISSION(p): Emission in Rho (p) received by the neuron. - - VTRUST: Validator trust score indicating the network's trust in the neuron as a validator. - - VAL: Validator status of the neuron. - - UPDATED: Number of blocks since the neuron's last update. - - ACTIVE: Activity status of the neuron. - - AXON: Network endpoint information of the neuron. - - HOTKEY: Partial hotkey (public key) of the neuron. - - COLDKEY: Partial coldkey (public key) of the neuron. - - The command also prints network-wide statistics such as total stake, issuance, and difficulty. - - Usage: - The user must specify the network UID to query the metagraph. If not specified, the default network UID is used. - - Example usage:: - - btcli subnet metagraph --netuid 0 # Root network - btcli subnet metagraph --netuid 1 --subtensor.network test - - Note: - This command provides a snapshot of the network's state at the time of calling. - It is useful for network analysis and diagnostics. It is intended to be used as - part of the Bittensor CLI and not as a standalone function within user code. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Prints an entire metagraph.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - MetagraphCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Prints an entire metagraph.""" - console = bittensor.__console__ - console.print( - ":satellite: Syncing with chain: [white]{}[/white] ...".format( - cli.config.subtensor.network - ) - ) - metagraph: bittensor.metagraph = subtensor.metagraph(netuid=cli.config.netuid) - metagraph.save() - difficulty = subtensor.difficulty(cli.config.netuid) - subnet_emission = bittensor.Balance.from_tao( - subtensor.get_emission_value_by_subnet(cli.config.netuid) - ) - total_issuance = bittensor.Balance.from_rao(subtensor.total_issuance().rao) - - TABLE_DATA = [] - total_stake = 0.0 - total_rank = 0.0 - total_validator_trust = 0.0 - total_trust = 0.0 - total_consensus = 0.0 - total_incentive = 0.0 - total_dividends = 0.0 - total_emission = 0 - for uid in metagraph.uids: - neuron = metagraph.neurons[uid] - ep = metagraph.axons[uid] - row = [ - str(neuron.uid), - "{:.5f}".format(metagraph.total_stake[uid]), - "{:.5f}".format(metagraph.ranks[uid]), - "{:.5f}".format(metagraph.trust[uid]), - "{:.5f}".format(metagraph.consensus[uid]), - "{:.5f}".format(metagraph.incentive[uid]), - "{:.5f}".format(metagraph.dividends[uid]), - "{}".format(int(metagraph.emission[uid] * 1000000000)), - "{:.5f}".format(metagraph.validator_trust[uid]), - "*" if metagraph.validator_permit[uid] else "", - str((metagraph.block.item() - metagraph.last_update[uid].item())), - str(metagraph.active[uid].item()), - ( - ep.ip + ":" + str(ep.port) - if ep.is_serving - else "[yellow]none[/yellow]" - ), - ep.hotkey[:10], - ep.coldkey[:10], - ] - total_stake += metagraph.total_stake[uid] - total_rank += metagraph.ranks[uid] - total_validator_trust += metagraph.validator_trust[uid] - total_trust += metagraph.trust[uid] - total_consensus += metagraph.consensus[uid] - total_incentive += metagraph.incentive[uid] - total_dividends += metagraph.dividends[uid] - total_emission += int(metagraph.emission[uid] * 1000000000) - TABLE_DATA.append(row) - total_neurons = len(metagraph.uids) - table = Table(show_footer=False) - table.title = "[white]Metagraph: net: {}:{}, block: {}, N: {}/{}, stake: {}, issuance: {}, difficulty: {}".format( - subtensor.network, - metagraph.netuid, - metagraph.block.item(), - sum(metagraph.active.tolist()), - metagraph.n.item(), - bittensor.Balance.from_tao(total_stake), - total_issuance, - difficulty, - ) - table.add_column( - "[overline white]UID", - str(total_neurons), - footer_style="overline white", - style="yellow", - ) - table.add_column( - "[overline white]STAKE(\u03c4)", - "\u03c4{:.5f}".format(total_stake), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]RANK", - "{:.5f}".format(total_rank), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]TRUST", - "{:.5f}".format(total_trust), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]CONSENSUS", - "{:.5f}".format(total_consensus), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]INCENTIVE", - "{:.5f}".format(total_incentive), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]DIVIDENDS", - "{:.5f}".format(total_dividends), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]EMISSION(\u03c1)", - "\u03c1{}".format(int(total_emission)), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]VTRUST", - "{:.5f}".format(total_validator_trust), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]VAL", justify="right", style="green", no_wrap=True - ) - table.add_column("[overline white]UPDATED", justify="right", no_wrap=True) - table.add_column( - "[overline white]ACTIVE", justify="right", style="green", no_wrap=True - ) - table.add_column( - "[overline white]AXON", justify="left", style="dim blue", no_wrap=True - ) - table.add_column("[overline white]HOTKEY", style="dim blue", no_wrap=False) - table.add_column("[overline white]COLDKEY", style="dim purple", no_wrap=False) - table.show_footer = True - - for row in TABLE_DATA: - table.add_row(*row) - table.box = None - table.pad_edge = False - table.width = None - console.print(table) - - @staticmethod - def check_config(config: "bittensor.config"): - check_netuid_set( - config, subtensor=bittensor.subtensor(config=config, log_verbose=False) - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - metagraph_parser = parser.add_parser( - "metagraph", help="""View a subnet metagraph information.""" - ) - metagraph_parser.add_argument( - "--netuid", - dest="netuid", - type=int, - help="""Set the netuid to get the metagraph of""", - default=False, - ) - - bittensor.subtensor.add_args(metagraph_parser) diff --git a/bittensor/commands/misc.py b/bittensor/commands/misc.py deleted file mode 100644 index ded1c78042..0000000000 --- a/bittensor/commands/misc.py +++ /dev/null @@ -1,117 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 os -import argparse -import bittensor -from rich.prompt import Prompt -from rich.table import Table - -console = bittensor.__console__ - - -class UpdateCommand: - """ - Executes the ``update`` command to update the local Bittensor package. - - This command performs a series of operations to ensure that the user's local Bittensor installation is updated to the latest version from the master branch of its GitHub repository. It primarily involves pulling the latest changes from the repository and reinstalling the package. - - Usage: - Upon invocation, the command first checks the user's configuration for the ``no_prompt`` setting. If ``no_prompt`` is set to ``True``, or if the user explicitly confirms with ``Y`` when prompted, the command proceeds to update the local Bittensor package. It changes the current directory to the Bittensor package directory, checks out the master branch of the Bittensor repository, pulls the latest changes, and then reinstalls the package using ``pip``. - - The command structure is as follows: - - 1. Change directory to the Bittensor package directory. - 2. Check out the master branch of the Bittensor GitHub repository. - 3. Pull the latest changes with the ``--ff-only`` option to ensure a fast-forward update. - 4. Reinstall the Bittensor package using pip. - - Example usage:: - - btcli legacy update - - Note: - This command is intended to be used within the Bittensor CLI to facilitate easy updates of the Bittensor package. It should be used with caution as it directly affects the local installation of the package. It is recommended to ensure that any important data or configurations are backed up before running this command. - """ - - @staticmethod - def run(cli): - if cli.config.no_prompt or cli.config.answer == "Y": - os.system( - " (cd ~/.bittensor/bittensor/ ; git checkout master ; git pull --ff-only )" - ) - os.system("pip install -e ~/.bittensor/bittensor/") - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.no_prompt: - answer = Prompt.ask( - "This will update the local bittensor package", - choices=["Y", "N"], - default="Y", - ) - config.answer = answer - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - update_parser = parser.add_parser( - "update", add_help=False, help="""Update bittensor """ - ) - - bittensor.subtensor.add_args(update_parser) - - -class AutocompleteCommand: - """Show users how to install and run autocompletion for Bittensor CLI.""" - - @staticmethod - def run(cli): - console = bittensor.__console__ - shell_commands = { - "Bash": "btcli --print-completion bash >> ~/.bashrc", - "Zsh": "btcli --print-completion zsh >> ~/.zshrc", - "Tcsh": "btcli --print-completion tcsh >> ~/.tcshrc", - } - - table = Table(show_header=True, header_style="bold magenta") - table.add_column("Shell", style="dim", width=12) - table.add_column("Command to Enable Autocompletion", justify="left") - - for shell, command in shell_commands.items(): - table.add_row(shell, command) - - console.print( - "To enable autocompletion for Bittensor CLI, run the appropriate command for your shell:" - ) - console.print(table) - - console.print( - "\n[bold]After running the command, execute the following to apply the changes:[/bold]" - ) - console.print(" [yellow]source ~/.bashrc[/yellow] # For Bash and Zsh") - console.print(" [yellow]source ~/.tcshrc[/yellow] # For Tcsh") - - @staticmethod - def add_args(parser): - parser.add_parser( - "autocomplete", - help="Instructions for enabling autocompletion for Bittensor CLI.", - ) - - @staticmethod - def check_config(config): - pass diff --git a/bittensor/commands/network.py b/bittensor/commands/network.py deleted file mode 100644 index 3564bc534d..0000000000 --- a/bittensor/commands/network.py +++ /dev/null @@ -1,672 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 argparse -import bittensor -from . import defaults # type: ignore -from rich.prompt import Prompt -from rich.table import Table -from typing import List, Optional, Dict, Union, Tuple -from .utils import ( - get_delegates_details, - DelegatesDetails, - check_netuid_set, - normalize_hyperparameters, -) -from .identity import SetIdentityCommand - -console = bittensor.__console__ - - -class RegisterSubnetworkCommand: - """ - Executes the ``register_subnetwork`` command to register a new subnetwork on the Bittensor network. - - This command facilitates the creation and registration of a subnetwork, which involves interaction with the user's wallet and the Bittensor subtensor. It ensures that the user has the necessary credentials and configurations to successfully register a new subnetwork. - - Usage: - Upon invocation, the command performs several key steps to register a subnetwork: - - 1. It copies the user's current configuration settings. - 2. It accesses the user's wallet using the provided configuration. - 3. It initializes the Bittensor subtensor object with the user's configuration. - 4. It then calls the ``register_subnetwork`` function of the subtensor object, passing the user's wallet and a prompt setting based on the user's configuration. - - If the user's configuration does not specify a wallet name and ``no_prompt`` is not set, the command will prompt the user to enter a wallet name. This name is then used in the registration process. - - The command structure includes: - - - Copying the user's configuration. - - Accessing and preparing the user's wallet. - - Initializing the Bittensor subtensor. - - Registering the subnetwork with the necessary credentials. - - Example usage:: - - btcli subnets create - - Note: - This command is intended for advanced users of the Bittensor network who wish to contribute by adding new subnetworks. It requires a clear understanding of the network's functioning and the roles of subnetworks. Users should ensure that they have secured their wallet and are aware of the implications of adding a new subnetwork to the Bittensor ecosystem. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Register a subnetwork""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - RegisterSubnetworkCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Register a subnetwork""" - wallet = bittensor.wallet(config=cli.config) - - # Call register command. - success = subtensor.register_subnetwork( - wallet=wallet, - prompt=not cli.config.no_prompt, - ) - if success and not cli.config.no_prompt: - # Prompt for user to set identity. - do_set_identity = Prompt.ask( - f"Subnetwork registered successfully. Would you like to set your identity? [y/n]", - choices=["y", "n"], - ) - - if do_set_identity.lower() == "y": - subtensor.close() - config = cli.config.copy() - SetIdentityCommand.check_config(config) - cli.config = config - SetIdentityCommand.run(cli) - - @classmethod - def check_config(cls, config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser = parser.add_parser( - "create", - help="""Create a new bittensor subnetwork on this chain.""", - ) - - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - - -class SubnetLockCostCommand: - """ - Executes the ``lock_cost`` command to view the locking cost required for creating a new subnetwork on the Bittensor network. - - This command is designed to provide users with the current cost of registering a new subnetwork, which is a critical piece of information for anyone considering expanding the network's infrastructure. - - The current implementation anneals the cost of creating a subnet over a period of two days. If the cost is unappealing currently, check back in a day or two to see if it has reached an amenble level. - - Usage: - Upon invocation, the command performs the following operations: - - 1. It copies the user's current Bittensor configuration. - 2. It initializes the Bittensor subtensor object with this configuration. - 3. It then retrieves the subnet lock cost using the ``get_subnet_burn_cost()`` method from the subtensor object. - 4. The cost is displayed to the user in a readable format, indicating the amount of Tao required to lock for registering a new subnetwork. - - In case of any errors during the process (e.g., network issues, configuration problems), the command will catch these exceptions and inform the user that it failed to retrieve the lock cost, along with the specific error encountered. - - The command structure includes: - - - Copying and using the user's configuration for Bittensor. - - Retrieving the current subnet lock cost from the Bittensor network. - - Displaying the cost in a user-friendly manner. - - Example usage:: - - btcli subnets lock_cost - - Note: - This command is particularly useful for users who are planning to contribute to the Bittensor network by adding new subnetworks. Understanding the lock cost is essential for these users to make informed decisions about their potential contributions and investments in the network. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""View locking cost of creating a new subnetwork""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - SubnetLockCostCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""View locking cost of creating a new subnetwork""" - config = cli.config.copy() - try: - bittensor.__console__.print( - f"Subnet lock cost: [green]{bittensor.utils.balance.Balance( subtensor.get_subnet_burn_cost() )}[/green]" - ) - except Exception as e: - bittensor.__console__.print( - f"Subnet lock cost: [red]Failed to get subnet lock cost[/red]" - f"Error: {e}" - ) - - @classmethod - def check_config(cls, config: "bittensor.config"): - pass - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - parser = parser.add_parser( - "lock_cost", - help=""" Return the lock cost to register a subnet""", - ) - - bittensor.subtensor.add_args(parser) - - -class SubnetListCommand: - """ - Executes the ``list`` command to list all subnets and their detailed information on the Bittensor network. - - This command is designed to provide users with comprehensive information about each subnet within the - network, including its unique identifier (netuid), the number of neurons, maximum neuron capacity, - emission rate, tempo, recycle register cost (burn), proof of work (PoW) difficulty, and the name or - SS58 address of the subnet owner. - - Usage: - Upon invocation, the command performs the following actions: - - 1. It initializes the Bittensor subtensor object with the user's configuration. - 2. It retrieves a list of all subnets in the network along with their detailed information. - 3. The command compiles this data into a table format, displaying key information about each subnet. - - In addition to the basic subnet details, the command also fetches delegate information to provide the - name of the subnet owner where available. If the owner's name is not available, the owner's ``SS58`` - address is displayed. - - The command structure includes: - - - Initializing the Bittensor subtensor and retrieving subnet information. - - Calculating the total number of neurons across all subnets. - - Constructing a table that includes columns for ``NETUID``, ``N`` (current neurons), ``MAX_N`` (maximum neurons), ``EMISSION``, ``TEMPO``, ``BURN``, ``POW`` (proof of work difficulty), and ``SUDO`` (owner's name or ``SS58`` address). - - Displaying the table with a footer that summarizes the total number of subnets and neurons. - - Example usage:: - - btcli subnets list - - Note: - This command is particularly useful for users seeking an overview of the Bittensor network's structure and the distribution of its resources and ownership information for each subnet. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""List all subnet netuids in the network.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - SubnetListCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""List all subnet netuids in the network.""" - subnets: List[bittensor.SubnetInfo] = subtensor.get_all_subnets_info() - - rows = [] - total_neurons = 0 - delegate_info: Optional[Dict[str, DelegatesDetails]] = get_delegates_details( - url=bittensor.__delegates_details_url__ - ) - - for subnet in subnets: - total_neurons += subnet.max_n - rows.append( - ( - str(subnet.netuid), - str(subnet.subnetwork_n), - str(bittensor.utils.formatting.millify(subnet.max_n)), - f"{subnet.emission_value / bittensor.utils.RAOPERTAO * 100:0.2f}%", - str(subnet.tempo), - f"{subnet.burn!s:8.8}", - str(bittensor.utils.formatting.millify(subnet.difficulty)), - f"{delegate_info[subnet.owner_ss58].name if subnet.owner_ss58 in delegate_info else subnet.owner_ss58}", - ) - ) - table = Table( - show_footer=True, - width=cli.config.get("width", None), - pad_edge=True, - box=None, - show_edge=True, - ) - table.title = "[white]Subnets - {}".format(subtensor.network) - table.add_column( - "[overline white]NETUID", - str(len(subnets)), - footer_style="overline white", - style="bold green", - justify="center", - ) - table.add_column( - "[overline white]N", - str(total_neurons), - footer_style="overline white", - style="green", - justify="center", - ) - table.add_column("[overline white]MAX_N", style="white", justify="center") - table.add_column("[overline white]EMISSION", style="white", justify="center") - table.add_column("[overline white]TEMPO", style="white", justify="center") - table.add_column("[overline white]RECYCLE", style="white", justify="center") - table.add_column("[overline white]POW", style="white", justify="center") - table.add_column("[overline white]SUDO", style="white") - for row in rows: - table.add_row(*row) - bittensor.__console__.print(table) - - @staticmethod - def check_config(config: "bittensor.config"): - pass - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - list_subnets_parser = parser.add_parser( - "list", help="""List all subnets on the network""" - ) - bittensor.subtensor.add_args(list_subnets_parser) - - -HYPERPARAMS = { - "serving_rate_limit": "sudo_set_serving_rate_limit", - "min_difficulty": "sudo_set_min_difficulty", - "max_difficulty": "sudo_set_max_difficulty", - "weights_version": "sudo_set_weights_version_key", - "weights_rate_limit": "sudo_set_weights_set_rate_limit", - "max_weight_limit": "sudo_set_max_weight_limit", - "immunity_period": "sudo_set_immunity_period", - "min_allowed_weights": "sudo_set_min_allowed_weights", - "activity_cutoff": "sudo_set_activity_cutoff", - "network_registration_allowed": "sudo_set_network_registration_allowed", - "network_pow_registration_allowed": "sudo_set_network_pow_registration_allowed", - "min_burn": "sudo_set_min_burn", - "max_burn": "sudo_set_max_burn", - "adjustment_alpha": "sudo_set_adjustment_alpha", - "rho": "sudo_set_rho", - "kappa": "sudo_set_kappa", - "difficulty": "sudo_set_difficulty", - "bonds_moving_avg": "sudo_set_bonds_moving_average", - "commit_reveal_weights_interval": "sudo_set_commit_reveal_weights_interval", - "commit_reveal_weights_enabled": "sudo_set_commit_reveal_weights_enabled", - "alpha_values": "sudo_set_alpha_values", - "liquid_alpha_enabled": "sudo_set_liquid_alpha_enabled", -} - - -class SubnetSudoCommand: - """ - Executes the ``set`` command to set hyperparameters for a specific subnet on the Bittensor network. - - This command allows subnet owners to modify various hyperparameters of theirs subnet, such as its tempo, - emission rates, and other network-specific settings. - - Usage: - The command first prompts the user to enter the hyperparameter they wish to change and its new value. - It then uses the user's wallet and configuration settings to authenticate and send the hyperparameter update - to the specified subnet. - - Example usage:: - - btcli sudo set --netuid 1 --param 'tempo' --value '0.5' - - Note: - This command requires the user to specify the subnet identifier (``netuid``) and both the hyperparameter - and its new value. It is intended for advanced users who are familiar with the network's functioning - and the impact of changing these parameters. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Set subnet hyperparameters.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - SubnetSudoCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run( - cli: "bittensor.cli", - subtensor: "bittensor.subtensor", - ): - r"""Set subnet hyperparameters.""" - wallet = bittensor.wallet(config=cli.config) - print("\n") - SubnetHyperparamsCommand.run(cli) - if not cli.config.is_set("param") and not cli.config.no_prompt: - param = Prompt.ask("Enter hyperparameter", choices=HYPERPARAMS) - cli.config.param = str(param) - if not cli.config.is_set("value") and not cli.config.no_prompt: - value = Prompt.ask("Enter new value") - cli.config.value = value - - if ( - cli.config.param == "network_registration_allowed" - or cli.config.param == "network_pow_registration_allowed" - or cli.config.param == "commit_reveal_weights_enabled" - or cli.config.param == "liquid_alpha_enabled" - ): - cli.config.value = ( - True - if (cli.config.value.lower() == "true" or cli.config.value == "1") - else False - ) - - is_allowed_value, value = allowed_value(cli.config.param, cli.config.value) - if not is_allowed_value: - raise ValueError( - f"Hyperparameter {cli.config.param} value is not within bounds. Value is {cli.config.value} but must be {value}" - ) - - subtensor.set_hyperparameter( - wallet, - netuid=cli.config.netuid, - parameter=cli.config.param, - value=value, - prompt=not cli.config.no_prompt, - ) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("netuid") and not config.no_prompt: - check_netuid_set( - config, bittensor.subtensor(config=config, log_verbose=False) - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser("set", help="""Set hyperparameters for a subnet""") - parser.add_argument( - "--netuid", dest="netuid", type=int, required=False, default=False - ) - parser.add_argument("--param", dest="param", type=str, required=False) - parser.add_argument("--value", dest="value", type=str, required=False) - - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - - -class SubnetHyperparamsCommand: - """ - Executes the '``hyperparameters``' command to view the current hyperparameters of a specific subnet on the Bittensor network. - - This command is useful for users who wish to understand the configuration and - operational parameters of a particular subnet. - - Usage: - Upon invocation, the command fetches and displays a list of all hyperparameters for the specified subnet. - These include settings like tempo, emission rates, and other critical network parameters that define - the subnet's behavior. - - Example usage:: - - $ btcli subnets hyperparameters --netuid 1 - - Subnet Hyperparameters - NETUID: 1 - finney - HYPERPARAMETER VALUE - rho 10 - kappa 32767 - immunity_period 7200 - min_allowed_weights 8 - max_weight_limit 455 - tempo 99 - min_difficulty 1000000000000000000 - max_difficulty 1000000000000000000 - weights_version 2013 - weights_rate_limit 100 - adjustment_interval 112 - activity_cutoff 5000 - registration_allowed True - target_regs_per_interval 2 - min_burn 1000000000 - max_burn 100000000000 - bonds_moving_avg 900000 - max_regs_per_block 1 - - Note: - The user must specify the subnet identifier (``netuid``) for which they want to view the hyperparameters. - This command is read-only and does not modify the network state or configurations. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""View hyperparameters of a subnetwork.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - SubnetHyperparamsCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""View hyperparameters of a subnetwork.""" - subnet: bittensor.SubnetHyperparameters = subtensor.get_subnet_hyperparameters( - cli.config.netuid - ) - - table = Table( - show_footer=True, - width=cli.config.get("width", None), - pad_edge=True, - box=None, - show_edge=True, - ) - table.title = "[white]Subnet Hyperparameters - NETUID: {} - {}".format( - cli.config.netuid, subtensor.network - ) - table.add_column("[overline white]HYPERPARAMETER", style="white") - table.add_column("[overline white]VALUE", style="green") - table.add_column("[overline white]NORMALIZED", style="cyan") - - normalized_values = normalize_hyperparameters(subnet) - - for param, value, norm_value in normalized_values: - table.add_row(" " + param, value, norm_value) - - bittensor.__console__.print(table) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("netuid") and not config.no_prompt: - check_netuid_set( - config, bittensor.subtensor(config=config, log_verbose=False) - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser( - "hyperparameters", help="""View subnet hyperparameters""" - ) - parser.add_argument( - "--netuid", dest="netuid", type=int, required=False, default=False - ) - bittensor.subtensor.add_args(parser) - - -class SubnetGetHyperparamsCommand: - """ - Executes the ``get`` command to retrieve the hyperparameters of a specific subnet on the Bittensor network. - - This command is similar to the ``hyperparameters`` command but may be used in different contexts within the CLI. - - Usage: - The command connects to the Bittensor network, queries the specified subnet, and returns a detailed list - of all its hyperparameters. This includes crucial operational parameters that determine the subnet's - performance and interaction within the network. - - Example usage:: - - $ btcli sudo get --netuid 1 - - Subnet Hyperparameters - NETUID: 1 - finney - HYPERPARAMETER VALUE - rho 10 - kappa 32767 - immunity_period 7200 - min_allowed_weights 8 - max_weight_limit 455 - tempo 99 - min_difficulty 1000000000000000000 - max_difficulty 1000000000000000000 - weights_version 2013 - weights_rate_limit 100 - adjustment_interval 112 - activity_cutoff 5000 - registration_allowed True - target_regs_per_interval 2 - min_burn 1000000000 - max_burn 100000000000 - bonds_moving_avg 900000 - max_regs_per_block 1 - - Note: - Users need to provide the ``netuid`` of the subnet whose hyperparameters they wish to view. This command is - designed for informational purposes and does not alter any network settings or configurations. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""View hyperparameters of a subnetwork.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - SubnetGetHyperparamsCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""View hyperparameters of a subnetwork.""" - subnet: bittensor.SubnetHyperparameters = subtensor.get_subnet_hyperparameters( - cli.config.netuid - ) - - table = Table( - show_footer=True, - width=cli.config.get("width", None), - pad_edge=True, - box=None, - show_edge=True, - ) - table.title = "[white]Subnet Hyperparameters - NETUID: {} - {}".format( - cli.config.netuid, subtensor.network - ) - table.add_column("[overline white]HYPERPARAMETER", style="white") - table.add_column("[overline white]VALUE", style="green") - table.add_column("[overline white]NORMALIZED", style="cyan") - - normalized_values = normalize_hyperparameters(subnet) - - for param, value, norm_value in normalized_values: - table.add_row(" " + param, value, norm_value) - - bittensor.__console__.print(table) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("netuid") and not config.no_prompt: - check_netuid_set( - config, bittensor.subtensor(config=config, log_verbose=False) - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser("get", help="""View subnet hyperparameters""") - parser.add_argument( - "--netuid", dest="netuid", type=int, required=False, default=False - ) - bittensor.subtensor.add_args(parser) - - -def allowed_value( - param: str, value: Union[str, bool, float] -) -> Tuple[bool, Union[str, list[float], float]]: - """ - Check the allowed values on hyperparameters. Return False if value is out of bounds. - """ - # Reminder error message ends like: Value is {value} but must be {error_message}. (the second part of return statement) - # Check if value is a boolean, only allow boolean and floats - try: - if not isinstance(value, bool): - if param == "alpha_values": - # Split the string into individual values - alpha_low_str, alpha_high_str = value.split(",") - alpha_high = float(alpha_high_str) - alpha_low = float(alpha_low_str) - - # Check alpha_high value - if alpha_high <= 52428 or alpha_high >= 65535: - return ( - False, - f"between 52428 and 65535 for alpha_high (but is {alpha_high})", - ) - - # Check alpha_low value - if alpha_low < 0 or alpha_low > 52428: - return ( - False, - f"between 0 and 52428 for alpha_low (but is {alpha_low})", - ) - - return True, [alpha_low, alpha_high] - except ValueError: - return False, "a number or a boolean" - - return True, value diff --git a/bittensor/commands/overview.py b/bittensor/commands/overview.py deleted file mode 100644 index b572847e49..0000000000 --- a/bittensor/commands/overview.py +++ /dev/null @@ -1,778 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 argparse -import bittensor -from tqdm import tqdm -from concurrent.futures import ProcessPoolExecutor -from collections import defaultdict -from fuzzywuzzy import fuzz -from rich.align import Align -from rich.table import Table -from rich.prompt import Prompt -from typing import List, Optional, Dict, Tuple -from .utils import ( - get_hotkey_wallets_for_wallet, - get_coldkey_wallets_for_path, - get_all_wallets_for_path, - filter_netuids_by_registered_hotkeys, -) -from . import defaults - -console = bittensor.__console__ - - -class OverviewCommand: - """ - Executes the ``overview`` command to present a detailed overview of the user's registered accounts on the Bittensor network. - - This command compiles and displays comprehensive information about each neuron associated with the user's wallets, - including both hotkeys and coldkeys. It is especially useful for users managing multiple accounts or seeking a summary - of their network activities and stake distributions. - - Usage: - The command offers various options to customize the output. Users can filter the displayed data by specific netuids, - sort by different criteria, and choose to include all wallets in the user's configuration directory. The output is - presented in a tabular format with the following columns: - - - COLDKEY: The SS58 address of the coldkey. - - HOTKEY: The SS58 address of the hotkey. - - UID: Unique identifier of the neuron. - - ACTIVE: Indicates if the neuron is active. - - STAKE(τ): Amount of stake in the neuron, in Tao. - - RANK: The rank of the neuron within the network. - - TRUST: Trust score of the neuron. - - CONSENSUS: Consensus score of the neuron. - - INCENTIVE: Incentive score of the neuron. - - DIVIDENDS: Dividends earned by the neuron. - - EMISSION(p): Emission received by the neuron, in Rho. - - VTRUST: Validator trust score of the neuron. - - VPERMIT: Indicates if the neuron has a validator permit. - - UPDATED: Time since last update. - - AXON: IP address and port of the neuron. - - HOTKEY_SS58: Human-readable representation of the hotkey. - - Example usage:: - - btcli wallet overview - btcli wallet overview --all --sort_by stake --sort_order descending - - Note: - This command is read-only and does not modify the network state or account configurations. It provides a quick and - comprehensive view of the user's network presence, making it ideal for monitoring account status, stake distribution, - and overall contribution to the Bittensor network. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Prints an overview for the wallet's colkey.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - OverviewCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _get_total_balance( - total_balance: "bittensor.Balance", - subtensor: "bittensor.subtensor", - cli: "bittensor.cli", - ) -> Tuple[List["bittensor.wallet"], "bittensor.Balance"]: - if cli.config.get("all", d=None): - cold_wallets = get_coldkey_wallets_for_path(cli.config.wallet.path) - for cold_wallet in tqdm(cold_wallets, desc="Pulling balances"): - if ( - cold_wallet.coldkeypub_file.exists_on_device() - and not cold_wallet.coldkeypub_file.is_encrypted() - ): - total_balance = total_balance + subtensor.get_balance( - cold_wallet.coldkeypub.ss58_address - ) - all_hotkeys = get_all_wallets_for_path(cli.config.wallet.path) - else: - # We are only printing keys for a single coldkey - coldkey_wallet = bittensor.wallet(config=cli.config) - if ( - coldkey_wallet.coldkeypub_file.exists_on_device() - and not coldkey_wallet.coldkeypub_file.is_encrypted() - ): - total_balance = subtensor.get_balance( - coldkey_wallet.coldkeypub.ss58_address - ) - if not coldkey_wallet.coldkeypub_file.exists_on_device(): - console.print("[bold red]No wallets found.") - return [], None - all_hotkeys = get_hotkey_wallets_for_wallet(coldkey_wallet) - - return all_hotkeys, total_balance - - @staticmethod - def _get_hotkeys( - cli: "bittensor.cli", all_hotkeys: List["bittensor.wallet"] - ) -> List["bittensor.wallet"]: - if not cli.config.get("all_hotkeys", False): - # We are only showing hotkeys that are specified. - all_hotkeys = [ - hotkey - for hotkey in all_hotkeys - if hotkey.hotkey_str in cli.config.hotkeys - ] - else: - # We are excluding the specified hotkeys from all_hotkeys. - all_hotkeys = [ - hotkey - for hotkey in all_hotkeys - if hotkey.hotkey_str not in cli.config.hotkeys - ] - return all_hotkeys - - @staticmethod - def _get_key_address(all_hotkeys: List["bittensor.wallet"]): - hotkey_coldkey_to_hotkey_wallet = {} - for hotkey_wallet in all_hotkeys: - if hotkey_wallet.hotkey.ss58_address not in hotkey_coldkey_to_hotkey_wallet: - hotkey_coldkey_to_hotkey_wallet[hotkey_wallet.hotkey.ss58_address] = {} - - hotkey_coldkey_to_hotkey_wallet[hotkey_wallet.hotkey.ss58_address][ - hotkey_wallet.coldkeypub.ss58_address - ] = hotkey_wallet - - all_hotkey_addresses = list(hotkey_coldkey_to_hotkey_wallet.keys()) - - return all_hotkey_addresses, hotkey_coldkey_to_hotkey_wallet - - @staticmethod - def _process_neuron_results( - results: List[Tuple[int, List["bittensor.NeuronInfoLite"], Optional[str]]], - neurons: Dict[str, List["bittensor.NeuronInfoLite"]], - netuids: List[int], - ) -> Dict[str, List["bittensor.NeuronInfoLite"]]: - for result in results: - netuid, neurons_result, err_msg = result - if err_msg is not None: - console.print(f"netuid '{netuid}': {err_msg}") - - if len(neurons_result) == 0: - # Remove netuid from overview if no neurons are found. - netuids.remove(netuid) - del neurons[str(netuid)] - else: - # Add neurons to overview. - neurons[str(netuid)] = neurons_result - return neurons - - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Prints an overview for the wallet's colkey.""" - console = bittensor.__console__ - wallet = bittensor.wallet(config=cli.config) - - all_hotkeys = [] - total_balance = bittensor.Balance(0) - - # We are printing for every coldkey. - all_hotkeys, total_balance = OverviewCommand._get_total_balance( - total_balance, subtensor, cli - ) - - # We are printing for a select number of hotkeys from all_hotkeys. - if cli.config.get("hotkeys"): - all_hotkeys = OverviewCommand._get_hotkeys(cli, all_hotkeys) - - # Check we have keys to display. - if len(all_hotkeys) == 0: - console.print("[red]No wallets found.[/red]") - return - - # Pull neuron info for all keys. - neurons: Dict[str, List[bittensor.NeuronInfoLite]] = {} - block = subtensor.block - - netuids = subtensor.get_all_subnet_netuids() - netuids = filter_netuids_by_registered_hotkeys( - cli, subtensor, netuids, all_hotkeys - ) - bittensor.logging.debug(f"Netuids to check: {netuids}") - - for netuid in netuids: - neurons[str(netuid)] = [] - - all_wallet_names = {wallet.name for wallet in all_hotkeys} - all_coldkey_wallets = [ - bittensor.wallet(name=wallet_name) for wallet_name in all_wallet_names - ] - - ( - all_hotkey_addresses, - hotkey_coldkey_to_hotkey_wallet, - ) = OverviewCommand._get_key_address(all_hotkeys) - - with console.status( - ":satellite: Syncing with chain: [white]{}[/white] ...".format( - cli.config.subtensor.get( - "network", bittensor.defaults.subtensor.network - ) - ) - ): - # Create a copy of the config without the parser and formatter_class. - ## This is needed to pass to the ProcessPoolExecutor, which cannot pickle the parser. - copy_config = cli.config.copy() - copy_config["__parser"] = None - copy_config["formatter_class"] = None - - # Pull neuron info for all keys. - ## Max len(netuids) or 5 threads. - with ProcessPoolExecutor(max_workers=max(len(netuids), 5)) as executor: - results = executor.map( - OverviewCommand._get_neurons_for_netuid, - [(copy_config, netuid, all_hotkey_addresses) for netuid in netuids], - ) - executor.shutdown(wait=True) # wait for all complete - - neurons = OverviewCommand._process_neuron_results( - results, neurons, netuids - ) - - total_coldkey_stake_from_metagraph = defaultdict( - lambda: bittensor.Balance(0.0) - ) - checked_hotkeys = set() - for neuron_list in neurons.values(): - for neuron in neuron_list: - if neuron.hotkey in checked_hotkeys: - continue - total_coldkey_stake_from_metagraph[neuron.coldkey] += ( - neuron.stake_dict[neuron.coldkey] - ) - checked_hotkeys.add(neuron.hotkey) - - alerts_table = Table(show_header=True, header_style="bold magenta") - alerts_table.add_column("🥩 alert!") - - coldkeys_to_check = [] - for coldkey_wallet in all_coldkey_wallets: - # Check if we have any stake with hotkeys that are not registered. - total_coldkey_stake_from_chain = subtensor.get_total_stake_for_coldkey( - ss58_address=coldkey_wallet.coldkeypub.ss58_address - ) - difference = ( - total_coldkey_stake_from_chain - - total_coldkey_stake_from_metagraph[ - coldkey_wallet.coldkeypub.ss58_address - ] - ) - if difference == 0: - continue # We have all our stake registered. - - coldkeys_to_check.append(coldkey_wallet) - alerts_table.add_row( - "Found {} stake with coldkey {} that is not registered.".format( - difference, coldkey_wallet.coldkeypub.ss58_address - ) - ) - - if coldkeys_to_check: - # We have some stake that is not with a registered hotkey. - if "-1" not in neurons: - neurons["-1"] = [] - - # Use process pool to check each coldkey wallet for de-registered stake. - with ProcessPoolExecutor( - max_workers=max(len(coldkeys_to_check), 5) - ) as executor: - results = executor.map( - OverviewCommand._get_de_registered_stake_for_coldkey_wallet, - [ - (cli.config, all_hotkey_addresses, coldkey_wallet) - for coldkey_wallet in coldkeys_to_check - ], - ) - executor.shutdown(wait=True) # wait for all complete - - for result in results: - coldkey_wallet, de_registered_stake, err_msg = result - if err_msg is not None: - console.print(err_msg) - - if len(de_registered_stake) == 0: - continue # We have no de-registered stake with this coldkey. - - de_registered_neurons = [] - for hotkey_addr, our_stake in de_registered_stake: - # Make a neuron info lite for this hotkey and coldkey. - de_registered_neuron = bittensor.NeuronInfoLite.get_null_neuron() - de_registered_neuron.hotkey = hotkey_addr - de_registered_neuron.coldkey = ( - coldkey_wallet.coldkeypub.ss58_address - ) - de_registered_neuron.total_stake = bittensor.Balance(our_stake) - - de_registered_neurons.append(de_registered_neuron) - - # Add this hotkey to the wallets dict - wallet_ = bittensor.wallet( - name=wallet, - ) - wallet_.hotkey_ss58 = hotkey_addr - wallet.hotkey_str = hotkey_addr[:5] # Max length of 5 characters - # Indicates a hotkey not on local machine but exists in stake_info obj on-chain - if hotkey_coldkey_to_hotkey_wallet.get(hotkey_addr) is None: - hotkey_coldkey_to_hotkey_wallet[hotkey_addr] = {} - hotkey_coldkey_to_hotkey_wallet[hotkey_addr][ - coldkey_wallet.coldkeypub.ss58_address - ] = wallet_ - - # Add neurons to overview. - neurons["-1"].extend(de_registered_neurons) - - # Setup outer table. - grid = Table.grid(pad_edge=False) - - # If there are any alerts, add them to the grid - if len(alerts_table.rows) > 0: - grid.add_row(alerts_table) - - title: str = "" - if not cli.config.get("all", d=None): - title = "[bold white italic]Wallet - {}:{}".format( - cli.config.wallet.name, wallet.coldkeypub.ss58_address - ) - else: - title = "[bold whit italic]All Wallets:" - - # Add title - grid.add_row(Align(title, vertical="middle", align="center")) - - # Generate rows per netuid - hotkeys_seen = set() - total_neurons = 0 - total_stake = 0.0 - for netuid in netuids: - subnet_tempo = subtensor.tempo(netuid=netuid) - last_subnet = netuid == netuids[-1] - TABLE_DATA = [] - total_rank = 0.0 - total_trust = 0.0 - total_consensus = 0.0 - total_validator_trust = 0.0 - total_incentive = 0.0 - total_dividends = 0.0 - total_emission = 0 - - for nn in neurons[str(netuid)]: - hotwallet = hotkey_coldkey_to_hotkey_wallet.get(nn.hotkey, {}).get( - nn.coldkey, None - ) - if not hotwallet: - # Indicates a mismatch between what the chain says the coldkey - # is for this hotkey and the local wallet coldkey-hotkey pair - hotwallet = argparse.Namespace() - hotwallet.name = nn.coldkey[:7] - hotwallet.hotkey_str = nn.hotkey[:7] - nn: bittensor.NeuronInfoLite - uid = nn.uid - active = nn.active - stake = nn.total_stake.tao - rank = nn.rank - trust = nn.trust - consensus = nn.consensus - validator_trust = nn.validator_trust - incentive = nn.incentive - dividends = nn.dividends - emission = int(nn.emission / (subnet_tempo + 1) * 1e9) - last_update = int(block - nn.last_update) - validator_permit = nn.validator_permit - row = [ - hotwallet.name, - hotwallet.hotkey_str, - str(uid), - str(active), - "{:.5f}".format(stake), - "{:.5f}".format(rank), - "{:.5f}".format(trust), - "{:.5f}".format(consensus), - "{:.5f}".format(incentive), - "{:.5f}".format(dividends), - "{:_}".format(emission), - "{:.5f}".format(validator_trust), - "*" if validator_permit else "", - str(last_update), - ( - bittensor.utils.networking.int_to_ip(nn.axon_info.ip) - + ":" - + str(nn.axon_info.port) - if nn.axon_info.port != 0 - else "[yellow]none[/yellow]" - ), - nn.hotkey, - ] - - total_rank += rank - total_trust += trust - total_consensus += consensus - total_incentive += incentive - total_dividends += dividends - total_emission += emission - total_validator_trust += validator_trust - - if not (nn.hotkey, nn.coldkey) in hotkeys_seen: - # Don't double count stake on hotkey-coldkey pairs. - hotkeys_seen.add((nn.hotkey, nn.coldkey)) - total_stake += stake - - # netuid -1 are neurons that are de-registered. - if netuid != "-1": - total_neurons += 1 - - TABLE_DATA.append(row) - - # Add subnet header - if netuid == "-1": - grid.add_row(f"Deregistered Neurons") - else: - grid.add_row(f"Subnet: [bold white]{netuid}[/bold white]") - - table = Table( - show_footer=False, - width=cli.config.get("width", None), - pad_edge=False, - box=None, - ) - if last_subnet: - table.add_column( - "[overline white]COLDKEY", - str(total_neurons), - footer_style="overline white", - style="bold white", - ) - table.add_column( - "[overline white]HOTKEY", - str(total_neurons), - footer_style="overline white", - style="white", - ) - else: - # No footer for non-last subnet. - table.add_column("[overline white]COLDKEY", style="bold white") - table.add_column("[overline white]HOTKEY", style="white") - table.add_column( - "[overline white]UID", - str(total_neurons), - footer_style="overline white", - style="yellow", - ) - table.add_column( - "[overline white]ACTIVE", justify="right", style="green", no_wrap=True - ) - if last_subnet: - table.add_column( - "[overline white]STAKE(\u03c4)", - "\u03c4{:.5f}".format(total_stake), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - else: - # No footer for non-last subnet. - table.add_column( - "[overline white]STAKE(\u03c4)", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]RANK", - "{:.5f}".format(total_rank), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]TRUST", - "{:.5f}".format(total_trust), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]CONSENSUS", - "{:.5f}".format(total_consensus), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]INCENTIVE", - "{:.5f}".format(total_incentive), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]DIVIDENDS", - "{:.5f}".format(total_dividends), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]EMISSION(\u03c1)", - "\u03c1{:_}".format(total_emission), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]VTRUST", - "{:.5f}".format(total_validator_trust), - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column("[overline white]VPERMIT", justify="right", no_wrap=True) - table.add_column("[overline white]UPDATED", justify="right", no_wrap=True) - table.add_column( - "[overline white]AXON", justify="left", style="dim blue", no_wrap=True - ) - table.add_column( - "[overline white]HOTKEY_SS58", style="dim blue", no_wrap=False - ) - table.show_footer = True - - sort_by: Optional[str] = cli.config.get("sort_by", None) - sort_order: Optional[str] = cli.config.get("sort_order", None) - - if sort_by is not None and sort_by != "": - column_to_sort_by: int = 0 - highest_matching_ratio: int = 0 - sort_descending: bool = False # Default sort_order to ascending - - for index, column in zip(range(len(table.columns)), table.columns): - # Fuzzy match the column name. Default to the first column. - column_name = column.header.lower().replace("[overline white]", "") - match_ratio = fuzz.ratio(sort_by.lower(), column_name) - # Finds the best matching column - if match_ratio > highest_matching_ratio: - highest_matching_ratio = match_ratio - column_to_sort_by = index - - if sort_order.lower() in {"desc", "descending", "reverse"}: - # Sort descending if the sort_order matches desc, descending, or reverse - sort_descending = True - - def overview_sort_function(row): - data = row[column_to_sort_by] - # Try to convert to number if possible - try: - data = float(data) - except ValueError: - pass - return data - - TABLE_DATA.sort(key=overview_sort_function, reverse=sort_descending) - - for row in TABLE_DATA: - table.add_row(*row) - - grid.add_row(table) - - console.clear() - - caption = "[italic][dim][white]Wallet balance: [green]\u03c4" + str( - total_balance.tao - ) - grid.add_row(Align(caption, vertical="middle", align="center")) - - # Print the entire table/grid - console.print(grid, width=cli.config.get("width", None)) - - @staticmethod - def _get_neurons_for_netuid( - args_tuple: Tuple["bittensor.Config", int, List[str]], - ) -> Tuple[int, List["bittensor.NeuronInfoLite"], Optional[str]]: - subtensor_config, netuid, hot_wallets = args_tuple - - result: List["bittensor.NeuronInfoLite"] = [] - - try: - subtensor = bittensor.subtensor(config=subtensor_config, log_verbose=False) - - all_neurons: List["bittensor.NeuronInfoLite"] = subtensor.neurons_lite( - netuid=netuid - ) - # Map the hotkeys to uids - hotkey_to_neurons = {n.hotkey: n.uid for n in all_neurons} - for hot_wallet_addr in hot_wallets: - uid = hotkey_to_neurons.get(hot_wallet_addr) - if uid is not None: - nn = all_neurons[uid] - result.append(nn) - except Exception as e: - return netuid, [], "Error: {}".format(e) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - return netuid, result, None - - @staticmethod - def _get_de_registered_stake_for_coldkey_wallet( - args_tuple, - ) -> Tuple[ - "bittensor.Wallet", List[Tuple[str, "bittensor.Balance"]], Optional[str] - ]: - subtensor_config, all_hotkey_addresses, coldkey_wallet = args_tuple - - # List of (hotkey_addr, our_stake) tuples. - result: List[Tuple[str, "bittensor.Balance"]] = [] - - try: - subtensor = bittensor.subtensor(config=subtensor_config, log_verbose=False) - - # Pull all stake for our coldkey - all_stake_info_for_coldkey = subtensor.get_stake_info_for_coldkey( - coldkey_ss58=coldkey_wallet.coldkeypub.ss58_address - ) - - ## Filter out hotkeys that are in our wallets - ## Filter out hotkeys that are delegates. - def _filter_stake_info(stake_info: "bittensor.StakeInfo") -> bool: - if stake_info.stake == 0: - return False # Skip hotkeys that we have no stake with. - if stake_info.hotkey_ss58 in all_hotkey_addresses: - return False # Skip hotkeys that are in our wallets. - if subtensor.is_hotkey_delegate(hotkey_ss58=stake_info.hotkey_ss58): - return False # Skip hotkeys that are delegates, they show up in btcli my_delegates table. - - return True - - all_staked_hotkeys = filter(_filter_stake_info, all_stake_info_for_coldkey) - result = [ - ( - stake_info.hotkey_ss58, - stake_info.stake.tao, - ) # stake is a Balance object - for stake_info in all_staked_hotkeys - ] - - except Exception as e: - return coldkey_wallet, [], "Error: {}".format(e) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - return coldkey_wallet, result, None - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - overview_parser = parser.add_parser( - "overview", help="""Show registered account overview.""" - ) - overview_parser.add_argument( - "--all", - dest="all", - action="store_true", - help="""View overview for all wallets.""", - default=False, - ) - overview_parser.add_argument( - "--width", - dest="width", - action="store", - type=int, - help="""Set the output width of the overview. Defaults to automatic width from terminal.""", - default=None, - ) - overview_parser.add_argument( - "--sort_by", - "--wallet.sort_by", - dest="sort_by", - required=False, - action="store", - default="", - type=str, - help="""Sort the hotkeys by the specified column title (e.g. name, uid, axon).""", - ) - overview_parser.add_argument( - "--sort_order", - "--wallet.sort_order", - dest="sort_order", - required=False, - action="store", - default="ascending", - type=str, - help="""Sort the hotkeys in the specified ordering. (ascending/asc or descending/desc/reverse)""", - ) - overview_parser.add_argument( - "--hotkeys", - "--exclude_hotkeys", - "--wallet.hotkeys", - "--wallet.exclude_hotkeys", - required=False, - action="store", - default=[], - type=str, - nargs="*", - help="""Specify the hotkeys by name or ss58 address. (e.g. hk1 hk2 hk3)""", - ) - overview_parser.add_argument( - "--all_hotkeys", - "--wallet.all_hotkeys", - required=False, - action="store_true", - default=False, - help="""To specify all hotkeys. Specifying hotkeys will exclude them from this all.""", - ) - overview_parser.add_argument( - "--netuids", - dest="netuids", - type=int, - nargs="*", - help="""Set the netuid(s) to filter by.""", - default=None, - ) - bittensor.wallet.add_args(overview_parser) - bittensor.subtensor.add_args(overview_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if ( - not config.is_set("wallet.name") - and not config.no_prompt - and not config.get("all", d=None) - ): - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if config.netuids != [] and config.netuids != None: - if not isinstance(config.netuids, list): - config.netuids = [int(config.netuids)] - else: - config.netuids = [int(netuid) for netuid in config.netuids] diff --git a/bittensor/commands/register.py b/bittensor/commands/register.py deleted file mode 100644 index a5a14773a2..0000000000 --- a/bittensor/commands/register.py +++ /dev/null @@ -1,613 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 sys -import argparse -import bittensor -from rich.prompt import Prompt, Confirm -from .utils import check_netuid_set, check_for_cuda_reg_config -from copy import deepcopy - -from . import defaults - -console = bittensor.__console__ - - -class RegisterCommand: - """ - Executes the ``register`` command to register a neuron on the Bittensor network by recycling some TAO (the network's native token). - - This command is used to add a new neuron to a specified subnet within the network, contributing to the decentralization and robustness of Bittensor. - - Usage: - Before registering, the command checks if the specified subnet exists and whether the user's balance is sufficient to cover the registration cost. - - The registration cost is determined by the current recycle amount for the specified subnet. If the balance is insufficient or the subnet does not exist, the command will exit with an appropriate error message. - - If the preconditions are met, and the user confirms the transaction (if ``no_prompt`` is not set), the command proceeds to register the neuron by recycling the required amount of TAO. - - The command structure includes: - - - Verification of subnet existence. - - Checking the user's balance against the current recycle amount for the subnet. - - User confirmation prompt for proceeding with registration. - - Execution of the registration process. - - Columns Displayed in the confirmation prompt: - - - Balance: The current balance of the user's wallet in TAO. - - Cost to Register: The required amount of TAO needed to register on the specified subnet. - - Example usage:: - - btcli subnets register --netuid 1 - - Note: - This command is critical for users who wish to contribute a new neuron to the network. It requires careful consideration of the subnet selection and an understanding of the registration costs. Users should ensure their wallet is sufficiently funded before attempting to register a neuron. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Register neuron by recycling some TAO.""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - RegisterCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Register neuron by recycling some TAO.""" - wallet = bittensor.wallet(config=cli.config) - - # Verify subnet exists - if not subtensor.subnet_exists(netuid=cli.config.netuid): - bittensor.__console__.print( - f"[red]Subnet {cli.config.netuid} does not exist[/red]" - ) - sys.exit(1) - - # Check current recycle amount - current_recycle = subtensor.recycle(netuid=cli.config.netuid) - balance = subtensor.get_balance(address=wallet.coldkeypub.ss58_address) - - # Check balance is sufficient - if balance < current_recycle: - bittensor.__console__.print( - f"[red]Insufficient balance {balance} to register neuron. Current recycle is {current_recycle} TAO[/red]" - ) - sys.exit(1) - - if not cli.config.no_prompt: - if ( - Confirm.ask( - f"Your balance is: [bold green]{balance}[/bold green]\nThe cost to register by recycle is [bold red]{current_recycle}[/bold red]\nDo you want to continue?", - default=False, - ) - == False - ): - sys.exit(1) - - subtensor.burned_register( - wallet=wallet, netuid=cli.config.netuid, prompt=not cli.config.no_prompt - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - register_parser = parser.add_parser( - "register", help="""Register a wallet to a network.""" - ) - register_parser.add_argument( - "--netuid", - type=int, - help="netuid for subnet to serve this neuron on", - default=argparse.SUPPRESS, - ) - - bittensor.wallet.add_args(register_parser) - bittensor.subtensor.add_args(register_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if ( - not config.is_set("subtensor.network") - and not config.is_set("subtensor.chain_endpoint") - and not config.no_prompt - ): - config.subtensor.network = Prompt.ask( - "Enter subtensor network", - choices=bittensor.__networks__, - default=defaults.subtensor.network, - ) - _, endpoint = bittensor.subtensor.determine_chain_endpoint_and_network( - config.subtensor.network - ) - config.subtensor.chain_endpoint = endpoint - - check_netuid_set( - config, subtensor=bittensor.subtensor(config=config, log_verbose=False) - ) - - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - -class PowRegisterCommand: - """ - Executes the ``pow_register`` command to register a neuron on the Bittensor network using Proof of Work (PoW). - - This method is an alternative registration process that leverages computational work for securing a neuron's place on the network. - - Usage: - The command starts by verifying the existence of the specified subnet. If the subnet does not exist, it terminates with an error message. - On successful verification, the PoW registration process is initiated, which requires solving computational puzzles. - - Optional arguments: - - ``--netuid`` (int): The netuid for the subnet on which to serve the neuron. Mandatory for specifying the target subnet. - - ``--pow_register.num_processes`` (int): The number of processors to use for PoW registration. Defaults to the system's default setting. - - ``--pow_register.update_interval`` (int): The number of nonces to process before checking for the next block during registration. Affects the frequency of update checks. - - ``--pow_register.no_output_in_place`` (bool): When set, disables the output of registration statistics in place. Useful for cleaner logs. - - ``--pow_register.verbose`` (bool): Enables verbose output of registration statistics for detailed information. - - ``--pow_register.cuda.use_cuda`` (bool): Enables the use of CUDA for GPU-accelerated PoW calculations. Requires a CUDA-compatible GPU. - - ``--pow_register.cuda.no_cuda`` (bool): Disables the use of CUDA, defaulting to CPU-based calculations. - - ``--pow_register.cuda.dev_id`` (int): Specifies the CUDA device ID, useful for systems with multiple CUDA-compatible GPUs. - - ``--pow_register.cuda.tpb`` (int): Sets the number of Threads Per Block for CUDA operations, affecting the GPU calculation dynamics. - - The command also supports additional wallet and subtensor arguments, enabling further customization of the registration process. - - Example usage:: - - btcli pow_register --netuid 1 --pow_register.num_processes 4 --cuda.use_cuda - - Note: - This command is suited for users with adequate computational resources to participate in PoW registration. It requires a sound understanding - of the network's operations and PoW mechanics. Users should ensure their systems meet the necessary hardware and software requirements, - particularly when opting for CUDA-based GPU acceleration. - - This command may be disabled according on the subnet owner's directive. For example, on netuid 1 this is permanently disabled. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Register neuron.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - PowRegisterCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Register neuron.""" - wallet = bittensor.wallet(config=cli.config) - - # Verify subnet exists - if not subtensor.subnet_exists(netuid=cli.config.netuid): - bittensor.__console__.print( - f"[red]Subnet {cli.config.netuid} does not exist[/red]" - ) - sys.exit(1) - - registered = subtensor.register( - wallet=wallet, - netuid=cli.config.netuid, - prompt=not cli.config.no_prompt, - tpb=cli.config.pow_register.cuda.get("tpb", None), - update_interval=cli.config.pow_register.get("update_interval", None), - num_processes=cli.config.pow_register.get("num_processes", None), - cuda=cli.config.pow_register.cuda.get( - "use_cuda", defaults.pow_register.cuda.use_cuda - ), - dev_id=cli.config.pow_register.cuda.get("dev_id", None), - output_in_place=cli.config.pow_register.get( - "output_in_place", defaults.pow_register.output_in_place - ), - log_verbose=cli.config.pow_register.get( - "verbose", defaults.pow_register.verbose - ), - ) - if not registered: - sys.exit(1) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - register_parser = parser.add_parser( - "pow_register", help="""Register a wallet to a network using PoW.""" - ) - register_parser.add_argument( - "--netuid", - type=int, - help="netuid for subnet to serve this neuron on", - default=argparse.SUPPRESS, - ) - register_parser.add_argument( - "--pow_register.num_processes", - "-n", - dest="pow_register.num_processes", - help="Number of processors to use for POW registration", - type=int, - default=defaults.pow_register.num_processes, - ) - register_parser.add_argument( - "--pow_register.update_interval", - "--pow_register.cuda.update_interval", - "--cuda.update_interval", - "-u", - help="The number of nonces to process before checking for next block during registration", - type=int, - default=defaults.pow_register.update_interval, - ) - register_parser.add_argument( - "--pow_register.no_output_in_place", - "--no_output_in_place", - dest="pow_register.output_in_place", - help="Whether to not ouput the registration statistics in-place. Set flag to disable output in-place.", - action="store_false", - required=False, - default=defaults.pow_register.output_in_place, - ) - register_parser.add_argument( - "--pow_register.verbose", - help="Whether to ouput the registration statistics verbosely.", - action="store_true", - required=False, - default=defaults.pow_register.verbose, - ) - - ## Registration args for CUDA registration. - register_parser.add_argument( - "--pow_register.cuda.use_cuda", - "--cuda", - "--cuda.use_cuda", - dest="pow_register.cuda.use_cuda", - default=defaults.pow_register.cuda.use_cuda, - help="""Set flag to use CUDA to register.""", - action="store_true", - required=False, - ) - register_parser.add_argument( - "--pow_register.cuda.no_cuda", - "--no_cuda", - "--cuda.no_cuda", - dest="pow_register.cuda.use_cuda", - default=not defaults.pow_register.cuda.use_cuda, - help="""Set flag to not use CUDA for registration""", - action="store_false", - required=False, - ) - - register_parser.add_argument( - "--pow_register.cuda.dev_id", - "--cuda.dev_id", - type=int, - nargs="+", - default=defaults.pow_register.cuda.dev_id, - help="""Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest).""", - required=False, - ) - register_parser.add_argument( - "--pow_register.cuda.tpb", - "--cuda.tpb", - type=int, - default=defaults.pow_register.cuda.tpb, - help="""Set the number of Threads Per Block for CUDA.""", - required=False, - ) - - bittensor.wallet.add_args(register_parser) - bittensor.subtensor.add_args(register_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if ( - not config.is_set("subtensor.network") - and not config.is_set("subtensor.chain_endpoint") - and not config.no_prompt - ): - config.subtensor.network = Prompt.ask( - "Enter subtensor network", - choices=bittensor.__networks__, - default=defaults.subtensor.network, - ) - _, endpoint = bittensor.subtensor.determine_chain_endpoint_and_network( - config.subtensor.network - ) - config.subtensor.chain_endpoint = endpoint - - check_netuid_set( - config, subtensor=bittensor.subtensor(config=config, log_verbose=False) - ) - - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - if not config.no_prompt: - check_for_cuda_reg_config(config) - - -class RunFaucetCommand: - """ - Executes the ``faucet`` command to obtain test TAO tokens by performing Proof of Work (PoW). - - IMPORTANT: - **THIS COMMAND IS CURRENTLY DISABLED.** - - This command is particularly useful for users who need test tokens for operations on the Bittensor testnet. - - Usage: - The command uses the PoW mechanism to validate the user's effort and rewards them with test TAO tokens. It is typically used in testnet environments where real value transactions are not necessary. - - Optional arguments: - - ``--faucet.num_processes`` (int): Specifies the number of processors to use for the PoW operation. A higher number of processors may increase the chances of successful computation. - - ``--faucet.update_interval`` (int): Sets the frequency of nonce processing before checking for the next block, which impacts the PoW operation's responsiveness. - - ``--faucet.no_output_in_place`` (bool): When set, it disables in-place output of registration statistics for cleaner log visibility. - - ``--faucet.verbose`` (bool): Enables verbose output for detailed statistical information during the PoW process. - - ``--faucet.cuda.use_cuda`` (bool): Activates the use of CUDA for GPU acceleration in the PoW process, suitable for CUDA-compatible GPUs. - - ``--faucet.cuda.no_cuda`` (bool): Disables the use of CUDA, opting for CPU-based calculations. - - ``--faucet.cuda.dev_id`` (int[]): Allows selection of specific CUDA device IDs for the operation, useful in multi-GPU setups. - - ``--faucet.cuda.tpb`` (int): Determines the number of Threads Per Block for CUDA operations, affecting GPU calculation efficiency. - - These options provide flexibility in configuring the PoW process according to the user's hardware capabilities and preferences. - - Example usage:: - - btcli wallet faucet --faucet.num_processes 4 --faucet.cuda.use_cuda - - Note: - This command is meant for use in testnet environments where users can experiment with the network without using real TAO tokens. - It's important for users to have the necessary hardware setup, especially when opting for CUDA-based GPU calculations. - - **THIS COMMAND IS CURRENTLY DISABLED.** - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Register neuron.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - RunFaucetCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Register neuron.""" - wallet = bittensor.wallet(config=cli.config) - success = subtensor.run_faucet( - wallet=wallet, - prompt=not cli.config.no_prompt, - tpb=cli.config.pow_register.cuda.get("tpb", None), - update_interval=cli.config.pow_register.get("update_interval", None), - num_processes=cli.config.pow_register.get("num_processes", None), - cuda=cli.config.pow_register.cuda.get( - "use_cuda", defaults.pow_register.cuda.use_cuda - ), - dev_id=cli.config.pow_register.cuda.get("dev_id", None), - output_in_place=cli.config.pow_register.get( - "output_in_place", defaults.pow_register.output_in_place - ), - log_verbose=cli.config.pow_register.get( - "verbose", defaults.pow_register.verbose - ), - ) - if not success: - bittensor.logging.error("Faucet run failed.") - sys.exit(1) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - run_faucet_parser = parser.add_parser( - "faucet", help="""Perform PoW to receieve test TAO in your wallet.""" - ) - run_faucet_parser.add_argument( - "--faucet.num_processes", - "-n", - dest="pow_register.num_processes", - help="Number of processors to use for POW registration", - type=int, - default=defaults.pow_register.num_processes, - ) - run_faucet_parser.add_argument( - "--faucet.update_interval", - "--faucet.cuda.update_interval", - "--cuda.update_interval", - "-u", - help="The number of nonces to process before checking for next block during registration", - type=int, - default=defaults.pow_register.update_interval, - ) - run_faucet_parser.add_argument( - "--faucet.no_output_in_place", - "--no_output_in_place", - dest="pow_register.output_in_place", - help="Whether to not ouput the registration statistics in-place. Set flag to disable output in-place.", - action="store_false", - required=False, - default=defaults.pow_register.output_in_place, - ) - run_faucet_parser.add_argument( - "--faucet.verbose", - help="Whether to ouput the registration statistics verbosely.", - action="store_true", - required=False, - default=defaults.pow_register.verbose, - ) - - ## Registration args for CUDA registration. - run_faucet_parser.add_argument( - "--faucet.cuda.use_cuda", - "--cuda", - "--cuda.use_cuda", - dest="pow_register.cuda.use_cuda", - default=defaults.pow_register.cuda.use_cuda, - help="""Set flag to use CUDA to pow_register.""", - action="store_true", - required=False, - ) - run_faucet_parser.add_argument( - "--faucet.cuda.no_cuda", - "--no_cuda", - "--cuda.no_cuda", - dest="pow_register.cuda.use_cuda", - default=not defaults.pow_register.cuda.use_cuda, - help="""Set flag to not use CUDA for registration""", - action="store_false", - required=False, - ) - run_faucet_parser.add_argument( - "--faucet.cuda.dev_id", - "--cuda.dev_id", - type=int, - nargs="+", - default=defaults.pow_register.cuda.dev_id, - help="""Set the CUDA device id(s). Goes by the order of speed. (i.e. 0 is the fastest).""", - required=False, - ) - run_faucet_parser.add_argument( - "--faucet.cuda.tpb", - "--cuda.tpb", - type=int, - default=defaults.pow_register.cuda.tpb, - help="""Set the number of Threads Per Block for CUDA.""", - required=False, - ) - bittensor.wallet.add_args(run_faucet_parser) - bittensor.subtensor.add_args(run_faucet_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - if not config.no_prompt: - check_for_cuda_reg_config(config) - - -class SwapHotkeyCommand: - @staticmethod - def run(cli: "bittensor.cli"): - """ - Executes the ``swap_hotkey`` command to swap the hotkeys for a neuron on the network. - - Usage: - The command is used to swap the hotkey of a wallet for another hotkey on that same wallet. - - Optional arguments: - - ``--wallet.name`` (str): Specifies the wallet for which the hotkey is to be swapped. - - ``--wallet.hotkey`` (str): The original hotkey name that is getting swapped out. - - ``--wallet.hotkey_b`` (str): The new hotkey name for which the old is getting swapped out for. - - Example usage:: - - btcli wallet swap_hotkey --wallet.name your_wallet_name --wallet.hotkey original_hotkey --wallet.hotkey_b new_hotkey - """ - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - SwapHotkeyCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Swap your hotkey for all registered axons on the network.""" - wallet = bittensor.wallet(config=cli.config) - - # This creates an unnecessary amount of extra data, but simplifies implementation. - new_config = deepcopy(cli.config) - new_config.wallet.hotkey = new_config.wallet.hotkey_b - new_wallet = bittensor.wallet(config=new_config) - - subtensor.swap_hotkey( - wallet=wallet, - new_wallet=new_wallet, - wait_for_finalization=False, - wait_for_inclusion=True, - prompt=False, - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - swap_hotkey_parser = parser.add_parser( - "swap_hotkey", help="""Swap your associated hotkey.""" - ) - - swap_hotkey_parser.add_argument( - "--wallet.hotkey_b", - type=str, - default=defaults.wallet.hotkey, - help="""Name of the new hotkey""", - required=False, - ) - - bittensor.wallet.add_args(swap_hotkey_parser) - bittensor.subtensor.add_args(swap_hotkey_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if ( - not config.is_set("subtensor.network") - and not config.is_set("subtensor.chain_endpoint") - and not config.no_prompt - ): - config.subtensor.network = Prompt.ask( - "Enter subtensor network", - choices=bittensor.__networks__, - default=defaults.subtensor.network, - ) - _, endpoint = bittensor.subtensor.determine_chain_endpoint_and_network( - config.subtensor.network - ) - config.subtensor.chain_endpoint = endpoint - - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter old hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - if not config.is_set("wallet.hotkey_b") and not config.no_prompt: - hotkey = Prompt.ask("Enter new hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey_b = str(hotkey) diff --git a/bittensor/commands/root.py b/bittensor/commands/root.py deleted file mode 100644 index 5607921b19..0000000000 --- a/bittensor/commands/root.py +++ /dev/null @@ -1,681 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 re -import typing -import argparse -import numpy as np -import bittensor -from typing import List, Optional, Dict -from rich.prompt import Prompt -from rich.table import Table -from .utils import get_delegates_details, DelegatesDetails - -from . import defaults - -console = bittensor.__console__ - - -class RootRegisterCommand: - """ - Executes the ``register`` command to register a wallet to the root network of the Bittensor network. - - This command is used to formally acknowledge a wallet's participation in the network's root layer. - - Usage: - The command registers the user's wallet with the root network, which is a crucial step for participating in network governance and other advanced functions. - - Optional arguments: - - None. The command primarily uses the wallet and subtensor configurations. - - Example usage:: - - btcli root register - - Note: - This command is important for users seeking to engage deeply with the Bittensor network, particularly in aspects related to network governance and decision-making. - - It is a straightforward process but requires the user to have an initialized and configured wallet. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Register to root network.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - RootRegisterCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Register to root network.""" - wallet = bittensor.wallet(config=cli.config) - - subtensor.root_register(wallet=wallet, prompt=not cli.config.no_prompt) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser( - "register", help="""Register a wallet to the root network.""" - ) - - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - -class RootList: - """ - Executes the ``list`` command to display the members of the root network on the Bittensor network. - - This command provides an overview of the neurons that constitute the network's foundational layer. - - Usage: - Upon execution, the command fetches and lists the neurons in the root network, showing their unique identifiers (UIDs), names, addresses, stakes, and whether they are part of the senate (network governance body). - - Optional arguments: - - None. The command uses the subtensor configuration to retrieve data. - - Example usage:: - - $ btcli root list - - UID NAME ADDRESS STAKE(τ) SENATOR - 0 5CaCUPsSSdKWcMJbmdmJdnWVa15fJQuz5HsSGgVdZffpHAUa 27086.37070 Yes - 1 RaoK9 5GmaAk7frPXnAxjbQvXcoEzMGZfkrDee76eGmKoB3wxUburE 520.24199 No - 2 Openτensor Foundaτion 5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3 1275437.45895 Yes - 3 RoundTable21 5FFApaS75bv5pJHfAp2FVLBj9ZaXuFDjEypsaBNc1wCfe52v 84718.42095 Yes - 4 5HK5tp6t2S59DywmHRWPBVJeJ86T61KjurYqeooqj8sREpeN 168897.40859 Yes - 5 Rizzo 5CXRfP2ekFhe62r7q3vppRajJmGhTi7vwvb2yr79jveZ282w 53383.34400 No - 6 τaosτaτs and BitAPAI 5Hddm3iBFD2GLT5ik7LZnT3XJUnRnN8PoeCFgGQgawUVKNm8 646944.73569 Yes - ... - - Note: - This command is useful for users interested in understanding the composition and governance structure of the Bittensor network's root layer. It provides insights into which neurons hold significant influence and responsibility within the network. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""List the root network""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - RootList._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""List the root network""" - console.print( - ":satellite: Syncing with chain: [white]{}[/white] ...".format( - subtensor.network - ) - ) - - senate_members = subtensor.get_senate_members() - root_neurons: typing.List[bittensor.NeuronInfoLite] = subtensor.neurons_lite( - netuid=0 - ) - delegate_info: Optional[Dict[str, DelegatesDetails]] = get_delegates_details( - url=bittensor.__delegates_details_url__ - ) - - table = Table(show_footer=False) - table.title = "[white]Root Network" - table.add_column( - "[overline white]UID", - footer_style="overline white", - style="rgb(50,163,219)", - no_wrap=True, - ) - table.add_column( - "[overline white]NAME", - footer_style="overline white", - style="rgb(50,163,219)", - no_wrap=True, - ) - table.add_column( - "[overline white]ADDRESS", - footer_style="overline white", - style="yellow", - no_wrap=True, - ) - table.add_column( - "[overline white]STAKE(\u03c4)", - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - table.add_column( - "[overline white]SENATOR", - footer_style="overline white", - style="green", - no_wrap=True, - ) - table.show_footer = True - - for neuron_data in root_neurons: - table.add_row( - str(neuron_data.uid), - ( - delegate_info[neuron_data.hotkey].name - if neuron_data.hotkey in delegate_info - else "" - ), - neuron_data.hotkey, - "{:.5f}".format( - float(subtensor.get_total_stake_for_hotkey(neuron_data.hotkey)) - ), - "Yes" if neuron_data.hotkey in senate_members else "No", - ) - - table.box = None - table.pad_edge = False - table.width = None - bittensor.__console__.print(table) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser("list", help="""List the root network""") - bittensor.subtensor.add_args(parser) - - @staticmethod - def check_config(config: "bittensor.config"): - pass - - -class RootSetBoostCommand: - """ - Executes the ``boost`` command to boost the weights for a specific subnet within the root network on the Bittensor network. - - Usage: - The command allows boosting the weights for different subnets within the root network. - - Optional arguments: - - ``--netuid`` (int): A single netuid for which weights are to be boosted. - - ``--increase`` (float): The cooresponding increase in the weight for this subnet. - - Example usage:: - - $ btcli root boost --netuid 1 --increase 0.01 - - Enter netuid (e.g. 1): 1 - Enter amount (e.g. 0.01): 0.1 - Boosting weight for subnet: 1 by amount: 0.1 - - Normalized weights: - tensor([ - 0.0000, 0.5455, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.4545, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]) -> tensor([0.0000, 0.5455, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.4545, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000] - ) - - Do you want to set the following root weights?: - weights: tensor([ - 0.0000, 0.5455, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.4545, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]) - uids: tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40])? [y/n]: y - True None - ✅ Finalized - ⠙ 📡 Setting root weights on test ...2023-11-28 22:09:14.001 | SUCCESS | Set weights Finalized: True - - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Set weights for root network.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - RootSetBoostCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Set weights for root network.""" - wallet = bittensor.wallet(config=cli.config) - - root = subtensor.metagraph(0, lite=False) - try: - my_uid = root.hotkeys.index(wallet.hotkey.ss58_address) - except ValueError: - bittensor.__console__.print( - "Wallet hotkey: {} not found in root metagraph".format(wallet.hotkey) - ) - exit() - my_weights = root.weights[my_uid] - prev_weight = my_weights[cli.config.netuid] - new_weight = prev_weight + cli.config.amount - - bittensor.__console__.print( - f"Boosting weight for netuid {cli.config.netuid} from {prev_weight} -> {new_weight}" - ) - my_weights[cli.config.netuid] = new_weight - all_netuids = np.arange(len(my_weights)) - - bittensor.__console__.print("Setting root weights...") - subtensor.root_set_weights( - wallet=wallet, - netuids=all_netuids, - weights=my_weights, - version_key=0, - prompt=not cli.config.no_prompt, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser( - "boost", help="""Boost weight for a specific subnet by increase amount.""" - ) - parser.add_argument("--netuid", dest="netuid", type=int, required=False) - parser.add_argument("--increase", dest="amount", type=float, required=False) - - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - if not config.is_set("netuid") and not config.no_prompt: - config.netuid = int(Prompt.ask(f"Enter netuid (e.g. 1)")) - if not config.is_set("amount") and not config.no_prompt: - config.amount = float(Prompt.ask(f"Enter amount (e.g. 0.01)")) - - -class RootSetSlashCommand: - """ - Executes the ``slash`` command to decrease the weights for a specific subnet within the root network on the Bittensor network. - - Usage: - The command allows slashing (decreasing) the weights for different subnets within the root network. - - Optional arguments: - - ``--netuid`` (int): A single netuid for which weights are to be slashed. - - ``--decrease`` (float): The corresponding decrease in the weight for this subnet. - - Example usage:: - - $ btcli root slash --netuid 1 --decrease 0.01 - - Enter netuid (e.g. 1): 1 - Enter decrease amount (e.g. 0.01): 0.2 - Slashing weight for subnet: 1 by amount: 0.2 - - Normalized weights: - tensor([ - 0.0000, 0.4318, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.5682, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]) -> tensor([ - 0.0000, 0.4318, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.5682, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000] - ) - - Do you want to set the following root weights?: - weights: tensor([ - 0.0000, 0.4318, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.5682, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, - 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]) - uids: tensor([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, - 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, - 36, 37, 38, 39, 40])? [y/n]: y - ⠙ 📡 Setting root weights on test ...2023-11-28 22:09:14.001 | SUCCESS | Set weights Finalized: True - """ - - @staticmethod - def run(cli: "bittensor.cli"): - """Set weights for root network with decreased values.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - RootSetSlashCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - wallet = bittensor.wallet(config=cli.config) - - bittensor.__console__.print( - "Slashing weight for subnet: {} by amount: {}".format( - cli.config.netuid, cli.config.amount - ) - ) - root = subtensor.metagraph(0, lite=False) - try: - my_uid = root.hotkeys.index(wallet.hotkey.ss58_address) - except ValueError: - bittensor.__console__.print( - "Wallet hotkey: {} not found in root metagraph".format(wallet.hotkey) - ) - exit() - my_weights = root.weights[my_uid] - my_weights[cli.config.netuid] -= cli.config.amount - my_weights[my_weights < 0] = 0 # Ensure weights don't go negative - all_netuids = np.arange(len(my_weights)) - - subtensor.root_set_weights( - wallet=wallet, - netuids=all_netuids, - weights=my_weights, - version_key=0, - prompt=not cli.config.no_prompt, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser( - "slash", help="""Slash weight for a specific subnet by decrease amount.""" - ) - parser.add_argument("--netuid", dest="netuid", type=int, required=False) - parser.add_argument("--decrease", dest="amount", type=float, required=False) - - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - if not config.is_set("netuid") and not config.no_prompt: - config.netuid = int(Prompt.ask(f"Enter netuid (e.g. 1)")) - if not config.is_set("amount") and not config.no_prompt: - config.amount = float(Prompt.ask(f"Enter decrease amount (e.g. 0.01)")) - - -class RootSetWeightsCommand: - """ - Executes the ``weights`` command to set the weights for the root network on the Bittensor network. - - This command is used by network senators to influence the distribution of network rewards and responsibilities. - - Usage: - The command allows setting weights for different subnets within the root network. Users need to specify the netuids (network unique identifiers) and corresponding weights they wish to assign. - - Optional arguments: - - ``--netuids`` (str): A comma-separated list of netuids for which weights are to be set. - - ``--weights`` (str): Corresponding weights for the specified netuids, in comma-separated format. - - Example usage:: - - btcli root weights --netuids 1,2,3 --weights 0.3,0.3,0.4 - - Note: - This command is particularly important for network senators and requires a comprehensive understanding of the network's dynamics. - It is a powerful tool that directly impacts the network's operational mechanics and reward distribution. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Set weights for root network.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - RootSetWeightsCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Set weights for root network.""" - wallet = bittensor.wallet(config=cli.config) - subnets: List[bittensor.SubnetInfo] = subtensor.get_all_subnets_info() - - # Get values if not set. - if not cli.config.is_set("netuids"): - example = ( - ", ".join(map(str, [subnet.netuid for subnet in subnets][:3])) + " ..." - ) - cli.config.netuids = Prompt.ask(f"Enter netuids (e.g. {example})") - - if not cli.config.is_set("weights"): - example = ( - ", ".join( - map( - str, - [ - "{:.2f}".format(float(1 / len(subnets))) - for subnet in subnets - ][:3], - ) - ) - + " ..." - ) - cli.config.weights = Prompt.ask(f"Enter weights (e.g. {example})") - - # Parse from string - matched_netuids = list(map(int, re.split(r"[ ,]+", cli.config.netuids))) - netuids = np.array(matched_netuids, dtype=np.int64) - - matched_weights = [ - float(weight) for weight in re.split(r"[ ,]+", cli.config.weights) - ] - weights = np.array(matched_weights, dtype=np.float32) - - # Run the set weights operation. - subtensor.root_set_weights( - wallet=wallet, - netuids=netuids, - weights=weights, - version_key=0, - prompt=not cli.config.no_prompt, - wait_for_finalization=True, - wait_for_inclusion=True, - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser("weights", help="""Set weights for root network.""") - parser.add_argument("--netuids", dest="netuids", type=str, required=False) - parser.add_argument("--weights", dest="weights", type=str, required=False) - - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - -class RootGetWeightsCommand: - """ - Executes the ``get_weights`` command to retrieve the weights set for the root network on the Bittensor network. - - This command provides visibility into how network responsibilities and rewards are distributed among various subnets. - - Usage: - The command outputs a table listing the weights assigned to each subnet within the root network. This information is crucial for understanding the current influence and reward distribution among the subnets. - - Optional arguments: - - None. The command fetches weight information based on the subtensor configuration. - - Example usage:: - - $ btcli root get_weights - - Root Network Weights - UID 0 1 2 3 4 5 8 9 11 13 18 19 - 1 100.00% - - - - - - - - - - - - 2 - 40.00% 5.00% 10.00% 10.00% 10.00% 10.00% 5.00% - - 10.00% - - 3 - - 25.00% - 25.00% - 25.00% - - - 25.00% - - 4 - - 7.00% 7.00% 20.00% 20.00% 20.00% - 6.00% - 20.00% - - 5 - 20.00% - 10.00% 15.00% 15.00% 15.00% 5.00% - - 10.00% 10.00% - 6 - - - - 10.00% 10.00% 25.00% 25.00% - - 30.00% - - 7 - 60.00% - - 20.00% - - - 20.00% - - - - 8 - 49.35% - 7.18% 13.59% 21.14% 1.53% 0.12% 7.06% 0.03% - - - 9 100.00% - - - - - - - - - - - - ... - - Note: - This command is essential for users interested in the governance and operational dynamics of the Bittensor network. It offers transparency into how network rewards and responsibilities are allocated across different subnets. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Get weights for root network.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - RootGetWeightsCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Get weights for root network.""" - weights = subtensor.weights(0) - - table = Table(show_footer=False) - table.title = "[white]Root Network Weights" - table.add_column( - "[white]UID", - header_style="overline white", - footer_style="overline white", - style="rgb(50,163,219)", - no_wrap=True, - ) - - uid_to_weights = {} - netuids = set() - for matrix in weights: - [uid, weights_data] = matrix - - if not len(weights_data): - uid_to_weights[uid] = {} - normalized_weights = [] - else: - normalized_weights = np.array(weights_data)[:, 1] / max( - np.sum(weights_data, axis=0)[1], 1 - ) - - for weight_data, normalized_weight in zip(weights_data, normalized_weights): - [netuid, _] = weight_data - netuids.add(netuid) - if uid not in uid_to_weights: - uid_to_weights[uid] = {} - - uid_to_weights[uid][netuid] = normalized_weight - - for netuid in netuids: - table.add_column( - f"[white]{netuid}", - header_style="overline white", - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - - for uid in uid_to_weights: - row = [str(uid)] - - uid_weights = uid_to_weights[uid] - for netuid in netuids: - if netuid in uid_weights: - normalized_weight = uid_weights[netuid] - row.append("{:0.2f}%".format(normalized_weight * 100)) - else: - row.append("-") - table.add_row(*row) - - table.show_footer = True - - table.box = None - table.pad_edge = False - table.width = None - bittensor.__console__.print(table) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser( - "get_weights", help="""Get weights for root network.""" - ) - - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - - @staticmethod - def check_config(config: "bittensor.config"): - pass diff --git a/bittensor/commands/senate.py b/bittensor/commands/senate.py deleted file mode 100644 index 03a73cde5b..0000000000 --- a/bittensor/commands/senate.py +++ /dev/null @@ -1,650 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 argparse -import bittensor -from rich.prompt import Prompt, Confirm -from rich.table import Table -from typing import Optional, Dict -from .utils import get_delegates_details, DelegatesDetails -from . import defaults - -console = bittensor.__console__ - - -class SenateCommand: - """ - Executes the ``senate`` command to view the members of Bittensor's governance protocol, known as the Senate. - - This command lists the delegates involved in the decision-making process of the Bittensor network. - - Usage: - The command retrieves and displays a list of Senate members, showing their names and wallet addresses. - This information is crucial for understanding who holds governance roles within the network. - - Example usage:: - - btcli root senate - - Note: - This command is particularly useful for users interested in the governance structure and participants of the Bittensor network. It provides transparency into the network's decision-making body. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""View Bittensor's governance protocol proposals""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - SenateCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""View Bittensor's governance protocol proposals""" - console = bittensor.__console__ - console.print( - ":satellite: Syncing with chain: [white]{}[/white] ...".format( - cli.config.subtensor.network - ) - ) - - senate_members = subtensor.get_senate_members() - delegate_info: Optional[Dict[str, DelegatesDetails]] = get_delegates_details( - url=bittensor.__delegates_details_url__ - ) - - table = Table(show_footer=False) - table.title = "[white]Senate" - table.add_column( - "[overline white]NAME", - footer_style="overline white", - style="rgb(50,163,219)", - no_wrap=True, - ) - table.add_column( - "[overline white]ADDRESS", - footer_style="overline white", - style="yellow", - no_wrap=True, - ) - table.show_footer = True - - for ss58_address in senate_members: - table.add_row( - ( - delegate_info[ss58_address].name - if ss58_address in delegate_info - else "" - ), - ss58_address, - ) - - table.box = None - table.pad_edge = False - table.width = None - console.print(table) - - @classmethod - def check_config(cls, config: "bittensor.config"): - None - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - senate_parser = parser.add_parser( - "senate", help="""View senate and it's members""" - ) - - bittensor.wallet.add_args(senate_parser) - bittensor.subtensor.add_args(senate_parser) - - -def format_call_data(call_data: "bittensor.ProposalCallData") -> str: - human_call_data = list() - - for arg in call_data["call_args"]: - arg_value = arg["value"] - - # If this argument is a nested call - func_args = ( - format_call_data( - { - "call_function": arg_value["call_function"], - "call_args": arg_value["call_args"], - } - ) - if isinstance(arg_value, dict) and "call_function" in arg_value - else str(arg_value) - ) - - human_call_data.append("{}: {}".format(arg["name"], func_args)) - - return "{}({})".format(call_data["call_function"], ", ".join(human_call_data)) - - -def display_votes( - vote_data: "bittensor.ProposalVoteData", delegate_info: "bittensor.DelegateInfo" -) -> str: - vote_list = list() - - for address in vote_data["ayes"]: - vote_list.append( - "{}: {}".format( - delegate_info[address].name if address in delegate_info else address, - "[bold green]Aye[/bold green]", - ) - ) - - for address in vote_data["nays"]: - vote_list.append( - "{}: {}".format( - delegate_info[address].name if address in delegate_info else address, - "[bold red]Nay[/bold red]", - ) - ) - - return "\n".join(vote_list) - - -class ProposalsCommand: - """ - Executes the ``proposals`` command to view active proposals within Bittensor's governance protocol. - - This command displays the details of ongoing proposals, including votes, thresholds, and proposal data. - - Usage: - The command lists all active proposals, showing their hash, voting threshold, number of ayes and nays, detailed votes by address, end block number, and call data associated with each proposal. - - Example usage:: - - btcli root proposals - - Note: - This command is essential for users who are actively participating in or monitoring the governance of the Bittensor network. - It provides a detailed view of the proposals being considered, along with the community's response to each. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""View Bittensor's governance protocol proposals""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - ProposalsCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""View Bittensor's governance protocol proposals""" - console = bittensor.__console__ - console.print( - ":satellite: Syncing with chain: [white]{}[/white] ...".format( - subtensor.network - ) - ) - - senate_members = subtensor.get_senate_members() - proposals = subtensor.get_proposals() - - registered_delegate_info: Optional[Dict[str, DelegatesDetails]] = ( - get_delegates_details(url=bittensor.__delegates_details_url__) - ) - - table = Table(show_footer=False) - table.title = ( - "[white]Proposals\t\tActive Proposals: {}\t\tSenate Size: {}".format( - len(proposals), len(senate_members) - ) - ) - table.add_column( - "[overline white]HASH", - footer_style="overline white", - style="yellow", - no_wrap=True, - ) - table.add_column( - "[overline white]THRESHOLD", footer_style="overline white", style="white" - ) - table.add_column( - "[overline white]AYES", footer_style="overline white", style="green" - ) - table.add_column( - "[overline white]NAYS", footer_style="overline white", style="red" - ) - table.add_column( - "[overline white]VOTES", - footer_style="overline white", - style="rgb(50,163,219)", - ) - table.add_column( - "[overline white]END", footer_style="overline white", style="blue" - ) - table.add_column( - "[overline white]CALLDATA", footer_style="overline white", style="white" - ) - table.show_footer = True - - for hash in proposals: - call_data, vote_data = proposals[hash] - - table.add_row( - hash, - str(vote_data["threshold"]), - str(len(vote_data["ayes"])), - str(len(vote_data["nays"])), - display_votes(vote_data, registered_delegate_info), - str(vote_data["end"]), - format_call_data(call_data), - ) - - table.box = None - table.pad_edge = False - table.width = None - console.print(table) - - @classmethod - def check_config(cls, config: "bittensor.config"): - None - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - proposals_parser = parser.add_parser( - "proposals", help="""View active triumvirate proposals and their status""" - ) - - bittensor.wallet.add_args(proposals_parser) - bittensor.subtensor.add_args(proposals_parser) - - -class ShowVotesCommand: - """ - Executes the ``proposal_votes`` command to view the votes for a specific proposal in Bittensor's governance protocol. - - IMPORTANT - **THIS COMMAND IS DEPRECATED**. Use ``btcli root proposals`` to see vote status. - - This command provides a detailed breakdown of the votes cast by the senators for a particular proposal. - - Usage: - Users need to specify the hash of the proposal they are interested in. The command then displays the voting addresses and their respective votes (Aye or Nay) for the specified proposal. - - Optional arguments: - - ``--proposal`` (str): The hash of the proposal for which votes need to be displayed. - - Example usage:: - - btcli root proposal_votes --proposal - - Note: - This command is crucial for users seeking detailed insights into the voting behavior of the Senate on specific governance proposals. - It helps in understanding the level of consensus or disagreement within the Senate on key decisions. - - **THIS COMMAND IS DEPRECATED**. Use ``btcli root proposals`` to see vote status. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""View Bittensor's governance protocol proposals active votes""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - ShowVotesCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""View Bittensor's governance protocol proposals active votes""" - console.print( - ":satellite: Syncing with chain: [white]{}[/white] ...".format( - cli.config.subtensor.network - ) - ) - - proposal_hash = cli.config.proposal_hash - if len(proposal_hash) == 0: - console.print( - 'Aborting: Proposal hash not specified. View all proposals with the "proposals" command.' - ) - return - - proposal_vote_data = subtensor.get_vote_data(proposal_hash) - if proposal_vote_data == None: - console.print(":cross_mark: [red]Failed[/red]: Proposal not found.") - return - - registered_delegate_info: Optional[Dict[str, DelegatesDetails]] = ( - get_delegates_details(url=bittensor.__delegates_details_url__) - ) - - table = Table(show_footer=False) - table.title = "[white]Votes for Proposal {}".format(proposal_hash) - table.add_column( - "[overline white]ADDRESS", - footer_style="overline white", - style="yellow", - no_wrap=True, - ) - table.add_column( - "[overline white]VOTE", footer_style="overline white", style="white" - ) - table.show_footer = True - - votes = display_votes(proposal_vote_data, registered_delegate_info).split("\n") - for vote in votes: - split_vote_data = vote.split(": ") # Nasty, but will work. - table.add_row(split_vote_data[0], split_vote_data[1]) - - table.box = None - table.pad_edge = False - table.min_width = 64 - console.print(table) - - @classmethod - def check_config(cls, config: "bittensor.config"): - if config.proposal_hash == "" and not config.no_prompt: - proposal_hash = Prompt.ask("Enter proposal hash") - config.proposal_hash = str(proposal_hash) - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - show_votes_parser = parser.add_parser( - "proposal_votes", help="""View an active proposal's votes by address.""" - ) - show_votes_parser.add_argument( - "--proposal", - dest="proposal_hash", - type=str, - nargs="?", - help="""Set the proposal to show votes for.""", - default="", - ) - bittensor.wallet.add_args(show_votes_parser) - bittensor.subtensor.add_args(show_votes_parser) - - -class SenateRegisterCommand: - """ - Executes the ``senate_register`` command to register as a member of the Senate in Bittensor's governance protocol. - - This command is used by delegates who wish to participate in the governance and decision-making process of the network. - - Usage: - The command checks if the user's hotkey is a delegate and not already a Senate member before registering them to the Senate. - Successful execution allows the user to participate in proposal voting and other governance activities. - - Example usage:: - - btcli root senate_register - - Note: - This command is intended for delegates who are interested in actively participating in the governance of the Bittensor network. - It is a significant step towards engaging in network decision-making processes. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Register to participate in Bittensor's governance protocol proposals""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - SenateRegisterCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Register to participate in Bittensor's governance protocol proposals""" - wallet = bittensor.wallet(config=cli.config) - - # Unlock the wallet. - wallet.hotkey - wallet.coldkey - - # Check if the hotkey is a delegate. - if not subtensor.is_hotkey_delegate(wallet.hotkey.ss58_address): - console.print( - "Aborting: Hotkey {} isn't a delegate.".format( - wallet.hotkey.ss58_address - ) - ) - return - - if subtensor.is_senate_member(hotkey_ss58=wallet.hotkey.ss58_address): - console.print( - "Aborting: Hotkey {} is already a senate member.".format( - wallet.hotkey.ss58_address - ) - ) - return - - subtensor.register_senate(wallet=wallet, prompt=not cli.config.no_prompt) - - @classmethod - def check_config(cls, config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - senate_register_parser = parser.add_parser( - "senate_register", - help="""Register as a senate member to participate in proposals""", - ) - - bittensor.wallet.add_args(senate_register_parser) - bittensor.subtensor.add_args(senate_register_parser) - - -class SenateLeaveCommand: - """ - Executes the ``senate_leave`` command to discard membership in Bittensor's Senate. - - This command allows a Senate member to voluntarily leave the governance body. - - Usage: - The command checks if the user's hotkey is currently a Senate member before processing the request to leave the Senate. - It effectively removes the user from participating in future governance decisions. - - Example usage:: - - btcli root senate_leave - - Note: - This command is relevant for Senate members who wish to step down from their governance responsibilities within the Bittensor network. - It should be used when a member no longer desires to participate in the Senate activities. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Discard membership in Bittensor's governance protocol proposals""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - SenateLeaveCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.cli"): - r"""Discard membership in Bittensor's governance protocol proposals""" - wallet = bittensor.wallet(config=cli.config) - - # Unlock the wallet. - wallet.hotkey - wallet.coldkey - - if not subtensor.is_senate_member(hotkey_ss58=wallet.hotkey.ss58_address): - console.print( - "Aborting: Hotkey {} isn't a senate member.".format( - wallet.hotkey.ss58_address - ) - ) - return - - subtensor.leave_senate(wallet=wallet, prompt=not cli.config.no_prompt) - - @classmethod - def check_config(cls, config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - senate_leave_parser = parser.add_parser( - "senate_leave", - help="""Discard senate membership in the governance protocol""", - ) - - bittensor.wallet.add_args(senate_leave_parser) - bittensor.subtensor.add_args(senate_leave_parser) - - -class VoteCommand: - """ - Executes the ``senate_vote`` command to cast a vote on an active proposal in Bittensor's governance protocol. - - This command is used by Senate members to vote on various proposals that shape the network's future. - - Usage: - The user needs to specify the hash of the proposal they want to vote on. The command then allows the Senate member to cast an 'Aye' or 'Nay' vote, contributing to the decision-making process. - - Optional arguments: - - ``--proposal`` (str): The hash of the proposal to vote on. - - Example usage:: - - btcli root senate_vote --proposal - - Note: - This command is crucial for Senate members to exercise their voting rights on key proposals. It plays a vital role in the governance and evolution of the Bittensor network. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Vote in Bittensor's governance protocol proposals""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - VoteCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Vote in Bittensor's governance protocol proposals""" - wallet = bittensor.wallet(config=cli.config) - - proposal_hash = cli.config.proposal_hash - if len(proposal_hash) == 0: - console.print( - 'Aborting: Proposal hash not specified. View all proposals with the "proposals" command.' - ) - return - - if not subtensor.is_senate_member(hotkey_ss58=wallet.hotkey.ss58_address): - console.print( - "Aborting: Hotkey {} isn't a senate member.".format( - wallet.hotkey.ss58_address - ) - ) - return - - # Unlock the wallet. - wallet.hotkey - wallet.coldkey - - vote_data = subtensor.get_vote_data(proposal_hash) - if vote_data == None: - console.print(":cross_mark: [red]Failed[/red]: Proposal not found.") - return - - vote = Confirm.ask("Desired vote for proposal") - subtensor.vote_senate( - wallet=wallet, - proposal_hash=proposal_hash, - proposal_idx=vote_data["index"], - vote=vote, - prompt=not cli.config.no_prompt, - ) - - @classmethod - def check_config(cls, config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - if config.proposal_hash == "" and not config.no_prompt: - proposal_hash = Prompt.ask("Enter proposal hash") - config.proposal_hash = str(proposal_hash) - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - vote_parser = parser.add_parser( - "senate_vote", help="""Vote on an active proposal by hash.""" - ) - vote_parser.add_argument( - "--proposal", - dest="proposal_hash", - type=str, - nargs="?", - help="""Set the proposal to show votes for.""", - default="", - ) - bittensor.wallet.add_args(vote_parser) - bittensor.subtensor.add_args(vote_parser) diff --git a/bittensor/commands/stake.py b/bittensor/commands/stake.py deleted file mode 100644 index 1bc2cf2786..0000000000 --- a/bittensor/commands/stake.py +++ /dev/null @@ -1,568 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 argparse -import os -import sys -from typing import List, Union, Optional, Dict, Tuple - -from rich.prompt import Confirm, Prompt -from rich.table import Table -from tqdm import tqdm - -import bittensor -from bittensor.utils.balance import Balance -from .utils import ( - get_hotkey_wallets_for_wallet, - get_delegates_details, - DelegatesDetails, -) -from . import defaults - -console = bittensor.__console__ - - -class StakeCommand: - """ - Executes the ``add`` command to stake tokens to one or more hotkeys from a user's coldkey on the Bittensor network. - - This command is used to allocate tokens to different hotkeys, securing their position and influence on the network. - - Usage: - Users can specify the amount to stake, the hotkeys to stake to (either by name or ``SS58`` address), and whether to stake to all hotkeys. The command checks for sufficient balance and hotkey registration - before proceeding with the staking process. - - Optional arguments: - - ``--all`` (bool): When set, stakes all available tokens from the coldkey. - - ``--uid`` (int): The unique identifier of the neuron to which the stake is to be added. - - ``--amount`` (float): The amount of TAO tokens to stake. - - ``--max_stake`` (float): Sets the maximum amount of TAO to have staked in each hotkey. - - ``--hotkeys`` (list): Specifies hotkeys by name or SS58 address to stake to. - - ``--all_hotkeys`` (bool): When set, stakes to all hotkeys associated with the wallet, excluding any specified in --hotkeys. - - The command prompts for confirmation before executing the staking operation. - - Example usage:: - - btcli stake add --amount 100 --wallet.name --wallet.hotkey - - Note: - This command is critical for users who wish to distribute their stakes among different neurons (hotkeys) on the network. - It allows for a strategic allocation of tokens to enhance network participation and influence. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Stake token of amount to hotkey(s).""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - StakeCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Stake token of amount to hotkey(s).""" - config = cli.config.copy() - wallet = bittensor.wallet(config=config) - - # Get the hotkey_names (if any) and the hotkey_ss58s. - hotkeys_to_stake_to: List[Tuple[Optional[str], str]] = [] - if config.get("all_hotkeys"): - # Stake to all hotkeys. - all_hotkeys: List[bittensor.wallet] = get_hotkey_wallets_for_wallet( - wallet=wallet - ) - # Get the hotkeys to exclude. (d)efault to no exclusions. - hotkeys_to_exclude: List[str] = cli.config.get("hotkeys", d=[]) - # Exclude hotkeys that are specified. - hotkeys_to_stake_to = [ - (wallet.hotkey_str, wallet.hotkey.ss58_address) - for wallet in all_hotkeys - if wallet.hotkey_str not in hotkeys_to_exclude - ] # definitely wallets - - elif config.get("hotkeys"): - # Stake to specific hotkeys. - for hotkey_ss58_or_hotkey_name in config.get("hotkeys"): - if bittensor.utils.is_valid_ss58_address(hotkey_ss58_or_hotkey_name): - # If the hotkey is a valid ss58 address, we add it to the list. - hotkeys_to_stake_to.append((None, hotkey_ss58_or_hotkey_name)) - else: - # If the hotkey is not a valid ss58 address, we assume it is a hotkey name. - # We then get the hotkey from the wallet and add it to the list. - wallet_ = bittensor.wallet( - config=config, hotkey=hotkey_ss58_or_hotkey_name - ) - hotkeys_to_stake_to.append( - (wallet_.hotkey_str, wallet_.hotkey.ss58_address) - ) - elif config.wallet.get("hotkey"): - # Only config.wallet.hotkey is specified. - # so we stake to that single hotkey. - hotkey_ss58_or_name = config.wallet.get("hotkey") - if bittensor.utils.is_valid_ss58_address(hotkey_ss58_or_name): - hotkeys_to_stake_to = [(None, hotkey_ss58_or_name)] - else: - # Hotkey is not a valid ss58 address, so we assume it is a hotkey name. - wallet_ = bittensor.wallet(config=config, hotkey=hotkey_ss58_or_name) - hotkeys_to_stake_to = [ - (wallet_.hotkey_str, wallet_.hotkey.ss58_address) - ] - else: - # Only config.wallet.hotkey is specified. - # so we stake to that single hotkey. - assert config.wallet.hotkey is not None - hotkeys_to_stake_to = [ - (None, bittensor.wallet(config=config).hotkey.ss58_address) - ] - - # Get coldkey balance - wallet_balance: Balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - final_hotkeys: List[Tuple[str, str]] = [] - final_amounts: List[Union[float, Balance]] = [] - for hotkey in tqdm(hotkeys_to_stake_to): - hotkey: Tuple[Optional[str], str] # (hotkey_name (or None), hotkey_ss58) - if not subtensor.is_hotkey_registered_any(hotkey_ss58=hotkey[1]): - # Hotkey is not registered. - if len(hotkeys_to_stake_to) == 1: - # Only one hotkey, error - bittensor.__console__.print( - f"[red]Hotkey [bold]{hotkey[1]}[/bold] is not registered. Aborting.[/red]" - ) - return None - else: - # Otherwise, print warning and skip - bittensor.__console__.print( - f"[yellow]Hotkey [bold]{hotkey[1]}[/bold] is not registered. Skipping.[/yellow]" - ) - continue - - stake_amount_tao: float = config.get("amount") - if config.get("max_stake"): - # Get the current stake of the hotkey from this coldkey. - hotkey_stake: Balance = subtensor.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=hotkey[1], coldkey_ss58=wallet.coldkeypub.ss58_address - ) - stake_amount_tao: float = config.get("max_stake") - hotkey_stake.tao - - # If the max_stake is greater than the current wallet balance, stake the entire balance. - stake_amount_tao: float = min(stake_amount_tao, wallet_balance.tao) - if ( - stake_amount_tao <= 0.00001 - ): # Threshold because of fees, might create a loop otherwise - # Skip hotkey if max_stake is less than current stake. - continue - wallet_balance = Balance.from_tao(wallet_balance.tao - stake_amount_tao) - - if wallet_balance.tao < 0: - # No more balance to stake. - break - - final_amounts.append(stake_amount_tao) - final_hotkeys.append(hotkey) # add both the name and the ss58 address. - - if len(final_hotkeys) == 0: - # No hotkeys to stake to. - bittensor.__console__.print( - "Not enough balance to stake to any hotkeys or max_stake is less than current stake." - ) - return None - - # Ask to stake - if not config.no_prompt: - if not Confirm.ask( - f"Do you want to stake to the following keys from {wallet.name}:\n" - + "".join( - [ - f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" - for hotkey, amount in zip(final_hotkeys, final_amounts) - ] - ) - ): - return None - - if len(final_hotkeys) == 1: - # do regular stake - return subtensor.add_stake( - wallet=wallet, - hotkey_ss58=final_hotkeys[0][1], - amount=None if config.get("stake_all") else final_amounts[0], - wait_for_inclusion=True, - prompt=not config.no_prompt, - ) - - subtensor.add_stake_multiple( - wallet=wallet, - hotkey_ss58s=[hotkey_ss58 for _, hotkey_ss58 in final_hotkeys], - amounts=None if config.get("stake_all") else final_amounts, - wait_for_inclusion=True, - prompt=False, - ) - - @classmethod - def check_config(cls, config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if ( - not config.is_set("wallet.hotkey") - and not config.no_prompt - and not config.wallet.get("all_hotkeys") - and not config.wallet.get("hotkeys") - ): - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - # Get amount. - if ( - not config.get("amount") - and not config.get("stake_all") - and not config.get("max_stake") - ): - if not Confirm.ask( - "Stake all Tao from account: [bold]'{}'[/bold]?".format( - config.wallet.get("name", defaults.wallet.name) - ) - ): - amount = Prompt.ask("Enter Tao amount to stake") - try: - config.amount = float(amount) - except ValueError: - console.print( - ":cross_mark:[red]Invalid Tao amount[/red] [bold white]{}[/bold white]".format( - amount - ) - ) - sys.exit() - else: - config.stake_all = True - - @classmethod - def add_args(cls, parser: argparse.ArgumentParser): - stake_parser = parser.add_parser( - "add", help="""Add stake to your hotkey accounts from your coldkey.""" - ) - stake_parser.add_argument("--all", dest="stake_all", action="store_true") - stake_parser.add_argument("--uid", dest="uid", type=int, required=False) - stake_parser.add_argument("--amount", dest="amount", type=float, required=False) - stake_parser.add_argument( - "--max_stake", - dest="max_stake", - type=float, - required=False, - action="store", - default=None, - help="""Specify the maximum amount of Tao to have staked in each hotkey.""", - ) - stake_parser.add_argument( - "--hotkeys", - "--exclude_hotkeys", - "--wallet.hotkeys", - "--wallet.exclude_hotkeys", - required=False, - action="store", - default=[], - type=str, - nargs="*", - help="""Specify the hotkeys by name or ss58 address. (e.g. hk1 hk2 hk3)""", - ) - stake_parser.add_argument( - "--all_hotkeys", - "--wallet.all_hotkeys", - required=False, - action="store_true", - default=False, - help="""To specify all hotkeys. Specifying hotkeys will exclude them from this all.""", - ) - bittensor.wallet.add_args(stake_parser) - bittensor.subtensor.add_args(stake_parser) - - -def _get_coldkey_wallets_for_path(path: str) -> List["bittensor.wallet"]: - try: - wallet_names = next(os.walk(os.path.expanduser(path)))[1] - return [bittensor.wallet(path=path, name=name) for name in wallet_names] - except StopIteration: - # No wallet files found. - wallets = [] - return wallets - - -def _get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: - hotkey_wallets = [] - hotkeys_path = wallet.path + "/" + wallet.name + "/hotkeys" - try: - hotkey_files = next(os.walk(os.path.expanduser(hotkeys_path)))[2] - except StopIteration: - hotkey_files = [] - for hotkey_file_name in hotkey_files: - try: - hotkey_for_name = bittensor.wallet( - path=wallet.path, name=wallet.name, hotkey=hotkey_file_name - ) - if ( - hotkey_for_name.hotkey_file.exists_on_device() - and not hotkey_for_name.hotkey_file.is_encrypted() - ): - hotkey_wallets.append(hotkey_for_name) - except Exception: - pass - return hotkey_wallets - - -class StakeShow: - """ - Executes the ``show`` command to list all stake accounts associated with a user's wallet on the Bittensor network. - - This command provides a comprehensive view of the stakes associated with both hotkeys and delegates linked to the user's coldkey. - - Usage: - The command lists all stake accounts for a specified wallet or all wallets in the user's configuration directory. - It displays the coldkey, balance, account details (hotkey/delegate name), stake amount, and the rate of return. - - Optional arguments: - - ``--all`` (bool): When set, the command checks all coldkey wallets instead of just the specified wallet. - - The command compiles a table showing: - - - Coldkey: The coldkey associated with the wallet. - - Balance: The balance of the coldkey. - - Account: The name of the hotkey or delegate. - - Stake: The amount of TAO staked to the hotkey or delegate. - - Rate: The rate of return on the stake, typically shown in TAO per day. - - Example usage:: - - btcli stake show --all - - Note: - This command is essential for users who wish to monitor their stake distribution and returns across various accounts on the Bittensor network. - It provides a clear and detailed overview of the user's staking activities. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Show all stake accounts.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - StakeShow._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Show all stake accounts.""" - if cli.config.get("all", d=False) == True: - wallets = _get_coldkey_wallets_for_path(cli.config.wallet.path) - else: - wallets = [bittensor.wallet(config=cli.config)] - registered_delegate_info: Optional[Dict[str, DelegatesDetails]] = ( - get_delegates_details(url=bittensor.__delegates_details_url__) - ) - - def get_stake_accounts( - wallet, subtensor - ) -> Dict[str, Dict[str, Union[str, Balance]]]: - """Get stake account details for the given wallet. - - Args: - wallet: The wallet object to fetch the stake account details for. - - Returns: - A dictionary mapping SS58 addresses to their respective stake account details. - """ - - wallet_stake_accounts = {} - - # Get this wallet's coldkey balance. - cold_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - - # Populate the stake accounts with local hotkeys data. - wallet_stake_accounts.update(get_stakes_from_hotkeys(subtensor, wallet)) - - # Populate the stake accounts with delegations data. - wallet_stake_accounts.update(get_stakes_from_delegates(subtensor, wallet)) - - return { - "name": wallet.name, - "balance": cold_balance, - "accounts": wallet_stake_accounts, - } - - def get_stakes_from_hotkeys( - subtensor, wallet - ) -> Dict[str, Dict[str, Union[str, Balance]]]: - """Fetch stakes from hotkeys for the provided wallet. - - Args: - wallet: The wallet object to fetch the stakes for. - - Returns: - A dictionary of stakes related to hotkeys. - """ - hotkeys = get_hotkey_wallets_for_wallet(wallet) - stakes = {} - for hot in hotkeys: - emission = sum( - [ - n.emission - for n in subtensor.get_all_neurons_for_pubkey( - hot.hotkey.ss58_address - ) - ] - ) - hotkey_stake = subtensor.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=hot.hotkey.ss58_address, - coldkey_ss58=wallet.coldkeypub.ss58_address, - ) - stakes[hot.hotkey.ss58_address] = { - "name": hot.hotkey_str, - "stake": hotkey_stake, - "rate": emission, - } - return stakes - - def get_stakes_from_delegates( - subtensor, wallet - ) -> Dict[str, Dict[str, Union[str, Balance]]]: - """Fetch stakes from delegates for the provided wallet. - - Args: - wallet: The wallet object to fetch the stakes for. - - Returns: - A dictionary of stakes related to delegates. - """ - delegates = subtensor.get_delegated( - coldkey_ss58=wallet.coldkeypub.ss58_address - ) - stakes = {} - for dele, staked in delegates: - for nom in dele.nominators: - if nom[0] == wallet.coldkeypub.ss58_address: - delegate_name = ( - registered_delegate_info[dele.hotkey_ss58].name - if dele.hotkey_ss58 in registered_delegate_info - else dele.hotkey_ss58 - ) - stakes[dele.hotkey_ss58] = { - "name": delegate_name, - "stake": nom[1], - "rate": dele.total_daily_return.tao - * (nom[1] / dele.total_stake.tao), - } - return stakes - - def get_all_wallet_accounts( - wallets, - subtensor, - ) -> List[Dict[str, Dict[str, Union[str, Balance]]]]: - """Fetch stake accounts for all provided wallets using a ThreadPool. - - Args: - wallets: List of wallets to fetch the stake accounts for. - - Returns: - A list of dictionaries, each dictionary containing stake account details for each wallet. - """ - - accounts = [] - # Create a progress bar using tqdm - with tqdm(total=len(wallets), desc="Fetching accounts", ncols=100) as pbar: - for wallet in wallets: - accounts.append(get_stake_accounts(wallet, subtensor)) - pbar.update() - return accounts - - accounts = get_all_wallet_accounts(wallets, subtensor) - - total_stake = 0 - total_balance = 0 - total_rate = 0 - for acc in accounts: - total_balance += acc["balance"].tao - for key, value in acc["accounts"].items(): - total_stake += value["stake"].tao - total_rate += float(value["rate"]) - table = Table(show_footer=True, pad_edge=False, box=None, expand=False) - table.add_column( - "[overline white]Coldkey", footer_style="overline white", style="bold white" - ) - table.add_column( - "[overline white]Balance", - "\u03c4{:.5f}".format(total_balance), - footer_style="overline white", - style="green", - ) - table.add_column( - "[overline white]Account", footer_style="overline white", style="blue" - ) - table.add_column( - "[overline white]Stake", - "\u03c4{:.5f}".format(total_stake), - footer_style="overline white", - style="green", - ) - table.add_column( - "[overline white]Rate", - "\u03c4{:.5f}/d".format(total_rate), - footer_style="overline white", - style="green", - ) - for acc in accounts: - table.add_row(acc["name"], acc["balance"], "", "") - for key, value in acc["accounts"].items(): - table.add_row( - "", "", value["name"], value["stake"], str(value["rate"]) + "/d" - ) - bittensor.__console__.print(table) - - @staticmethod - def check_config(config: "bittensor.config"): - if ( - not config.get("all", d=None) - and not config.is_set("wallet.name") - and not config.no_prompt - ): - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - list_parser = parser.add_parser( - "show", help="""List all stake accounts for wallet.""" - ) - list_parser.add_argument( - "--all", - action="store_true", - help="""Check all coldkey wallets.""", - default=False, - ) - - bittensor.wallet.add_args(list_parser) - bittensor.subtensor.add_args(list_parser) diff --git a/bittensor/commands/transfer.py b/bittensor/commands/transfer.py deleted file mode 100644 index 24c6e78402..0000000000 --- a/bittensor/commands/transfer.py +++ /dev/null @@ -1,133 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 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 sys -import argparse -import bittensor -from rich.prompt import Prompt -from . import defaults - -console = bittensor.__console__ - - -class TransferCommand: - """ - Executes the ``transfer`` command to transfer TAO tokens from one account to another on the Bittensor network. - - This command is used for transactions between different accounts, enabling users to send tokens to other participants on the network. - - Usage: - The command requires specifying the destination address (public key) and the amount of TAO to be transferred. - It checks for sufficient balance and prompts for confirmation before proceeding with the transaction. - - Optional arguments: - - ``--dest`` (str): The destination address for the transfer. This can be in the form of an SS58 or ed2519 public key. - - ``--amount`` (float): The amount of TAO tokens to transfer. - - The command displays the user's current balance before prompting for the amount to transfer, ensuring transparency and accuracy in the transaction. - - Example usage:: - - btcli wallet transfer --dest 5Dp8... --amount 100 - - Note: - This command is crucial for executing token transfers within the Bittensor network. Users should verify the destination address and amount before confirming the transaction to avoid errors or loss of funds. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Transfer token of amount to destination.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - TransferCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Transfer token of amount to destination.""" - wallet = bittensor.wallet(config=cli.config) - subtensor.transfer( - wallet=wallet, - dest=cli.config.dest, - amount=cli.config.amount, - wait_for_inclusion=True, - prompt=not cli.config.no_prompt, - ) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - # Get destination. - if not config.dest and not config.no_prompt: - dest = Prompt.ask("Enter destination public key: (ss58 or ed2519)") - if not bittensor.utils.is_valid_bittensor_address_or_public_key(dest): - sys.exit() - else: - config.dest = str(dest) - - # Get current balance and print to user. - if not config.no_prompt: - wallet = bittensor.wallet(config=config) - subtensor = bittensor.subtensor(config=config, log_verbose=False) - with bittensor.__console__.status(":satellite: Checking Balance..."): - account_balance = subtensor.get_balance(wallet.coldkeypub.ss58_address) - bittensor.__console__.print( - "Balance: [green]{}[/green]".format(account_balance) - ) - - # Get amount. - if not config.get("amount"): - if not config.no_prompt: - amount = Prompt.ask("Enter TAO amount to transfer") - try: - config.amount = float(amount) - except ValueError: - console.print( - ":cross_mark:[red] Invalid TAO amount[/red] [bold white]{}[/bold white]".format( - amount - ) - ) - sys.exit() - else: - console.print( - ":cross_mark:[red] Invalid TAO amount[/red] [bold white]{}[/bold white]".format( - amount - ) - ) - sys.exit(1) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - transfer_parser = parser.add_parser( - "transfer", help="""Transfer Tao between accounts.""" - ) - transfer_parser.add_argument("--dest", dest="dest", type=str, required=False) - transfer_parser.add_argument( - "--amount", dest="amount", type=float, required=False - ) - - bittensor.wallet.add_args(transfer_parser) - bittensor.subtensor.add_args(transfer_parser) diff --git a/bittensor/commands/unstake.py b/bittensor/commands/unstake.py deleted file mode 100644 index 87d13aab91..0000000000 --- a/bittensor/commands/unstake.py +++ /dev/null @@ -1,296 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 sys -import bittensor -from tqdm import tqdm -from rich.prompt import Confirm, Prompt -from bittensor.utils.balance import Balance -from typing import List, Union, Optional, Tuple -from .utils import get_hotkey_wallets_for_wallet -from . import defaults - -console = bittensor.__console__ - - -class UnStakeCommand: - """ - Executes the ``remove`` command to unstake TAO tokens from one or more hotkeys and transfer them back to the user's coldkey on the Bittensor network. - - This command is used to withdraw tokens previously staked to different hotkeys. - - Usage: - Users can specify the amount to unstake, the hotkeys to unstake from (either by name or ``SS58`` address), and whether to unstake from all hotkeys. The command checks for sufficient stake and prompts for confirmation before proceeding with the unstaking process. - - Optional arguments: - - ``--all`` (bool): When set, unstakes all staked tokens from the specified hotkeys. - - ``--amount`` (float): The amount of TAO tokens to unstake. - - --hotkey_ss58address (str): The SS58 address of the hotkey to unstake from. - - ``--max_stake`` (float): Sets the maximum amount of TAO to remain staked in each hotkey. - - ``--hotkeys`` (list): Specifies hotkeys by name or SS58 address to unstake from. - - ``--all_hotkeys`` (bool): When set, unstakes from all hotkeys associated with the wallet, excluding any specified in --hotkeys. - - The command prompts for confirmation before executing the unstaking operation. - - Example usage:: - - btcli stake remove --amount 100 --hotkeys hk1,hk2 - - Note: - This command is important for users who wish to reallocate their stakes or withdraw them from the network. - It allows for flexible management of token stakes across different neurons (hotkeys) on the network. - """ - - @classmethod - def check_config(cls, config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if ( - not config.get("hotkey_ss58address", d=None) - and not config.is_set("wallet.hotkey") - and not config.no_prompt - and not config.get("all_hotkeys") - and not config.get("hotkeys") - ): - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - # Get amount. - if ( - not config.get("hotkey_ss58address") - and not config.get("amount") - and not config.get("unstake_all") - and not config.get("max_stake") - ): - hotkeys: str = "" - if config.get("all_hotkeys"): - hotkeys = "all hotkeys" - elif config.get("hotkeys"): - hotkeys = str(config.hotkeys).replace("[", "").replace("]", "") - else: - hotkeys = str(config.wallet.hotkey) - if config.no_prompt: - config.unstake_all = True - else: - # I really don't like this logic flow. It can be a bit confusing to read for something - # as serious as unstaking all. - if Confirm.ask(f"Unstake all Tao from: [bold]'{hotkeys}'[/bold]?"): - config.unstake_all = True - else: - config.unstake_all = False - amount = Prompt.ask("Enter Tao amount to unstake") - try: - config.amount = float(amount) - except ValueError: - console.print( - f":cross_mark:[red] Invalid Tao amount[/red] [bold white]{amount}[/bold white]" - ) - sys.exit() - - @staticmethod - def add_args(command_parser): - unstake_parser = command_parser.add_parser( - "remove", - help="""Remove stake from the specified hotkey into the coldkey balance.""", - ) - unstake_parser.add_argument( - "--all", dest="unstake_all", action="store_true", default=False - ) - unstake_parser.add_argument( - "--amount", dest="amount", type=float, required=False - ) - unstake_parser.add_argument( - "--hotkey_ss58address", dest="hotkey_ss58address", type=str, required=False - ) - unstake_parser.add_argument( - "--max_stake", - dest="max_stake", - type=float, - required=False, - action="store", - default=None, - help="""Specify the maximum amount of Tao to have staked in each hotkey.""", - ) - unstake_parser.add_argument( - "--hotkeys", - "--exclude_hotkeys", - "--wallet.hotkeys", - "--wallet.exclude_hotkeys", - required=False, - action="store", - default=[], - type=str, - nargs="*", - help="""Specify the hotkeys by name or ss58 address. (e.g. hk1 hk2 hk3)""", - ) - unstake_parser.add_argument( - "--all_hotkeys", - "--wallet.all_hotkeys", - required=False, - action="store_true", - default=False, - help="""To specify all hotkeys. Specifying hotkeys will exclude them from this all.""", - ) - bittensor.wallet.add_args(unstake_parser) - bittensor.subtensor.add_args(unstake_parser) - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Unstake token of amount from hotkey(s).""" - try: - config = cli.config.copy() - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=config, log_verbose=False - ) - UnStakeCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Unstake token of amount from hotkey(s).""" - config = cli.config.copy() - wallet = bittensor.wallet(config=config) - - # Get the hotkey_names (if any) and the hotkey_ss58s. - hotkeys_to_unstake_from: List[Tuple[Optional[str], str]] = [] - if cli.config.get("hotkey_ss58address"): - # Stake to specific hotkey. - hotkeys_to_unstake_from = [(None, cli.config.get("hotkey_ss58address"))] - elif cli.config.get("all_hotkeys"): - # Stake to all hotkeys. - all_hotkeys: List[bittensor.wallet] = get_hotkey_wallets_for_wallet( - wallet=wallet - ) - # Get the hotkeys to exclude. (d)efault to no exclusions. - hotkeys_to_exclude: List[str] = cli.config.get("hotkeys", d=[]) - # Exclude hotkeys that are specified. - hotkeys_to_unstake_from = [ - (wallet.hotkey_str, wallet.hotkey.ss58_address) - for wallet in all_hotkeys - if wallet.hotkey_str not in hotkeys_to_exclude - ] # definitely wallets - - elif cli.config.get("hotkeys"): - # Stake to specific hotkeys. - for hotkey_ss58_or_hotkey_name in cli.config.get("hotkeys"): - if bittensor.utils.is_valid_ss58_address(hotkey_ss58_or_hotkey_name): - # If the hotkey is a valid ss58 address, we add it to the list. - hotkeys_to_unstake_from.append((None, hotkey_ss58_or_hotkey_name)) - else: - # If the hotkey is not a valid ss58 address, we assume it is a hotkey name. - # We then get the hotkey from the wallet and add it to the list. - wallet_ = bittensor.wallet( - config=cli.config, hotkey=hotkey_ss58_or_hotkey_name - ) - hotkeys_to_unstake_from.append( - (wallet_.hotkey_str, wallet_.hotkey.ss58_address) - ) - elif cli.config.wallet.get("hotkey"): - # Only cli.config.wallet.hotkey is specified. - # so we stake to that single hotkey. - hotkey_ss58_or_name = cli.config.wallet.get("hotkey") - if bittensor.utils.is_valid_ss58_address(hotkey_ss58_or_name): - hotkeys_to_unstake_from = [(None, hotkey_ss58_or_name)] - else: - # Hotkey is not a valid ss58 address, so we assume it is a hotkey name. - wallet_ = bittensor.wallet( - config=cli.config, hotkey=hotkey_ss58_or_name - ) - hotkeys_to_unstake_from = [ - (wallet_.hotkey_str, wallet_.hotkey.ss58_address) - ] - else: - # Only cli.config.wallet.hotkey is specified. - # so we stake to that single hotkey. - assert cli.config.wallet.hotkey is not None - hotkeys_to_unstake_from = [ - (None, bittensor.wallet(config=cli.config).hotkey.ss58_address) - ] - - final_hotkeys: List[Tuple[str, str]] = [] - final_amounts: List[Union[float, Balance]] = [] - for hotkey in tqdm(hotkeys_to_unstake_from): - hotkey: Tuple[Optional[str], str] # (hotkey_name (or None), hotkey_ss58) - unstake_amount_tao: float = cli.config.get( - "amount" - ) # The amount specified to unstake. - hotkey_stake: Balance = subtensor.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=hotkey[1], coldkey_ss58=wallet.coldkeypub.ss58_address - ) - if unstake_amount_tao == None: - unstake_amount_tao = hotkey_stake.tao - if cli.config.get("max_stake"): - # Get the current stake of the hotkey from this coldkey. - unstake_amount_tao: float = hotkey_stake.tao - cli.config.get( - "max_stake" - ) - cli.config.amount = unstake_amount_tao - if unstake_amount_tao < 0: - # Skip if max_stake is greater than current stake. - continue - else: - if unstake_amount_tao is not None: - # There is a specified amount to unstake. - if unstake_amount_tao > hotkey_stake.tao: - # Skip if the specified amount is greater than the current stake. - continue - - final_amounts.append(unstake_amount_tao) - final_hotkeys.append(hotkey) # add both the name and the ss58 address. - - if len(final_hotkeys) == 0: - # No hotkeys to unstake from. - bittensor.__console__.print( - "Not enough stake to unstake from any hotkeys or max_stake is more than current stake." - ) - return None - - # Ask to unstake - if not cli.config.no_prompt: - if not Confirm.ask( - f"Do you want to unstake from the following keys to {wallet.name}:\n" - + "".join( - [ - f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: {f'{amount} {bittensor.__tao_symbol__}' if amount else 'All'}[/bold white]\n" - for hotkey, amount in zip(final_hotkeys, final_amounts) - ] - ) - ): - return None - - if len(final_hotkeys) == 1: - # do regular unstake - return subtensor.unstake( - wallet=wallet, - hotkey_ss58=final_hotkeys[0][1], - amount=None if cli.config.get("unstake_all") else final_amounts[0], - wait_for_inclusion=True, - prompt=not cli.config.no_prompt, - ) - - subtensor.unstake_multiple( - wallet=wallet, - hotkey_ss58s=[hotkey_ss58 for _, hotkey_ss58 in final_hotkeys], - amounts=None if cli.config.get("unstake_all") else final_amounts, - wait_for_inclusion=True, - prompt=False, - ) diff --git a/bittensor/commands/utils.py b/bittensor/commands/utils.py deleted file mode 100644 index 661cd818cc..0000000000 --- a/bittensor/commands/utils.py +++ /dev/null @@ -1,283 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 sys -import os -import bittensor -import requests -from bittensor.utils.registration import torch -from bittensor.utils.balance import Balance -from bittensor.utils import U64_NORMALIZED_FLOAT, U16_NORMALIZED_FLOAT -from typing import List, Dict, Any, Optional, Tuple -from rich.prompt import Confirm, PromptBase -from dataclasses import dataclass -from . import defaults - -console = bittensor.__console__ - - -class IntListPrompt(PromptBase): - """Prompt for a list of integers.""" - - def check_choice(self, value: str) -> bool: - assert self.choices is not None - # check if value is a valid choice or all the values in a list of ints are valid choices - return ( - value == "All" - or value in self.choices - or all( - val.strip() in self.choices for val in value.replace(",", " ").split() - ) - ) - - -def check_netuid_set( - config: "bittensor.config", - subtensor: "bittensor.subtensor", - allow_none: bool = False, -): - if subtensor.network != "nakamoto": - all_netuids = [str(netuid) for netuid in subtensor.get_subnets()] - if len(all_netuids) == 0: - console.print(":cross_mark:[red]There are no open networks.[/red]") - sys.exit() - - # Make sure netuid is set. - if not config.is_set("netuid"): - if not config.no_prompt: - netuid = IntListPrompt.ask( - "Enter netuid", choices=all_netuids, default=str(all_netuids[0]) - ) - else: - netuid = str(defaults.netuid) if not allow_none else "None" - else: - netuid = config.netuid - - if isinstance(netuid, str) and netuid.lower() in ["none"] and allow_none: - config.netuid = None - else: - if isinstance(netuid, list): - netuid = netuid[0] - try: - config.netuid = int(netuid) - except: - raise ValueError('netuid must be an integer or "None" (if applicable)') - - -def check_for_cuda_reg_config(config: "bittensor.config") -> None: - """Checks, when CUDA is available, if the user would like to register with their CUDA device.""" - if torch and torch.cuda.is_available(): - if not config.no_prompt: - if config.pow_register.cuda.get("use_cuda") is None: # flag not set - # Ask about cuda registration only if a CUDA device is available. - cuda = Confirm.ask("Detected CUDA device, use CUDA for registration?\n") - config.pow_register.cuda.use_cuda = cuda - - # Only ask about which CUDA device if the user has more than one CUDA device. - if ( - config.pow_register.cuda.use_cuda - and config.pow_register.cuda.get("dev_id") is None - ): - devices: List[str] = [str(x) for x in range(torch.cuda.device_count())] - device_names: List[str] = [ - torch.cuda.get_device_name(x) - for x in range(torch.cuda.device_count()) - ] - console.print("Available CUDA devices:") - choices_str: str = "" - for i, device in enumerate(devices): - choices_str += " {}: {}\n".format(device, device_names[i]) - console.print(choices_str) - dev_id = IntListPrompt.ask( - "Which GPU(s) would you like to use? Please list one, or comma-separated", - choices=devices, - default="All", - ) - if dev_id.lower() == "all": - dev_id = list(range(torch.cuda.device_count())) - else: - try: - # replace the commas with spaces then split over whitespace., - # then strip the whitespace and convert to ints. - dev_id = [ - int(dev_id.strip()) - for dev_id in dev_id.replace(",", " ").split() - ] - except ValueError: - console.log( - ":cross_mark:[red]Invalid GPU device[/red] [bold white]{}[/bold white]\nAvailable CUDA devices:{}".format( - dev_id, choices_str - ) - ) - sys.exit(1) - config.pow_register.cuda.dev_id = dev_id - else: - # flag was not set, use default value. - if config.pow_register.cuda.get("use_cuda") is None: - config.pow_register.cuda.use_cuda = defaults.pow_register.cuda.use_cuda - - -def get_hotkey_wallets_for_wallet(wallet) -> List["bittensor.wallet"]: - hotkey_wallets = [] - hotkeys_path = wallet.path + "/" + wallet.name + "/hotkeys" - try: - hotkey_files = next(os.walk(os.path.expanduser(hotkeys_path)))[2] - except StopIteration: - hotkey_files = [] - for hotkey_file_name in hotkey_files: - try: - hotkey_for_name = bittensor.wallet( - path=wallet.path, name=wallet.name, hotkey=hotkey_file_name - ) - if ( - hotkey_for_name.hotkey_file.exists_on_device() - and not hotkey_for_name.hotkey_file.is_encrypted() - ): - hotkey_wallets.append(hotkey_for_name) - except Exception: - pass - return hotkey_wallets - - -def get_coldkey_wallets_for_path(path: str) -> List["bittensor.wallet"]: - try: - wallet_names = next(os.walk(os.path.expanduser(path)))[1] - return [bittensor.wallet(path=path, name=name) for name in wallet_names] - except StopIteration: - # No wallet files found. - wallets = [] - return wallets - - -def get_all_wallets_for_path(path: str) -> List["bittensor.wallet"]: - all_wallets = [] - cold_wallets = get_coldkey_wallets_for_path(path) - for cold_wallet in cold_wallets: - if ( - cold_wallet.coldkeypub_file.exists_on_device() - and not cold_wallet.coldkeypub_file.is_encrypted() - ): - all_wallets.extend(get_hotkey_wallets_for_wallet(cold_wallet)) - return all_wallets - - -def filter_netuids_by_registered_hotkeys( - cli, subtensor, netuids, all_hotkeys -) -> List[int]: - netuids_with_registered_hotkeys = [] - for wallet in all_hotkeys: - netuids_list = subtensor.get_netuids_for_hotkey(wallet.hotkey.ss58_address) - bittensor.logging.debug( - f"Hotkey {wallet.hotkey.ss58_address} registered in netuids: {netuids_list}" - ) - netuids_with_registered_hotkeys.extend(netuids_list) - - if not cli.config.netuids: - netuids = netuids_with_registered_hotkeys - - else: - netuids = [netuid for netuid in netuids if netuid in cli.config.netuids] - netuids.extend(netuids_with_registered_hotkeys) - - return list(set(netuids)) - - -def normalize_hyperparameters( - subnet: bittensor.SubnetHyperparameters, -) -> List[Tuple[str, str, str]]: - """ - Normalizes the hyperparameters of a subnet. - - Args: - subnet: The subnet hyperparameters object. - - Returns: - A list of tuples containing the parameter name, value, and normalized value. - """ - param_mappings = { - "adjustment_alpha": U64_NORMALIZED_FLOAT, - "min_difficulty": U64_NORMALIZED_FLOAT, - "max_difficulty": U64_NORMALIZED_FLOAT, - "difficulty": U64_NORMALIZED_FLOAT, - "bonds_moving_avg": U64_NORMALIZED_FLOAT, - "max_weight_limit": U16_NORMALIZED_FLOAT, - "kappa": U16_NORMALIZED_FLOAT, - "alpha_high": U16_NORMALIZED_FLOAT, - "alpha_low": U16_NORMALIZED_FLOAT, - "min_burn": Balance.from_rao, - "max_burn": Balance.from_rao, - } - - normalized_values: List[Tuple[str, str, str]] = [] - subnet_dict = subnet.__dict__ - - for param, value in subnet_dict.items(): - try: - if param in param_mappings: - norm_value = param_mappings[param](value) - if isinstance(norm_value, float): - norm_value = f"{norm_value:.{10}g}" - else: - norm_value = value - except Exception as e: - bittensor.logging.warning(f"Error normalizing parameter '{param}': {e}") - norm_value = "-" - - normalized_values.append((param, str(value), str(norm_value))) - - return normalized_values - - -@dataclass -class DelegatesDetails: - name: str - url: str - description: str - signature: str - - @classmethod - def from_json(cls, json: Dict[str, any]) -> "DelegatesDetails": - return cls( - name=json["name"], - url=json["url"], - description=json["description"], - signature=json["signature"], - ) - - -def _get_delegates_details_from_github( - requests_get, url: str -) -> Dict[str, DelegatesDetails]: - response = requests_get(url) - - if response.status_code == 200: - all_delegates: Dict[str, Any] = response.json() - all_delegates_details = {} - for delegate_hotkey, delegates_details in all_delegates.items(): - all_delegates_details[delegate_hotkey] = DelegatesDetails.from_json( - delegates_details - ) - return all_delegates_details - else: - return {} - - -def get_delegates_details(url: str) -> Optional[Dict[str, DelegatesDetails]]: - try: - return _get_delegates_details_from_github(requests.get, url) - except Exception: - return None # Fail silently diff --git a/bittensor/commands/wallets.py b/bittensor/commands/wallets.py deleted file mode 100644 index 15819ece7b..0000000000 --- a/bittensor/commands/wallets.py +++ /dev/null @@ -1,1101 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao - -# 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 argparse -import os -import sys -from typing import List, Optional, Tuple - -import requests -from rich.prompt import Confirm, Prompt -from rich.table import Table - -import bittensor - -from ..utils import RAOPERTAO -from . import defaults - - -class RegenColdkeyCommand: - """ - Executes the ``regen_coldkey`` command to regenerate a coldkey for a wallet on the Bittensor network. - - This command is used to create a new coldkey from an existing mnemonic, seed, or JSON file. - - Usage: - Users can specify a mnemonic, a seed string, or a JSON file path to regenerate a coldkey. - The command supports optional password protection for the generated key and can overwrite an existing coldkey. - - Optional arguments: - - ``--mnemonic`` (str): A mnemonic phrase used to regenerate the key. - - ``--seed`` (str): A seed hex string used for key regeneration. - - ``--json`` (str): Path to a JSON file containing an encrypted key backup. - - ``--json_password`` (str): Password to decrypt the JSON file. - - ``--use_password`` (bool): Enables password protection for the generated key. - - ``--overwrite_coldkey`` (bool): Overwrites the existing coldkey with the new one. - - Example usage:: - - btcli wallet regen_coldkey --mnemonic "word1 word2 ... word12" - - Note: - This command is critical for users who need to regenerate their coldkey, possibly for recovery or security reasons. - It should be used with caution to avoid overwriting existing keys unintentionally. - """ - - def run(cli): - r"""Creates a new coldkey under this wallet.""" - wallet = bittensor.wallet(config=cli.config) - - json_str: Optional[str] = None - json_password: Optional[str] = None - if cli.config.get("json"): - file_name: str = cli.config.get("json") - if not os.path.exists(file_name) or not os.path.isfile(file_name): - raise ValueError("File {} does not exist".format(file_name)) - with open(cli.config.get("json"), "r") as f: - json_str = f.read() - # Password can be "", assume if None - json_password = cli.config.get("json_password", "") - wallet.regenerate_coldkey( - mnemonic=cli.config.mnemonic, - seed=cli.config.seed, - json=(json_str, json_password), - use_password=cli.config.use_password, - overwrite=cli.config.overwrite_coldkey, - ) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - if ( - config.mnemonic == None - and config.get("seed", d=None) == None - and config.get("json", d=None) == None - ): - prompt_answer = Prompt.ask("Enter mnemonic, seed, or json file location") - if prompt_answer.startswith("0x"): - config.seed = prompt_answer - elif len(prompt_answer.split(" ")) > 1: - config.mnemonic = prompt_answer - else: - config.json = prompt_answer - - if config.get("json", d=None) and config.get("json_password", d=None) == None: - config.json_password = Prompt.ask( - "Enter json backup password", password=True - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - regen_coldkey_parser = parser.add_parser( - "regen_coldkey", help="""Regenerates a coldkey from a passed value""" - ) - regen_coldkey_parser.add_argument( - "--mnemonic", - required=False, - nargs="+", - help="Mnemonic used to regen your key i.e. horse cart dog ...", - ) - regen_coldkey_parser.add_argument( - "--seed", - required=False, - default=None, - help="Seed hex string used to regen your key i.e. 0x1234...", - ) - regen_coldkey_parser.add_argument( - "--json", - required=False, - default=None, - help="""Path to a json file containing the encrypted key backup. (e.g. from PolkadotJS)""", - ) - regen_coldkey_parser.add_argument( - "--json_password", - required=False, - default=None, - help="""Password to decrypt the json file.""", - ) - regen_coldkey_parser.add_argument( - "--use_password", - dest="use_password", - action="store_true", - help="""Set true to protect the generated bittensor key with a password.""", - default=True, - ) - regen_coldkey_parser.add_argument( - "--no_password", - dest="use_password", - action="store_false", - help="""Set off protects the generated bittensor key with a password.""", - ) - regen_coldkey_parser.add_argument( - "--overwrite_coldkey", - default=False, - action="store_true", - help="""Overwrite the old coldkey with the newly generated coldkey""", - ) - bittensor.wallet.add_args(regen_coldkey_parser) - bittensor.subtensor.add_args(regen_coldkey_parser) - - -class RegenColdkeypubCommand: - """ - Executes the ``regen_coldkeypub`` command to regenerate the public part of a coldkey (coldkeypub) for a wallet on the Bittensor network. - - This command is used when a user needs to recreate their coldkeypub from an existing public key or SS58 address. - - Usage: - The command requires either a public key in hexadecimal format or an ``SS58`` address to regenerate the coldkeypub. It optionally allows overwriting an existing coldkeypub file. - - Optional arguments: - - ``--public_key_hex`` (str): The public key in hex format. - - ``--ss58_address`` (str): The SS58 address of the coldkey. - - ``--overwrite_coldkeypub`` (bool): Overwrites the existing coldkeypub file with the new one. - - Example usage:: - - btcli wallet regen_coldkeypub --ss58_address 5DkQ4... - - Note: - This command is particularly useful for users who need to regenerate their coldkeypub, perhaps due to file corruption or loss. - It is a recovery-focused utility that ensures continued access to wallet functionalities. - """ - - def run(cli): - r"""Creates a new coldkeypub under this wallet.""" - wallet = bittensor.wallet(config=cli.config) - wallet.regenerate_coldkeypub( - ss58_address=cli.config.get("ss58_address"), - public_key=cli.config.get("public_key_hex"), - overwrite=cli.config.overwrite_coldkeypub, - ) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - if config.ss58_address == None and config.public_key_hex == None: - prompt_answer = Prompt.ask( - "Enter the ss58_address or the public key in hex" - ) - if prompt_answer.startswith("0x"): - config.public_key_hex = prompt_answer - else: - config.ss58_address = prompt_answer - if not bittensor.utils.is_valid_bittensor_address_or_public_key( - address=( - config.ss58_address if config.ss58_address else config.public_key_hex - ) - ): - sys.exit(1) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - regen_coldkeypub_parser = parser.add_parser( - "regen_coldkeypub", - help="""Regenerates a coldkeypub from the public part of the coldkey.""", - ) - regen_coldkeypub_parser.add_argument( - "--public_key", - "--pubkey", - dest="public_key_hex", - required=False, - default=None, - type=str, - help="The public key (in hex) of the coldkey to regen e.g. 0x1234 ...", - ) - regen_coldkeypub_parser.add_argument( - "--ss58_address", - "--addr", - "--ss58", - dest="ss58_address", - required=False, - default=None, - type=str, - help="The ss58 address of the coldkey to regen e.g. 5ABCD ...", - ) - regen_coldkeypub_parser.add_argument( - "--overwrite_coldkeypub", - default=False, - action="store_true", - help="""Overwrite the old coldkeypub file with the newly generated coldkeypub""", - ) - bittensor.wallet.add_args(regen_coldkeypub_parser) - bittensor.subtensor.add_args(regen_coldkeypub_parser) - - -class RegenHotkeyCommand: - """ - Executes the ``regen_hotkey`` command to regenerate a hotkey for a wallet on the Bittensor network. - - Similar to regenerating a coldkey, this command creates a new hotkey from a mnemonic, seed, or JSON file. - - Usage: - Users can provide a mnemonic, seed string, or a JSON file to regenerate the hotkey. - The command supports optional password protection and can overwrite an existing hotkey. - - Optional arguments: - - ``--mnemonic`` (str): A mnemonic phrase used to regenerate the key. - - ``--seed`` (str): A seed hex string used for key regeneration. - - ``--json`` (str): Path to a JSON file containing an encrypted key backup. - - ``--json_password`` (str): Password to decrypt the JSON file. - - ``--use_password`` (bool): Enables password protection for the generated key. - - ``--overwrite_hotkey`` (bool): Overwrites the existing hotkey with the new one. - - Example usage:: - - btcli wallet regen_hotkey - btcli wallet regen_hotkey --seed 0x1234... - - Note: - This command is essential for users who need to regenerate their hotkey, possibly for security upgrades or key recovery. - It should be used cautiously to avoid accidental overwrites of existing keys. - """ - - def run(cli): - r"""Creates a new coldkey under this wallet.""" - wallet = bittensor.wallet(config=cli.config) - - json_str: Optional[str] = None - json_password: Optional[str] = None - if cli.config.get("json"): - file_name: str = cli.config.get("json") - if not os.path.exists(file_name) or not os.path.isfile(file_name): - raise ValueError("File {} does not exist".format(file_name)) - with open(cli.config.get("json"), "r") as f: - json_str = f.read() - - # Password can be "", assume if None - json_password = cli.config.get("json_password", "") - - wallet.regenerate_hotkey( - mnemonic=cli.config.mnemonic, - seed=cli.config.seed, - json=(json_str, json_password), - use_password=cli.config.use_password, - overwrite=cli.config.overwrite_hotkey, - ) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - if ( - config.mnemonic == None - and config.get("seed", d=None) == None - and config.get("json", d=None) == None - ): - prompt_answer = Prompt.ask("Enter mnemonic, seed, or json file location") - if prompt_answer.startswith("0x"): - config.seed = prompt_answer - elif len(prompt_answer.split(" ")) > 1: - config.mnemonic = prompt_answer - else: - config.json = prompt_answer - - if config.get("json", d=None) and config.get("json_password", d=None) == None: - config.json_password = Prompt.ask( - "Enter json backup password", password=True - ) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - regen_hotkey_parser = parser.add_parser( - "regen_hotkey", help="""Regenerates a hotkey from a passed mnemonic""" - ) - regen_hotkey_parser.add_argument( - "--mnemonic", - required=False, - nargs="+", - help="Mnemonic used to regen your key i.e. horse cart dog ...", - ) - regen_hotkey_parser.add_argument( - "--seed", - required=False, - default=None, - help="Seed hex string used to regen your key i.e. 0x1234...", - ) - regen_hotkey_parser.add_argument( - "--json", - required=False, - default=None, - help="""Path to a json file containing the encrypted key backup. (e.g. from PolkadotJS)""", - ) - regen_hotkey_parser.add_argument( - "--json_password", - required=False, - default=None, - help="""Password to decrypt the json file.""", - ) - regen_hotkey_parser.add_argument( - "--use_password", - dest="use_password", - action="store_true", - help="""Set true to protect the generated bittensor key with a password.""", - default=False, - ) - regen_hotkey_parser.add_argument( - "--no_password", - dest="use_password", - action="store_false", - help="""Set off protects the generated bittensor key with a password.""", - ) - regen_hotkey_parser.add_argument( - "--overwrite_hotkey", - dest="overwrite_hotkey", - action="store_true", - default=False, - help="""Overwrite the old hotkey with the newly generated hotkey""", - ) - bittensor.wallet.add_args(regen_hotkey_parser) - bittensor.subtensor.add_args(regen_hotkey_parser) - - -class NewHotkeyCommand: - """ - Executes the ``new_hotkey`` command to create a new hotkey under a wallet on the Bittensor network. - - This command is used to generate a new hotkey for managing a neuron or participating in the network. - - Usage: - The command creates a new hotkey with an optional word count for the mnemonic and supports password protection. - It also allows overwriting an existing hotkey. - - Optional arguments: - - ``--n_words`` (int): The number of words in the mnemonic phrase. - - ``--use_password`` (bool): Enables password protection for the generated key. - - ``--overwrite_hotkey`` (bool): Overwrites the existing hotkey with the new one. - - Example usage:: - - btcli wallet new_hotkey --n_words 24 - - Note: - This command is useful for users who wish to create additional hotkeys for different purposes, - such as running multiple miners or separating operational roles within the network. - """ - - def run(cli): - """Creates a new hotke under this wallet.""" - wallet = bittensor.wallet(config=cli.config) - wallet.create_new_hotkey( - n_words=cli.config.n_words, - use_password=cli.config.use_password, - overwrite=cli.config.overwrite_hotkey, - ) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - new_hotkey_parser = parser.add_parser( - "new_hotkey", - help="""Creates a new hotkey (for running a miner) under the specified path.""", - ) - new_hotkey_parser.add_argument( - "--n_words", - type=int, - choices=[12, 15, 18, 21, 24], - default=12, - help="""The number of words representing the mnemonic. i.e. horse cart dog ... x 24""", - ) - new_hotkey_parser.add_argument( - "--use_password", - dest="use_password", - action="store_true", - help="""Set true to protect the generated bittensor key with a password.""", - default=False, - ) - new_hotkey_parser.add_argument( - "--no_password", - dest="use_password", - action="store_false", - help="""Set off protects the generated bittensor key with a password.""", - ) - new_hotkey_parser.add_argument( - "--overwrite_hotkey", - action="store_true", - default=False, - help="""Overwrite the old hotkey with the newly generated hotkey""", - ) - bittensor.wallet.add_args(new_hotkey_parser) - bittensor.subtensor.add_args(new_hotkey_parser) - - -class NewColdkeyCommand: - """ - Executes the ``new_coldkey`` command to create a new coldkey under a wallet on the Bittensor network. - - This command generates a coldkey, which is essential for holding balances and performing high-value transactions. - - Usage: - The command creates a new coldkey with an optional word count for the mnemonic and supports password protection. - It also allows overwriting an existing coldkey. - - Optional arguments: - - ``--n_words`` (int): The number of words in the mnemonic phrase. - - ``--use_password`` (bool): Enables password protection for the generated key. - - ``--overwrite_coldkey`` (bool): Overwrites the existing coldkey with the new one. - - Example usage:: - - btcli wallet new_coldkey --n_words 15 - - Note: - This command is crucial for users who need to create a new coldkey for enhanced security or as part of setting up a new wallet. - It's a foundational step in establishing a secure presence on the Bittensor network. - """ - - def run(cli): - r"""Creates a new coldkey under this wallet.""" - wallet = bittensor.wallet(config=cli.config) - wallet.create_new_coldkey( - n_words=cli.config.n_words, - use_password=cli.config.use_password, - overwrite=cli.config.overwrite_coldkey, - ) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - new_coldkey_parser = parser.add_parser( - "new_coldkey", - help="""Creates a new coldkey (for containing balance) under the specified path. """, - ) - new_coldkey_parser.add_argument( - "--n_words", - type=int, - choices=[12, 15, 18, 21, 24], - default=12, - help="""The number of words representing the mnemonic. i.e. horse cart dog ... x 24""", - ) - new_coldkey_parser.add_argument( - "--use_password", - dest="use_password", - action="store_true", - help="""Set true to protect the generated bittensor key with a password.""", - default=True, - ) - new_coldkey_parser.add_argument( - "--no_password", - dest="use_password", - action="store_false", - help="""Set off protects the generated bittensor key with a password.""", - ) - new_coldkey_parser.add_argument( - "--overwrite_coldkey", - action="store_true", - default=False, - help="""Overwrite the old coldkey with the newly generated coldkey""", - ) - bittensor.wallet.add_args(new_coldkey_parser) - bittensor.subtensor.add_args(new_coldkey_parser) - - -class WalletCreateCommand: - """ - Executes the ``create`` command to generate both a new coldkey and hotkey under a specified wallet on the Bittensor network. - - This command is a comprehensive utility for creating a complete wallet setup with both cold and hotkeys. - - Usage: - The command facilitates the creation of a new coldkey and hotkey with an optional word count for the mnemonics. - It supports password protection for the coldkey and allows overwriting of existing keys. - - Optional arguments: - - ``--n_words`` (int): The number of words in the mnemonic phrase for both keys. - - ``--use_password`` (bool): Enables password protection for the coldkey. - - ``--overwrite_coldkey`` (bool): Overwrites the existing coldkey with the new one. - - ``--overwrite_hotkey`` (bool): Overwrites the existing hotkey with the new one. - - Example usage:: - - btcli wallet create --n_words 21 - - Note: - This command is ideal for new users setting up their wallet for the first time or for those who wish to completely renew their wallet keys. - It ensures a fresh start with new keys for secure and effective participation in the network. - """ - - def run(cli): - r"""Creates a new coldkey and hotkey under this wallet.""" - wallet = bittensor.wallet(config=cli.config) - wallet.create_new_coldkey( - n_words=cli.config.n_words, - use_password=cli.config.use_password, - overwrite=cli.config.overwrite_coldkey, - ) - wallet.create_new_hotkey( - n_words=cli.config.n_words, - use_password=False, - overwrite=cli.config.overwrite_hotkey, - ) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - new_coldkey_parser = parser.add_parser( - "create", - help="""Creates a new coldkey (for containing balance) under the specified path. """, - ) - new_coldkey_parser.add_argument( - "--n_words", - type=int, - choices=[12, 15, 18, 21, 24], - default=12, - help="""The number of words representing the mnemonic. i.e. horse cart dog ... x 24""", - ) - new_coldkey_parser.add_argument( - "--use_password", - dest="use_password", - action="store_true", - help="""Set true to protect the generated bittensor key with a password.""", - default=True, - ) - new_coldkey_parser.add_argument( - "--no_password", - dest="use_password", - action="store_false", - help="""Set off protects the generated bittensor key with a password.""", - ) - new_coldkey_parser.add_argument( - "--overwrite_coldkey", - action="store_true", - default=False, - help="""Overwrite the old coldkey with the newly generated coldkey""", - ) - new_coldkey_parser.add_argument( - "--overwrite_hotkey", - action="store_true", - default=False, - help="""Overwrite the old hotkey with the newly generated hotkey""", - ) - bittensor.wallet.add_args(new_coldkey_parser) - bittensor.subtensor.add_args(new_coldkey_parser) - - -def _get_coldkey_wallets_for_path(path: str) -> List["bittensor.wallet"]: - """Get all coldkey wallet names from path.""" - try: - wallet_names = next(os.walk(os.path.expanduser(path)))[1] - return [bittensor.wallet(path=path, name=name) for name in wallet_names] - except StopIteration: - # No wallet files found. - wallets = [] - return wallets - - -class UpdateWalletCommand: - """ - Executes the ``update`` command to check and potentially update the security of the wallets in the Bittensor network. - - This command is used to enhance wallet security using modern encryption standards. - - Usage: - The command checks if any of the wallets need an update in their security protocols. - It supports updating all legacy wallets or a specific one based on the user's choice. - - Optional arguments: - - ``--all`` (bool): When set, updates all legacy wallets. - - Example usage:: - - btcli wallet update --all - - Note: - This command is important for maintaining the highest security standards for users' wallets. - It is recommended to run this command periodically to ensure wallets are up-to-date with the latest security practices. - """ - - @staticmethod - def run(cli): - """Check if any of the wallets needs an update.""" - config = cli.config.copy() - if config.get("all", d=False) == True: - wallets = _get_coldkey_wallets_for_path(config.wallet.path) - else: - wallets = [bittensor.wallet(config=config)] - - for wallet in wallets: - print("\n===== ", wallet, " =====") - wallet.coldkey_file.check_and_update_encryption() - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - update_wallet_parser = parser.add_parser( - "update", - help="""Updates the wallet security using NaCL instead of ansible vault.""", - ) - update_wallet_parser.add_argument("--all", action="store_true") - bittensor.wallet.add_args(update_wallet_parser) - bittensor.subtensor.add_args(update_wallet_parser) - - @staticmethod - def check_config(config: "bittensor.Config"): - if config.get("all", d=False) == False: - if not config.no_prompt: - if Confirm.ask("Do you want to update all legacy wallets?"): - config["all"] = True - - # Ask the user to specify the wallet if the wallet name is not clear. - if ( - config.get("all", d=False) == False - and config.wallet.get("name") == bittensor.defaults.wallet.name - and not config.no_prompt - ): - wallet_name = Prompt.ask( - "Enter wallet name", default=bittensor.defaults.wallet.name - ) - config.wallet.name = str(wallet_name) - - -def _get_coldkey_ss58_addresses_for_path(path: str) -> Tuple[List[str], List[str]]: - """Get all coldkey ss58 addresses from path.""" - - def list_coldkeypub_files(dir_path): - abspath = os.path.abspath(os.path.expanduser(dir_path)) - coldkey_files = [] - wallet_names = [] - - for potential_wallet_name in os.listdir(abspath): - coldkey_path = os.path.join( - abspath, potential_wallet_name, "coldkeypub.txt" - ) - if os.path.isdir( - os.path.join(abspath, potential_wallet_name) - ) and os.path.exists(coldkey_path): - coldkey_files.append(coldkey_path) - wallet_names.append(potential_wallet_name) - else: - bittensor.logging.warning( - f"{coldkey_path} does not exist. Excluding..." - ) - return coldkey_files, wallet_names - - coldkey_files, wallet_names = list_coldkeypub_files(path) - addresses = [ - bittensor.keyfile(coldkey_path).keypair.ss58_address - for coldkey_path in coldkey_files - ] - return addresses, wallet_names - - -class WalletBalanceCommand: - """ - Executes the ``balance`` command to check the balance of the wallet on the Bittensor network. - - This command provides a detailed view of the wallet's coldkey balances, including free and staked balances. - - Usage: - The command lists the balances of all wallets in the user's configuration directory, showing the wallet name, coldkey address, and the respective free and staked balances. - - Optional arguments: - None. The command uses the wallet and subtensor configurations to fetch balance data. - - Example usages: - - - To display the balance of a single wallet, use the command with the `--wallet.name` argument to specify the wallet name: - - ``` - btcli w balance --wallet.name WALLET - ``` - - - Alternatively, you can invoke the command without specifying a wallet name, which will prompt you to enter the wallets path: - - ``` - btcli w balance - ``` - - - To display the balances of all wallets, use the `--all` argument: - - ``` - btcli w balance --all - ``` - - Note: - When using `btcli`, `w` is used interchangeably with `wallet`. You may use either based on your preference for brevity or clarity. - This command is essential for users to monitor their financial status on the Bittensor network. - It helps in keeping track of assets and ensuring the wallet's financial health. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - """Check the balance of the wallet.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - WalletBalanceCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - wallet = bittensor.wallet(config=cli.config) - - wallet_names = [] - coldkeys = [] - free_balances = [] - staked_balances = [] - total_free_balance = 0 - total_staked_balance = 0 - balances = {} - - if cli.config.get("all", d=None): - coldkeys, wallet_names = _get_coldkey_ss58_addresses_for_path( - cli.config.wallet.path - ) - - free_balances = [ - subtensor.get_balance(coldkeys[i]) for i in range(len(coldkeys)) - ] - - staked_balances = [ - subtensor.get_total_stake_for_coldkey(coldkeys[i]) - for i in range(len(coldkeys)) - ] - - total_free_balance = sum(free_balances) - total_staked_balance = sum(staked_balances) - - balances = { - name: (coldkey, free, staked) - for name, coldkey, free, staked in sorted( - zip(wallet_names, coldkeys, free_balances, staked_balances) - ) - } - else: - coldkey_wallet = bittensor.wallet(config=cli.config) - if ( - coldkey_wallet.coldkeypub_file.exists_on_device() - and not coldkey_wallet.coldkeypub_file.is_encrypted() - ): - coldkeys = [coldkey_wallet.coldkeypub.ss58_address] - wallet_names = [coldkey_wallet.name] - - free_balances = [ - subtensor.get_balance(coldkeys[i]) for i in range(len(coldkeys)) - ] - - staked_balances = [ - subtensor.get_total_stake_for_coldkey(coldkeys[i]) - for i in range(len(coldkeys)) - ] - - total_free_balance = sum(free_balances) - total_staked_balance = sum(staked_balances) - - balances = { - name: (coldkey, free, staked) - for name, coldkey, free, staked in sorted( - zip(wallet_names, coldkeys, free_balances, staked_balances) - ) - } - - if not coldkey_wallet.coldkeypub_file.exists_on_device(): - bittensor.__console__.print("[bold red]No wallets found.") - return - - table = Table(show_footer=False) - table.title = "[white]Wallet Coldkey Balances" - table.add_column( - "[white]Wallet Name", - header_style="overline white", - footer_style="overline white", - style="rgb(50,163,219)", - no_wrap=True, - ) - - table.add_column( - "[white]Coldkey Address", - header_style="overline white", - footer_style="overline white", - style="rgb(50,163,219)", - no_wrap=True, - ) - - for typestr in ["Free", "Staked", "Total"]: - table.add_column( - f"[white]{typestr} Balance", - header_style="overline white", - footer_style="overline white", - justify="right", - style="green", - no_wrap=True, - ) - - for name, (coldkey, free, staked) in balances.items(): - table.add_row( - name, - coldkey, - str(free), - str(staked), - str(free + staked), - ) - table.add_row() - table.add_row( - "Total Balance Across All Coldkeys", - "", - str(total_free_balance), - str(total_staked_balance), - str(total_free_balance + total_staked_balance), - ) - table.show_footer = True - - table.box = None - table.pad_edge = False - table.width = None - bittensor.__console__.print(table) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - balance_parser = parser.add_parser( - "balance", help="""Checks the balance of the wallet.""" - ) - balance_parser.add_argument( - "--all", - dest="all", - action="store_true", - help="""View balance for all wallets.""", - default=False, - ) - - bittensor.wallet.add_args(balance_parser) - bittensor.subtensor.add_args(balance_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if ( - not config.is_set("wallet.path") - and not config.no_prompt - and not config.get("all", d=None) - ): - path = Prompt.ask("Enter wallets path", default=defaults.wallet.path) - config.wallet.path = str(path) - - if ( - not config.is_set("wallet.name") - and not config.no_prompt - and not config.get("all", d=None) - ): - wallet_name = Prompt.ask( - "Enter wallet name", default=defaults.wallet.name - ) - config.wallet.name = str(wallet_name) - - if not config.is_set("subtensor.network") and not config.no_prompt: - network = Prompt.ask( - "Enter network", - default=defaults.subtensor.network, - choices=bittensor.__networks__, - ) - config.subtensor.network = str(network) - ( - _, - config.subtensor.chain_endpoint, - ) = bittensor.subtensor.determine_chain_endpoint_and_network(str(network)) - - -API_URL = "https://api.subquery.network/sq/TaoStats/bittensor-indexer" -MAX_TXN = 1000 -GRAPHQL_QUERY = """ -query ($first: Int!, $after: Cursor, $filter: TransferFilter, $order: [TransfersOrderBy!]!) { - transfers(first: $first, after: $after, filter: $filter, orderBy: $order) { - nodes { - id - from - to - amount - extrinsicId - blockNumber - } - pageInfo { - endCursor - hasNextPage - hasPreviousPage - } - totalCount - } -} -""" - - -class GetWalletHistoryCommand: - """ - Executes the ``history`` command to fetch the latest transfers of the provided wallet on the Bittensor network. - - This command provides a detailed view of the transfers carried out on the wallet. - - Usage: - The command lists the latest transfers of the provided wallet, showing the From, To, Amount, Extrinsic Id and Block Number. - - Optional arguments: - None. The command uses the wallet and subtensor configurations to fetch latest transfer data associated with a wallet. - - Example usage:: - - btcli wallet history - - Note: - This command is essential for users to monitor their financial status on the Bittensor network. - It helps in fetching info on all the transfers so that user can easily tally and cross check the transactions. - """ - - @staticmethod - def run(cli): - r"""Check the transfer history of the provided wallet.""" - wallet = bittensor.wallet(config=cli.config) - wallet_address = wallet.get_coldkeypub().ss58_address - # Fetch all transfers - transfers = get_wallet_transfers(wallet_address) - - # Create output table - table = create_transfer_history_table(transfers) - - bittensor.__console__.print(table) - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - history_parser = parser.add_parser( - "history", - help="""Fetch transfer history associated with the provided wallet""", - ) - bittensor.wallet.add_args(history_parser) - bittensor.subtensor.add_args(history_parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - - -def get_wallet_transfers(wallet_address) -> List[dict]: - """Get all transfers associated with the provided wallet address.""" - - variables = { - "first": MAX_TXN, - "filter": { - "or": [ - {"from": {"equalTo": wallet_address}}, - {"to": {"equalTo": wallet_address}}, - ] - }, - "order": "BLOCK_NUMBER_DESC", - } - - response = requests.post( - API_URL, json={"query": GRAPHQL_QUERY, "variables": variables} - ) - data = response.json() - - # Extract nodes and pageInfo from the response - transfer_data = data.get("data", {}).get("transfers", {}) - transfers = transfer_data.get("nodes", []) - - return transfers - - -def create_transfer_history_table(transfers): - """Get output transfer table""" - - table = Table(show_footer=False) - # Define the column names - column_names = [ - "Id", - "From", - "To", - "Amount (Tao)", - "Extrinsic Id", - "Block Number", - "URL (taostats)", - ] - taostats_url_base = "https://x.taostats.io/extrinsic" - - # Create a table - table = Table(show_footer=False) - table.title = "[white]Wallet Transfers" - - # Define the column styles - header_style = "overline white" - footer_style = "overline white" - column_style = "rgb(50,163,219)" - no_wrap = True - - # Add columns to the table - for column_name in column_names: - table.add_column( - f"[white]{column_name}", - header_style=header_style, - footer_style=footer_style, - style=column_style, - no_wrap=no_wrap, - justify="left" if column_name == "Id" else "right", - ) - - # Add rows to the table - for item in transfers: - try: - tao_amount = int(item["amount"]) / RAOPERTAO - except: - tao_amount = item["amount"] - table.add_row( - item["id"], - item["from"], - item["to"], - f"{tao_amount:.3f}", - str(item["extrinsicId"]), - item["blockNumber"], - f"{taostats_url_base}/{item['blockNumber']}-{item['extrinsicId']}", - ) - table.add_row() - table.show_footer = True - table.box = None - table.pad_edge = False - table.width = None - return table diff --git a/bittensor/commands/weights.py b/bittensor/commands/weights.py deleted file mode 100644 index ac4d9dfc36..0000000000 --- a/bittensor/commands/weights.py +++ /dev/null @@ -1,290 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 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. - -"""Module that encapsulates the CommitWeightCommand and the RevealWeightCommand. Used to commit and reveal weights -for a specific subnet on the Bittensor Network.""" - -import argparse -import os -import re - -import numpy as np -from rich.prompt import Prompt, Confirm - -import bittensor -import bittensor.utils.weight_utils as weight_utils -from . import defaults # type: ignore - - -class CommitWeightCommand: - """ - Executes the ``commit`` command to commit weights for specific subnet on the Bittensor network. - - Usage: - The command allows committing weights for a specific subnet. Users need to specify the netuid (network unique identifier), corresponding UIDs, and weights they wish to commit. - - Optional arguments: - - ``--netuid`` (int): The netuid of the subnet for which weights are to be commited. - - ``--uids`` (str): Corresponding UIDs for the specified netuid, in comma-separated format. - - ``--weights`` (str): Corresponding weights for the specified UIDs, in comma-separated format. - - Example usage: - $ btcli wt commit --netuid 1 --uids 1,2,3,4 --weights 0.1,0.2,0.3,0.4 - - Note: - This command is used to commit weights for a specific subnet and requires the user to have the necessary permissions. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Commit weights for a specific subnet.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - CommitWeightCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Commit weights for a specific subnet""" - wallet = bittensor.wallet(config=cli.config) - - # Get values if not set - if not cli.config.is_set("netuid"): - cli.config.netuid = int(Prompt.ask(f"Enter netuid")) - - if not cli.config.is_set("uids"): - cli.config.uids = Prompt.ask(f"Enter UIDs (comma-separated)") - - if not cli.config.is_set("weights"): - cli.config.weights = Prompt.ask(f"Enter weights (comma-separated)") - - # Parse from string - netuid = cli.config.netuid - uids = np.array( - [int(x) for x in re.split(r"[ ,]+", cli.config.uids)], dtype=np.int64 - ) - weights = np.array( - [float(x) for x in re.split(r"[ ,]+", cli.config.weights)], dtype=np.float32 - ) - weight_uids, weight_vals = weight_utils.convert_weights_and_uids_for_emit( - uids=uids, weights=weights - ) - - if not cli.config.is_set("salt"): - # Generate random salt - salt_length = 8 - salt = list(os.urandom(salt_length)) - - if not Confirm.ask( - f"Have you recorded the [red]salt[/red]: [bold white]'{salt}'[/bold white]? It will be " - f"required to reveal weights." - ): - return False, "User cancelled the operation." - else: - salt = np.array( - [int(x) for x in re.split(r"[ ,]+", cli.config.salt)], - dtype=np.int64, - ).tolist() - - # Run the commit weights operation - success, message = subtensor.commit_weights( - wallet=wallet, - netuid=netuid, - uids=weight_uids, - weights=weight_vals, - salt=salt, - wait_for_inclusion=cli.config.wait_for_inclusion, - wait_for_finalization=cli.config.wait_for_finalization, - prompt=cli.config.prompt, - ) - - # Result - if success: - bittensor.__console__.print(f"Weights committed successfully") - else: - bittensor.__console__.print(f"Failed to commit weights: {message}") - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser( - "commit", help="""Commit weights for a specific subnet.""" - ) - parser.add_argument("--netuid", dest="netuid", type=int, required=False) - parser.add_argument("--uids", dest="uids", type=str, required=False) - parser.add_argument("--weights", dest="weights", type=str, required=False) - parser.add_argument("--salt", dest="salt", type=str, required=False) - parser.add_argument( - "--wait-for-inclusion", - dest="wait_for_inclusion", - action="store_true", - default=False, - ) - parser.add_argument( - "--wait-for-finalization", - dest="wait_for_finalization", - action="store_true", - default=True, - ) - parser.add_argument( - "--prompt", - dest="prompt", - action="store_true", - default=False, - ) - - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.no_prompt and not config.is_set("wallet.name"): - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - if not config.no_prompt and not config.is_set("wallet.hotkey"): - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) - - -class RevealWeightCommand: - """ - Executes the ``reveal`` command to reveal weights for a specific subnet on the Bittensor network. - Usage: - The command allows revealing weights for a specific subnet. Users need to specify the netuid (network unique identifier), corresponding UIDs, and weights they wish to reveal. - Optional arguments: - - ``--netuid`` (int): The netuid of the subnet for which weights are to be revealed. - - ``--uids`` (str): Corresponding UIDs for the specified netuid, in comma-separated format. - - ``--weights`` (str): Corresponding weights for the specified UIDs, in comma-separated format. - - ``--salt`` (str): Corresponding salt for the hash function, integers in comma-separated format. - Example usage:: - $ btcli wt reveal --netuid 1 --uids 1,2,3,4 --weights 0.1,0.2,0.3,0.4 --salt 163,241,217,11,161,142,147,189 - Note: - This command is used to reveal weights for a specific subnet and requires the user to have the necessary permissions. - """ - - @staticmethod - def run(cli: "bittensor.cli"): - r"""Reveal weights for a specific subnet.""" - try: - subtensor: "bittensor.subtensor" = bittensor.subtensor( - config=cli.config, log_verbose=False - ) - RevealWeightCommand._run(cli, subtensor) - finally: - if "subtensor" in locals(): - subtensor.close() - bittensor.logging.debug("closing subtensor connection") - - @staticmethod - def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"): - r"""Reveal weights for a specific subnet.""" - wallet = bittensor.wallet(config=cli.config) - - # Get values if not set. - if not cli.config.is_set("netuid"): - cli.config.netuid = int(Prompt.ask(f"Enter netuid")) - - if not cli.config.is_set("uids"): - cli.config.uids = Prompt.ask(f"Enter UIDs (comma-separated)") - - if not cli.config.is_set("weights"): - cli.config.weights = Prompt.ask(f"Enter weights (comma-separated)") - - if not cli.config.is_set("salt"): - cli.config.salt = Prompt.ask(f"Enter salt (comma-separated)") - - # Parse from string - netuid = cli.config.netuid - version = bittensor.__version_as_int__ - uids = np.array( - [int(x) for x in re.split(r"[ ,]+", cli.config.uids)], - dtype=np.int64, - ) - weights = np.array( - [float(x) for x in re.split(r"[ ,]+", cli.config.weights)], - dtype=np.float32, - ) - salt = np.array( - [int(x) for x in re.split(r"[ ,]+", cli.config.salt)], - dtype=np.int64, - ) - weight_uids, weight_vals = weight_utils.convert_weights_and_uids_for_emit( - uids=uids, weights=weights - ) - - # Run the reveal weights operation. - success, message = subtensor.reveal_weights( - wallet=wallet, - netuid=netuid, - uids=weight_uids, - weights=weight_vals, - salt=salt, - version_key=version, - wait_for_inclusion=cli.config.wait_for_inclusion, - wait_for_finalization=cli.config.wait_for_finalization, - prompt=cli.config.prompt, - ) - - if success: - bittensor.__console__.print(f"Weights revealed successfully") - else: - bittensor.__console__.print(f"Failed to reveal weights: {message}") - - @staticmethod - def add_args(parser: argparse.ArgumentParser): - parser = parser.add_parser( - "reveal", help="""Reveal weights for a specific subnet.""" - ) - parser.add_argument("--netuid", dest="netuid", type=int, required=False) - parser.add_argument("--uids", dest="uids", type=str, required=False) - parser.add_argument("--weights", dest="weights", type=str, required=False) - parser.add_argument("--salt", dest="salt", type=str, required=False) - parser.add_argument( - "--wait-for-inclusion", - dest="wait_for_inclusion", - action="store_true", - default=False, - ) - parser.add_argument( - "--wait-for-finalization", - dest="wait_for_finalization", - action="store_true", - default=True, - ) - parser.add_argument( - "--prompt", - dest="prompt", - action="store_true", - default=False, - ) - - bittensor.wallet.add_args(parser) - bittensor.subtensor.add_args(parser) - - @staticmethod - def check_config(config: "bittensor.config"): - if not config.is_set("wallet.name") and not config.no_prompt: - wallet_name = Prompt.ask("Enter wallet name", default=defaults.wallet.name) - config.wallet.name = str(wallet_name) - if not config.is_set("wallet.hotkey") and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name", default=defaults.wallet.hotkey) - config.wallet.hotkey = str(hotkey) diff --git a/bittensor/constants.py b/bittensor/constants.py deleted file mode 100644 index 74d3dd2e08..0000000000 --- a/bittensor/constants.py +++ /dev/null @@ -1,43 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2023 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. - -# Standard Library -import asyncio -from typing import Dict, Type - -# 3rd Party -import aiohttp - - -ALLOWED_DELTA = 4_000_000_000 # Delta of 4 seconds for nonce validation -V_7_2_0 = 7002000 -NANOSECONDS_IN_SECOND = 1_000_000_000 - -#### Dendrite #### -DENDRITE_ERROR_MAPPING: Dict[Type[Exception], tuple] = { - aiohttp.ClientConnectorError: ("503", "Service unavailable"), - asyncio.TimeoutError: ("408", "Request timeout"), - aiohttp.ClientResponseError: (None, "Client response error"), - aiohttp.ClientPayloadError: ("400", "Payload error"), - aiohttp.ClientError: ("500", "Client error"), - aiohttp.ServerTimeoutError: ("504", "Server timeout error"), - aiohttp.ServerDisconnectedError: ("503", "Service disconnected"), - aiohttp.ServerConnectionError: ("503", "Service connection error"), -} - -DENDRITE_DEFAULT_ERROR = ("422", "Failed to parse response") -#### End Dendrite #### diff --git a/bittensor/core/__init__.py b/bittensor/core/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/bittensor/axon.py b/bittensor/core/axon.py similarity index 89% rename from bittensor/axon.py rename to bittensor/core/axon.py index 13c60cdbd3..48a4ee9086 100644 --- a/bittensor/axon.py +++ b/bittensor/core/axon.py @@ -1,31 +1,28 @@ -"""Create and initialize Axon, which services the forward and backward requests from other neurons.""" - # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies Inc - +# 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. +"""Create and initialize Axon, which services the forward and backward requests from other neurons.""" + import argparse import asyncio import contextlib import copy import inspect import json -import os import threading import time import traceback @@ -35,6 +32,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple import uvicorn +from bittensor_wallet import Wallet from fastapi import APIRouter, Depends, FastAPI from fastapi.responses import JSONResponse from fastapi.routing import serialize_response @@ -43,10 +41,9 @@ from starlette.responses import Response from substrateinterface import Keypair -import bittensor -from bittensor.utils.axon_utils import allowed_nonce_window_ns, calculate_diff_seconds -from bittensor.constants import V_7_2_0 -from bittensor.errors import ( +from bittensor.core.chain_data import AxonInfo +from bittensor.core.config import Config +from bittensor.core.errors import ( BlacklistedException, InvalidRequestNameError, NotVerifiedException, @@ -57,8 +54,18 @@ SynapseParsingError, UnknownSynapseError, ) -from bittensor.threadpool import PriorityThreadPoolExecutor +from bittensor.core.settings import defaults, version_as_int +from bittensor.core.synapse import Synapse, TerminalInfo +from bittensor.core.threadpool import PriorityThreadPoolExecutor from bittensor.utils import networking +from bittensor.utils.axon_utils import allowed_nonce_window_ns, calculate_diff_seconds +from bittensor.utils.btlogging import logging + +# Just for annotation checker +if typing.TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor + +V_7_2_0 = 7002000 class FastAPIThreadedServer(uvicorn.Server): @@ -103,7 +110,6 @@ def install_signal_handlers(self): """ Overrides the default signal handlers provided by ``uvicorn.Server``. This method is essential to ensure that the signal handling in the threaded server does not interfere with the main application's flow, especially in a complex asynchronous environment like the Axon server. """ - pass @contextlib.contextmanager def run_in_thread(self): @@ -133,8 +139,7 @@ def _wrapper_run(self): def start(self): """ - Starts the FastAPI server in a separate thread if it is not already running. This method sets up the server to handle HTTP requests concurrently, enabling the Axon server to efficiently manage - incoming network requests. + Starts the FastAPI server in a separate thread if it is not already running. This method sets up the server to handle HTTP requests concurrently, enabling the Axon server to efficiently manage incoming network requests. The method ensures that the server starts running in a non-blocking manner, allowing the Axon server to continue its other operations seamlessly. """ @@ -154,7 +159,7 @@ def stop(self): self.should_exit = True -class axon: +class Axon: """ The ``axon`` class in Bittensor is a fundamental component that serves as the server-side interface for a neuron within the Bittensor network. @@ -187,30 +192,30 @@ class is designed to be flexible and customizable, allowing users to specify cus import bittensor # Define your custom synapse class - class MySyanpse( bittensor.Synapse ): + class MySynapse( bittensor.Synapse ): input: int = 1 output: int = None # Define a custom request forwarding function using your synapse class - def forward( synapse: MySyanpse ) -> MySyanpse: + def forward( synapse: MySynapse ) -> MySynapse: # Apply custom logic to synapse and return it synapse.output = 2 return synapse # Define a custom request verification function - def verify_my_synapse( synapse: MySyanpse ): + def verify_my_synapse( synapse: MySynapse ): # Apply custom verification logic to synapse # Optionally raise Exception assert synapse.input == 1 ... - # Define a custom request blacklist fucntion - def blacklist_my_synapse( synapse: MySyanpse ) -> bool: + # Define a custom request blacklist function + def blacklist_my_synapse( synapse: MySynapse ) -> bool: # Apply custom blacklist return False ( if non blacklisted ) or True ( if blacklisted ) - # Define a custom request priority fucntion - def prioritize_my_synape( synapse: MySyanpse ) -> float: + # Define a custom request priority function + def prioritize_my_synapse( synapse: MySynapse ) -> float: # Apply custom priority return 1.0 @@ -229,7 +234,7 @@ def prioritize_my_synape( synapse: MySyanpse ) -> float: forward_fn = forward_my_synapse, verify_fn = verify_my_synapse, blacklist_fn = blacklist_my_synapse, - priority_fn = prioritize_my_synape + priority_fn = prioritize_my_synapse ) # Serve and start your axon. @@ -243,12 +248,12 @@ def prioritize_my_synape( synapse: MySyanpse ) -> float: forward_fn = forward_my_synapse, verify_fn = verify_my_synapse, blacklist_fn = blacklist_my_synapse, - priority_fn = prioritize_my_synape + priority_fn = prioritize_my_synapse ).attach( forward_fn = forward_my_synapse_2, verify_fn = verify_my_synapse_2, blacklist_fn = blacklist_my_synapse_2, - priority_fn = prioritize_my_synape_2 + priority_fn = prioritize_my_synapse_2 ).serve( netuid = ... subtensor = ... @@ -287,56 +292,43 @@ def prioritize_my_synape( synapse: MySyanpse ) -> float: Error Handling and Validation The method ensures that the attached functions meet the required signatures, providing error handling to prevent runtime issues. - """ def __init__( self, - wallet: Optional["bittensor.wallet"] = None, - config: Optional["bittensor.config"] = None, + wallet: Optional["Wallet"] = None, + config: Optional["Config"] = None, port: Optional[int] = None, ip: Optional[str] = None, external_ip: Optional[str] = None, external_port: Optional[int] = None, max_workers: Optional[int] = None, ): - r"""Creates a new bittensor.Axon object from passed arguments. + """Creates a new bittensor.Axon object from passed arguments. + Args: - config (:obj:`Optional[bittensor.config]`, `optional`): - bittensor.axon.config() - wallet (:obj:`Optional[bittensor.wallet]`, `optional`): - bittensor wallet with hotkey and coldkeypub. - port (:type:`Optional[int]`, `optional`): - Binding port. - ip (:type:`Optional[str]`, `optional`): - Binding ip. - external_ip (:type:`Optional[str]`, `optional`): - The external ip of the server to broadcast to the network. - external_port (:type:`Optional[int]`, `optional`): - The external port of the server to broadcast to the network. - max_workers (:type:`Optional[int]`, `optional`): - Used to create the threadpool if not passed, specifies the number of active threads servicing requests. + config (:obj:`Optional[bittensor.config]`, `optional`): bittensor.axon.config() + wallet (:obj:`Optional[bittensor.wallet]`, `optional`): bittensor wallet with hotkey and coldkeypub. + port (:type:`Optional[int]`, `optional`): Binding port. + ip (:type:`Optional[str]`, `optional`): Binding ip. + external_ip (:type:`Optional[str]`, `optional`): The external ip of the server to broadcast to the network. + external_port (:type:`Optional[int]`, `optional`): The external port of the server to broadcast to the network. + max_workers (:type:`Optional[int]`, `optional`): Used to create the threadpool if not passed, specifies the number of active threads servicing requests. """ # Build and check config. if config is None: - config = axon.config() + config = Axon.config() config = copy.deepcopy(config) - config.axon.ip = ip or config.axon.get("ip", bittensor.defaults.axon.ip) - config.axon.port = port or config.axon.get("port", bittensor.defaults.axon.port) - config.axon.external_ip = external_ip or config.axon.get( - "external_ip", bittensor.defaults.axon.external_ip - ) - config.axon.external_port = external_port or config.axon.get( - "external_port", bittensor.defaults.axon.external_port - ) - config.axon.max_workers = max_workers or config.axon.get( - "max_workers", bittensor.defaults.axon.max_workers - ) - axon.check_config(config) + config.axon.ip = ip or defaults.axon.ip + config.axon.port = port or defaults.axon.port + config.axon.external_ip = external_ip or defaults.axon.external_ip + config.axon.external_port = external_port or defaults.axon.external_port + config.axon.max_workers = max_workers or defaults.axon.max_workers + Axon.check_config(config) self.config = config # type: ignore # Get wallet or use default. - self.wallet = wallet or bittensor.wallet() + self.wallet = wallet or Wallet() # Build axon objects. self.uuid = str(uuid.uuid1()) @@ -345,7 +337,7 @@ def __init__( self.external_ip = ( self.config.axon.external_ip # type: ignore if self.config.axon.external_ip is not None # type: ignore - else bittensor.utils.networking.get_external_ip() + else networking.get_external_ip() ) self.external_port = ( self.config.axon.external_port # type: ignore @@ -356,7 +348,7 @@ def __init__( self.started = False # Build middleware - self.thread_pool = bittensor.PriorityThreadPoolExecutor( + self.thread_pool = PriorityThreadPoolExecutor( max_workers=self.config.axon.max_workers # type: ignore ) self.nonces: Dict[str, int] = {} @@ -370,7 +362,7 @@ def __init__( # Instantiate FastAPI self.app = FastAPI() - log_level = "trace" if bittensor.logging.__trace_on__ else "critical" + log_level = "trace" if logging.__trace_on__ else "critical" self.fast_config = uvicorn.Config( self.app, host="0.0.0.0", @@ -386,17 +378,17 @@ def __init__( self.app.add_middleware(self.middleware_cls, axon=self) # Attach default forward. - def ping(r: bittensor.Synapse) -> bittensor.Synapse: + def ping(r: Synapse) -> Synapse: return r self.attach( forward_fn=ping, verify_fn=None, blacklist_fn=None, priority_fn=None ) - def info(self) -> "bittensor.AxonInfo": + def info(self) -> "AxonInfo": """Returns the axon info object associated with this axon.""" - return bittensor.AxonInfo( - version=bittensor.__version_as_int__, + return AxonInfo( + version=version_as_int, ip=self.external_ip, ip_type=networking.ip_version(self.external_ip), port=self.external_port, @@ -413,7 +405,7 @@ def attach( blacklist_fn: Optional[Callable] = None, priority_fn: Optional[Callable] = None, verify_fn: Optional[Callable] = None, - ) -> "bittensor.axon": + ) -> "Axon": """ Attaches custom functions to the Axon server for handling incoming requests. This method enables @@ -423,7 +415,7 @@ def attach( Registers an API endpoint to the FastAPI application router. It uses the name of the first argument of the :func:`forward_fn` function as the endpoint name. - The attach method in the Bittensor framework's axon class is a crucial function for registering + The `attach` method in the Bittensor framework's axon class is a crucial function for registering API endpoints to the Axon's FastAPI application router. This method allows the Axon server to define how it handles incoming requests by attaching functions for forwarding, verifying, blacklisting, and prioritizing requests. It's a key part of customizing the server's behavior @@ -482,7 +474,7 @@ def verify_custom(synapse: MyCustomSynapse): param_class = first_param.annotation assert issubclass( - param_class, bittensor.Synapse + param_class, Synapse ), "The first argument of forward_fn must inherit from bittensor.Synapse" request_name = param_class.__name__ @@ -503,8 +495,8 @@ async def endpoint(*args, **kwargs): # Add the endpoint to the router, making it available on both GET and POST methods self.router.add_api_route( - f"/{request_name}", - endpoint, + path=f"/{request_name}", + endpoint=endpoint, methods=["GET", "POST"], dependencies=[Depends(self.verify_body_integrity)], ) @@ -513,8 +505,8 @@ async def endpoint(*args, **kwargs): # Check the signature of blacklist_fn, priority_fn and verify_fn if they are provided expected_params = [ Parameter( - "synapse", - Parameter.POSITIONAL_OR_KEYWORD, + name="synapse", + kind=Parameter.POSITIONAL_OR_KEYWORD, annotation=forward_sig.parameters[ list(forward_sig.parameters)[0] ].annotation, @@ -556,7 +548,7 @@ async def endpoint(*args, **kwargs): return self @classmethod - def config(cls) -> "bittensor.config": + def config(cls) -> "Config": """ Parses the command-line arguments to form a Bittensor configuration object. @@ -564,16 +556,14 @@ def config(cls) -> "bittensor.config": bittensor.config: Configuration object with settings from command-line arguments. """ parser = argparse.ArgumentParser() - axon.add_args(parser) # Add specific axon-related arguments - return bittensor.config(parser, args=[]) + Axon.add_args(parser) # Add specific axon-related arguments + return Config(parser, args=[]) @classmethod def help(cls): - """ - Prints the help text (list of command-line arguments and their descriptions) to stdout. - """ + """Prints the help text (list of command-line arguments and their descriptions) to stdout.""" parser = argparse.ArgumentParser() - axon.add_args(parser) # Add specific axon-related arguments + Axon.add_args(parser) # Add specific axon-related arguments print(cls.__new__.__doc__) # Print docstring of the class parser.print_help() # Print parser's help text @@ -591,46 +581,39 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: Optional[str] = None) """ prefix_str = "" if prefix is None else prefix + "." try: - # Get default values from environment variables or use default values - default_axon_port = os.getenv("BT_AXON_PORT") or 8091 - default_axon_ip = os.getenv("BT_AXON_IP") or "[::]" - default_axon_external_port = os.getenv("BT_AXON_EXTERNAL_PORT") or None - default_axon_external_ip = os.getenv("BT_AXON_EXTERNAL_IP") or None - default_axon_max_workers = os.getenv("BT_AXON_MAX_WORERS") or 10 - # Add command-line arguments to the parser parser.add_argument( "--" + prefix_str + "axon.port", type=int, help="The local port this axon endpoint is bound to. i.e. 8091", - default=default_axon_port, + default=defaults.axon.port, ) parser.add_argument( "--" + prefix_str + "axon.ip", type=str, help="""The local ip this axon binds to. ie. [::]""", - default=default_axon_ip, + default=defaults.axon.ip, ) parser.add_argument( "--" + prefix_str + "axon.external_port", type=int, required=False, help="""The public port this axon broadcasts to the network. i.e. 8091""", - default=default_axon_external_port, + default=defaults.axon.external_port, ) parser.add_argument( "--" + prefix_str + "axon.external_ip", type=str, required=False, help="""The external ip this axon broadcasts to the network to. ie. [::]""", - default=default_axon_external_ip, + default=defaults.axon.external_ip, ) parser.add_argument( "--" + prefix_str + "axon.max_workers", type=int, help="""The maximum number connection handler threads working simultaneously on this endpoint. The grpc server distributes new worker threads to service requests up to this number.""", - default=default_axon_max_workers, + default=defaults.axon.max_workers, ) except argparse.ArgumentError: @@ -652,13 +635,10 @@ async def verify_body_integrity(self, request: Request): request (Request): The incoming FastAPI request object containing both headers and the request body. Returns: - dict: Returns the parsed body of the request as a dictionary if all the hash comparisons match, - indicating that the body is intact and has not been tampered with. + dict: Returns the parsed body of the request as a dictionary if all the hash comparisons match, indicating that the body is intact and has not been tampered with. Raises: - JSONResponse: Raises a JSONResponse with a 400 status code if any of the hash comparisons fail, - indicating a potential integrity issue with the incoming request payload. - The response includes the detailed error message specifying which field has a hash mismatch. + JSONResponse: Raises a JSONResponse with a 400 status code if any of the hash comparisons fail, indicating a potential integrity issue with the incoming request payload. The response includes the detailed error message specifying which field has a hash mismatch. This method performs several key functions: @@ -669,11 +649,9 @@ async def verify_body_integrity(self, request: Request): 5. Comparing the recomputed hash with the hash provided in the request headers for verification. Note: - The integrity verification is an essential step in ensuring the security of the data exchange - within the Bittensor network. It helps prevent tampering and manipulation of data during transit, - thereby maintaining the reliability and trust in the network communication. + The integrity verification is an essential step in ensuring the security of the data exchange within the Bittensor network. It helps prevent tampering and manipulation of data during transit, thereby maintaining the reliability and trust in the network communication. """ - # Await and load the request body so we can inspect it + # Await and load the request body, so we can inspect it body = await request.body() request_body = body.decode() if isinstance(body, bytes) else body @@ -696,7 +674,7 @@ async def verify_body_integrity(self, request: Request): return body_dict @classmethod - def check_config(cls, config: "bittensor.config"): + def check_config(cls, config: "Config"): """ This method checks the configuration for the axon's port and wallet. @@ -707,23 +685,19 @@ def check_config(cls, config: "bittensor.config"): AssertionError: If the axon or external ports are not in range [1024, 65535] """ assert ( - config.axon.port > 1024 and config.axon.port < 65535 + 1024 < config.axon.port < 65535 ), "Axon port must be in range [1024, 65535]" assert config.axon.external_port is None or ( - config.axon.external_port > 1024 and config.axon.external_port < 65535 + 1024 < config.axon.external_port < 65535 ), "External port must be in range [1024, 65535]" def to_string(self): - """ - Provides a human-readable representation of the AxonInfo for this Axon. - """ + """Provides a human-readable representation of the AxonInfo for this Axon.""" return self.info().to_string() def __str__(self) -> str: - """ - Provides a human-readable representation of the Axon instance. - """ + """Provides a human-readable representation of the Axon instance.""" return "Axon({}, {}, {}, {}, {})".format( self.ip, self.port, @@ -746,7 +720,7 @@ def __del__(self): """ self.stop() - def start(self) -> "bittensor.axon": + def start(self) -> "Axon": """ Starts the Axon server and its underlying FastAPI server thread, transitioning the state of the Axon instance to ``started``. This method initiates the server's ability to accept and process @@ -772,7 +746,7 @@ def start(self) -> "bittensor.axon": self.started = True return self - def stop(self) -> "bittensor.axon": + def stop(self) -> "Axon": """ Stops the Axon server and its underlying GRPC server thread, transitioning the state of the Axon instance to ``stopped``. This method ceases the server's ability to accept new network requests, @@ -800,9 +774,7 @@ def stop(self) -> "bittensor.axon": self.started = False return self - def serve( - self, netuid: int, subtensor: Optional[bittensor.subtensor] = None - ) -> "bittensor.axon": + def serve(self, netuid: int, subtensor: Optional["Subtensor"] = None) -> "Axon": """ Serves the Axon on the specified subtensor connection using the configured wallet. This method registers the Axon with a specific subnet within the Bittensor network, identified by the ``netuid``. @@ -830,7 +802,7 @@ def serve( subtensor.serve_axon(netuid=netuid, axon=self) return self - async def default_verify(self, synapse: bittensor.Synapse): + async def default_verify(self, synapse: Synapse): """ This method is used to verify the authenticity of a received message using a digital signature. @@ -870,8 +842,7 @@ async def default_verify(self, synapse: bittensor.Synapse): cryptographic keys can participate in secure communication. Args: - synapse: bittensor.Synapse - bittensor request synapse. + synapse(Synapse): bittensor request synapse. Raises: Exception: If the ``receiver_hotkey`` doesn't match with ``self.receiver_hotkey``. @@ -948,7 +919,7 @@ async def default_verify(self, synapse: bittensor.Synapse): raise SynapseDendriteNoneException(synapse=synapse) -def create_error_response(synapse: bittensor.Synapse): +def create_error_response(synapse: Synapse): if synapse.axon is None: return JSONResponse( status_code=400, @@ -964,27 +935,27 @@ def create_error_response(synapse: bittensor.Synapse): def log_and_handle_error( - synapse: bittensor.Synapse, + synapse: Synapse, exception: Exception, status_code: typing.Optional[int] = None, start_time: typing.Optional[float] = None, -) -> bittensor.Synapse: +) -> Synapse: if isinstance(exception, SynapseException): synapse = exception.synapse or synapse - bittensor.logging.trace(f"Forward handled exception: {exception}") + logging.trace(f"Forward handled exception: {exception}") else: - bittensor.logging.trace(f"Forward exception: {traceback.format_exc()}") + logging.trace(f"Forward exception: {traceback.format_exc()}") if synapse.axon is None: - synapse.axon = bittensor.TerminalInfo() + synapse.axon = TerminalInfo() # Set the status code of the synapse to the given status code. error_id = str(uuid.uuid4()) error_type = exception.__class__.__name__ # Log the detailed error message for internal use - bittensor.logging.error(f"{error_type}#{error_id}: {exception}") + logging.error(f"{error_type}#{error_id}: {exception}") if not status_code and synapse.axon.status_code != 100: status_code = synapse.axon.status_code @@ -1040,7 +1011,7 @@ class AxonMiddleware(BaseHTTPMiddleware): then handling any postprocessing steps such as response header updating and logging. """ - def __init__(self, app: "AxonMiddleware", axon: "bittensor.axon"): + def __init__(self, app: "AxonMiddleware", axon: "Axon"): """ Initialize the AxonMiddleware class. @@ -1085,21 +1056,21 @@ async def dispatch( try: # Set up the synapse from its headers. try: - synapse: bittensor.Synapse = await self.preprocess(request) + synapse: Synapse = await self.preprocess(request) except Exception as exc: if isinstance(exc, SynapseException) and exc.synapse is not None: synapse = exc.synapse else: - synapse = bittensor.Synapse() + synapse = Synapse() raise # Logs the start of the request processing if synapse.dendrite is not None: - bittensor.logging.trace( + logging.trace( f"axon | <-- | {request.headers.get('content-length', -1)} B | {synapse.name} | {synapse.dendrite.hotkey} | {synapse.dendrite.ip}:{synapse.dendrite.port} | 200 | Success " ) else: - bittensor.logging.trace( + logging.trace( f"axon | <-- | {request.headers.get('content-length', -1)} B | {synapse.name} | None | None | 200 | Success " ) @@ -1118,11 +1089,12 @@ async def dispatch( # Handle errors related to preprocess. except InvalidRequestNameError as e: if synapse.axon is None: - synapse.axon = bittensor.TerminalInfo() + synapse.axon = TerminalInfo() synapse.axon.status_code = 400 synapse.axon.status_message = str(e) synapse = log_and_handle_error(synapse, e, start_time=start_time) response = create_error_response(synapse) + except SynapseException as e: synapse = e.synapse or synapse synapse = log_and_handle_error(synapse, e, start_time=start_time) @@ -1138,22 +1110,22 @@ async def dispatch( # Log the details of the processed synapse, including total size, name, hotkey, IP, port, # status code, and status message, using the debug level of the logger. if synapse.dendrite is not None and synapse.axon is not None: - bittensor.logging.trace( + logging.trace( f"axon | --> | {response.headers.get('content-length', -1)} B | {synapse.name} | {synapse.dendrite.hotkey} | {synapse.dendrite.ip}:{synapse.dendrite.port} | {synapse.axon.status_code} | {synapse.axon.status_message}" ) elif synapse.axon is not None: - bittensor.logging.trace( + logging.trace( f"axon | --> | {response.headers.get('content-length', -1)} B | {synapse.name} | None | None | {synapse.axon.status_code} | {synapse.axon.status_message}" ) else: - bittensor.logging.trace( + logging.trace( f"axon | --> | {response.headers.get('content-length', -1)} B | {synapse.name} | None | None | 200 | Success " ) # Return the response to the requester. return response - async def preprocess(self, request: Request) -> bittensor.Synapse: + async def preprocess(self, request: Request) -> "Synapse": """ Performs the initial processing of the incoming request. This method is responsible for extracting relevant information from the request and setting up the Synapse object, which @@ -1202,7 +1174,7 @@ async def preprocess(self, request: Request) -> bittensor.Synapse: # Fills the local axon information into the synapse. synapse.axon.__dict__.update( { - "version": str(bittensor.__version_as_int__), + "version": str(version_as_int), "uuid": str(self.axon.uuid), "nonce": time.time_ns(), "status_code": 100, @@ -1221,7 +1193,7 @@ async def preprocess(self, request: Request) -> bittensor.Synapse: # Return the setup synapse. return synapse - async def verify(self, synapse: bittensor.Synapse): + async def verify(self, synapse: "Synapse"): """ Verifies the authenticity and integrity of the request. This method ensures that the incoming request meets the predefined security and validation criteria. @@ -1262,7 +1234,7 @@ async def verify(self, synapse: bittensor.Synapse): except Exception as e: # If there was an exception during the verification process, we log that # there was a verification exception. - bittensor.logging.trace(f"Verify exception {str(e)}") + logging.trace(f"Verify exception {str(e)}") # Check if the synapse.axon object exists if synapse.axon is not None: @@ -1278,7 +1250,7 @@ async def verify(self, synapse: bittensor.Synapse): f"Not Verified with error: {str(e)}", synapse=synapse ) - async def blacklist(self, synapse: bittensor.Synapse): + async def blacklist(self, synapse: "Synapse"): """ Checks if the request should be blacklisted. This method ensures that requests from disallowed sources or with malicious intent are blocked from processing. This can be extremely useful for @@ -1319,7 +1291,7 @@ async def blacklist(self, synapse: bittensor.Synapse): ) if blacklisted: # We log that the key or identifier is blacklisted. - bittensor.logging.trace(f"Blacklisted: {blacklisted}, {reason}") + logging.trace(f"Blacklisted: {blacklisted}, {reason}") # Check if the synapse.axon object exists if synapse.axon is not None: @@ -1334,7 +1306,7 @@ async def blacklist(self, synapse: bittensor.Synapse): f"Forbidden. Key is blacklisted: {reason}.", synapse=synapse ) - async def priority(self, synapse: bittensor.Synapse): + async def priority(self, synapse: "Synapse"): """ Executes the priority function for the request. This method assesses and assigns a priority level to the request, determining its urgency and importance in the processing queue. @@ -1368,7 +1340,7 @@ async def submit_task( """ loop = asyncio.get_event_loop() future = loop.run_in_executor(executor, lambda: priority) - result = await future + await future return priority, result # If a priority function exists for the request's name @@ -1388,7 +1360,7 @@ async def submit_task( except TimeoutError as e: # If the execution of the priority function exceeds the timeout, # it raises an exception to handle the timeout error. - bittensor.logging.trace(f"TimeoutError: {str(e)}") + logging.trace(f"TimeoutError: {str(e)}") # Set the status code of the synapse to 408 which indicates a timeout error. if synapse.axon is not None: @@ -1401,7 +1373,7 @@ async def submit_task( async def run( self, - synapse: bittensor.Synapse, + synapse: "Synapse", call_next: RequestResponseEndpoint, request: Request, ) -> Response: @@ -1420,6 +1392,8 @@ async def run( This method is a critical part of the request lifecycle, where the actual processing of the request takes place, leading to the generation of a response. """ + assert isinstance(synapse, Synapse) + try: # The requested function is executed by calling the 'call_next' function, # passing the original request as an argument. This function processes the request @@ -1428,7 +1402,7 @@ async def run( except Exception as e: # Log the exception for debugging purposes. - bittensor.logging.trace(f"Run exception: {str(e)}") + logging.trace(f"Run exception: {str(e)}") raise # Return the starlet response @@ -1436,7 +1410,7 @@ async def run( @classmethod async def synapse_to_response( - cls, synapse: bittensor.Synapse, start_time: float + cls, synapse: "Synapse", start_time: float ) -> JSONResponse: """ Converts the Synapse object into a JSON response with HTTP headers. @@ -1452,7 +1426,7 @@ async def synapse_to_response( properly formatted and contains all necessary information. """ if synapse.axon is None: - synapse.axon = bittensor.TerminalInfo() + synapse.axon = TerminalInfo() if synapse.axon.status_code is None: synapse.axon.status_code = 200 diff --git a/bittensor/chain_data.py b/bittensor/core/chain_data.py similarity index 95% rename from bittensor/chain_data.py rename to bittensor/core/chain_data.py index 029cb29829..9228afce08 100644 --- a/bittensor/chain_data.py +++ b/bittensor/core/chain_data.py @@ -1,5 +1,5 @@ # The MIT License (MIT) -# Copyright © 2023 Opentensor Foundation +# 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 @@ -30,10 +30,11 @@ from scalecodec.types import GenericCall from scalecodec.utils.ss58 import ss58_encode -import bittensor -from .utils import networking as net, RAOPERTAO, U16_NORMALIZED_FLOAT -from .utils.balance import Balance -from .utils.registration import torch, use_torch +from .settings import ss58_format +from bittensor.utils import networking as net, RAOPERTAO, U16_NORMALIZED_FLOAT +from bittensor.utils.balance import Balance +from bittensor.utils.registration import torch, use_torch +from bittensor.utils.btlogging import logging custom_rpc_type_registry = { "types": { @@ -253,7 +254,7 @@ def to_string(self) -> str: try: return json.dumps(asdict(self)) except (TypeError, ValueError) as e: - bittensor.logging.error(f"Error converting AxonInfo to string: {e}") + logging.error(f"Error converting AxonInfo to string: {e}") return AxonInfo(0, "", 0, 0, "", "").to_string() @classmethod @@ -276,11 +277,11 @@ def from_string(cls, json_string: str) -> "AxonInfo": data = json.loads(json_string) return cls(**data) except json.JSONDecodeError as e: - bittensor.logging.error(f"Error decoding JSON: {e}") + logging.error(f"Error decoding JSON: {e}") except TypeError as e: - bittensor.logging.error(f"Type error: {e}") + logging.error(f"Type error: {e}") except ValueError as e: - bittensor.logging.error(f"Value error: {e}") + logging.error(f"Value error: {e}") return AxonInfo(0, "", 0, 0, "", "") @classmethod @@ -333,6 +334,7 @@ class ChainDataType(Enum): IPInfo = 7 SubnetHyperparameters = 8 ScheduledColdkeySwapInfo = 9 + AccountId = 10 def from_scale_encoding( @@ -424,15 +426,13 @@ class NeuronInfo: def fix_decoded_values(cls, neuron_info_decoded: Any) -> "NeuronInfo": """Fixes the values of the NeuronInfo object.""" neuron_info_decoded["hotkey"] = ss58_encode( - neuron_info_decoded["hotkey"], bittensor.__ss58_format__ + neuron_info_decoded["hotkey"], ss58_format ) neuron_info_decoded["coldkey"] = ss58_encode( - neuron_info_decoded["coldkey"], bittensor.__ss58_format__ + neuron_info_decoded["coldkey"], ss58_format ) stake_dict = { - ss58_encode(coldkey, bittensor.__ss58_format__): Balance.from_rao( - int(stake) - ) + ss58_encode(coldkey, ss58_format): Balance.from_rao(int(stake)) for coldkey, stake in neuron_info_decoded["stake"] } neuron_info_decoded["stake_dict"] = stake_dict @@ -571,15 +571,13 @@ class NeuronInfoLite: def fix_decoded_values(cls, neuron_info_decoded: Any) -> "NeuronInfoLite": """Fixes the values of the NeuronInfoLite object.""" neuron_info_decoded["hotkey"] = ss58_encode( - neuron_info_decoded["hotkey"], bittensor.__ss58_format__ + neuron_info_decoded["hotkey"], ss58_format ) neuron_info_decoded["coldkey"] = ss58_encode( - neuron_info_decoded["coldkey"], bittensor.__ss58_format__ + neuron_info_decoded["coldkey"], ss58_format ) stake_dict = { - ss58_encode(coldkey, bittensor.__ss58_format__): Balance.from_rao( - int(stake) - ) + ss58_encode(coldkey, ss58_format): Balance.from_rao(int(stake)) for coldkey, stake in neuron_info_decoded["stake"] } neuron_info_decoded["stake_dict"] = stake_dict @@ -750,14 +748,12 @@ def fix_decoded_values(cls, decoded: Any) -> "DelegateInfo": """Fixes the decoded values.""" return cls( - hotkey_ss58=ss58_encode( - decoded["delegate_ss58"], bittensor.__ss58_format__ - ), - owner_ss58=ss58_encode(decoded["owner_ss58"], bittensor.__ss58_format__), + hotkey_ss58=ss58_encode(decoded["delegate_ss58"], ss58_format), + owner_ss58=ss58_encode(decoded["owner_ss58"], ss58_format), take=U16_NORMALIZED_FLOAT(decoded["take"]), nominators=[ ( - ss58_encode(nom[0], bittensor.__ss58_format__), + ss58_encode(nom[0], ss58_format), Balance.from_rao(nom[1]), ) for nom in decoded["nominators"] @@ -823,8 +819,8 @@ class StakeInfo: def fix_decoded_values(cls, decoded: Any) -> "StakeInfo": """Fixes the decoded values.""" return cls( - hotkey_ss58=ss58_encode(decoded["hotkey"], bittensor.__ss58_format__), - coldkey_ss58=ss58_encode(decoded["coldkey"], bittensor.__ss58_format__), + hotkey_ss58=ss58_encode(decoded["hotkey"], ss58_format), + coldkey_ss58=ss58_encode(decoded["coldkey"], ss58_format), stake=Balance.from_rao(decoded["stake"]), ) @@ -855,7 +851,7 @@ def list_of_tuple_from_vec_u8( return {} return { - ss58_encode(address=account_id, ss58_format=bittensor.__ss58_format__): [ + ss58_encode(address=account_id, ss58_format=ss58_format): [ StakeInfo.fix_decoded_values(d) for d in stake_info ] for account_id, stake_info in decoded @@ -945,7 +941,7 @@ def fix_decoded_values(cls, decoded: Dict) -> "SubnetInfo": }, emission_value=decoded["emission_values"], burn=Balance.from_rao(decoded["burn"]), - owner_ss58=ss58_encode(decoded["owner"], bittensor.__ss58_format__), + owner_ss58=ss58_encode(decoded["owner"], ss58_format), ) def to_parameter_dict(self) -> Union[dict[str, Any], "torch.nn.ParameterDict"]: @@ -1163,8 +1159,8 @@ class ScheduledColdkeySwapInfo: def fix_decoded_values(cls, decoded: Any) -> "ScheduledColdkeySwapInfo": """Fixes the decoded values.""" return cls( - old_coldkey=ss58_encode(decoded["old_coldkey"], bittensor.__ss58_format__), - new_coldkey=ss58_encode(decoded["new_coldkey"], bittensor.__ss58_format__), + old_coldkey=ss58_encode(decoded["old_coldkey"], ss58_format), + new_coldkey=ss58_encode(decoded["new_coldkey"], ss58_format), arbitration_block=decoded["arbitration_block"], ) @@ -1199,6 +1195,4 @@ def decode_account_id_list(cls, vec_u8: List[int]) -> Optional[List[str]]: ) if decoded is None: return None - return [ - ss58_encode(account_id, bittensor.__ss58_format__) for account_id in decoded - ] + return [ss58_encode(account_id, ss58_format) for account_id in decoded] diff --git a/bittensor/core/config.py b/bittensor/core/config.py new file mode 100644 index 0000000000..86f46bdc9a --- /dev/null +++ b/bittensor/core/config.py @@ -0,0 +1,418 @@ +""" +Implementation of the config class, which manages the configuration of different Bittensor modules. +""" + +# The MIT License (MIT) +# Copyright © 2021 Yuma Rao +# Copyright © 2022 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 os +import sys +import yaml +import copy +from copy import deepcopy +from munch import DefaultMunch +from typing import List, Optional, Dict, Any, TypeVar, Type +import argparse + + +class InvalidConfigFile(Exception): + """In place of YAMLError""" + + pass + + +class config(DefaultMunch): + """ + Implementation of the config class, which manages the configuration of different Bittensor modules. + """ + + __is_set: Dict[str, bool] + + r""" Translates the passed parser into a nested Bittensor config. + + Args: + parser (argparse.ArgumentParser): + Command line parser object. + strict (bool): + If ``true``, the command line arguments are strictly parsed. + args (list of str): + Command line arguments. + default (Optional[Any]): + Default value for the Config. Defaults to ``None``. + This default will be returned for attributes that are undefined. + Returns: + config (bittensor.config): + Nested config object created from parser arguments. + """ + + def __init__( + self, + parser: argparse.ArgumentParser = None, + args: Optional[List[str]] = None, + strict: bool = False, + default: Optional[Any] = None, + ) -> None: + super().__init__(default) + + self["__is_set"] = {} + + if parser == None: + return None + + # Optionally add config specific arguments + try: + parser.add_argument( + "--config", + type=str, + help="If set, defaults are overridden by passed file.", + ) + except: + # this can fail if --config has already been added. + pass + + try: + parser.add_argument( + "--strict", + action="store_true", + help="""If flagged, config will check that only exact arguments have been set.""", + default=False, + ) + except: + # this can fail if --strict has already been added. + pass + + try: + parser.add_argument( + "--no_version_checking", + action="store_true", + help="Set ``true`` to stop cli version checking.", + default=False, + ) + except: + # this can fail if --no_version_checking has already been added. + pass + + try: + parser.add_argument( + "--no_prompt", + dest="no_prompt", + action="store_true", + help="Set ``true`` to stop cli from prompting the user.", + default=False, + ) + except: + # this can fail if --no_version_checking has already been added. + pass + + # Get args from argv if not passed in. + if args == None: + args = sys.argv[1:] + + # Check for missing required arguments before proceeding + missing_required_args = self.__check_for_missing_required_args(parser, args) + if missing_required_args: + # Handle missing required arguments gracefully + raise ValueError( + f"Missing required arguments: {', '.join(missing_required_args)}" + ) + + # 1.1 Optionally load defaults if the --config is set. + try: + config_file_path = ( + str(os.getcwd()) + + "/" + + vars(parser.parse_known_args(args)[0])["config"] + ) + except Exception as e: + config_file_path = None + + # Parse args not strict + config_params = config.__parse_args__(args=args, parser=parser, strict=False) + + # 2. Optionally check for --strict + ## strict=True when passed in OR when --strict is set + strict = config_params.strict or strict + + if config_file_path != None: + config_file_path = os.path.expanduser(config_file_path) + try: + with open(config_file_path) as f: + params_config = yaml.safe_load(f) + print("Loading config defaults from: {}".format(config_file_path)) + parser.set_defaults(**params_config) + except Exception as e: + print("Error in loading: {} using default parser settings".format(e)) + + # 2. Continue with loading in params. + params = config.__parse_args__(args=args, parser=parser, strict=strict) + + _config = self + + # Splits params and add to config + config.__split_params__(params=params, _config=_config) + + # Make the is_set map + _config["__is_set"] = {} + + ## Reparse args using default of unset + parser_no_defaults = copy.deepcopy(parser) + + # Only command as the arg, else no args + default_param_args = ( + [_config.get("command")] + if _config.get("command") != None and _config.get("subcommand") == None + else [] + ) + if _config.get("command") != None and _config.get("subcommand") != None: + default_param_args = [_config.get("command"), _config.get("subcommand")] + + ## Get all args by name + default_params = parser.parse_args(args=default_param_args) + + all_default_args = default_params.__dict__.keys() | [] + ## Make a dict with keys as args and values as argparse.SUPPRESS + defaults_as_suppress = {key: argparse.SUPPRESS for key in all_default_args} + ## Set the defaults to argparse.SUPPRESS, should remove them from the namespace + parser_no_defaults.set_defaults(**defaults_as_suppress) + parser_no_defaults._defaults.clear() # Needed for quirk of argparse + + ### Check for subparsers and do the same + if parser_no_defaults._subparsers != None: + for action in parser_no_defaults._subparsers._actions: + # Should only be the "command" subparser action + if isinstance(action, argparse._SubParsersAction): + # Set the defaults to argparse.SUPPRESS, should remove them from the namespace + # Each choice is the keyword for a command, we need to set the defaults for each of these + ## Note: we also need to clear the _defaults dict for each, this is a quirk of argparse + cmd_parser: argparse.ArgumentParser + for cmd_parser in action.choices.values(): + # If this choice is also a subparser, set defaults recursively + if cmd_parser._subparsers: + for action in cmd_parser._subparsers._actions: + # Should only be the "command" subparser action + if isinstance(action, argparse._SubParsersAction): + cmd_parser: argparse.ArgumentParser + for cmd_parser in action.choices.values(): + cmd_parser.set_defaults(**defaults_as_suppress) + cmd_parser._defaults.clear() # Needed for quirk of argparse + else: + cmd_parser.set_defaults(**defaults_as_suppress) + cmd_parser._defaults.clear() # Needed for quirk of argparse + + # Reparse the args, but this time with the defaults as argparse.SUPPRESS + params_no_defaults = config.__parse_args__( + args=args, parser=parser_no_defaults, strict=strict + ) + + # Diff the params and params_no_defaults to get the is_set map + _config["__is_set"] = { + arg_key: True + for arg_key in [ + k + for k, _ in filter( + lambda kv: kv[1] != argparse.SUPPRESS, + params_no_defaults.__dict__.items(), + ) + ] + } + + @staticmethod + def __split_params__(params: argparse.Namespace, _config: "config"): + # Splits params on dot syntax i.e neuron.axon_port and adds to _config + for arg_key, arg_val in params.__dict__.items(): + split_keys = arg_key.split(".") + head = _config + keys = split_keys + while len(keys) > 1: + if ( + hasattr(head, keys[0]) and head[keys[0]] is not None + ): # Needs to be Config + head = getattr(head, keys[0]) + keys = keys[1:] + else: + head[keys[0]] = config() + head = head[keys[0]] + keys = keys[1:] + if len(keys) == 1: + head[keys[0]] = arg_val + + @staticmethod + def __parse_args__( + args: List[str], parser: argparse.ArgumentParser = None, strict: bool = False + ) -> argparse.Namespace: + """Parses the passed args use the passed parser. + + Args: + args (List[str]): + List of arguments to parse. + parser (argparse.ArgumentParser): + Command line parser object. + strict (bool): + If ``true``, the command line arguments are strictly parsed. + Returns: + Namespace: + Namespace object created from parser arguments. + """ + if not strict: + params, unrecognized = parser.parse_known_args(args=args) + params_list = list(params.__dict__) + # bug within argparse itself, does not correctly set value for boolean flags + for unrec in unrecognized: + if unrec.startswith("--") and unrec[2:] in params_list: + # Set the missing boolean value to true + setattr(params, unrec[2:], True) + else: + params = parser.parse_args(args=args) + + return params + + def __deepcopy__(self, memo) -> "config": + _default = self.__default__ + + config_state = self.__getstate__() + config_copy = config() + memo[id(self)] = config_copy + + config_copy.__setstate__(config_state) + config_copy.__default__ = _default + + config_copy["__is_set"] = deepcopy(self["__is_set"], memo) + + return config_copy + + def __repr__(self) -> str: + return self.__str__() + + @staticmethod + def _remove_private_keys(d): + if "__parser" in d: + d.pop("__parser", None) + if "__is_set" in d: + d.pop("__is_set", None) + for k, v in list(d.items()): + if isinstance(v, dict): + config._remove_private_keys(v) + return d + + def __str__(self) -> str: + # remove the parser and is_set map from the visible config + visible = copy.deepcopy(self.toDict()) + visible.pop("__parser", None) + visible.pop("__is_set", None) + cleaned = config._remove_private_keys(visible) + return "\n" + yaml.dump(cleaned, sort_keys=False) + + def copy(self) -> "config": + return copy.deepcopy(self) + + def to_string(self, items) -> str: + """Get string from items""" + return "\n" + yaml.dump(items.toDict()) + + def update_with_kwargs(self, kwargs): + """Add config to self""" + for key, val in kwargs.items(): + self[key] = val + + @classmethod + def _merge(cls, a, b): + """Merge two configurations recursively. + If there is a conflict, the value from the second configuration will take precedence. + """ + for key in b: + if key in a: + if isinstance(a[key], dict) and isinstance(b[key], dict): + a[key] = cls._merge(a[key], b[key]) + else: + a[key] = b[key] + else: + a[key] = b[key] + return a + + def merge(self, b): + """ + Merges the current config with another config. + + Args: + b: Another config to merge. + """ + self = self._merge(self, b) + + @classmethod + def merge_all(cls, configs: List["config"]) -> "config": + """ + Merge all configs in the list into one config. + If there is a conflict, the value from the last configuration in the list will take precedence. + + Args: + configs (list of config): + List of configs to be merged. + + Returns: + config: + Merged config object. + """ + result = cls() + for cfg in configs: + result.merge(cfg) + return result + + def is_set(self, param_name: str) -> bool: + """ + Returns a boolean indicating whether the parameter has been set or is still the default. + """ + if param_name not in self.get("__is_set"): + return False + else: + return self.get("__is_set")[param_name] + + def __check_for_missing_required_args( + self, parser: argparse.ArgumentParser, args: List[str] + ) -> List[str]: + required_args = self.__get_required_args_from_parser(parser) + missing_args = [arg for arg in required_args if not any(arg in s for s in args)] + return missing_args + + @staticmethod + def __get_required_args_from_parser(parser: argparse.ArgumentParser) -> List[str]: + required_args = [] + for action in parser._actions: + if action.required: + # Prefix the argument with '--' if it's a long argument, or '-' if it's short + prefix = "--" if len(action.dest) > 1 else "-" + required_args.append(prefix + action.dest) + return required_args + + +T = TypeVar("T", bound="DefaultConfig") + + +class DefaultConfig(config): + """ + A Config with a set of default values. + """ + + @classmethod + def default(cls: Type[T]) -> T: + """ + Get default config. + """ + raise NotImplementedError("Function default is not implemented.") + + +Config = config diff --git a/bittensor/dendrite.py b/bittensor/core/dendrite.py similarity index 82% rename from bittensor/dendrite.py rename to bittensor/core/dendrite.py index 61aa83663e..0d74831006 100644 --- a/bittensor/dendrite.py +++ b/bittensor/core/dendrite.py @@ -1,37 +1,52 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies Inc - +# 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. -# Standard Library from __future__ import annotations + import asyncio import time -from typing import Optional, List, Union, AsyncGenerator, Any import uuid +from typing import Any, AsyncGenerator, Dict, List, Optional, Union, Type -# 3rd Party import aiohttp - -# Application -import bittensor -from bittensor.constants import DENDRITE_ERROR_MAPPING, DENDRITE_DEFAULT_ERROR +from bittensor_wallet import Wallet +from substrateinterface import Keypair + +from bittensor.core.axon import Axon +from bittensor.core.chain_data import AxonInfo +from bittensor.core.settings import version_as_int +from bittensor.core.stream import StreamingSynapse +from bittensor.core.synapse import Synapse, TerminalInfo +from bittensor.utils import networking +from bittensor.utils.btlogging import logging from bittensor.utils.registration import torch, use_torch +DENDRITE_ERROR_MAPPING: Dict[Type[Exception], tuple] = { + aiohttp.ClientConnectorError: ("503", "Service unavailable"), + asyncio.TimeoutError: ("408", "Request timeout"), + aiohttp.ClientResponseError: (None, "Client response error"), + aiohttp.ClientPayloadError: ("400", "Payload error"), + aiohttp.ClientError: ("500", "Client error"), + aiohttp.ServerTimeoutError: ("504", "Server timeout error"), + aiohttp.ServerDisconnectedError: ("503", "Service disconnected"), + aiohttp.ServerConnectionError: ("503", "Service connection error"), +} +DENDRITE_DEFAULT_ERROR = ("422", "Failed to parse response") + class DendriteMixin: """ @@ -39,7 +54,7 @@ class DendriteMixin: In the brain analogy, dendrites receive signals from other neurons (in this case, network servers or axons), and the Dendrite class here is designed - to send requests to those endpoint to recieve inputs. + to send requests to those endpoint to receive inputs. This class includes a wallet or keypair used for signing messages, and methods for making HTTP requests to the network servers. It also provides functionalities such as logging @@ -53,19 +68,19 @@ class DendriteMixin: Methods: __str__(): Returns a string representation of the Dendrite object. __repr__(): Returns a string representation of the Dendrite object, acting as a fallback for __str__(). - query(self, *args, **kwargs) -> Union[bittensor.Synapse, List[bittensor.Synapse]]: + query(self, *args, **kwargs) -> Union[Synapse, List[Synapse]]: Makes synchronous requests to one or multiple target Axons and returns responses. - forward(self, axons, synapse=bittensor.Synapse(), timeout=12, deserialize=True, run_async=True, streaming=False) -> bittensor.Synapse: + forward(self, axons, synapse=Synapse(), timeout=12, deserialize=True, run_async=True, streaming=False) -> Synapse: Asynchronously sends requests to one or multiple Axons and collates their responses. - call(self, target_axon, synapse=bittensor.Synapse(), timeout=12.0, deserialize=True) -> bittensor.Synapse: + call(self, target_axon, synapse=Synapse(), timeout=12.0, deserialize=True) -> Synapse: Asynchronously sends a request to a specified Axon and processes the response. - call_stream(self, target_axon, synapse=bittensor.Synapse(), timeout=12.0, deserialize=True) -> AsyncGenerator[bittensor.Synapse, None]: + call_stream(self, target_axon, synapse=Synapse(), timeout=12.0, deserialize=True) -> AsyncGenerator[Synapse, None]: Sends a request to a specified Axon and yields an AsyncGenerator that contains streaming response chunks before finally yielding the filled Synapse as the final element. - preprocess_synapse_for_request(self, target_axon_info, synapse, timeout=12.0) -> bittensor.Synapse: + preprocess_synapse_for_request(self, target_axon_info, synapse, timeout=12.0) -> Synapse: Preprocesses the synapse for making a request, including building headers and signing. process_server_response(self, server_response, json_response, local_synapse): @@ -82,31 +97,29 @@ class DendriteMixin: Example with a context manager:: - >>> aysnc with dendrite(wallet = bittensor.wallet()) as d: - >>> print(d) - >>> d( ) # ping axon - >>> d( [] ) # ping multiple - >>> d( bittensor.axon(), bittensor.Synapse ) + async with dendrite(wallet = bittensor_wallet.Wallet()) as d: + print(d) + d( ) # ping axon + d( [] ) # ping multiple + d( Axon(), Synapse ) However, you are able to safely call :func:`dendrite.query()` without a context manager in a synchronous setting. Example without a context manager:: - >>> d = dendrite(wallet = bittensor.wallet() ) - >>> print(d) - >>> d( ) # ping axon - >>> d( [] ) # ping multiple - >>> d( bittensor.axon(), bittensor.Synapse ) + d = dendrite(wallet = bittensor_wallet.Wallet() ) + print(d) + d( ) # ping axon + d( [] ) # ping multiple + d( bittensor.core.axon.Axon(), bittensor.core.synapse.Synapse ) """ - def __init__( - self, wallet: Optional[Union[bittensor.wallet, bittensor.Keypair]] = None - ): + def __init__(self, wallet: Optional[Union["Wallet", Keypair]] = None): """ Initializes the Dendrite object, setting up essential properties. Args: - wallet (Optional[Union['bittensor.wallet', 'bittensor.keypair']], optional): + wallet (Optional[Union['bittensor_wallet.Wallet', 'bittensor.keypair']], optional): The user's wallet or keypair used for signing messages. Defaults to ``None``, in which case a new :func:`bittensor.wallet().hotkey` is generated and used. """ # Initialize the parent class @@ -116,12 +129,12 @@ def __init__( self.uuid = str(uuid.uuid1()) # Get the external IP - self.external_ip = bittensor.utils.networking.get_external_ip() + self.external_ip = networking.get_external_ip() # If a wallet or keypair is provided, use its hotkey. If not, generate a new one. self.keypair = ( - wallet.hotkey if isinstance(wallet, bittensor.wallet) else wallet - ) or bittensor.wallet().hotkey + wallet.hotkey if isinstance(wallet, Wallet) else wallet + ) or Wallet().hotkey self.synapse_history: list = [] @@ -244,14 +257,14 @@ def log_exception(self, exception: Exception): """ error_id = str(uuid.uuid4()) error_type = exception.__class__.__name__ - bittensor.logging.error(f"{error_type}#{error_id}: {exception}") + logging.error(f"{error_type}#{error_id}: {exception}") def process_error_message( self, - synapse: Union[bittensor.Synapse, bittensor.StreamingSynapse], + synapse: Union["Synapse", "StreamingSynapse"], request_name: str, exception: Exception, - ) -> Union[bittensor.Synapse, bittensor.StreamingSynapse]: + ) -> Union["Synapse", "StreamingSynapse"]: """ Handles exceptions that occur during network requests, updating the synapse with appropriate status codes and messages. @@ -264,7 +277,7 @@ def process_error_message( exception: The exception object caught during the request. Returns: - bittensor.Synapse: The updated synapse object with the error status code and message. + Synapse: The updated synapse object with the error status code and message. Note: This method updates the synapse object in-place. @@ -306,7 +319,7 @@ def _log_outgoing_request(self, synapse): Args: synapse: The synapse object representing the request being sent. """ - bittensor.logging.trace( + logging.trace( f"dendrite | --> | {synapse.get_total_size()} B | {synapse.name} | {synapse.axon.hotkey} | {synapse.axon.ip}:{str(synapse.axon.port)} | 0 | Success" ) @@ -321,15 +334,13 @@ def _log_incoming_response(self, synapse): Args: synapse: The synapse object representing the received response. """ - bittensor.logging.trace( + logging.trace( f"dendrite | <-- | {synapse.get_total_size()} B | {synapse.name} | {synapse.axon.hotkey} | {synapse.axon.ip}:{str(synapse.axon.port)} | {synapse.dendrite.status_code} | {synapse.dendrite.status_message}" ) def query( self, *args, **kwargs - ) -> List[ - Union[AsyncGenerator[Any, Any], bittensor.Synapse, bittensor.StreamingSynapse] - ]: + ) -> "List[Union[AsyncGenerator[Any, Any], Synapse, StreamingSynapse]]": """ Makes a synchronous request to multiple target Axons and returns the server responses. @@ -338,11 +349,11 @@ def query( Args: axons (Union[List[Union['bittensor.AxonInfo', 'bittensor.axon']], Union['bittensor.AxonInfo', 'bittensor.axon']]): The list of target Axon information. - synapse (bittensor.Synapse, optional): The Synapse object. Defaults to :func:`bittensor.Synapse()`. + synapse (Synapse, optional): The Synapse object. Defaults to :func:`Synapse()`. timeout (float, optional): The request timeout duration in seconds. Defaults to ``12.0`` seconds. Returns: - Union[bittensor.Synapse, List[bittensor.Synapse]]: If a single target axon is provided, returns the response from that axon. If multiple target axons are provided, returns a list of responses from all target axons. + Union[Synapse, List[Synapse]]: If a single target axon is provided, returns the response from that axon. If multiple target axons are provided, returns a list of responses from all target axons. """ result = None try: @@ -351,7 +362,7 @@ def query( except Exception: new_loop = asyncio.new_event_loop() asyncio.set_event_loop(new_loop) - result = loop.run_until_complete(self.forward(*args, **kwargs)) + result = new_loop.run_until_complete(self.forward(*args, **kwargs)) new_loop.close() finally: self.close_session() @@ -359,18 +370,13 @@ def query( async def forward( self, - axons: Union[ - List[Union[bittensor.AxonInfo, bittensor.axon]], - Union[bittensor.AxonInfo, bittensor.axon], - ], - synapse: bittensor.Synapse = bittensor.Synapse(), + axons: "Union[List[Union[AxonInfo, Axon]], Union[AxonInfo, Axon]]", + synapse: "Synapse" = Synapse(), timeout: float = 12, deserialize: bool = True, run_async: bool = True, streaming: bool = False, - ) -> List[ - Union[AsyncGenerator[Any, Any], bittensor.Synapse, bittensor.StreamingSynapse] - ]: + ) -> "List[Union[AsyncGenerator[Any, Any], Synapse, StreamingSynapse]]": """ Asynchronously sends requests to one or multiple Axons and collates their responses. @@ -385,12 +391,12 @@ async def forward( For example:: - >>> ... - >>> wallet = bittensor.wallet() # Initialize a wallet - >>> synapse = bittensor.Synapse(...) # Create a synapse object that contains query data - >>> dendrte = bittensor.dendrite(wallet = wallet) # Initialize a dendrite instance - >>> axons = metagraph.axons # Create a list of axons to query - >>> responses = await dendrite(axons, synapse) # Send the query to all axons and await the responses + ... + wallet = bittensor.wallet() # Initialize a wallet + synapse = Synapse(...) # Create a synapse object that contains query data + dendrte = bittensor.dendrite(wallet = wallet) # Initialize a dendrite instance + axons = metagraph.axons # Create a list of axons to query + responses = await dendrite(axons, synapse) # Send the query to all axons and await the responses When querying an Axon that sends back data in chunks using the Dendrite, this function returns an AsyncGenerator that yields each chunk as it is received. The generator can be @@ -398,23 +404,23 @@ async def forward( For example:: - >>> ... - >>> dendrte = bittensor.dendrite(wallet = wallet) - >>> async for chunk in dendrite.forward(axons, synapse, timeout, deserialize, run_async, streaming): - >>> # Process each chunk here - >>> print(chunk) + ... + dendrte = bittensor.dendrite(wallet = wallet) + async for chunk in dendrite.forward(axons, synapse, timeout, deserialize, run_async, streaming): + # Process each chunk here + print(chunk) Args: axons (Union[List[Union['bittensor.AxonInfo', 'bittensor.axon']], Union['bittensor.AxonInfo', 'bittensor.axon']]): The target Axons to send requests to. Can be a single Axon or a list of Axons. - synapse (bittensor.Synapse, optional): The Synapse object encapsulating the data. Defaults to a new :func:`bittensor.Synapse` instance. + synapse (Synapse, optional): The Synapse object encapsulating the data. Defaults to a new :func:`Synapse` instance. timeout (float, optional): Maximum duration to wait for a response from an Axon in seconds. Defaults to ``12.0``. deserialize (bool, optional): Determines if the received response should be deserialized. Defaults to ``True``. run_async (bool, optional): If ``True``, sends requests concurrently. Otherwise, sends requests sequentially. Defaults to ``True``. streaming (bool, optional): Indicates if the response is expected to be in streaming format. Defaults to ``False``. Returns: - Union[AsyncGenerator, bittensor.Synapse, List[bittensor.Synapse]]: If a single Axon is targeted, returns its response. + Union[AsyncGenerator, Synapse, List[Synapse]]: If a single Axon is targeted, returns its response. If multiple Axons are targeted, returns a list of their responses. """ is_list = True @@ -424,20 +430,16 @@ async def forward( axons = [axons] # Check if synapse is an instance of the StreamingSynapse class or if streaming flag is set. - is_streaming_subclass = issubclass( - synapse.__class__, bittensor.StreamingSynapse - ) + is_streaming_subclass = issubclass(synapse.__class__, StreamingSynapse) if streaming != is_streaming_subclass: - bittensor.logging.warning( + logging.warning( f"Argument streaming is {streaming} while issubclass(synapse, StreamingSynapse) is {synapse.__class__.__name__}. This may cause unexpected behavior." ) streaming = is_streaming_subclass or streaming async def query_all_axons( is_stream: bool, - ) -> Union[ - AsyncGenerator[Any, Any], bittensor.Synapse, bittensor.StreamingSynapse - ]: + ) -> Union[AsyncGenerator[Any, Any], Synapse, StreamingSynapse]: """ Handles the processing of requests to all targeted axons, accommodating both streaming and non-streaming responses. @@ -450,16 +452,14 @@ async def query_all_axons( If ``True``, responses are handled in streaming mode. Returns: - List[Union[AsyncGenerator, bittensor.Synapse, bittensor.StreamingSynapse]]: A list + List[Union[AsyncGenerator, Synapse, bittensor.StreamingSynapse]]: A list containing the responses from each axon. The type of each response depends on the streaming mode and the type of synapse used. """ async def single_axon_response( target_axon, - ) -> Union[ - AsyncGenerator[Any, Any], bittensor.Synapse, bittensor.StreamingSynapse - ]: + ) -> "Union[AsyncGenerator[Any, Any], Synapse, StreamingSynapse]": """ Manages the request and response process for a single axon, supporting both streaming and non-streaming modes. @@ -472,7 +472,7 @@ async def single_axon_response( target_axon: The target axon object to which the request is to be sent. This object contains the necessary information like IP address and port to formulate the request. Returns: - Union[AsyncGenerator, bittensor.Synapse, bittensor.StreamingSynapse]: The response + Union[AsyncGenerator, Synapse, bittensor.StreamingSynapse]: The response from the targeted axon. In streaming mode, an AsyncGenerator is returned, yielding data chunks. In non-streaming mode, a Synapse or StreamingSynapse object is returned containing the response. @@ -511,11 +511,11 @@ async def single_axon_response( async def call( self, - target_axon: Union[bittensor.AxonInfo, bittensor.axon], - synapse: bittensor.Synapse = bittensor.Synapse(), + target_axon: Union["AxonInfo", "Axon"], + synapse: "Synapse" = Synapse(), timeout: float = 12.0, deserialize: bool = True, - ) -> bittensor.Synapse: + ) -> "Synapse": """ Asynchronously sends a request to a specified Axon and processes the response. @@ -525,20 +525,18 @@ async def call( Args: target_axon (Union['bittensor.AxonInfo', 'bittensor.axon']): The target Axon to send the request to. - synapse (bittensor.Synapse, optional): The Synapse object encapsulating the data. Defaults to a new :func:`bittensor.Synapse` instance. + synapse (Synapse, optional): The Synapse object encapsulating the data. Defaults to a new :func:`Synapse` instance. timeout (float, optional): Maximum duration to wait for a response from the Axon in seconds. Defaults to ``12.0``. deserialize (bool, optional): Determines if the received response should be deserialized. Defaults to ``True``. Returns: - bittensor.Synapse: The Synapse object, updated with the response data from the Axon. + Synapse: The Synapse object, updated with the response data from the Axon. """ # Record start time start_time = time.time() target_axon = ( - target_axon.info() - if isinstance(target_axon, bittensor.axon) - else target_axon + target_axon.info() if isinstance(target_axon, Axon) else target_axon ) # Build request endpoint from the synapse class @@ -554,10 +552,10 @@ async def call( # Make the HTTP POST request async with (await self.session).post( - url, + url=url, headers=synapse.to_headers(), json=synapse.model_dump(), - timeout=timeout, + timeout=aiohttp.ClientTimeout(total=timeout), ) as response: # Extract the JSON response from the server json_response = await response.json() @@ -574,20 +572,18 @@ async def call( self._log_incoming_response(synapse) # Log synapse event history - self.synapse_history.append( - bittensor.Synapse.from_headers(synapse.to_headers()) - ) + self.synapse_history.append(Synapse.from_headers(synapse.to_headers())) # Return the updated synapse object after deserializing if requested return synapse.deserialize() if deserialize else synapse async def call_stream( self, - target_axon: Union[bittensor.AxonInfo, bittensor.axon], - synapse: bittensor.StreamingSynapse = bittensor.Synapse(), # type: ignore + target_axon: "Union[AxonInfo, Axon]", + synapse: "StreamingSynapse" = Synapse(), # type: ignore timeout: float = 12.0, deserialize: bool = True, - ) -> AsyncGenerator[Any, Any]: + ) -> "AsyncGenerator[Any, Any]": """ Sends a request to a specified Axon and yields streaming responses. @@ -598,21 +594,19 @@ async def call_stream( Args: target_axon (Union['bittensor.AxonInfo', 'bittensor.axon']): The target Axon to send the request to. - synapse (bittensor.Synapse, optional): The Synapse object encapsulating the data. Defaults to a new :func:`bittensor.Synapse` instance. + synapse (Synapse, optional): The Synapse object encapsulating the data. Defaults to a new :func:`Synapse` instance. timeout (float, optional): Maximum duration to wait for a response (or a chunk of the response) from the Axon in seconds. Defaults to ``12.0``. deserialize (bool, optional): Determines if each received chunk should be deserialized. Defaults to ``True``. Yields: object: Each yielded object contains a chunk of the arbitrary response data from the Axon. - bittensor.Synapse: After the AsyncGenerator has been exhausted, yields the final filled Synapse. + Synapse: After the AsyncGenerator has been exhausted, yields the final filled Synapse. """ # Record start time start_time = time.time() target_axon = ( - target_axon.info() - if isinstance(target_axon, bittensor.axon) - else target_axon + target_axon.info() if isinstance(target_axon, Axon) else target_axon ) # Build request endpoint from the synapse class @@ -636,7 +630,7 @@ async def call_stream( url, headers=synapse.to_headers(), json=synapse.model_dump(), - timeout=timeout, + timeout=aiohttp.ClientTimeout(total=timeout), ) as response: # Use synapse subclass' process_streaming_response method to yield the response chunks async for chunk in synapse.process_streaming_response(response): # type: ignore @@ -656,9 +650,7 @@ async def call_stream( self._log_incoming_response(synapse) # Log synapse event history - self.synapse_history.append( - bittensor.Synapse.from_headers(synapse.to_headers()) - ) + self.synapse_history.append(Synapse.from_headers(synapse.to_headers())) # Return the updated synapse object after deserializing if requested if deserialize: @@ -668,35 +660,35 @@ async def call_stream( def preprocess_synapse_for_request( self, - target_axon_info: bittensor.AxonInfo, - synapse: bittensor.Synapse, + target_axon_info: "AxonInfo", + synapse: "Synapse", timeout: float = 12.0, - ) -> bittensor.Synapse: + ) -> "Synapse": """ Preprocesses the synapse for making a request. This includes building headers for Dendrite and Axon and signing the request. Args: target_axon_info (bittensor.AxonInfo): The target axon information. - synapse (bittensor.Synapse): The synapse object to be preprocessed. + synapse (Synapse): The synapse object to be preprocessed. timeout (float, optional): The request timeout duration in seconds. Defaults to ``12.0`` seconds. Returns: - bittensor.Synapse: The preprocessed synapse. + Synapse: The preprocessed synapse. """ # Set the timeout for the synapse synapse.timeout = timeout - synapse.dendrite = bittensor.TerminalInfo( + synapse.dendrite = TerminalInfo( ip=self.external_ip, - version=bittensor.__version_as_int__, + version=version_as_int, nonce=time.time_ns(), uuid=self.uuid, hotkey=self.keypair.ss58_address, ) # Build the Axon headers using the target axon's details - synapse.axon = bittensor.TerminalInfo( + synapse.axon = TerminalInfo( ip=target_axon_info.ip, port=target_axon_info.port, hotkey=target_axon_info.hotkey, @@ -712,7 +704,7 @@ def process_server_response( self, server_response: aiohttp.ClientResponse, json_response: dict, - local_synapse: bittensor.Synapse, + local_synapse: Synapse, ): """ Processes the server response, updates the local synapse state with the @@ -721,7 +713,7 @@ def process_server_response( Args: server_response (object): The `aiohttp `_ response object from the server. json_response (dict): The parsed JSON response from the server. - local_synapse (bittensor.Synapse): The local synapse object to be updated. + local_synapse (Synapse): The local synapse object to be updated. Raises: None: But errors in attribute setting are silently ignored. @@ -743,12 +735,12 @@ def process_server_response( else: # If the server responded with an error, update the local synapse state if local_synapse.axon is None: - local_synapse.axon = bittensor.TerminalInfo() + local_synapse.axon = TerminalInfo() local_synapse.axon.status_code = server_response.status local_synapse.axon.status_message = json_response.get("message") # Extract server headers and overwrite None values in local synapse headers - server_headers = bittensor.Synapse.from_headers(server_response.headers) # type: ignore + server_headers = Synapse.from_headers(server_response.headers) # type: ignore # Merge dendrite headers local_synapse.dendrite.__dict__.update( @@ -850,10 +842,8 @@ def __del__(self): BaseModel: Union["torch.nn.Module", object] = torch.nn.Module if use_torch() else object -class dendrite(DendriteMixin, BaseModel): # type: ignore - def __init__( - self, wallet: Optional[Union[bittensor.wallet, bittensor.Keypair]] = None - ): +class Dendrite(DendriteMixin, BaseModel): # type: ignore + def __init__(self, wallet: Optional[Union[Wallet, Keypair]] = None): if use_torch(): torch.nn.Module.__init__(self) DendriteMixin.__init__(self, wallet) @@ -864,4 +854,4 @@ def __init__( async def call(self, *args, **kwargs): return await self.forward(*args, **kwargs) - dendrite.__call__ = call + Dendrite.__call__ = call diff --git a/bittensor/errors.py b/bittensor/core/errors.py similarity index 93% rename from bittensor/errors.py rename to bittensor/core/errors.py index 65c6c66e6e..6fd9729e8b 100644 --- a/bittensor/errors.py +++ b/bittensor/core/errors.py @@ -17,10 +17,7 @@ from __future__ import annotations -import typing - -if typing.TYPE_CHECKING: - import bittensor +from bittensor.core.synapse import Synapse class ChainError(BaseException): @@ -56,7 +53,7 @@ class NominationError(ChainTransactionError): class TakeError(ChainTransactionError): - """Error raised when a increase / decrease take transaction fails.""" + """Error raised when an increase / decrease take transaction fails.""" class TransferError(ChainTransactionError): @@ -84,9 +81,7 @@ class InvalidRequestNameError(Exception): class SynapseException(Exception): - def __init__( - self, message="Synapse Exception", synapse: "bittensor.Synapse" | None = None - ): + def __init__(self, message="Synapse Exception", synapse: "Synapse" | None = None): self.message = message self.synapse = synapse super().__init__(self.message) @@ -128,7 +123,7 @@ class SynapseDendriteNoneException(SynapseException): def __init__( self, message="Synapse Dendrite is None", - synapse: "bittensor.Synapse" | None = None, + synapse: "Synapse" | None = None, ): self.message = message super().__init__(self.message, synapse) diff --git a/bittensor/metagraph.py b/bittensor/core/metagraph.py similarity index 94% rename from bittensor/metagraph.py rename to bittensor/core/metagraph.py index 8d7e97bcc0..a550a2bd35 100644 --- a/bittensor/metagraph.py +++ b/bittensor/core/metagraph.py @@ -1,34 +1,45 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies Inc - +# 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 abc import ABC, abstractmethod import os import pickle -import numpy as np -from numpy.typing import NDArray -import bittensor +import typing +from abc import ABC, abstractmethod from os import listdir from os.path import join from typing import List, Optional, Union, Tuple -from bittensor.chain_data import AxonInfo +import numpy as np +from numpy.typing import NDArray + +from bittensor.utils.btlogging import logging from bittensor.utils.registration import torch, use_torch +from bittensor.utils.weight_utils import ( + convert_weight_uids_and_vals_to_tensor, + convert_bond_uids_and_vals_to_tensor, + convert_root_weight_uids_and_vals_to_tensor, +) +from . import settings +from .chain_data import AxonInfo + +# For annotation purposes +if typing.TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor + METAGRAPH_STATE_DICT_NDARRAY_KEYS = [ "version", @@ -85,7 +96,7 @@ def latest_block_path(dir_path: str) -> str: if block_number > latest_block: latest_block = block_number latest_file_full_path = full_path_filename - except Exception as e: + except Exception: pass if not latest_file_full_path: raise ValueError(f"Metagraph not found at: {dir_path}") @@ -382,7 +393,6 @@ def __init__( Initializing a metagraph object for the Bittensor network with a specific network UID:: metagraph = metagraph(netuid=123, network="finney", lite=True, sync=True) """ - pass def __str__(self) -> str: """ @@ -395,7 +405,7 @@ def __str__(self) -> str: Example: When printing the metagraph object or using it in a string context, this method is automatically invoked:: - print(metagraph) # Output: "metagraph(netuid:1, n:100, block:500, network:finney)" + print(metagraph) # Output: "metagraph(netuid:1, n:100, block:500, network:finney)" """ return "metagraph(netuid:{}, n:{}, block:{}, network:{})".format( self.netuid, self.n.item(), self.block.item(), self.network @@ -441,7 +451,7 @@ def metadata(self) -> dict: "n": self.n.item(), "block": self.block.item(), "network": self.network, - "version": bittensor.__version__, + "version": settings.__version__, } def state_dict(self): @@ -473,7 +483,7 @@ def sync( self, block: Optional[int] = None, lite: bool = True, - subtensor: Optional["bittensor.subtensor"] = None, + subtensor: Optional["Subtensor"] = None, ): """ Synchronizes the metagraph with the Bittensor network's current state. It updates the metagraph's attributes @@ -484,7 +494,7 @@ def sync( This allows for historical analysis or specific state examination of the network. lite (bool): If True, a lite version of the metagraph is used for quicker synchronization. This is beneficial when full detail is not necessary, allowing for reduced computational and time overhead. - subtensor (Optional[bittensor.subtensor]): An instance of the subtensor class from Bittensor, providing an + subtensor (Optional[Subtensor]): An instance of the subtensor class from Bittensor, providing an interface to the underlying blockchain data. If provided, this instance is used for data retrieval during synchronization. @@ -506,20 +516,21 @@ def sync( For example:: - subtensor = bittensor.subtensor(network='archive') + subtensor = bittensor.core.subtensor.Subtensor(network='archive') """ # Initialize subtensor subtensor = self._initialize_subtensor(subtensor) if ( - subtensor.chain_endpoint != bittensor.__archive_entrypoint__ # type: ignore - or subtensor.network != "archive" # type: ignore + subtensor.chain_endpoint != settings.archive_entrypoint + or subtensor.network != settings.networks[3] ): - cur_block = subtensor.get_current_block() # type: ignore + cur_block = subtensor.get_current_block() if block and block < (cur_block - 300): - bittensor.logging.warning( - "Attempting to sync longer than 300 blocks ago on a non-archive node. Please use the 'archive' network for subtensor and retry." + logging.warning( + "Attempting to sync longer than 300 blocks ago on a non-archive node. Please use the 'archive' " + "network for subtensor and retry." ) # Assign neurons based on 'lite' flag @@ -553,7 +564,10 @@ def _initialize_subtensor(self, subtensor): """ if not subtensor: # TODO: Check and test the initialization of the new subtensor - subtensor = bittensor.subtensor(network=self.network) + # Lazy import due to circular import (subtensor -> metagraph, metagraph -> subtensor) + from bittensor.core.subtensor import Subtensor + + subtensor = Subtensor(network=self.network) return subtensor def _assign_neurons(self, block, lite, subtensor): @@ -603,7 +617,7 @@ def _create_tensor(data, dtype) -> Union[NDArray, "torch.nn.Parameter"]: else np.array(data, dtype=dtype) ) - def _set_weights_and_bonds(self, subtensor: Optional[bittensor.subtensor] = None): + def _set_weights_and_bonds(self, subtensor: "Optional[Subtensor]" = None): """ Computes and sets the weights and bonds for each neuron in the metagraph. This method is responsible for processing the raw weight and bond data obtained from the network and converting it into a structured format suitable for the metagraph model. @@ -620,7 +634,7 @@ def _set_weights_and_bonds(self, subtensor: Optional[bittensor.subtensor] = None self.weights = self._process_root_weights( [neuron.weights for neuron in self.neurons], "weights", - subtensor, # type: ignore + subtensor, ) else: self.weights = self._process_weights_or_bonds( @@ -652,23 +666,23 @@ def _process_weights_or_bonds( for item in data: if len(item) == 0: if use_torch(): - data_array.append(torch.zeros(len(self.neurons))) # type: ignore + data_array.append(torch.zeros(len(self.neurons))) else: - data_array.append(np.zeros(len(self.neurons), dtype=np.float32)) # type: ignore + data_array.append(np.zeros(len(self.neurons), dtype=np.float32)) else: uids, values = zip(*item) # TODO: Validate and test the conversion of uids and values to tensor if attribute == "weights": data_array.append( - bittensor.utils.weight_utils.convert_weight_uids_and_vals_to_tensor( + convert_weight_uids_and_vals_to_tensor( len(self.neurons), list(uids), - list(values), # type: ignore + list(values), ) ) else: data_array.append( - bittensor.utils.weight_utils.convert_bond_uids_and_vals_to_tensor( # type: ignore + convert_bond_uids_and_vals_to_tensor( len(self.neurons), list(uids), list(values) ).astype(np.float32) ) @@ -686,7 +700,7 @@ def _process_weights_or_bonds( ) ) if len(data_array) == 0: - bittensor.logging.warning( + logging.warning( f"Empty {attribute}_array on metagraph.sync(). The '{attribute}' tensor is empty." ) return tensor_param @@ -696,7 +710,7 @@ def _set_metagraph_attributes(self, block, subtensor): pass def _process_root_weights( - self, data, attribute: str, subtensor: bittensor.subtensor + self, data, attribute: str, subtensor: "Subtensor" ) -> Union[NDArray, "torch.nn.Parameter"]: """ Specifically processes the root weights data for the metagraph. This method is similar to :func:`_process_weights_or_bonds` but is tailored for processing root weights, which have a different structure and significance in the network. @@ -712,10 +726,7 @@ def _process_root_weights( Internal Usage: Used internally to process and set root weights for the metagraph:: - self.root_weights = self._process_root_weights( - raw_root_weights_data, "weights", subtensor - ) - + self.root_weights = self._process_root_weights(raw_root_weights_data, "weights", subtensor) """ data_array = [] n_subnets = subtensor.get_total_subnets() or 0 @@ -725,12 +736,12 @@ def _process_root_weights( if use_torch(): data_array.append(torch.zeros(n_subnets)) else: - data_array.append(np.zeros(n_subnets, dtype=np.float32)) # type: ignore + data_array.append(np.zeros(n_subnets, dtype=np.float32)) else: uids, values = zip(*item) # TODO: Validate and test the conversion of uids and values to tensor data_array.append( - bittensor.utils.weight_utils.convert_root_weight_uids_and_vals_to_tensor( # type: ignore + convert_root_weight_uids_and_vals_to_tensor( n_subnets, list(uids), list(values), subnets ) ) @@ -749,12 +760,12 @@ def _process_root_weights( ) ) if len(data_array) == 0: - bittensor.logging.warning( + logging.warning( f"Empty {attribute}_array on metagraph.sync(). The '{attribute}' tensor is empty." ) return tensor_param - def save(self) -> "metagraph": # type: ignore + def save(self) -> "Metagraph": """ Saves the current state of the metagraph to a file on disk. This function is crucial for persisting the current state of the network's metagraph, which can later be reloaded or analyzed. The save operation includes all neuron attributes and parameters, ensuring a complete snapshot of the metagraph's state. @@ -783,9 +794,7 @@ def save(self) -> "metagraph": # type: ignore state_dict = self.state_dict() state_dict["axons"] = self.axons torch.save(state_dict, graph_filename) - state_dict = torch.load( - graph_filename - ) # verifies that the file can be loaded correctly + torch.load(graph_filename) # verifies that the file can be loaded correctly else: graph_filename = f"{save_directory}/block-{self.block.item()}.pt" state_dict = self.state_dict() @@ -819,7 +828,7 @@ def load(self): self.load_from_path(get_save_dir(self.network, self.netuid)) @abstractmethod - def load_from_path(self, dir_path: str) -> "metagraph": # type: ignore + def load_from_path(self, dir_path: str) -> "Metagraph": """ Loads the state of the metagraph from a specified directory path. This method is crucial for restoring the metagraph to a specific state based on saved data. It locates the latest block file in the given directory and loads all metagraph parameters from it. This is particularly useful for analyses that require historical states of the network or for restoring previous states of the metagraph in different @@ -852,7 +861,7 @@ def load_from_path(self, dir_path: str) -> "metagraph": # type: ignore BaseClass: Union["torch.nn.Module", object] = torch.nn.Module if use_torch() else object -class TorchMetaGraph(MetagraphMixin, BaseClass): # type: ignore +class TorchMetaGraph(MetagraphMixin, BaseClass): def __init__( self, netuid: int, network: str = "finney", lite: bool = True, sync: bool = True ): @@ -874,7 +883,7 @@ def __init__( self.netuid = netuid self.network = network self.version = torch.nn.Parameter( - torch.tensor([bittensor.__version_as_int__], dtype=torch.int64), + torch.tensor([settings.version_as_int], dtype=torch.int64), requires_grad=False, ) self.n: torch.nn.Parameter = torch.nn.Parameter( @@ -948,9 +957,7 @@ def _set_metagraph_attributes(self, block, subtensor): self._set_metagraph_attributes(block, subtensor) """ self.n = self._create_tensor(len(self.neurons), dtype=torch.int64) - self.version = self._create_tensor( - [bittensor.__version_as_int__], dtype=torch.int64 - ) + self.version = self._create_tensor([settings.version_as_int], dtype=torch.int64) self.block = self._create_tensor( block if block else subtensor.block, dtype=torch.int64 ) @@ -995,7 +1002,7 @@ def _set_metagraph_attributes(self, block, subtensor): ) self.axons = [n.axon_info for n in self.neurons] - def load_from_path(self, dir_path: str) -> "metagraph": # type: ignore + def load_from_path(self, dir_path: str) -> "Metagraph": graph_file = latest_block_path(dir_path) state_dict = torch.load(graph_file) self.n = torch.nn.Parameter(state_dict["n"], requires_grad=False) @@ -1047,7 +1054,7 @@ def __init__( self.netuid = netuid self.network = network - self.version = (np.array([bittensor.__version_as_int__], dtype=np.int64),) + self.version = (np.array([settings.version_as_int], dtype=np.int64),) self.n = np.array([0], dtype=np.int64) self.block = np.array([0], dtype=np.int64) self.stake = np.array([], dtype=np.float32) @@ -1086,9 +1093,7 @@ def _set_metagraph_attributes(self, block, subtensor): """ # TODO: Check and test the setting of each attribute self.n = self._create_tensor(len(self.neurons), dtype=np.int64) - self.version = self._create_tensor( - [bittensor.__version_as_int__], dtype=np.int64 - ) + self.version = self._create_tensor([settings.version_as_int], dtype=np.int64) self.block = self._create_tensor( block if block else subtensor.block, dtype=np.int64 ) @@ -1133,16 +1138,16 @@ def _set_metagraph_attributes(self, block, subtensor): ) self.axons = [n.axon_info for n in self.neurons] - def load_from_path(self, dir_path: str) -> "metagraph": # type: ignore + def load_from_path(self, dir_path: str) -> "Metagraph": graph_filename = latest_block_path(dir_path) try: with open(graph_filename, "rb") as graph_file: state_dict = pickle.load(graph_file) except pickle.UnpicklingError: - bittensor.__console__.print( + settings.bt_console.print( "Unable to load file. Attempting to restore metagraph using torch." ) - bittensor.__console__.print( + settings.bt_console.print( ":warning:[yellow]Warning:[/yellow] This functionality exists to load " "metagraph state from legacy saves, but will not be supported in the future." ) @@ -1154,7 +1159,7 @@ def load_from_path(self, dir_path: str) -> "metagraph": # type: ignore state_dict[key] = state_dict[key].detach().numpy() del real_torch except (RuntimeError, ImportError): - bittensor.__console__.print("Unable to load file. It may be corrupted.") + settings.bt_console.print("Unable to load file. It may be corrupted.") raise self.n = state_dict["n"] @@ -1180,4 +1185,4 @@ def load_from_path(self, dir_path: str) -> "metagraph": # type: ignore return self -metagraph = TorchMetaGraph if use_torch() else NonTorchMetagraph +Metagraph = TorchMetaGraph if use_torch() else NonTorchMetagraph diff --git a/bittensor/core/settings.py b/bittensor/core/settings.py new file mode 100644 index 0000000000..75781d500f --- /dev/null +++ b/bittensor/core/settings.py @@ -0,0 +1,274 @@ +# 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. + +__version__ = "7.3.0" + +import os +import re +from pathlib import Path +from rich.console import Console +from rich.traceback import install + +from munch import munchify + + +# Rich console. +__console__ = Console() +__use_console__ = True + +# Remove overdue locals in debug training. +install(show_locals=False) + + +def turn_console_off(): + global __use_console__ + global __console__ + from io import StringIO + + __use_console__ = False + __console__ = Console(file=StringIO(), stderr=False) + + +def turn_console_on(): + global __use_console__ + global __console__ + __use_console__ = True + __console__ = Console() + + +turn_console_off() + +bt_console = __console__ + + +HOME_DIR = Path.home() +USER_BITTENSOR_DIR = HOME_DIR / ".bittensor" +WALLETS_DIR = USER_BITTENSOR_DIR / "wallets" +MINERS_DIR = USER_BITTENSOR_DIR / "miners" + +DEFAULT_ENDPOINT = "wss://entrypoint-finney.opentensor.ai:443" +DEFAULT_NETWORK = "finney" + +# Create dirs if they don't exist +WALLETS_DIR.mkdir(parents=True, exist_ok=True) +MINERS_DIR.mkdir(parents=True, exist_ok=True) + +# Bittensor networks name +networks = ["local", "finney", "test", "archive"] + +# Bittensor endpoints (Needs to use wss://) +finney_entrypoint = "wss://entrypoint-finney.opentensor.ai:443" +finney_test_entrypoint = "wss://test.finney.opentensor.ai:443/" +archive_entrypoint = "wss://archive.chain.opentensor.ai:443/" +local_entrypoint = os.getenv("BT_SUBTENSOR_CHAIN_ENDPOINT") or "ws://127.0.0.1:9944" + +# Currency Symbols Bittensor +tao_symbol: str = chr(0x03C4) +rao_symbol: str = chr(0x03C1) + +# Pip address for versioning +pipaddress = "https://pypi.org/pypi/bittensor/json" + +# Substrate chain block time (seconds). +blocktime = 12 + +# Substrate ss58_format +ss58_format = 42 + +# Wallet ss58 address length +ss58_address_length = 48 + +# Raw GitHub url for delegates registry file +delegates_details_url = "https://raw.githubusercontent.com/opentensor/bittensor-delegates/main/public/delegates.json" + +# Block Explorers map network to explorer url +# Must all be polkadotjs explorer urls +network_explorer_map = { + "opentensor": { + "local": "https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fentrypoint-finney.opentensor.ai%3A443#/explorer", + "endpoint": "https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fentrypoint-finney.opentensor.ai%3A443#/explorer", + "finney": "https://polkadot.js.org/apps/?rpc=wss%3A%2F%2Fentrypoint-finney.opentensor.ai%3A443#/explorer", + }, + "taostats": { + "local": "https://x.taostats.io", + "endpoint": "https://x.taostats.io", + "finney": "https://x.taostats.io", + }, +} + +# --- Type Registry --- +type_registry: dict = { + "types": { + "Balance": "u64", # Need to override default u128 + }, + "runtime_api": { + "NeuronInfoRuntimeApi": { + "methods": { + "get_neuron_lite": { + "params": [ + { + "name": "netuid", + "type": "u16", + }, + { + "name": "uid", + "type": "u16", + }, + ], + "type": "Vec", + }, + "get_neurons_lite": { + "params": [ + { + "name": "netuid", + "type": "u16", + }, + ], + "type": "Vec", + }, + } + }, + "StakeInfoRuntimeApi": { + "methods": { + "get_stake_info_for_coldkey": { + "params": [ + { + "name": "coldkey_account_vec", + "type": "Vec", + }, + ], + "type": "Vec", + }, + "get_stake_info_for_coldkeys": { + "params": [ + { + "name": "coldkey_account_vecs", + "type": "Vec>", + }, + ], + "type": "Vec", + }, + }, + }, + "ValidatorIPRuntimeApi": { + "methods": { + "get_associated_validator_ip_info_for_subnet": { + "params": [ + { + "name": "netuid", + "type": "u16", + }, + ], + "type": "Vec", + }, + }, + }, + "SubnetInfoRuntimeApi": { + "methods": { + "get_subnet_hyperparams": { + "params": [ + { + "name": "netuid", + "type": "u16", + }, + ], + "type": "Vec", + } + } + }, + "SubnetRegistrationRuntimeApi": { + "methods": {"get_network_registration_cost": {"params": [], "type": "u64"}} + }, + "ColdkeySwapRuntimeApi": { + "methods": { + "get_scheduled_coldkey_swap": { + "params": [ + { + "name": "coldkey_account_vec", + "type": "Vec", + }, + ], + "type": "Vec", + }, + "get_remaining_arbitration_period": { + "params": [ + { + "name": "coldkey_account_vec", + "type": "Vec", + }, + ], + "type": "Vec", + }, + "get_coldkey_swap_destinations": { + "params": [ + { + "name": "coldkey_account_vec", + "type": "Vec", + }, + ], + "type": "Vec", + }, + } + }, + }, +} + +defaults = Munch = munchify( + { + "axon": { + "port": os.getenv("BT_AXON_PORT") or 8091, + "ip": os.getenv("BT_AXON_IP") or "[::]", + "external_port": os.getenv("BT_AXON_EXTERNAL_PORT") or None, + "external_ip": os.getenv("BT_AXON_EXTERNAL_IP") or None, + "max_workers": os.getenv("BT_AXON_MAX_WORKERS") or 10, + }, + "logging": { + "debug": os.getenv("BT_LOGGING_DEBUG") or False, + "trace": os.getenv("BT_LOGGING_TRACE") or False, + "record_log": os.getenv("BT_LOGGING_RECORD_LOG") or False, + "logging_dir": os.getenv("BT_LOGGING_LOGGING_DIR") or str(MINERS_DIR), + }, + "priority": { + "max_workers": os.getenv("BT_PRIORITY_MAX_WORKERS") or 5, + "maxsize": os.getenv("BT_PRIORITY_MAXSIZE") or 10, + }, + "subtensor": { + "chain_endpoint": DEFAULT_ENDPOINT, + "network": DEFAULT_NETWORK, + "_mock": False, + }, + "wallet": { + "name": "default", + "hotkey": "default", + "path": str(WALLETS_DIR), + }, + } +) + + +# Parsing version without any literals. +__version__ = re.match(r"^\d+\.\d+\.\d+", __version__).group(0) + +version_split = __version__.split(".") +_version_info = tuple(int(part) for part in version_split) +_version_int_base = 1000 +assert max(_version_info) < _version_int_base + +version_as_int: int = sum( + e * (_version_int_base**i) for i, e in enumerate(reversed(_version_info)) +) +assert version_as_int < 2**31 # fits in int32 diff --git a/bittensor/stream.py b/bittensor/core/stream.py similarity index 85% rename from bittensor/stream.py rename to bittensor/core/stream.py index e0dc17c42c..df37da557c 100644 --- a/bittensor/stream.py +++ b/bittensor/core/stream.py @@ -1,11 +1,29 @@ -from aiohttp import ClientResponse -import bittensor +# 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 starlette.responses import StreamingResponse as _StreamingResponse -from starlette.types import Send, Receive, Scope +from abc import ABC, abstractmethod from typing import Callable, Awaitable + +from aiohttp import ClientResponse from pydantic import ConfigDict, BaseModel -from abc import ABC, abstractmethod +from starlette.responses import StreamingResponse as _StreamingResponse +from starlette.types import Send, Receive, Scope + +from .synapse import Synapse class BTStreamingResponseModel(BaseModel): @@ -29,7 +47,7 @@ class BTStreamingResponseModel(BaseModel): token_streamer: Callable[[Send], Awaitable[None]] -class StreamingSynapse(bittensor.Synapse, ABC): +class StreamingSynapse(Synapse, ABC): """ The :func:`StreamingSynapse` class is designed to be subclassed for handling streaming responses in the Bittensor network. It provides abstract methods that must be implemented by the subclass to deserialize, process streaming responses, diff --git a/bittensor/subtensor.py b/bittensor/core/subtensor.py similarity index 93% rename from bittensor/subtensor.py rename to bittensor/core/subtensor.py index 75484fd69f..5f914d68f1 100644 --- a/bittensor/subtensor.py +++ b/bittensor/core/subtensor.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 @@ -24,11 +23,13 @@ import argparse import copy import socket +import sys import time from typing import List, Dict, Union, Optional, Tuple, TypedDict, Any import numpy as np import scalecodec +from bittensor_wallet import Wallet from numpy.typing import NDArray from retry import retry from scalecodec.base import RuntimeConfiguration @@ -38,73 +39,90 @@ from substrateinterface.base import QueryMapResult, SubstrateInterface, ExtrinsicReceipt from substrateinterface.exceptions import SubstrateRequestException -import bittensor -from bittensor.btlogging import logging as _logger -from bittensor.utils import torch, weight_utils, format_error_message -from .chain_data import ( - DelegateInfoLite, - NeuronInfo, - DelegateInfo, - PrometheusInfo, - SubnetInfo, - SubnetHyperparameters, - StakeInfo, - NeuronInfoLite, - AxonInfo, - ProposalVoteData, - IPInfo, - custom_rpc_type_registry, -) -from .errors import IdentityError, NominationError, StakeError, TakeError -from .extrinsics.commit_weights import ( +from bittensor.api.extrinsics.commit_weights import ( commit_weights_extrinsic, reveal_weights_extrinsic, ) -from .extrinsics.delegation import ( +from bittensor.api.extrinsics.delegation import ( delegate_extrinsic, nominate_extrinsic, undelegate_extrinsic, increase_take_extrinsic, decrease_take_extrinsic, ) -from .extrinsics.network import ( +from bittensor.api.extrinsics.network import ( register_subnetwork_extrinsic, set_hyperparameter_extrinsic, ) -from .extrinsics.prometheus import prometheus_extrinsic -from .extrinsics.registration import ( +from bittensor.api.extrinsics.prometheus import prometheus_extrinsic +from bittensor.api.extrinsics.registration import ( register_extrinsic, burned_register_extrinsic, run_faucet_extrinsic, swap_hotkey_extrinsic, ) -from .extrinsics.root import root_register_extrinsic, set_root_weights_extrinsic -from .extrinsics.senate import ( +from bittensor.api.extrinsics.root import ( + root_register_extrinsic, + set_root_weights_extrinsic, +) +from bittensor.api.extrinsics.senate import ( register_senate_extrinsic, leave_senate_extrinsic, vote_senate_extrinsic, ) -from .extrinsics.serving import ( +from bittensor.api.extrinsics.serving import ( serve_extrinsic, serve_axon_extrinsic, publish_metadata, get_metadata, ) -from .extrinsics.set_weights import set_weights_extrinsic -from .extrinsics.staking import add_stake_extrinsic, add_stake_multiple_extrinsic -from .extrinsics.transfer import transfer_extrinsic -from .extrinsics.unstaking import unstake_extrinsic, unstake_multiple_extrinsic -from .types import AxonServeCallParams, PrometheusServeCallParams -from .utils import ( +from bittensor.api.extrinsics.set_weights import set_weights_extrinsic +from bittensor.api.extrinsics.staking import ( + add_stake_extrinsic, + add_stake_multiple_extrinsic, +) +from bittensor.api.extrinsics.transfer import transfer_extrinsic +from bittensor.api.extrinsics.unstaking import ( + unstake_extrinsic, + unstake_multiple_extrinsic, +) +from bittensor.core import settings +from bittensor.core.axon import Axon +from bittensor.core.chain_data import ( + DelegateInfoLite, + NeuronInfo, + DelegateInfo, + PrometheusInfo, + SubnetInfo, + SubnetHyperparameters, + StakeInfo, + NeuronInfoLite, + AxonInfo, + ProposalVoteData, + IPInfo, + custom_rpc_type_registry, +) +from bittensor.core.config import Config +from bittensor.core.errors import IdentityError, NominationError, StakeError, TakeError +from bittensor.core.metagraph import Metagraph +from bittensor.core.types import AxonServeCallParams, PrometheusServeCallParams +from bittensor.utils import ( U16_NORMALIZED_FLOAT, ss58_to_vec_u8, U64_NORMALIZED_FLOAT, networking, ) -from .utils.balance import Balance -from .utils.registration import POWSolution -from .utils.registration import legacy_torch_api_compat -from .utils.subtensor import get_subtensor_errors +from bittensor.utils import ( + torch, + weight_utils, + format_error_message, + create_identity_dict, + decode_hex_identity_dict, +) +from bittensor.utils.balance import Balance +from bittensor.utils.btlogging import logging +from bittensor.utils.registration import POWSolution +from bittensor.utils.registration import legacy_torch_api_compat KEY_NONCE: Dict[str, int] = {} @@ -149,7 +167,7 @@ class Subtensor: finney_subtensor.connect_websocket() # Register a new neuron on the network. - wallet = bittensor.wallet(...) # Assuming a wallet instance is created. + wallet = bittensor_wallet.wallet(...) # Assuming a wallet instance is created. success = finney_subtensor.register(wallet=wallet, netuid=netuid) # Set inter-neuronal weights for collaborative learning. @@ -170,7 +188,7 @@ class Subtensor: def __init__( self, network: Optional[str] = None, - config: Optional[bittensor.config] = None, + config: Optional["Config"] = None, _mock: bool = False, log_verbose: bool = True, ) -> None: @@ -186,15 +204,11 @@ def __init__( instructions on how to run a local subtensor node in the documentation in a subsequent release. Args: - network (str, optional): The network name to connect to (e.g., ``finney``, ``local``). This can also be the - chain endpoint (e.g., ``wss://entrypoint-finney.opentensor.ai:443``) and will be correctly parsed into - the network and chain endpoint. If not specified, defaults to the main Bittensor network. - config (bittensor.config, optional): Configuration object for the subtensor. If not provided, a default - configuration is used. + network (str, optional): The network name to connect to (e.g., ``finney``, ``local``). This can also be the chain endpoint (e.g., ``wss://entrypoint-finney.opentensor.ai:443``) and will be correctly parsed into the network and chain endpoint. If not specified, defaults to the main Bittensor network. + config (bittensor.core.config.Config, optional): Configuration object for the subtensor. If not provided, a default configuration is used. _mock (bool, optional): If set to ``True``, uses a mocked connection for testing purposes. - This initialization sets up the connection to the specified Bittensor network, allowing for various - blockchain operations such as neuron registration, stake management, and setting weights. + This initialization sets up the connection to the specified Bittensor network, allowing for various blockchain operations such as neuron registration, stake management, and setting weights. """ # Determine config.subtensor.chain_endpoint and config.subtensor.network config. @@ -202,17 +216,6 @@ def __init__( # network. # Argument importance: network > chain_endpoint > config.subtensor.chain_endpoint > config.subtensor.network - # Check if network is a config object. (Single argument passed as first positional) - if isinstance(network, bittensor.config): - if network.subtensor is None: - _logger.warning( - "If passing a bittensor config object, it must not be empty. Using default subtensor config." - ) - config = None - else: - config = network - network = None - if config is None: config = Subtensor.config() self.config = copy.deepcopy(config) # type: ignore @@ -222,16 +225,16 @@ def __init__( if ( self.network == "finney" - or self.chain_endpoint == bittensor.__finney_entrypoint__ + or self.chain_endpoint == settings.finney_entrypoint ) and log_verbose: - _logger.info( + logging.info( f"You are connecting to {self.network} network with endpoint {self.chain_endpoint}." ) - _logger.warning( + logging.warning( "We strongly encourage running a local subtensor node whenever possible. " "This increases decentralization and resilience of the network." ) - _logger.warning( + logging.warning( "In a future release, local subtensor will become the default endpoint. " "To get ahead of this change, please run a local subtensor node and point to it." ) @@ -240,35 +243,33 @@ def __init__( try: # Set up params. self.substrate = SubstrateInterface( - ss58_format=bittensor.__ss58_format__, + ss58_format=settings.ss58_format, use_remote_preset=True, url=self.chain_endpoint, - type_registry=bittensor.__type_registry__, + type_registry=settings.type_registry, ) except ConnectionRefusedError: - _logger.error( + logging.error( f"Could not connect to {self.network} network with {self.chain_endpoint} chain endpoint. Exiting...", ) - _logger.info( + logging.info( "You can check if you have connectivity by running this command: nc -vz localhost " f"{self.chain_endpoint.split(':')[2]}" ) - exit(1) + sys.exit(1) # TODO (edu/phil): Advise to run local subtensor and point to dev docs. try: self.substrate.websocket.settimeout(600) - # except: - # bittensor.logging.warning("Could not set websocket timeout.") except AttributeError as e: - _logger.warning(f"AttributeError: {e}") + logging.warning(f"AttributeError: {e}") except TypeError as e: - _logger.warning(f"TypeError: {e}") + logging.warning(f"TypeError: {e}") except (socket.error, OSError) as e: - _logger.warning(f"Socket error: {e}") + logging.warning(f"Socket error: {e}") if log_verbose: - _logger.info( + logging.info( f"Connected to {self.network} network and {self.chain_endpoint}." ) @@ -286,17 +287,17 @@ def __repr__(self) -> str: return self.__str__() @staticmethod - def config() -> "bittensor.config": + def config() -> "Config": """ Creates and returns a Bittensor configuration object. Returns: - config (bittensor.config): A Bittensor configuration object configured with arguments added by the + config (bittensor.core.config.Config): A Bittensor configuration object configured with arguments added by the `subtensor.add_args` method. """ parser = argparse.ArgumentParser() Subtensor.add_args(parser) - return bittensor.config(parser, args=[]) + return Config(parser, args=[]) @classmethod def help(cls): @@ -328,8 +329,8 @@ def add_args(cls, parser: "argparse.ArgumentParser", prefix: Optional[str] = Non """ prefix_str = "" if prefix is None else f"{prefix}." try: - default_network = bittensor.__networks__[1] - default_chain_endpoint = bittensor.__finney_entrypoint__ + default_network = settings.networks[1] + default_chain_endpoint = settings.finney_entrypoint parser.add_argument( f"--{prefix_str}subtensor.network", @@ -377,36 +378,36 @@ def determine_chain_endpoint_and_network(network: str): if network in ["finney", "local", "test", "archive"]: if network == "finney": # Kiru Finney staging network. - return network, bittensor.__finney_entrypoint__ + return network, settings.finney_entrypoint elif network == "local": - return network, bittensor.__local_entrypoint__ + return network, settings.local_entrypoint elif network == "test": - return network, bittensor.__finney_test_entrypoint__ + return network, settings.finney_test_entrypoint elif network == "archive": - return network, bittensor.__archive_entrypoint__ + return network, settings.archive_entrypoint else: if ( - network == bittensor.__finney_entrypoint__ + network == settings.finney_entrypoint or "entrypoint-finney.opentensor.ai" in network ): - return "finney", bittensor.__finney_entrypoint__ + return "finney", settings.finney_entrypoint elif ( - network == bittensor.__finney_test_entrypoint__ + network == settings.finney_test_entrypoint or "test.finney.opentensor.ai" in network ): - return "test", bittensor.__finney_test_entrypoint__ + return "test", settings.finney_test_entrypoint elif ( - network == bittensor.__archive_entrypoint__ + network == settings.archive_entrypoint or "archive.chain.opentensor.ai" in network ): - return "archive", bittensor.__archive_entrypoint__ + return "archive", settings.archive_entrypoint elif "127.0.0.1" in network or "localhost" in network: return "local", network else: return "unknown", network @staticmethod - def setup_config(network: str, config: "bittensor.config"): + def setup_config(network: str, config: "Config"): """ Sets up and returns the configuration for the Subtensor network and endpoint. @@ -421,7 +422,7 @@ def setup_config(network: str, config: "bittensor.config"): Args: network (str): The name of the Subtensor network. If None, the network and endpoint will be determined from the `config` object. - config (bittensor.config): The configuration object containing the network and chain endpoint settings. + config (bittensor.core.config.Config): The configuration object containing the network and chain endpoint settings. Returns: tuple: A tuple containing the formatted WebSocket endpoint URL and the evaluated network name. @@ -469,7 +470,7 @@ def setup_config(network: str, config: "bittensor.config"): evaluated_network, evaluated_endpoint, ) = Subtensor.determine_chain_endpoint_and_network( - bittensor.defaults.subtensor.network + settings.defaults.subtensor.network ) return ( @@ -486,7 +487,7 @@ def close(self): ############## def nominate( self, - wallet: "bittensor.wallet", + wallet: "Wallet", wait_for_finalization: bool = False, wait_for_inclusion: bool = True, ) -> bool: @@ -496,9 +497,8 @@ def nominate( to participate in consensus and validation processes. Args: - wallet (bittensor.wallet): The wallet containing the hotkey to be nominated. - wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the - blockchain. + wallet (bittensor_wallet.Wallet): The wallet containing the hotkey to be nominated. + wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the blockchain. wait_for_inclusion (bool, optional): If ``True``, waits until the transaction is included in a block. Returns: @@ -516,9 +516,9 @@ def nominate( def delegate( self, - wallet: "bittensor.wallet", + wallet: "Wallet", delegate_ss58: Optional[str] = None, - amount: Optional[Union[Balance, float]] = None, + amount: Optional[Union["Balance", float]] = None, wait_for_inclusion: bool = True, wait_for_finalization: bool = False, prompt: bool = False, @@ -529,7 +529,7 @@ def delegate( to participate in consensus and validation processes. Args: - wallet (bittensor.wallet): The wallet containing the hotkey to be nominated. + wallet (bittensor_wallet.Wallet): The wallet containing the hotkey to be nominated. delegate_ss58 (Optional[str]): The ``SS58`` address of the delegate neuron. amount (Union[Balance, float]): The amount of TAO to undelegate. wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the @@ -555,7 +555,7 @@ def delegate( def undelegate( self, - wallet: "bittensor.wallet", + wallet: "Wallet", delegate_ss58: Optional[str] = None, amount: Optional[Union[Balance, float]] = None, wait_for_inclusion: bool = True, @@ -567,7 +567,7 @@ def undelegate( reduces the staked amount on another neuron, effectively withdrawing support or speculation. Args: - wallet (bittensor.wallet): The wallet used for the undelegation process. + wallet (bittensor_wallet.Wallet): The wallet used for the undelegation process. delegate_ss58 (Optional[str]): The ``SS58`` address of the delegate neuron. amount (Union[Balance, float]): The amount of TAO to undelegate. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. @@ -592,7 +592,7 @@ def undelegate( def set_take( self, - wallet: "bittensor.wallet", + wallet: "Wallet", delegate_ss58: Optional[str] = None, take: float = 0.0, wait_for_inclusion: bool = True, @@ -601,7 +601,7 @@ def set_take( """ Set delegate hotkey take Args: - wallet (bittensor.wallet): The wallet containing the hotkey to be nominated. + wallet (bittensor_wallet.Wallet): The wallet containing the hotkey to be nominated. delegate_ss58 (str, optional): Hotkey take (float): Delegate take on subnet ID wait_for_finalization (bool, optional): If ``True``, waits until the transaction is finalized on the @@ -628,10 +628,10 @@ def set_take( current_take = int(float(delegate.take) * 65535.0) if takeu16 == current_take: - bittensor.__console__.print("Nothing to do, take hasn't changed") + settings.bt_console.print("Nothing to do, take hasn't changed") return True if current_take is None or current_take < takeu16: - bittensor.__console__.print( + settings.bt_console.print( "Current take is either not set or is lower than the new one. Will use increase_take" ) return increase_take_extrinsic( @@ -643,7 +643,7 @@ def set_take( wait_for_finalization=wait_for_finalization, ) else: - bittensor.__console__.print( + settings.bt_console.print( "Current take is higher than the new one. Will use decrease_take" ) return decrease_take_extrinsic( @@ -657,7 +657,7 @@ def set_take( def send_extrinsic( self, - wallet: "bittensor.wallet", + wallet: "Wallet", module: str, function: str, params: dict, @@ -673,7 +673,7 @@ def send_extrinsic( constructs and submits the extrinsic, handling retries and blockchain communication. Args: - wallet (bittensor.wallet): The wallet associated with the extrinsic. + wallet (bittensor_wallet.Wallet): The wallet associated with the extrinsic. module (str): The module name for the extrinsic. function (str): The function name for the extrinsic. params (dict): The parameters for the extrinsic. @@ -744,14 +744,14 @@ def send_extrinsic( except SubstrateRequestException as e: if "Priority is too low" in e.args[0]["message"]: wait = min(wait_time * attempt, max_wait) - _logger.warning( + logging.warning( f"Priority is too low, retrying with new nonce: {nonce} in {wait} seconds." ) nonce = nonce + 1 time.sleep(wait) continue else: - _logger.error(f"Error sending extrinsic: {e}") + logging.error(f"Error sending extrinsic: {e}") response = None return response @@ -762,11 +762,11 @@ def send_extrinsic( # TODO: still needed? Can't find any usage of this method. def set_weights( self, - wallet: "bittensor.wallet", + wallet: "Wallet", netuid: int, uids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], - version_key: int = bittensor.__version_as_int__, + version_key: int = settings.version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, prompt: bool = False, @@ -778,7 +778,7 @@ def set_weights( of Bittensor's decentralized learning architecture. Args: - wallet (bittensor.wallet): The wallet associated with the neuron setting the weights. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron setting the weights. netuid (int): The unique identifier of the subnet. uids (Union[NDArray[np.int64], torch.LongTensor, list]): The list of neuron UIDs that the weights are being set for. @@ -818,7 +818,7 @@ def set_weights( prompt=prompt, ) except Exception as e: - _logger.error(f"Error setting weights: {e}") + logging.error(f"Error setting weights: {e}") finally: retries += 1 @@ -826,11 +826,11 @@ def set_weights( def _do_set_weights( self, - wallet: "bittensor.wallet", + wallet: "Wallet", uids: List[int], vals: List[int], netuid: int, - version_key: int = bittensor.__version_as_int__, + version_key: int = settings.version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, ) -> Tuple[bool, Optional[str]]: # (success, error_message) @@ -840,7 +840,7 @@ def _do_set_weights( retries and blockchain communication. Args: - wallet (bittensor.wallet): The wallet associated with the neuron setting the weights. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron setting the weights. uids (List[int]): List of neuron UIDs for which weights are being set. vals (List[int]): List of weight values corresponding to each UID. netuid (int): Unique identifier for the network. @@ -855,7 +855,7 @@ def _do_set_weights( trust in other neurons based on observed performance and contributions. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -895,12 +895,12 @@ def make_substrate_call_with_retry(): ################## def commit_weights( self, - wallet: "bittensor.wallet", + wallet: "Wallet", netuid: int, salt: List[int], uids: Union[NDArray[np.int64], list], weights: Union[NDArray[np.int64], list], - version_key: int = bittensor.__version_as_int__, + version_key: int = settings.version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, prompt: bool = False, @@ -911,7 +911,7 @@ def commit_weights( This action serves as a commitment or snapshot of the neuron's current weight distribution. Args: - wallet (bittensor.wallet): The wallet associated with the neuron committing the weights. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the weights. netuid (int): The unique identifier of the subnet. salt (List[int]): list of randomly generated integers as salt to generated weighted hash. uids (np.ndarray): NumPy array of neuron UIDs for which weights are being committed. @@ -933,7 +933,7 @@ def commit_weights( success = False message = "No attempt made. Perhaps it is too soon to commit weights!" - _logger.info( + logging.info( "Committing weights with params: netuid={}, uids={}, weights={}, version_key={}".format( netuid, uids, weights, version_key ) @@ -949,7 +949,7 @@ def commit_weights( version_key=version_key, ) - _logger.info("Commit Hash: {}".format(commit_hash)) + logging.info("Commit Hash: {}".format(commit_hash)) while retries < max_retries: try: @@ -965,7 +965,7 @@ def commit_weights( if success: break except Exception as e: - bittensor.logging.error(f"Error committing weights: {e}") + logging.error(f"Error committing weights: {e}") finally: retries += 1 @@ -973,7 +973,7 @@ def commit_weights( def _do_commit_weights( self, - wallet: "bittensor.wallet", + wallet: "Wallet", netuid: int, commit_hash: str, wait_for_inclusion: bool = False, @@ -984,7 +984,7 @@ def _do_commit_weights( This method constructs and submits the transaction, handling retries and blockchain communication. Args: - wallet (bittensor.wallet): The wallet associated with the neuron committing the weights. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the weights. netuid (int): The unique identifier of the subnet. commit_hash (str): The hash of the neuron's weights to be committed. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. @@ -997,7 +997,7 @@ def _do_commit_weights( verifiable record of the neuron's weight distribution at a specific point in time. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -1033,12 +1033,12 @@ def make_substrate_call_with_retry(): ################## def reveal_weights( self, - wallet: "bittensor.wallet", + wallet: "Wallet", netuid: int, uids: Union[NDArray[np.int64], list], weights: Union[NDArray[np.int64], list], salt: Union[NDArray[np.int64], list], - version_key: int = bittensor.__version_as_int__, + version_key: int = settings.version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, prompt: bool = False, @@ -1049,7 +1049,7 @@ def reveal_weights( This action serves as a revelation of the neuron's previously committed weight distribution. Args: - wallet (bittensor.wallet): The wallet associated with the neuron revealing the weights. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. netuid (int): The unique identifier of the subnet. uids (np.ndarray): NumPy array of neuron UIDs for which weights are being revealed. weights (np.ndarray): NumPy array of weight values corresponding to each UID. @@ -1089,7 +1089,7 @@ def reveal_weights( if success: break except Exception as e: - bittensor.logging.error(f"Error revealing weights: {e}") + logging.error(f"Error revealing weights: {e}") finally: retries += 1 @@ -1097,7 +1097,7 @@ def reveal_weights( def _do_reveal_weights( self, - wallet: "bittensor.wallet", + wallet: "Wallet", netuid: int, uids: List[int], values: List[int], @@ -1111,7 +1111,7 @@ def _do_reveal_weights( This method constructs and submits the transaction, handling retries and blockchain communication. Args: - wallet (bittensor.wallet): The wallet associated with the neuron revealing the weights. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron revealing the weights. netuid (int): The unique identifier of the subnet. uids (List[int]): List of neuron UIDs for which weights are being revealed. values (List[int]): List of weight values corresponding to each UID. @@ -1127,7 +1127,7 @@ def _do_reveal_weights( and accountability for the neuron's weight distribution. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -1166,7 +1166,7 @@ def make_substrate_call_with_retry(): ################ def register( self, - wallet: "bittensor.wallet", + wallet: "Wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -1186,7 +1186,7 @@ def register( it to stake, set weights, and receive incentives. Args: - wallet (bittensor.wallet): The wallet associated with the neuron to be registered. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron to be registered. netuid (int): The unique identifier of the subnet. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. Defaults to `False`. @@ -1228,8 +1228,8 @@ def register( def swap_hotkey( self, - wallet: "bittensor.wallet", - new_wallet: "bittensor.wallet", + wallet: "Wallet", + new_wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, @@ -1241,8 +1241,8 @@ def swap_hotkey( options to wait for inclusion and finalization of the transaction, and to prompt the user for confirmation. Args: - wallet (bittensor.wallet): The wallet whose hotkey is to be swapped. - new_wallet (bittensor.wallet): The new wallet with the hotkey to be set. + wallet (bittensor_wallet.Wallet): The wallet whose hotkey is to be swapped. + new_wallet (bittensor_wallet.Wallet): The new wallet with the hotkey to be set. wait_for_inclusion (bool): Whether to wait for the transaction to be included in a block. Default is `False`. wait_for_finalization (bool): Whether to wait for the transaction to be finalized. Default is `True`. @@ -1262,7 +1262,7 @@ def swap_hotkey( def run_faucet( self, - wallet: "bittensor.wallet", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, @@ -1281,7 +1281,7 @@ def run_faucet( Bittensor network, enabling them to start with a small stake on testnet only. Args: - wallet (bittensor.wallet): The wallet for which the faucet transaction is to be run. + wallet (bittensor_wallet.Wallet): The wallet for which the faucet transaction is to be run. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. Defaults to `False`. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. @@ -1326,7 +1326,7 @@ def run_faucet( def burned_register( self, - wallet: "bittensor.wallet", + wallet: "Wallet", netuid: int, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -1337,7 +1337,7 @@ def burned_register( involves recycling TAO tokens, allowing them to be re-mined by performing work on the network. Args: - wallet (bittensor.wallet): The wallet associated with the neuron to be registered. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron to be registered. netuid (int): The unique identifier of the subnet. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. Defaults to `False`. @@ -1360,7 +1360,7 @@ def burned_register( def _do_pow_register( self, netuid: int, - wallet: "bittensor.wallet", + wallet: "Wallet", pow_result: POWSolution, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -1369,7 +1369,7 @@ def _do_pow_register( Args: netuid (int): The subnet to register on. - wallet (bittensor.wallet): The wallet to register. + wallet (bittensor_wallet.Wallet): The wallet to register. pow_result (POWSolution): The PoW result to register. wait_for_inclusion (bool): If ``True``, waits for the extrinsic to be included in a block. Default to `False`. @@ -1381,7 +1381,7 @@ def _do_pow_register( message. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): # create extrinsic call call = self.substrate.compose_call( @@ -1422,7 +1422,7 @@ def make_substrate_call_with_retry(): def _do_burned_register( self, netuid: int, - wallet: "bittensor.wallet", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> Tuple[bool, Optional[str]]: @@ -1434,7 +1434,7 @@ def _do_burned_register( Args: netuid (int): The network unique identifier to register on. - wallet (bittensor.wallet): The wallet to be registered. + wallet (bittensor_wallet.Wallet): The wallet to be registered. wait_for_inclusion (bool): Whether to wait for the transaction to be included in a block. Default is False. wait_for_finalization (bool): Whether to wait for the transaction to be finalized. Default is True. @@ -1442,7 +1442,7 @@ def _do_burned_register( Tuple[bool, Optional[str]]: A tuple containing a boolean indicating success or failure, and an optional error message. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): # create extrinsic call call = self.substrate.compose_call( @@ -1478,8 +1478,8 @@ def make_substrate_call_with_retry(): def _do_swap_hotkey( self, - wallet: "bittensor.wallet", - new_wallet: "bittensor.wallet", + wallet: "Wallet", + new_wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> Tuple[bool, Optional[str]]: @@ -1487,8 +1487,8 @@ def _do_swap_hotkey( Performs a hotkey swap extrinsic call to the Subtensor chain. Args: - wallet (bittensor.wallet): The wallet whose hotkey is to be swapped. - new_wallet (bittensor.wallet): The wallet with the new hotkey to be set. + wallet (bittensor_wallet.Wallet): The wallet whose hotkey is to be swapped. + new_wallet (bittensor_wallet.Wallet): The wallet with the new hotkey to be set. wait_for_inclusion (bool): Whether to wait for the transaction to be included in a block. Default is `False`. wait_for_finalization (bool): Whether to wait for the transaction to be finalized. Default is `True`. @@ -1498,7 +1498,7 @@ def _do_swap_hotkey( error message. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): # create extrinsic call call = self.substrate.compose_call( @@ -1537,7 +1537,7 @@ def make_substrate_call_with_retry(): ############ def transfer( self, - wallet: "bittensor.wallet", + wallet: "Wallet", dest: str, amount: Union[Balance, float], wait_for_inclusion: bool = True, @@ -1550,7 +1550,7 @@ def transfer( between neurons. Args: - wallet (bittensor.wallet): The wallet from which funds are being transferred. + wallet (bittensor_wallet.Wallet): The wallet from which funds are being transferred. dest (str): The destination public key address. amount (Union[Balance, float]): The amount of TAO to be transferred. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. @@ -1574,7 +1574,7 @@ def transfer( ) def get_transfer_fee( - self, wallet: "bittensor.wallet", dest: str, value: Union["Balance", float, int] + self, wallet: "Wallet", dest: str, value: Union["Balance", float, int] ) -> "Balance": """ Calculates the transaction fee for transferring tokens from a wallet to a specified destination address. @@ -1582,7 +1582,7 @@ def get_transfer_fee( network conditions and transaction complexity. Args: - wallet (bittensor.wallet): The wallet from which the transfer is initiated. + wallet (bittensor_wallet.Wallet): The wallet from which the transfer is initiated. dest (str): The ``SS58`` address of the destination account. value (Union[Balance, float, int]): The amount of tokens to be transferred, specified as a Balance object, or in Tao (float) or Rao (int) units. @@ -1611,7 +1611,7 @@ def get_transfer_fee( call=call, keypair=wallet.coldkeypub ) except Exception as e: - bittensor.__console__.print( + settings.bt_console.print( ":cross_mark: [red]Failed to get payment info[/red]:[bold white]\n {}[/bold white]".format( e ) @@ -1622,7 +1622,7 @@ def get_transfer_fee( return fee else: fee = Balance.from_rao(int(2e7)) - _logger.error( + logging.error( "To calculate the transaction fee, the value must be Balance, float, or int. Received type: %s. Fee " "is %s", type(value), @@ -1632,7 +1632,7 @@ def get_transfer_fee( def _do_transfer( self, - wallet: "bittensor.wallet", + wallet: "Wallet", dest: str, transfer_balance: "Balance", wait_for_inclusion: bool = True, @@ -1641,19 +1641,19 @@ def _do_transfer( """Sends a transfer extrinsic to the chain. Args: - wallet (:func:`bittensor.wallet`): Wallet object. + wallet (bittensor_wallet.Wallet): Wallet object. dest (str): Destination public key address. - transfer_balance (:func:`Balance`): Amount to transfer. + transfer_balance (bittensor.utils.balance.Balance): Amount to transfer. wait_for_inclusion (bool): If ``true``, waits for inclusion. wait_for_finalization (bool): If ``true``, waits for finalization. + Returns: success (bool): ``True`` if transfer was successful. - block_hash (str): Block hash of the transfer. On success and if wait_for_ finalization/inclusion is - ``True``. + block_hash (str): Block hash of the transfer. On success and if wait_for_ finalization/inclusion is ``True``. error (str): Error message if transfer failed. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="Balances", @@ -1714,7 +1714,7 @@ def get_existential_deposit( ########### def register_subnetwork( self, - wallet: "bittensor.wallet", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization=True, prompt: bool = False, @@ -1725,7 +1725,7 @@ def register_subnetwork( overall Bittensor network. Args: - wallet (bittensor.wallet): The wallet to be used for registration. + wallet (bittensor_wallet.Wallet): The wallet to be used for registration. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. @@ -1746,7 +1746,7 @@ def register_subnetwork( def set_hyperparameter( self, - wallet: "bittensor.wallet", + wallet: "Wallet", netuid: int, parameter: str, value, @@ -1760,7 +1760,7 @@ def set_hyperparameter( subnetwork. Args: - wallet (bittensor.wallet): The wallet used for setting the hyperparameter. + wallet (bittensor_wallet.Wallet): The wallet used for setting the hyperparameter. netuid (int): The unique identifier of the subnetwork. parameter (str): The name of the hyperparameter to be set. value: The new value for the hyperparameter. @@ -1790,7 +1790,7 @@ def set_hyperparameter( ########### def serve( self, - wallet: "bittensor.wallet", + wallet: "Wallet", ip: str, port: int, protocol: int, @@ -1806,7 +1806,7 @@ def serve( communication within the network. Args: - wallet (bittensor.wallet): The wallet associated with the neuron being served. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron being served. ip (str): The IP address of the serving neuron. port (int): The port number on which the neuron is serving. protocol (int): The protocol type used by the neuron (e.g., GRPC, HTTP). @@ -1840,7 +1840,7 @@ def serve( def serve_axon( self, netuid: int, - axon: "bittensor.axon", + axon: "Axon", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> bool: @@ -1851,7 +1851,7 @@ def serve_axon( Args: netuid (int): The unique identifier of the subnetwork. - axon (bittensor.Axon): The Axon instance to be registered for serving. + axon (bittensor.core.axon.Axon): The Axon instance to be registered for serving. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. @@ -1867,7 +1867,7 @@ def serve_axon( def _do_serve_axon( self, - wallet: "bittensor.wallet", + wallet: "Wallet", call_params: AxonServeCallParams, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -1877,7 +1877,7 @@ def _do_serve_axon( creates and submits a transaction, enabling a neuron's Axon to serve requests on the network. Args: - wallet (bittensor.wallet): The wallet associated with the neuron. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron. call_params (AxonServeCallParams): Parameters required for the serve axon call. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. @@ -1889,7 +1889,7 @@ def _do_serve_axon( enhancing the decentralized computation capabilities of Bittensor. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -1917,7 +1917,7 @@ def make_substrate_call_with_retry(): def serve_prometheus( self, - wallet: "bittensor.wallet", + wallet: "Wallet", port: int, netuid: int, wait_for_inclusion: bool = False, @@ -1934,7 +1934,7 @@ def serve_prometheus( def _do_serve_prometheus( self, - wallet: "bittensor.wallet", + wallet: "Wallet", call_params: PrometheusServeCallParams, wait_for_inclusion: bool = False, wait_for_finalization: bool = True, @@ -1942,7 +1942,7 @@ def _do_serve_prometheus( """ Sends a serve prometheus extrinsic to the chain. Args: - wallet (:func:`bittensor.wallet`): Wallet object. + wallet (:func:`bittensor_wallet.Wallet`): Wallet object. call_params (:func:`PrometheusServeCallParams`): Prometheus serve call parameters. wait_for_inclusion (bool): If ``true``, waits for inclusion. wait_for_finalization (bool): If ``true``, waits for finalization. @@ -1951,7 +1951,7 @@ def _do_serve_prometheus( error (:func:`Optional[str]`): Error message if serve prometheus failed, ``None`` otherwise. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -1979,7 +1979,7 @@ def make_substrate_call_with_retry(): def _do_associate_ips( self, - wallet: "bittensor.wallet", + wallet: "Wallet", ip_info_list: List["IPInfo"], netuid: int, wait_for_inclusion: bool = False, @@ -1989,7 +1989,7 @@ def _do_associate_ips( Sends an associate IPs extrinsic to the chain. Args: - wallet (:func:`bittensor.wallet`): Wallet object. + wallet (bittensor_wallet.Wallet): Wallet object. ip_info_list (:func:`List[IPInfo]`): List of IPInfo objects. netuid (int): Netuid to associate IPs to. wait_for_inclusion (bool): If ``true``, waits for inclusion. @@ -2000,7 +2000,7 @@ def _do_associate_ips( error (:func:`Optional[str]`): Error message if associate IPs failed, None otherwise. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -2034,7 +2034,7 @@ def make_substrate_call_with_retry(): ########### def add_stake( self, - wallet: "bittensor.wallet", + wallet: "Wallet", hotkey_ss58: Optional[str] = None, amount: Optional[Union["Balance", float]] = None, wait_for_inclusion: bool = True, @@ -2047,7 +2047,7 @@ def add_stake( and earn incentives. Args: - wallet (bittensor.wallet): The wallet to be used for staking. + wallet (bittensor_wallet.Wallet): The wallet to be used for staking. hotkey_ss58 (Optional[str]): The ``SS58`` address of the hotkey associated with the neuron. amount (Union[Balance, float]): The amount of TAO to stake. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. @@ -2072,7 +2072,7 @@ def add_stake( def add_stake_multiple( self, - wallet: "bittensor.wallet", + wallet: "Wallet", hotkey_ss58s: List[str], amounts: Optional[List[Union["Balance", float]]] = None, wait_for_inclusion: bool = True, @@ -2084,7 +2084,7 @@ def add_stake_multiple( allows for efficient staking across different neurons from a single wallet. Args: - wallet (bittensor.wallet): The wallet used for staking. + wallet (bittensor_wallet.Wallet): The wallet used for staking. hotkey_ss58s (List[str]): List of ``SS58`` addresses of hotkeys to stake to. amounts (List[Union[Balance, float]], optional): Corresponding amounts of TAO to stake for each hotkey. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. @@ -2109,7 +2109,7 @@ def add_stake_multiple( def _do_stake( self, - wallet: "bittensor.wallet", + wallet: "Wallet", hotkey_ss58: str, amount: "Balance", wait_for_inclusion: bool = True, @@ -2118,18 +2118,20 @@ def _do_stake( """Sends a stake extrinsic to the chain. Args: - wallet (:func:`bittensor.wallet`): Wallet object that can sign the extrinsic. + wallet (bittensor_wallet.Wallet): Wallet object that can sign the extrinsic. hotkey_ss58 (str): Hotkey ``ss58`` address to stake to. amount (:func:`Balance`): Amount to stake. wait_for_inclusion (bool): If ``true``, waits for inclusion before returning. wait_for_finalization (bool): If ``true``, waits for finalization before returning. + Returns: success (bool): ``True`` if the extrinsic was successful. + Raises: StakeError: If the extrinsic failed. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -2161,7 +2163,7 @@ def make_substrate_call_with_retry(): ############# def unstake_multiple( self, - wallet: "bittensor.wallet", + wallet: "Wallet", hotkey_ss58s: List[str], amounts: Optional[List[Union["Balance", float]]] = None, wait_for_inclusion: bool = True, @@ -2173,7 +2175,7 @@ def unstake_multiple( efficiently. This function is useful for managing the distribution of stakes across multiple neurons. Args: - wallet (bittensor.wallet): The wallet linked to the coldkey from which the stakes are being withdrawn. + wallet (bittensor_wallet.Wallet): The wallet linked to the coldkey from which the stakes are being withdrawn. hotkey_ss58s (List[str]): A list of hotkey ``SS58`` addresses to unstake from. amounts (List[Union[Balance, float]], optional): The amounts of TAO to unstake from each hotkey. If not provided, unstakes all available stakes. @@ -2199,7 +2201,7 @@ def unstake_multiple( def unstake( self, - wallet: "bittensor.wallet", + wallet: "Wallet", hotkey_ss58: Optional[str] = None, amount: Optional[Union["Balance", float]] = None, wait_for_inclusion: bool = True, @@ -2211,7 +2213,7 @@ def unstake( individual neuron stakes within the Bittensor network. Args: - wallet (bittensor.wallet): The wallet associated with the neuron from which the stake is being removed. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron from which the stake is being removed. hotkey_ss58 (Optional[str]): The ``SS58`` address of the hotkey account to unstake from. amount (Union[Balance, float], optional): The amount of TAO to unstake. If not specified, unstakes all. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. @@ -2236,7 +2238,7 @@ def unstake( def _do_unstake( self, - wallet: "bittensor.wallet", + wallet: "Wallet", hotkey_ss58: str, amount: "Balance", wait_for_inclusion: bool = True, @@ -2245,7 +2247,7 @@ def _do_unstake( """Sends an unstake extrinsic to the chain. Args: - wallet (:func:`bittensor.wallet`): Wallet object that can sign the extrinsic. + wallet (:func:`Wallet`): Wallet object that can sign the extrinsic. hotkey_ss58 (str): Hotkey ``ss58`` address to unstake from. amount (:func:`Balance`): Amount to unstake. wait_for_inclusion (bool): If ``true``, waits for inclusion before returning. @@ -2256,7 +2258,7 @@ def _do_unstake( StakeError: If the extrinsic failed. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -2329,7 +2331,7 @@ def get_remaining_arbitration_period( def register_senate( self, - wallet: "bittensor.wallet", + wallet: "Wallet", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, prompt: bool = False, @@ -2339,7 +2341,7 @@ def register_senate( individual neuron stakes within the Bittensor network. Args: - wallet (bittensor.wallet): The wallet associated with the neuron from which the stake is being removed. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron from which the stake is being removed. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. @@ -2356,7 +2358,7 @@ def register_senate( def leave_senate( self, - wallet: "bittensor.wallet", + wallet: "Wallet", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, prompt: bool = False, @@ -2366,7 +2368,7 @@ def leave_senate( individual neuron stakes within the Bittensor network. Args: - wallet (bittensor.wallet): The wallet associated with the neuron from which the stake is being removed. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron from which the stake is being removed. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. @@ -2383,7 +2385,7 @@ def leave_senate( def vote_senate( self, - wallet: "bittensor.wallet", + wallet: "Wallet", proposal_hash: str, proposal_idx: int, vote: bool, @@ -2396,7 +2398,7 @@ def vote_senate( individual neuron stakes within the Bittensor network. Args: - wallet (bittensor.wallet): The wallet associated with the neuron from which the stake is being removed. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron from which the stake is being removed. proposal_hash (str): The hash of the proposal being voted on. proposal_idx (int): The index of the proposal being voted on. vote (bool): The vote to be cast (True for yes, False for no). @@ -2553,11 +2555,9 @@ def get_proposals( block (Optional[int]): The blockchain block number to query the proposals. Returns: - Optional[Dict[str, Tuple[bittensor.ProposalCallData, bittensor.ProposalVoteData]]]: A dictionary mapping - proposal hashes to their corresponding call and vote data, or ``None`` if not available. + Optional[Dict[str, Tuple[bittensor.core.chain_data.ProposalCallData, bittensor.core.chain_data.ProposalVoteData]]]: A dictionary mapping proposal hashes to their corresponding call and vote data, or ``None`` if not available. - This function is integral for analyzing the governance activity on the Bittensor network, - providing a holistic view of the proposals and their impact or potential changes within the network. + This function is integral for analyzing the governance activity on the Bittensor network, providing a holistic view of the proposals and their impact or potential changes within the network. """ proposal_hashes: Optional[List[str]] = self.get_proposal_hashes(block=block) if proposal_hashes is None: @@ -2576,17 +2576,16 @@ def get_proposals( def root_register( self, - wallet: "bittensor.wallet", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, prompt: bool = False, ) -> bool: """ - Registers the neuron associated with the wallet on the root network. This process is integral for - participating in the highest layer of decision-making and governance within the Bittensor network. + Registers the neuron associated with the wallet on the root network. This process is integral for participating in the highest layer of decision-making and governance within the Bittensor network. Args: - wallet (bittensor.wallet): The wallet associated with the neuron to be registered on the root network. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron to be registered on the root network. wait_for_inclusion (bool, optional): Waits for the transaction to be included in a block. wait_for_finalization (bool, optional): Waits for the transaction to be finalized on the blockchain. prompt (bool, optional): If ``True``, prompts for user confirmation before proceeding. @@ -2594,8 +2593,7 @@ def root_register( Returns: bool: ``True`` if the registration on the root network is successful, False otherwise. - This function enables neurons to engage in the most critical and influential aspects of the network's - governance, signifying a high level of commitment and responsibility in the Bittensor ecosystem. + This function enables neurons to engage in the most critical and influential aspects of the network's governance, signifying a high level of commitment and responsibility in the Bittensor ecosystem. """ return root_register_extrinsic( subtensor=self, @@ -2607,11 +2605,11 @@ def root_register( def _do_root_register( self, - wallet: "bittensor.wallet", + wallet: "Wallet", wait_for_inclusion: bool = False, wait_for_finalization: bool = True, ) -> Tuple[bool, Optional[str]]: - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): # create extrinsic call call = self.substrate.compose_call( @@ -2645,7 +2643,7 @@ def make_substrate_call_with_retry(): @legacy_torch_api_compat def root_set_weights( self, - wallet: "bittensor.wallet", + wallet: "Wallet", netuids: Union[NDArray[np.int64], "torch.LongTensor", list], weights: Union[NDArray[np.float32], "torch.FloatTensor", list], version_key: int = 0, @@ -2658,7 +2656,7 @@ def root_set_weights( and interactions of neurons at the root level of the Bittensor network. Args: - wallet (bittensor.wallet): The wallet associated with the neuron setting the weights. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron setting the weights. netuids (Union[NDArray[np.int64], torch.LongTensor, list]): The list of neuron UIDs for which weights are being set. weights (Union[NDArray[np.float32], torch.FloatTensor, list]): The corresponding weights to be set for each @@ -2687,11 +2685,11 @@ def root_set_weights( def _do_set_root_weights( self, - wallet: "bittensor.wallet", + wallet: "Wallet", uids: List[int], vals: List[int], netuid: int = 0, - version_key: int = bittensor.__version_as_int__, + version_key: int = settings.version_as_int, wait_for_inclusion: bool = False, wait_for_finalization: bool = False, ) -> Tuple[bool, Optional[str]]: # (success, error_message) @@ -2701,7 +2699,7 @@ def _do_set_root_weights( retries and blockchain communication. Args: - wallet (bittensor.wallet): The wallet associated with the neuron setting the weights. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron setting the weights. uids (List[int]): List of neuron UIDs for which weights are being set. vals (List[int]): List of weight values corresponding to each UID. netuid (int): Unique identifier for the network. @@ -2713,10 +2711,10 @@ def _do_set_root_weights( Tuple[bool, Optional[str]]: A tuple containing a success flag and an optional error message. This method is vital for the dynamic weighting mechanism in Bittensor, where neurons adjust their - trust in other neurons based on observed performance and contributions on the root network. + trust in other neurons based on observed performance and contributions to the root network. """ - @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=2, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -2742,7 +2740,7 @@ def make_substrate_call_with_retry(): ) # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: - return True, "Not waiting for finalziation or inclusion." + return True, "Not waiting for finalization or inclusion." response.process_events() if response.is_success: @@ -2782,7 +2780,7 @@ def query_identity( network-specific details, providing insights into the neuron's role and status within the Bittensor network. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry() -> "ScaleType": return self.substrate.query( module="Registry", @@ -2795,13 +2793,11 @@ def make_substrate_call_with_retry() -> "ScaleType": identity_info = make_substrate_call_with_retry() - return bittensor.utils.wallet_utils.decode_hex_identity_dict( - identity_info.value["info"] - ) + return decode_hex_identity_dict(identity_info.value["info"]) def update_identity( self, - wallet: "bittensor.wallet", + wallet: "Wallet", identified: Optional[str] = None, params: Optional[dict] = None, wait_for_inclusion: bool = True, @@ -2816,7 +2812,7 @@ def update_identity( parameters. Args: - wallet (bittensor.wallet): The wallet associated with the neuron whose identity is being updated. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron whose identity is being updated. identified (str, optional): The identified ``SS58`` address of the neuron. Defaults to the wallet's coldkey address. params (dict, optional): A dictionary of parameters to update in the neuron's identity. @@ -2834,10 +2830,10 @@ def update_identity( params = {} if params is None else params - call_params = bittensor.utils.wallet_utils.create_identity_dict(**params) + call_params = create_identity_dict(**params) call_params["identified"] = identified - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry() -> bool: call = self.substrate.compose_call( call_module="Registry", @@ -2869,7 +2865,7 @@ def commit(self, wallet, netuid: int, data: str): Commits arbitrary data to the Bittensor network by publishing metadata. Args: - wallet (bittensor.wallet): The wallet associated with the neuron committing the data. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron committing the data. netuid (int): The unique identifier of the subnetwork. data (str): The data to be committed to the network. """ @@ -2924,7 +2920,7 @@ def query_subtensor( providing valuable insights into the state and dynamics of the Bittensor ecosystem. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry() -> "ScaleType": return self.substrate.query( module="SubtensorModule", @@ -2961,7 +2957,7 @@ def query_map_subtensor( relationships within the Bittensor ecosystem, such as inter-neuronal connections and stake distributions. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): return self.substrate.query_map( module="SubtensorModule", @@ -2995,7 +2991,7 @@ def query_constant( operational parameters. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): return self.substrate.get_constant( module_name=module_name, @@ -3033,7 +3029,7 @@ def query_module( parts of the Bittensor blockchain, enhancing the understanding and analysis of the network's state and dynamics. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry() -> "ScaleType": return self.substrate.query( module=module, @@ -3072,7 +3068,7 @@ def query_map( modules, offering insights into the network's state and the relationships between its different components. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry() -> "QueryMapResult": return self.substrate.query_map( module=module, @@ -3107,7 +3103,7 @@ def state_call( useful for specific use cases where standard queries are insufficient. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry() -> Dict[Any, Any]: block_hash = None if block is None else self.substrate.get_block_hash(block) @@ -3142,9 +3138,9 @@ def query_runtime_api( This function enables access to the deeper layers of the Bittensor blockchain, allowing for detailed and specific interactions with the network's runtime environment. """ - call_definition = bittensor.__type_registry__["runtime_api"][runtime_api][ # type: ignore - "methods" # type: ignore - ][method] # type: ignore + call_definition = settings.type_registry["runtime_api"][runtime_api]["methods"][ + method + ] json_result = self.state_call( method=f"{runtime_api}_{method}", @@ -4165,7 +4161,7 @@ def get_all_subnets_info(self, block: Optional[int] = None) -> List[SubnetInfo]: the roles of different subnets, and their unique features. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): block_hash = None if block is None else self.substrate.get_block_hash(block) @@ -4199,7 +4195,7 @@ def get_subnet_info( subnet, including its governance, performance, and role within the broader network. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): block_hash = None if block is None else self.substrate.get_block_hash(block) @@ -4357,7 +4353,7 @@ def get_delegate_by_hotkey( the Bittensor network's consensus and governance structures. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(encoded_hotkey_: List[int]): block_hash = None if block is None else self.substrate.get_block_hash(block) @@ -4394,7 +4390,7 @@ def get_delegates_lite(self, block: Optional[int] = None) -> List[DelegateInfoLi """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): block_hash = None if block is None else self.substrate.get_block_hash(block) @@ -4426,7 +4422,7 @@ def get_delegates(self, block: Optional[int] = None) -> List[DelegateInfo]: """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): block_hash = None if block is None else self.substrate.get_block_hash(block) @@ -4461,7 +4457,7 @@ def get_delegated( involvement in the network's delegation and consensus mechanisms. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(encoded_coldkey_: List[int]): block_hash = None if block is None else self.substrate.get_block_hash(block) @@ -4574,7 +4570,7 @@ def get_minimum_required_stake( Exception: If the substrate call fails after the maximum number of retries. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): return self.substrate.query( module="SubtensorModule", storage_function="NominatorMinRequiredStake" @@ -4789,7 +4785,7 @@ def neuron_has_validator_permit( return getattr(_result, "value", None) def neuron_for_wallet( - self, wallet: "bittensor.wallet", netuid: int, block: Optional[int] = None + self, wallet: "Wallet", netuid: int, block: Optional[int] = None ) -> Optional[NeuronInfo]: """ Retrieves information about a neuron associated with a given wallet on a specific subnet. @@ -4797,7 +4793,7 @@ def neuron_for_wallet( the wallet's hotkey address. Args: - wallet (bittensor.wallet): The wallet associated with the neuron. + wallet (bittensor_wallet.Wallet): The wallet associated with the neuron. netuid (int): The unique identifier of the subnet. block (Optional[int]): The blockchain block number at which to perform the query. @@ -4833,7 +4829,7 @@ def neuron_for_uid( if uid is None: return NeuronInfo.get_null_neuron() - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): block_hash = None if block is None else self.substrate.get_block_hash(block) params = [netuid, uid] @@ -4964,7 +4960,7 @@ def metagraph( netuid: int, lite: bool = True, block: Optional[int] = None, - ) -> "bittensor.metagraph": # type: ignore + ) -> "metagraph": # type: ignore """ Returns a synced metagraph for a specified subnet within the Bittensor network. The metagraph represents the network's structure, including neuron connections and interactions. @@ -4975,13 +4971,12 @@ def metagraph( block (Optional[int]): Block number for synchronization, or ``None`` for the latest block. Returns: - bittensor.Metagraph: The metagraph representing the subnet's structure and neuron relationships. + bittensor.core.metagraph.Metagraph: The metagraph representing the subnet's structure and neuron relationships. The metagraph is an essential tool for understanding the topology and dynamics of the Bittensor - network's decentralized architecture, particularly in relation to neuron interconnectivity and consensus - processes. + network's decentralized architecture, particularly in relation to neuron interconnectivity and consensus processes. """ - metagraph_ = bittensor.metagraph( + metagraph_ = Metagraph( network=self.network, netuid=netuid, lite=lite, sync=False ) metagraph_.sync(block=block, lite=lite, subtensor=self) @@ -5077,19 +5072,16 @@ def associated_validator_ip_info( self, netuid: int, block: Optional[int] = None ) -> Optional[List["IPInfo"]]: """ - Retrieves the list of all validator IP addresses associated with a specific subnet in the Bittensor - network. This information is crucial for network communication and the identification of validator nodes. + Retrieves the list of all validator IP addresses associated with a specific subnet in the Bittensor network. This information is crucial for network communication and the identification of validator nodes. Args: netuid (int): The network UID of the subnet to query. block (Optional[int]): The blockchain block number for the query. Returns: - Optional[List[IPInfo]]: A list of IPInfo objects for validator nodes in the subnet, or ``None`` if no - validators are associated. + Optional[List[IPInfo]]: A list of IPInfo objects for validator nodes in the subnet, or ``None`` if no validators are associated. - Validator IP information is key for establishing secure and reliable connections within the network, - facilitating consensus and validation processes critical for the network's integrity and performance. + Validator IP information is key for establishing secure and reliable connections within the network, facilitating consensus and validation processes critical for the network's integrity and performance. """ hex_bytes_result = self.query_runtime_api( runtime_api="ValidatorIPRuntimeApi", @@ -5110,8 +5102,7 @@ def associated_validator_ip_info( def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[str]: """ - Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost - represents the amount of Tao that needs to be locked or burned to establish a new subnet. + Retrieves the burn cost for registering a new subnet within the Bittensor network. This cost represents the amount of Tao that needs to be locked or burned to establish a new subnet. Args: block (Optional[int]): The blockchain block number for the query. @@ -5119,8 +5110,7 @@ def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[str]: Returns: int: The burn cost for subnet registration. - The subnet burn cost is an important economic parameter, reflecting the network's mechanisms for - controlling the proliferation of subnets and ensuring their commitment to the network's long-term viability. + The subnet burn cost is an important economic parameter, reflecting the network's mechanisms for controlling the proliferation of subnets and ensuring their commitment to the network's long-term viability. """ lock_cost = self.query_runtime_api( runtime_api="SubnetRegistrationRuntimeApi", @@ -5140,7 +5130,7 @@ def get_subnet_burn_cost(self, block: Optional[int] = None) -> Optional[str]: def _do_delegation( self, - wallet: "bittensor.wallet", + wallet: "Wallet", delegate_ss58: str, amount: "Balance", wait_for_inclusion: bool = True, @@ -5149,11 +5139,10 @@ def _do_delegation( """ Delegates a specified amount of stake to a delegate's hotkey. - This method sends a transaction to add stake to a delegate's hotkey and retries the call up to three times - with exponential backoff in case of failures. + This method sends a transaction to add stake to a delegate's hotkey and retries the call up to three times with exponential backoff in case of failures. Args: - wallet (bittensor.wallet): The wallet from which the stake will be delegated. + wallet (bittensor_wallet.Wallet): The wallet from which the stake will be delegated. delegate_ss58 (str): The SS58 address of the delegate's hotkey. amount (Balance): The amount of stake to be delegated. wait_for_inclusion (bool, optional): Whether to wait for the transaction to be included in a block. Default is ``True``. @@ -5163,7 +5152,7 @@ def _do_delegation( bool: ``True`` if the delegation is successful, ``False`` otherwise. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -5191,7 +5180,7 @@ def make_substrate_call_with_retry(): def _do_undelegation( self, - wallet: "bittensor.wallet", + wallet: "Wallet", delegate_ss58: str, amount: "Balance", wait_for_inclusion: bool = True, @@ -5200,11 +5189,10 @@ def _do_undelegation( """ Removes a specified amount of stake from a delegate's hotkey. - This method sends a transaction to remove stake from a delegate's hotkey and retries the call up to three times - with exponential backoff in case of failures. + This method sends a transaction to remove stake from a delegate's hotkey and retries the call up to three times with exponential backoff in case of failures. Args: - wallet (bittensor.wallet): The wallet from which the stake will be removed. + wallet (bittensor_wallet.Wallet): The wallet from which the stake will be removed. delegate_ss58 (str): The SS58 address of the delegate's hotkey. amount (Balance): The amount of stake to be removed. wait_for_inclusion (bool, optional): Whether to wait for the transaction to be included in a block. Default is ``True``. @@ -5214,7 +5202,7 @@ def _do_undelegation( bool: ``True`` if the undelegation is successful, ``False`` otherwise. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -5245,18 +5233,17 @@ def make_substrate_call_with_retry(): def _do_nominate( self, - wallet: "bittensor.wallet", + wallet: "Wallet", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> bool: """ Nominates the wallet's hotkey to become a delegate. - This method sends a transaction to nominate the wallet's hotkey to become a delegate and retries the call up to - three times with exponential backoff in case of failures. + This method sends a transaction to nominate the wallet's hotkey to become a delegate and retries the call up to three times with exponential backoff in case of failures. Args: - wallet (bittensor.wallet): The wallet whose hotkey will be nominated. + wallet (bittensor_wallet.Wallet): The wallet whose hotkey will be nominated. wait_for_inclusion (bool, optional): Whether to wait for the transaction to be included in a block. Default is ``True``. wait_for_finalization (bool, optional): Whether to wait for the transaction to be finalized. Default is ``False``. @@ -5264,7 +5251,7 @@ def _do_nominate( bool: ``True`` if the nomination is successful, ``False`` otherwise. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="SubtensorModule", @@ -5292,7 +5279,7 @@ def make_substrate_call_with_retry(): def _do_increase_take( self, - wallet: "bittensor.wallet", + wallet: "Wallet", hotkey_ss58: str, take: int, wait_for_inclusion: bool = True, @@ -5301,11 +5288,10 @@ def _do_increase_take( """ Increases the take rate for a delegate's hotkey. - This method sends a transaction to increase the take rate for a delegate's hotkey and retries the call up to - three times with exponential backoff in case of failures. + This method sends a transaction to increase the take rate for a delegate's hotkey and retries the call up to three times with exponential backoff in case of failures. Args: - wallet (bittensor.wallet): The wallet from which the transaction will be signed. + wallet (bittensor_wallet.Wallet): The wallet from which the transaction will be signed. hotkey_ss58 (str): The SS58 address of the delegate's hotkey. take (int): The new take rate to be set. wait_for_inclusion (bool, optional): Whether to wait for the transaction to be included in a block. Default is ``True``. @@ -5347,7 +5333,7 @@ def make_substrate_call_with_retry(): def _do_decrease_take( self, - wallet: "bittensor.wallet", + wallet: "Wallet", hotkey_ss58: str, take: int, wait_for_inclusion: bool = True, @@ -5356,11 +5342,10 @@ def _do_decrease_take( """ Decreases the take rate for a delegate's hotkey. - This method sends a transaction to decrease the take rate for a delegate's hotkey and retries the call up to - three times with exponential backoff in case of failures. + This method sends a transaction to decrease the take rate for a delegate's hotkey and retries the call up to three times with exponential backoff in case of failures. Args: - wallet (bittensor.wallet): The wallet from which the transaction will be signed. + wallet (bittensor_wallet.Wallet): The wallet from which the transaction will be signed. hotkey_ss58 (str): The SS58 address of the delegate's hotkey. take (int): The new take rate to be set. wait_for_inclusion (bool, optional): Whether to wait for the transaction to be included in a block. Default is ``True``. @@ -5406,8 +5391,7 @@ def make_substrate_call_with_retry(): def get_balance(self, address: str, block: Optional[int] = None) -> Balance: """ - Retrieves the token balance of a specific address within the Bittensor network. This function queries - the blockchain to determine the amount of Tao held by a given account. + Retrieves the token balance of a specific address within the Bittensor network. This function queries the blockchain to determine the amount of Tao held by a given account. Args: address (str): The Substrate address in ``ss58`` format. @@ -5416,12 +5400,11 @@ def get_balance(self, address: str, block: Optional[int] = None) -> Balance: Returns: Balance: The account balance at the specified block, represented as a Balance object. - This function is important for monitoring account holdings and managing financial transactions - within the Bittensor ecosystem. It helps in assessing the economic status and capacity of network participants. + This function is important for monitoring account holdings and managing financial transactions within the Bittensor ecosystem. It helps in assessing the economic status and capacity of network participants. """ try: - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): return self.substrate.query( module="System", @@ -5434,7 +5417,7 @@ def make_substrate_call_with_retry(): result = make_substrate_call_with_retry() except RemainingScaleBytesNotEmptyException: - _logger.error( + logging.error( "Received a corrupted message. This likely points to an error with the network or subnet." ) return Balance(1000) @@ -5442,17 +5425,15 @@ def make_substrate_call_with_retry(): def get_current_block(self) -> int: """ - Returns the current block number on the Bittensor blockchain. This function provides the latest block - number, indicating the most recent state of the blockchain. + Returns the current block number on the Bittensor blockchain. This function provides the latest block number, indicating the most recent state of the blockchain. Returns: int: The current chain block number. - Knowing the current block number is essential for querying real-time data and performing time-sensitive - operations on the blockchain. It serves as a reference point for network activities and data synchronization. + Knowing the current block number is essential for querying real-time data and performing time-sensitive operations on the blockchain. It serves as a reference point for network activities and data synchronization. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): return self.substrate.get_block_number(None) # type: ignore @@ -5469,11 +5450,10 @@ def get_balances(self, block: Optional[int] = None) -> Dict[str, Balance]: Returns: Dict[str, Balance]: A dictionary mapping each account's ``ss58`` address to its balance. - This function is valuable for analyzing the overall economic landscape of the Bittensor network, - including the distribution of financial resources and the financial status of network participants. + This function is valuable for analyzing the overall economic landscape of the Bittensor network, including the distribution of financial resources and the financial status of network participants. """ - @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=_logger) + @retry(delay=1, tries=3, backoff=2, max_delay=4, logger=logging) def make_substrate_call_with_retry(): return self.substrate.query_map( module="System", @@ -5529,36 +5509,6 @@ def get_block_hash(self, block_id: int) -> str: Returns: str: The cryptographic hash of the specified block. - The block hash is a fundamental aspect of blockchain technology, providing a secure reference to - each block's data. It is crucial for verifying transactions, ensuring data consistency, and - maintaining the trustworthiness of the blockchain. + The block hash is a fundamental aspect of blockchain technology, providing a secure reference to each block's data. It is crucial for verifying transactions, ensuring data consistency, and maintaining the trustworthiness of the blockchain. """ return self.substrate.get_block_hash(block_id=block_id) - - def get_error_info_by_index(self, error_index: int) -> Tuple[str, str]: - """ - Returns the error name and description from the Subtensor error list. - - Args: - error_index (int): The index of the error to retrieve. - - Returns: - Tuple[str, str]: A tuple containing the error name and description from substrate metadata. If the error index is not found, returns ("Unknown Error", "") and logs a warning. - """ - unknown_error = ("Unknown Error", "") - - if not self._subtensor_errors: - self._subtensor_errors = get_subtensor_errors(self.substrate) - - name, description = self._subtensor_errors.get(str(error_index), unknown_error) - - if name == unknown_error[0]: - _logger.warning( - f"Subtensor returned an error with an unknown index: {error_index}" - ) - - return name, description - - -# TODO: remove this after fully migrate `bittensor.subtensor` to `bittensor.Subtensor` in `bittensor/__init__.py` -subtensor = Subtensor diff --git a/bittensor/synapse.py b/bittensor/core/synapse.py similarity index 97% rename from bittensor/synapse.py rename to bittensor/core/synapse.py index 80053f7065..9cfd292aad 100644 --- a/bittensor/synapse.py +++ b/bittensor/core/synapse.py @@ -1,26 +1,25 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation - +# 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 base64 import json import sys import warnings +from typing import cast, Any, ClassVar, Dict, Optional, Tuple, Union from pydantic import ( BaseModel, @@ -29,8 +28,9 @@ field_validator, model_validator, ) -import bittensor -from typing import Optional, Any, Dict, ClassVar, Tuple + +from bittensor.utils import get_hash +from bittensor.utils.btlogging import logging def get_size(obj, seen=None) -> int: @@ -456,7 +456,7 @@ def set_name_type(cls, values) -> dict: dendrite: Optional[TerminalInfo] = Field( title="dendrite", description="Dendrite Terminal Information", - examples=["bittensor.TerminalInfo"], + examples=["TerminalInfo"], default=TerminalInfo(), frozen=False, repr=False, @@ -466,7 +466,7 @@ def set_name_type(cls, values) -> dict: axon: Optional[TerminalInfo] = Field( title="axon", description="Axon Terminal Information", - examples=["bittensor.TerminalInfo"], + examples=["TerminalInfo"], default=TerminalInfo(), frozen=False, repr=False, @@ -717,9 +717,9 @@ def body_hash(self) -> str: if required_hash_fields: instance_fields = instance_fields or self.model_dump() for field in required_hash_fields: - hashes.append(bittensor.utils.hash(str(instance_fields[field]))) + hashes.append(get_hash(str(instance_fields[field]))) - return bittensor.utils.hash("".join(hashes)) + return get_hash("".join(hashes)) @classmethod def parse_headers_to_inputs(cls, headers: dict) -> dict: @@ -756,7 +756,10 @@ def parse_headers_to_inputs(cls, headers: dict) -> dict: """ # Initialize the input dictionary with empty sub-dictionaries for 'axon' and 'dendrite' - inputs_dict: Dict[str, Dict[str, str]] = {"axon": {}, "dendrite": {}} + inputs_dict: Dict[str, Union[Dict, Optional[str]]] = { + "axon": {}, + "dendrite": {}, + } # Iterate over each item in the headers for key, value in headers.items(): @@ -764,21 +767,19 @@ def parse_headers_to_inputs(cls, headers: dict) -> dict: if "bt_header_axon_" in key: try: new_key = key.split("bt_header_axon_")[1] - inputs_dict["axon"][new_key] = value + axon_dict = cast(dict, inputs_dict["axon"]) + axon_dict[new_key] = value except Exception as e: - bittensor.logging.error( - f"Error while parsing 'axon' header {key}: {e}" - ) + logging.error(f"Error while parsing 'axon' header {key}: {str(e)}") continue # Handle 'dendrite' headers elif "bt_header_dendrite_" in key: try: new_key = key.split("bt_header_dendrite_")[1] - inputs_dict["dendrite"][new_key] = value + dendrite_dict = cast(dict, inputs_dict["dendrite"]) + dendrite_dict[new_key] = value except Exception as e: - bittensor.logging.error( - f"Error while parsing 'dendrite' header {key}: {e}" - ) + logging.error(f"Error while parsing 'dendrite' header {key}: {e}") continue # Handle 'input_obj' headers elif "bt_header_input_obj" in key: @@ -792,14 +793,12 @@ def parse_headers_to_inputs(cls, headers: dict) -> dict: base64.b64decode(value.encode()).decode("utf-8") ) except json.JSONDecodeError as e: - bittensor.logging.error( + logging.error( f"Error while json decoding 'input_obj' header {key}: {e}" ) continue except Exception as e: - bittensor.logging.error( - f"Error while parsing 'input_obj' header {key}: {e}" - ) + logging.error(f"Error while parsing 'input_obj' header {key}: {e}") continue else: pass # TODO: log unexpected keys diff --git a/bittensor/tensor.py b/bittensor/core/tensor.py similarity index 99% rename from bittensor/tensor.py rename to bittensor/core/tensor.py index ab46560d99..3eebd404cd 100644 --- a/bittensor/tensor.py +++ b/bittensor/core/tensor.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation - +# 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 diff --git a/bittensor/threadpool.py b/bittensor/core/threadpool.py similarity index 94% rename from bittensor/threadpool.py rename to bittensor/core/threadpool.py index 3e49786ff6..0b10a622ad 100644 --- a/bittensor/threadpool.py +++ b/bittensor/core/threadpool.py @@ -13,14 +13,15 @@ import weakref import logging import argparse -import bittensor import itertools import threading from typing import Callable from concurrent.futures import _base -from bittensor.btlogging.defines import BITTENSOR_LOGGER_NAME +from bittensor.core.config import Config +from bittensor.utils.btlogging.defines import BITTENSOR_LOGGER_NAME +from bittensor.core.settings import blocktime # Workers are created as daemon threads. This is done to allow the interpreter # to exit when there are still idle threads in a ThreadPoolExecutor's thread @@ -54,7 +55,7 @@ def run(self): """Run the given work item""" # Checks if future is canceled or if work item is stale if (not self.future.set_running_or_notify_cancel()) or ( - time.time() - self.start_time > bittensor.__blocktime__ + time.time() - self.start_time > blocktime ): return @@ -120,7 +121,7 @@ class BrokenThreadPool(_base.BrokenExecutor): class PriorityThreadPoolExecutor(_base.Executor): - """Base threadpool executor with a priority queue""" + """Base threadpool executor with a priority queue.""" # Used to assign unique thread names when thread_name_prefix is not supplied. _counter = itertools.count().__next__ @@ -168,16 +169,16 @@ def __init__( @classmethod def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): """Accept specific arguments from parser""" - prefix_str = "" if prefix == None else prefix + "." + prefix_str = "" if prefix is None else prefix + "." try: default_max_workers = ( os.getenv("BT_PRIORITY_MAX_WORKERS") - if os.getenv("BT_PRIORITY_MAX_WORKERS") != None + if os.getenv("BT_PRIORITY_MAX_WORKERS") is not None else 5 ) default_maxsize = ( os.getenv("BT_PRIORITY_MAXSIZE") - if os.getenv("BT_PRIORITY_MAXSIZE") != None + if os.getenv("BT_PRIORITY_MAXSIZE") is not None else 10 ) parser.add_argument( @@ -197,14 +198,14 @@ def add_args(cls, parser: argparse.ArgumentParser, prefix: str = None): pass @classmethod - def config(cls) -> "bittensor.config": + def config(cls) -> "Config": """Get config from the argument parser. - Return: :func:`bittensor.config` object. + Return: :func:`bittensor.Config` object. """ parser = argparse.ArgumentParser() PriorityThreadPoolExecutor.add_args(parser) - return bittensor.config(parser, args=[]) + return Config(parser, args=[]) @property def is_empty(self): diff --git a/bittensor/types.py b/bittensor/core/types.py similarity index 89% rename from bittensor/types.py rename to bittensor/core/types.py index 8aa9b7cde4..9fd2b4d052 100644 --- a/bittensor/types.py +++ b/bittensor/core/types.py @@ -1,14 +1,14 @@ # The MIT License (MIT) -# Copyright © 2023 Opentensor Technologies Inc - +# 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 @@ -19,9 +19,7 @@ class AxonServeCallParams(TypedDict): - """ - Axon serve chain call parameters. - """ + """Axon serve chain call parameters.""" version: int ip: int @@ -31,9 +29,7 @@ class AxonServeCallParams(TypedDict): class PrometheusServeCallParams(TypedDict): - """ - Prometheus serve chain call parameters. - """ + """Prometheus serve chain call parameters.""" version: int ip: int diff --git a/bittensor/mock/keyfile_mock.py b/bittensor/mock/keyfile_mock.py deleted file mode 100644 index e13126cc17..0000000000 --- a/bittensor/mock/keyfile_mock.py +++ /dev/null @@ -1,90 +0,0 @@ -# The MIT License (MIT) - -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies - -# 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 bittensor import serialized_keypair_to_keyfile_data, keyfile, Keypair - - -class MockKeyfile(keyfile): - """Defines an interface to a mocked keyfile object (nothing is created on device) keypair is treated as non encrypted and the data is just the string version.""" - - def __init__(self, path: str): - super().__init__(path) - - self._mock_keypair = Keypair.create_from_mnemonic( - mnemonic="arrive produce someone view end scout bargain coil slight festival excess struggle" - ) - self._mock_data = serialized_keypair_to_keyfile_data(self._mock_keypair) - - def __str__(self): - if not self.exists_on_device(): - return "Keyfile (empty, {})>".format(self.path) - if self.is_encrypted(): - return "Keyfile (encrypted, {})>".format(self.path) - else: - return "Keyfile (decrypted, {})>".format(self.path) - - def __repr__(self): - return self.__str__() - - @property - def keypair(self) -> "Keypair": - return self._mock_keypair - - @property - def data(self) -> bytes: - return bytes(self._mock_data) - - @property - def keyfile_data(self) -> bytes: - return bytes(self._mock_data) - - def set_keypair( - self, - keypair: "Keypair", - encrypt: bool = True, - overwrite: bool = False, - password: str = None, - ): - self._mock_keypair = keypair - self._mock_data = serialized_keypair_to_keyfile_data(self._mock_keypair) - - def get_keypair(self, password: str = None) -> "Keypair": - return self._mock_keypair - - def make_dirs(self): - return - - def exists_on_device(self) -> bool: - return True - - def is_readable(self) -> bool: - return True - - def is_writable(self) -> bool: - return True - - def is_encrypted(self) -> bool: - return False - - def encrypt(self, password: str = None): - raise ValueError("Cannot encrypt a mock keyfile") - - def decrypt(self, password: str = None): - return diff --git a/bittensor/mock/wallet_mock.py b/bittensor/mock/wallet_mock.py deleted file mode 100644 index 35179f8c94..0000000000 --- a/bittensor/mock/wallet_mock.py +++ /dev/null @@ -1,127 +0,0 @@ -# The MIT License (MIT) - -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies - -# 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 os -import bittensor -from typing import Optional -from Crypto.Hash import keccak - -from .keyfile_mock import MockKeyfile - - -class MockWallet(bittensor.wallet): - """ - Mocked Version of the bittensor wallet class, meant to be used for testing - """ - - def __init__(self, **kwargs): - r"""Init bittensor wallet object containing a hot and coldkey. - Args: - _mock (required=True, default=False): - If true creates a mock wallet with random keys. - """ - super().__init__(**kwargs) - # For mocking. - self._is_mock = True - self._mocked_coldkey_keyfile = None - self._mocked_hotkey_keyfile = None - - @property - def hotkey_file(self) -> "bittensor.keyfile": - if self._is_mock: - if self._mocked_hotkey_keyfile == None: - self._mocked_hotkey_keyfile = MockKeyfile(path="MockedHotkey") - return self._mocked_hotkey_keyfile - else: - wallet_path = os.path.expanduser(os.path.join(self.path, self.name)) - hotkey_path = os.path.join(wallet_path, "hotkeys", self.hotkey_str) - return bittensor.keyfile(path=hotkey_path) - - @property - def coldkey_file(self) -> "bittensor.keyfile": - if self._is_mock: - if self._mocked_coldkey_keyfile == None: - self._mocked_coldkey_keyfile = MockKeyfile(path="MockedColdkey") - return self._mocked_coldkey_keyfile - else: - wallet_path = os.path.expanduser(os.path.join(self.path, self.name)) - coldkey_path = os.path.join(wallet_path, "coldkey") - return bittensor.keyfile(path=coldkey_path) - - @property - def coldkeypub_file(self) -> "bittensor.keyfile": - if self._is_mock: - if self._mocked_coldkey_keyfile == None: - self._mocked_coldkey_keyfile = MockKeyfile(path="MockedColdkeyPub") - return self._mocked_coldkey_keyfile - else: - wallet_path = os.path.expanduser(os.path.join(self.path, self.name)) - coldkeypub_path = os.path.join(wallet_path, "coldkeypub.txt") - return bittensor.keyfile(path=coldkeypub_path) - - -def get_mock_wallet( - coldkey: "bittensor.Keypair" = None, hotkey: "bittensor.Keypair" = None -): - wallet = MockWallet(name="mock_wallet", hotkey="mock", path="/tmp/mock_wallet") - - if not coldkey: - coldkey = bittensor.Keypair.create_from_mnemonic( - bittensor.Keypair.generate_mnemonic() - ) - if not hotkey: - hotkey = bittensor.Keypair.create_from_mnemonic( - bittensor.Keypair.generate_mnemonic() - ) - - wallet.set_coldkey(coldkey, encrypt=False, overwrite=True) - wallet.set_coldkeypub(coldkey, encrypt=False, overwrite=True) - wallet.set_hotkey(hotkey, encrypt=False, overwrite=True) - - return wallet - - -def get_mock_keypair(uid: int, test_name: Optional[str] = None) -> bittensor.Keypair: - """ - Returns a mock keypair from a uid and optional test_name. - If test_name is not provided, the uid is the only seed. - If test_name is provided, the uid is hashed with the test_name to create a unique seed for the test. - """ - if test_name is not None: - hashed_test_name: bytes = keccak.new( - digest_bits=256, data=test_name.encode("utf-8") - ).digest() - hashed_test_name_as_int: int = int.from_bytes( - hashed_test_name, byteorder="big", signed=False - ) - uid = uid + hashed_test_name_as_int - - return bittensor.Keypair.create_from_seed( - seed_hex=int.to_bytes(uid, 32, "big", signed=False), - ss58_format=bittensor.__ss58_format__, - ) - - -def get_mock_hotkey(uid: int) -> str: - return get_mock_keypair(uid).ss58_address - - -def get_mock_coldkey(uid: int) -> str: - return get_mock_keypair(uid).ss58_address diff --git a/bittensor/utils/__init__.py b/bittensor/utils/__init__.py index 700a656131..80f478fe92 100644 --- a/bittensor/utils/__init__.py +++ b/bittensor/utils/__init__.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2022 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies Inc - +# 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 @@ -17,15 +16,17 @@ # DEALINGS IN THE SOFTWARE. import hashlib -from typing import Callable, List, Dict, Literal, Tuple +from typing import Callable, List, Dict, Literal, Tuple, Union, Optional import numpy as np import scalecodec +from substrateinterface import Keypair as Keypair +from substrateinterface.utils import ss58 -import bittensor +from bittensor.core.settings import ss58_format from .registration import torch, use_torch from .version import version_checking, check_version, VersionCheckError -from .wallet_utils import * # noqa F401 + RAOPERTAO = 1e9 U16_MAX = 65535 @@ -33,7 +34,7 @@ def ss58_to_vec_u8(ss58_address: str) -> List[int]: - ss58_bytes: bytes = bittensor.utils.ss58_address_to_bytes(ss58_address) + ss58_bytes: bytes = ss58_address_to_bytes(ss58_address) encoded_address: List[int] = [int(byte) for byte in ss58_bytes] return encoded_address @@ -49,26 +50,17 @@ def _unbiased_topk( ) -> Union[Tuple[np.ndarray, np.ndarray], Tuple["torch.Tensor", "torch.LongTensor"]]: """Selects topk as in torch.topk but does not bias lower indices when values are equal. Args: - values: (np.ndarray) if using numpy, (torch.Tensor) if using torch: - Values to index into. - k: (int): - Number to take. - dim: (int): - Dimension to index into (used by Torch) - sorted: (bool): - Whether to sort indices. - largest: (bool): - Whether to take the largest value. - axis: (int): - Axis along which to index into (used by Numpy) - return_type: (str): - Whether or use torch or numpy approach + values: (np.ndarray) if using numpy, (torch.Tensor) if using torch: Values to index into. + k (int): Number to take. + dim (int): Dimension to index into (used by Torch) + sorted (bool): Whether to sort indices. + largest (bool): Whether to take the largest value. + axis (int): Axis along which to index into (used by Numpy) + return_type (str): Whether or use torch or numpy approach Return: - topk: (np.ndarray) if using numpy, (torch.Tensor) if using torch: - topk k values. - indices: (np.ndarray) if using numpy, (torch.LongTensor) if using torch: - indices of the topk values. + topk (np.ndarray): if using numpy, (torch.Tensor) if using torch: topk k values. + indices (np.ndarray): if using numpy, (torch.LongTensor) if using torch: indices of the topk values. """ if return_type == "torch": permutation = torch.randperm(values.shape[dim]) @@ -225,9 +217,7 @@ def get_explorer_url_for_network( 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, bittensor.__ss58_format__ - ) + account_id_hex: str = scalecodec.ss58_decode(ss58_address, ss58_format) return bytes.fromhex(account_id_hex) @@ -247,10 +237,10 @@ def u8_key_to_ss58(u8_key: List[int]) -> str: u8_key (List[int]): The u8-encoded account key. """ # First byte is length, then 32 bytes of key. - return scalecodec.ss58_encode(bytes(u8_key).hex(), bittensor.__ss58_format__) + return scalecodec.ss58_encode(bytes(u8_key).hex(), ss58_format) -def hash(content, encoding="utf-8"): +def get_hash(content, encoding="utf-8"): sha3 = hashlib.sha3_256() # Update the hash object with the concatenated string @@ -280,3 +270,142 @@ def format_error_message(error_message: dict) -> str: err_docs = error_message.get("docs", []) err_description = err_docs[0] if len(err_docs) > 0 else err_description return f"Subtensor returned `{err_name} ({err_type})` error. This means: `{err_description}`" + + +def create_identity_dict( + display: str = "", + legal: str = "", + web: str = "", + riot: str = "", + email: str = "", + pgp_fingerprint: Optional[str] = None, + image: str = "", + info: str = "", + twitter: str = "", +) -> dict: + """ + Creates a dictionary with structure for identity extrinsic. Must fit within 64 bits. + + Args: + display (str): String to be converted and stored under 'display'. + legal (str): String to be converted and stored under 'legal'. + web (str): String to be converted and stored under 'web'. + riot (str): String to be converted and stored under 'riot'. + email (str): String to be converted and stored under 'email'. + pgp_fingerprint (str): String to be converted and stored under 'pgp_fingerprint'. + image (str): String to be converted and stored under 'image'. + info (str): String to be converted and stored under 'info'. + twitter (str): String to be converted and stored under 'twitter'. + + Returns: + dict: A dictionary with the specified structure and byte string conversions. + + Raises: + ValueError: If pgp_fingerprint is not exactly 20 bytes long when encoded. + """ + if pgp_fingerprint and len(pgp_fingerprint.encode()) != 20: + raise ValueError("pgp_fingerprint must be exactly 20 bytes long when encoded") + + return { + "info": { + "additional": [[]], + "display": {f"Raw{len(display.encode())}": display.encode()}, + "legal": {f"Raw{len(legal.encode())}": legal.encode()}, + "web": {f"Raw{len(web.encode())}": web.encode()}, + "riot": {f"Raw{len(riot.encode())}": riot.encode()}, + "email": {f"Raw{len(email.encode())}": email.encode()}, + "pgp_fingerprint": pgp_fingerprint.encode() if pgp_fingerprint else None, + "image": {f"Raw{len(image.encode())}": image.encode()}, + "info": {f"Raw{len(info.encode())}": info.encode()}, + "twitter": {f"Raw{len(twitter.encode())}": twitter.encode()}, + } + } + + +def decode_hex_identity_dict(info_dictionary): + for key, value in info_dictionary.items(): + if isinstance(value, dict): + item = list(value.values())[0] + if isinstance(item, str) and item.startswith("0x"): + try: + info_dictionary[key] = bytes.fromhex(item[2:]).decode() + except UnicodeDecodeError: + print(f"Could not decode: {key}: {item}") + else: + info_dictionary[key] = item + return info_dictionary + + +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 + + +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_format=ss58_format) + + ss58_addr = keypair.ss58_address + return ss58_addr is not None + + except (ValueError, IndexError): + return False + + +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 diff --git a/bittensor/utils/axon_utils.py b/bittensor/utils/axon_utils.py index 5912f389a4..c380f14ae2 100644 --- a/bittensor/utils/axon_utils.py +++ b/bittensor/utils/axon_utils.py @@ -1,26 +1,24 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies Inc - +# 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 typing import Optional -from bittensor.constants import ALLOWED_DELTA, NANOSECONDS_IN_SECOND +ALLOWED_DELTA = 4_000_000_000 # Delta of 4 seconds for nonce validation +NANOSECONDS_IN_SECOND = 1_000_000_000 def allowed_nonce_window_ns(current_time_ns: int, synapse_timeout: Optional[float]): diff --git a/bittensor/utils/backwards_compatibility.py b/bittensor/utils/backwards_compatibility.py new file mode 100644 index 0000000000..ca45a45993 --- /dev/null +++ b/bittensor/utils/backwards_compatibility.py @@ -0,0 +1,155 @@ +# 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. + +""" +The Bittensor Compatibility Module is designed to ensure seamless integration and functionality with legacy versions of +the Bittensor framework, specifically up to and including version 7.3.0. This module addresses changes and deprecated +features in recent versions, allowing users to maintain compatibility with older systems and projects. +""" + +import sys +import importlib + +from bittensor_wallet.errors import KeyFileError # noqa: F401 +from bittensor_wallet.keyfile import ( # noqa: F401 + serialized_keypair_to_keyfile_data, + deserialize_keypair_from_keyfile_data, + validate_password, + ask_password_to_encrypt, + keyfile_data_is_encrypted_nacl, + keyfile_data_is_encrypted_ansible, + keyfile_data_is_encrypted_legacy, + keyfile_data_is_encrypted, + keyfile_data_encryption_method, + legacy_encrypt_keyfile_data, + encrypt_keyfile_data, + get_coldkey_password_from_environment, + decrypt_keyfile_data, + Keyfile, +) +from bittensor_wallet.wallet import display_mnemonic_msg, Wallet # noqa: F401 +from substrateinterface import Keypair # noqa: F401 + +from bittensor.core import settings +from bittensor.core.axon import Axon +from bittensor.core.chain_data import ( # noqa: F401 + AxonInfo, + NeuronInfo, + NeuronInfoLite, + PrometheusInfo, + DelegateInfo, + StakeInfo, + SubnetInfo, + SubnetHyperparameters, + IPInfo, + ProposalCallData, + ProposalVoteData, +) +from bittensor.core.config import ( # noqa: F401 + InvalidConfigFile, + DefaultConfig, + Config, + T, +) +from bittensor.core.dendrite import Dendrite # noqa: F401 +from bittensor.core.errors import ( # noqa: F401 + BlacklistedException, + ChainConnectionError, + ChainError, + ChainQueryError, + ChainTransactionError, + IdentityError, + InternalServerError, + InvalidRequestNameError, + MetadataError, + NominationError, + NotDelegateError, + NotRegisteredError, + NotVerifiedException, + PostProcessException, + PriorityException, + RegistrationError, + RunException, + StakeError, + SynapseDendriteNoneException, + SynapseParsingError, + TransferError, + UnknownSynapseError, + UnstakeError, +) +from bittensor.core.metagraph import Metagraph +from bittensor.core.settings import blocktime +from bittensor.core.stream import StreamingSynapse # noqa: F401 +from bittensor.core.subtensor import Subtensor +from bittensor.core.synapse import TerminalInfo, Synapse # noqa: F401 +from bittensor.core.tensor import tensor, Tensor # noqa: F401 +from bittensor.core.threadpool import ( # noqa: F401 + PriorityThreadPoolExecutor as PriorityThreadPoolExecutor, +) +from bittensor.utils.mock.subtensor_mock import MockSubtensor as MockSubtensor # noqa: F401 +from bittensor.utils import ( # noqa: F401 + ss58_to_vec_u8, + unbiased_topk, + version_checking, + strtobool, + strtobool_with_default, + get_explorer_root_url_by_network_from_map, + get_explorer_root_url_by_network_from_map, + get_explorer_url_for_network, + ss58_address_to_bytes, + U16_NORMALIZED_FLOAT, + U64_NORMALIZED_FLOAT, + u8_key_to_ss58, + get_hash, +) +from bittensor.utils.balance import Balance as Balance # noqa: F401 +from bittensor.utils.subnets import SubnetsAPI # noqa: F401 + + +# Backwards compatibility with previous bittensor versions. +axon = Axon +config = Config +keyfile = Keyfile +metagraph = Metagraph +wallet = Wallet +subtensor = Subtensor +synapse = Synapse + +__blocktime__ = blocktime +__network_explorer_map__ = settings.network_explorer_map +__pipaddress__ = settings.pipaddress +__ss58_format__ = settings.ss58_format +__type_registry__ = settings.type_registry +__ss58_address_length__ = settings.ss58_address_length + +__networks__ = settings.networks + +__finney_entrypoint__ = settings.finney_entrypoint +__finney_test_entrypoint__ = settings.finney_test_entrypoint +__archive_entrypoint__ = settings.archive_entrypoint +__local_entrypoint__ = settings.local_entrypoint + +__tao_symbol__ = settings.tao_symbol +__rao_symbol__ = settings.rao_symbol + +# Makes the `bittensor.api.extrinsics` subpackage available as `bittensor.extrinsics` for backwards compatibility. +extrinsics = importlib.import_module("bittensor.api.extrinsics") +sys.modules["bittensor.extrinsics"] = extrinsics + +# Makes the `bittensor.utils.mock` subpackage available as `bittensor.mock` for backwards compatibility. +extrinsics = importlib.import_module("bittensor.utils.mock") +sys.modules["bittensor.mock"] = extrinsics diff --git a/bittensor/utils/balance.py b/bittensor/utils/balance.py index 63ca6cd5ba..f95efbbc76 100644 --- a/bittensor/utils/balance.py +++ b/bittensor/utils/balance.py @@ -19,7 +19,7 @@ from typing import Union -import bittensor +from ..core import settings class Balance: @@ -35,8 +35,8 @@ class Balance: tao: A float property that gives the balance in tao units. """ - unit: str = bittensor.__tao_symbol__ # This is the tao unit - rao_unit: str = bittensor.__rao_symbol__ # This is the rao unit + unit: str = settings.tao_symbol # This is the tao unit + rao_unit: str = settings.rao_symbol # This is the rao unit rao: int tao: float diff --git a/bittensor/btlogging/__init__.py b/bittensor/utils/btlogging/__init__.py similarity index 93% rename from bittensor/btlogging/__init__.py rename to bittensor/utils/btlogging/__init__.py index 6bf6d2bf35..6b02c51bac 100644 --- a/bittensor/btlogging/__init__.py +++ b/bittensor/utils/btlogging/__init__.py @@ -1,14 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao - +# 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 @@ -22,7 +22,7 @@ formatters to ensure consistent logging throughout the project. """ -from bittensor.btlogging.loggingmachine import LoggingMachine +from .loggingmachine import LoggingMachine logging = LoggingMachine(LoggingMachine.config()) diff --git a/bittensor/btlogging/defines.py b/bittensor/utils/btlogging/defines.py similarity index 96% rename from bittensor/btlogging/defines.py rename to bittensor/utils/btlogging/defines.py index c87177ffd0..9e1dada25b 100644 --- a/bittensor/btlogging/defines.py +++ b/bittensor/utils/btlogging/defines.py @@ -1,14 +1,14 @@ # The MIT License (MIT) -# Copyright © 2023 OpenTensor Foundation - +# 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 diff --git a/bittensor/btlogging/format.py b/bittensor/utils/btlogging/format.py similarity index 99% rename from bittensor/btlogging/format.py rename to bittensor/utils/btlogging/format.py index 5f2c8cb866..70629128f7 100644 --- a/bittensor/btlogging/format.py +++ b/bittensor/utils/btlogging/format.py @@ -1,14 +1,14 @@ # The MIT License (MIT) -# Copyright © 2023 OpenTensor Foundation - +# 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 diff --git a/bittensor/btlogging/helpers.py b/bittensor/utils/btlogging/helpers.py similarity index 98% rename from bittensor/btlogging/helpers.py rename to bittensor/utils/btlogging/helpers.py index 532c1f7166..892df10527 100644 --- a/bittensor/btlogging/helpers.py +++ b/bittensor/utils/btlogging/helpers.py @@ -1,14 +1,14 @@ # The MIT License (MIT) -# Copyright © 2023 OpenTensor Foundation - +# 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 diff --git a/bittensor/btlogging/loggingmachine.py b/bittensor/utils/btlogging/loggingmachine.py similarity index 99% rename from bittensor/btlogging/loggingmachine.py rename to bittensor/utils/btlogging/loggingmachine.py index 8bc94fde3c..7c7ea3376c 100644 --- a/bittensor/btlogging/loggingmachine.py +++ b/bittensor/utils/btlogging/loggingmachine.py @@ -31,10 +31,11 @@ from logging.handlers import QueueHandler, QueueListener, RotatingFileHandler from typing import NamedTuple -from bittensor_wallet.config import Config from statemachine import State, StateMachine -from bittensor.btlogging.defines import ( +from bittensor.core.config import Config + +from .defines import ( BITTENSOR_LOGGER_NAME, DATE_FORMAT, DEFAULT_LOG_BACKUP_COUNT, diff --git a/bittensor/mock/__init__.py b/bittensor/utils/mock/__init__.py similarity index 95% rename from bittensor/mock/__init__.py rename to bittensor/utils/mock/__init__.py index b4f0efd5ca..218579a153 100644 --- a/bittensor/mock/__init__.py +++ b/bittensor/utils/mock/__init__.py @@ -15,4 +15,4 @@ # 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 .subtensor_mock import MockSubtensor as MockSubtensor +from .subtensor_mock import MockSubtensor diff --git a/bittensor/mock/subtensor_mock.py b/bittensor/utils/mock/subtensor_mock.py similarity index 99% rename from bittensor/mock/subtensor_mock.py rename to bittensor/utils/mock/subtensor_mock.py index 63cfc891d9..dcc8112f8c 100644 --- a/bittensor/mock/subtensor_mock.py +++ b/bittensor/utils/mock/subtensor_mock.py @@ -27,7 +27,7 @@ from bittensor_wallet import Wallet -from ..chain_data import ( +from bittensor.core.chain_data import ( NeuronInfo, NeuronInfoLite, PrometheusInfo, @@ -35,20 +35,18 @@ SubnetInfo, AxonInfo, ) -from ..errors import ChainQueryError -from ..subtensor import Subtensor -from ..utils import RAOPERTAO, U16_NORMALIZED_FLOAT -from ..utils.balance import Balance -from ..utils.registration import POWSolution +from bittensor.core.subtensor import Subtensor +from bittensor.core.errors import ChainQueryError +from bittensor.utils import RAOPERTAO, U16_NORMALIZED_FLOAT +from bittensor.utils.balance import Balance +from bittensor.utils.registration import POWSolution # Mock Testing Constant __GLOBAL_MOCK_STATE__ = {} class AxonServeCallParams(TypedDict): - """ - Axon serve chain call parameters. - """ + """Axon serve chain call parameters.""" version: int ip: int @@ -58,9 +56,7 @@ class AxonServeCallParams(TypedDict): class PrometheusServeCallParams(TypedDict): - """ - Prometheus serve chain call parameters. - """ + """Prometheus serve chain call parameters.""" version: int ip: int diff --git a/bittensor/utils/registration.py b/bittensor/utils/registration.py index d606929dcb..83406ff70f 100644 --- a/bittensor/utils/registration.py +++ b/bittensor/utils/registration.py @@ -1,7 +1,22 @@ -import binascii +# 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 functools import hashlib -import math import multiprocessing import multiprocessing.queues # this must be imported separately, or could break type annotations import os @@ -14,15 +29,21 @@ from typing import Any, Callable, Dict, List, Optional, Tuple, Union import backoff +import binascii +import math import numpy - -import bittensor from Crypto.Hash import keccak from rich import console as rich_console from rich import status as rich_status -from .formatting import get_human_readable, millify -from ._register_cuda import solve_cuda +from bittensor.core.settings import bt_console +from bittensor.utils._register_cuda import solve_cuda +from bittensor.utils.btlogging import logging +from bittensor.utils.formatting import get_human_readable, millify + +if typing.TYPE_CHECKING: + from bittensor.core.subtensor import Subtensor + from bittensor_wallet import Wallet def use_torch() -> bool: @@ -35,11 +56,10 @@ def legacy_torch_api_compat(func): Convert function operating on numpy Input&Output to legacy torch Input&Output API if `use_torch()` is True. Args: - func (function): - Function with numpy Input/Output to be decorated. + func (function): Function with numpy Input/Output to be decorated. + Returns: - decorated (function): - Decorated function. + decorated (function): Decorated function. """ @functools.wraps(func) @@ -74,7 +94,7 @@ def _get_real_torch(): def log_no_torch_error(): - bittensor.logging.error( + logging.error( "This command requires torch. You can install torch for bittensor" ' with `pip install bittensor[torch]` or `pip install ".[torch]"`' " if installing from source, and then run the command with USE_TORCH=1 {command}" @@ -102,8 +122,6 @@ def __getattr__(self, name): class CUDAException(Exception): """An exception raised when an error occurs in the CUDA environment.""" - pass - def _hex_bytes_to_u8_list(hex_bytes: bytes): hex_chunks = [int(hex_bytes[i : i + 2], 16) for i in range(0, len(hex_bytes), 2)] @@ -134,9 +152,10 @@ class POWSolution: difficulty: int seal: bytes - def is_stale(self, subtensor: "bittensor.subtensor") -> bool: - """Returns True if the POW is stale. - This means the block the POW is solved for is within 3 blocks of the current block. + def is_stale(self, subtensor: "Subtensor") -> bool: + """ + Returns True if the POW is stale. This means the block the POW is solved for is within 3 blocks of the current + block. """ return self.block_number < subtensor.get_current_block() - 3 @@ -525,8 +544,8 @@ def update(self, stats: RegistrationStatistics, verbose: bool = False) -> None: def _solve_for_difficulty_fast( - subtensor, - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", netuid: int, output_in_place: bool = True, num_processes: Optional[int] = None, @@ -538,29 +557,23 @@ def _solve_for_difficulty_fast( """ Solves the POW for registration using multiprocessing. Args: - subtensor - Subtensor to connect to for block information and to submit. - wallet: - wallet to use for registration. - netuid: int - The netuid of the subnet to register to. - output_in_place: bool - If true, prints the status in place. Otherwise, prints the status on a new line. - num_processes: int - Number of processes to use. - update_interval: int - Number of nonces to solve before updating block information. - n_samples: int - The number of samples of the hash_rate to keep for the EWMA - alpha_: float - The alpha for the EWMA for the hash_rate calculation - log_verbose: bool - If true, prints more verbose logging of the registration metrics. - Note: The hash rate is calculated as an exponentially weighted moving average in order to make the measure more robust. + subtensor (bittensor.core.subtensor.Subtensor): Subtensor to connect to for block information and to submit. + wallet (bittensor_wallet.Wallet): wallet to use for registration. + netuid (int): The netuid of the subnet to register to. + output_in_place (bool): If true, prints the status in place. Otherwise, prints the status on a new line. + num_processes (int): Number of processes to use. + update_interval (int): Number of nonces to solve before updating block information. + n_samples (int): The number of samples of the hash_rate to keep for the EWMA. + alpha_ (float): The alpha for the EWMA for the hash_rate calculation. + log_verbose (bool): If true, prints more verbose logging of the registration metrics. + Note: - - We can also modify the update interval to do smaller blocks of work, - while still updating the block information after a different number of nonces, - to increase the transparency of the process while still keeping the speed. + The hash rate is calculated as an exponentially weighted moving average in order to make the measure more + robust. + Note: + - We can also modify the update interval to do smaller blocks of work, while still updating the block + information after a different number of nonces, to increase the transparency of the process while still keeping + the speed. """ if num_processes is None: # get the number of allowed processes for this process @@ -574,7 +587,7 @@ def _solve_for_difficulty_fast( curr_block, curr_block_num, curr_diff = _Solver.create_shared_memory() # Establish communication queues - ## See the _Solver class for more information on the queues. + # See the _Solver class for more information on the queues. stopEvent = multiprocessing.Event() stopEvent.clear() @@ -646,8 +659,7 @@ def _solve_for_difficulty_fast( start_time_perpetual = time.time() - console = bittensor.__console__ - logger = RegistrationStatisticsLogger(console, output_in_place) + logger = RegistrationStatisticsLogger(bt_console, output_in_place) logger.start() solution = None @@ -735,27 +747,19 @@ def _solve_for_difficulty_fast( @backoff.on_exception(backoff.constant, Exception, interval=1, max_tries=3) def _get_block_with_retry( - subtensor: "bittensor.subtensor", netuid: int + subtensor: "Subtensor", netuid: int ) -> Tuple[int, int, bytes]: """ Gets the current block number, difficulty, and block hash from the substrate node. Args: - subtensor (:obj:`bittensor.subtensor`, `required`): - The subtensor object to use to get the block number, difficulty, and block hash. - - netuid (:obj:`int`, `required`): - The netuid of the network to get the block number, difficulty, and block hash from. + subtensor (bittensor.core.subtensor.Subtensor): The subtensor object to use to get the block number, difficulty, and block hash. + netuid (int): The netuid of the network to get the block number, difficulty, and block hash from. Returns: - block_number (:obj:`int`): - The current block number. - - difficulty (:obj:`int`): - The current difficulty of the subnet. - - block_hash (:obj:`bytes`): - The current block hash. + block_number (int): The current block number. + difficulty (int): The current difficulty of the subnet. + block_hash (bytes): The current block hash. Raises: Exception: If the block hash is None. @@ -791,7 +795,7 @@ def __exit__(self, *args): def _check_for_newest_block_and_update( - subtensor: "bittensor.subtensor", + subtensor: "Subtensor", netuid: int, old_block_number: int, hotkey_bytes: bytes, @@ -807,28 +811,17 @@ def _check_for_newest_block_and_update( Checks for a new block and updates the current block information if a new block is found. Args: - subtensor (:obj:`bittensor.subtensor`, `required`): - The subtensor object to use for getting the current block. - netuid (:obj:`int`, `required`): - The netuid to use for retrieving the difficulty. - old_block_number (:obj:`int`, `required`): - The old block number to check against. - hotkey_bytes (:obj:`bytes`, `required`): - The bytes of the hotkey's pubkey. - curr_diff (:obj:`multiprocessing.Array`, `required`): - The current difficulty as a multiprocessing array. - curr_block (:obj:`multiprocessing.Array`, `required`): - Where the current block is stored as a multiprocessing array. - curr_block_num (:obj:`multiprocessing.Value`, `required`): - Where the current block number is stored as a multiprocessing value. - update_curr_block (:obj:`Callable`, `required`): - A function that updates the current block. - check_block (:obj:`multiprocessing.Lock`, `required`): - A mp lock that is used to check for a new block. - solvers (:obj:`List[_Solver]`, `required`): - A list of solvers to update the current block for. - curr_stats (:obj:`RegistrationStatistics`, `required`): - The current registration statistics to update. + subtensor (bittensor.core.subtensor.Subtensor): The subtensor object to use for getting the current block. + netuid (int): The netuid to use for retrieving the difficulty. + old_block_number (int): The old block number to check against. + hotkey_bytes (bytes): The bytes of the hotkey's pubkey. + curr_diff (multiprocessing.Array): The current difficulty as a multiprocessing array. + curr_block (multiprocessing.Array): Where the current block is stored as a multiprocessing array. + curr_block_num (multiprocessing.Value): Where the current block number is stored as a multiprocessing value. + update_curr_block (Callable): A function that updates the current block. + check_block (multiprocessing.Lock): A mp lock that is used to check for a new block. + solvers (List[_Solver]): A list of solvers to update the current block for. + curr_stats (RegistrationStatistics): The current registration statistics to update. Returns: (int) The current block number. @@ -866,8 +859,8 @@ def _check_for_newest_block_and_update( def _solve_for_difficulty_fast_cuda( - subtensor: "bittensor.subtensor", - wallet: "bittensor.wallet", + subtensor: "Subtensor", + wallet: "Wallet", netuid: int, output_in_place: bool = True, update_interval: int = 50_000, @@ -880,27 +873,19 @@ def _solve_for_difficulty_fast_cuda( """ Solves the registration fast using CUDA Args: - subtensor: bittensor.subtensor - The subtensor node to grab blocks - wallet: bittensor.wallet - The wallet to register - netuid: int - The netuid of the subnet to register to. - output_in_place: bool - If true, prints the output in place, otherwise prints to new lines - update_interval: int - The number of nonces to try before checking for more blocks - tpb: int - The number of threads per block. CUDA param that should match the GPU capability - dev_id: Union[List[int], int] - The CUDA device IDs to execute the registration on, either a single device or a list of devices - n_samples: int - The number of samples of the hash_rate to keep for the EWMA - alpha_: float - The alpha for the EWMA for the hash_rate calculation - log_verbose: bool - If true, prints more verbose logging of the registration metrics. - Note: The hash rate is calculated as an exponentially weighted moving average in order to make the measure more robust. + subtensor (bittensor.core.subtensor.Subtensor): The subtensor node to grab blocks. + wallet (bittensor_wallet.Wallet): The wallet to register. + netuid (int): The netuid of the subnet to register to. + output_in_place (bool): If true, prints the output in place, otherwise prints to new lines. + update_interval (int): The number of nonces to try before checking for more blocks. + tpb (int): The number of threads per block. CUDA param that should match the GPU capability. + dev_id (Union[List[int], int]): The CUDA device IDs to execute the registration on, either a single device or a list of devices. + n_samples (int): The number of samples of the hash_rate to keep for the EWMA. + alpha_ (float): The alpha for the EWMA for the hash_rate calculation. + log_verbose (bool): If true, prints more verbose logging of the registration metrics. + + Note: + The hash rate is calculated as an exponentially weighted moving average in order to make the measure more robust. """ if isinstance(dev_id, int): dev_id = [dev_id] @@ -919,7 +904,7 @@ def _solve_for_difficulty_fast_cuda( with _UsingSpawnStartMethod(force=True): curr_block, curr_block_num, curr_diff = _CUDASolver.create_shared_memory() - ## Create a worker per CUDA device + # Create a worker per CUDA device num_processes = len(dev_id) # Establish communication queues @@ -994,8 +979,7 @@ def _solve_for_difficulty_fast_cuda( start_time_perpetual = time.time() - console = bittensor.__console__ - logger = RegistrationStatisticsLogger(console, output_in_place) + logger = RegistrationStatisticsLogger(bt_console, output_in_place) logger.start() hash_rates = [0] * n_samples # The last n true hash_rates @@ -1109,37 +1093,22 @@ def create_pow( """ Creates a proof of work for the given subtensor and wallet. Args: - subtensor (:obj:`bittensor.subtensor.subtensor`, `required`): - The subtensor to create a proof of work for. - wallet (:obj:`bittensor.wallet.wallet`, `required`): - The wallet to create a proof of work for. - netuid (:obj:`int`, `required`): - The netuid for the subnet to create a proof of work for. - output_in_place (:obj:`bool`, `optional`, defaults to :obj:`True`): - If true, prints the progress of the proof of work to the console - in-place. Meaning the progress is printed on the same lines. - cuda (:obj:`bool`, `optional`, defaults to :obj:`False`): - If true, uses CUDA to solve the proof of work. - dev_id (:obj:`Union[List[int], int]`, `optional`, defaults to :obj:`0`): - The CUDA device id(s) to use. If cuda is true and dev_id is a list, - then multiple CUDA devices will be used to solve the proof of work. - tpb (:obj:`int`, `optional`, defaults to :obj:`256`): - The number of threads per block to use when solving the proof of work. - Should be a multiple of 32. - num_processes (:obj:`int`, `optional`, defaults to :obj:`None`): - The number of processes to use when solving the proof of work. - If None, then the number of processes is equal to the number of - CPU cores. - update_interval (:obj:`int`, `optional`, defaults to :obj:`None`): - The number of nonces to run before checking for a new block. - log_verbose (:obj:`bool`, `optional`, defaults to :obj:`False`): - If true, prints the progress of the proof of work more verbosely. + subtensor (bittensor.core.subtensor.Subtensor): The subtensor to create a proof of work for. + wallet (bittensor_wallet.Wallet): The wallet to create a proof of work for. + netuid (int): The netuid for the subnet to create a proof of work for. + output_in_place (bool): If true, prints the progress of the proof of work to the console in-place. Meaning the progress is printed on the same lines. + cuda (bool): If true, uses CUDA to solve the proof of work. + dev_id (Union[List[int], int]): The CUDA device id(s) to use. If cuda is true and dev_id is a list, then multiple CUDA devices will be used to solve the proof of work. + tpb (int): The number of threads per block to use when solving the proof of work. Should be a multiple of 32. + num_processes (int): The number of processes to use when solving the proof of work. If None, then the number of processes is equal to the number of CPU cores. + update_interval (int): The number of nonces to run before checking for a new block. + log_verbose (bool): If true, prints the progress of the proof of work more verbosely. + Returns: - :obj:`Optional[Dict[str, Any]]`: The proof of work solution or None if - the wallet is already registered or there is a different error. + Optional[Dict[str, Any]] : The proof of work solution or None if the wallet is already registered or there is a different error. Raises: - :obj:`ValueError`: If the subnet does not exist. + ValueError: If the subnet does not exist. """ if netuid != -1: if not subtensor.subnet_exists(netuid=netuid): diff --git a/bittensor/subnets.py b/bittensor/utils/subnets.py similarity index 79% rename from bittensor/subnets.py rename to bittensor/utils/subnets.py index 836a20dcb7..e550f4c776 100644 --- a/bittensor/subnets.py +++ b/bittensor/utils/subnets.py @@ -1,31 +1,37 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies Inc - +# 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 bittensor as bt from abc import ABC, abstractmethod from typing import Any, List, Union, Optional +from bittensor_wallet import Wallet + +from bittensor.core.axon import Axon +from bittensor.core.dendrite import Dendrite +from bittensor.core.synapse import Synapse +from bittensor.utils.btlogging import logging + class SubnetsAPI(ABC): - def __init__(self, wallet: "bt.wallet"): + """This class is not used within the bittensor package, but is actively used by the community.""" + + def __init__(self, wallet: "Wallet"): self.wallet = wallet - self.dendrite = bt.dendrite(wallet=wallet) + self.dendrite = Dendrite(wallet=wallet) async def __call__(self, *args, **kwargs): return await self.query_api(*args, **kwargs) @@ -38,7 +44,7 @@ def prepare_synapse(self, *args, **kwargs) -> Any: ... @abstractmethod - def process_responses(self, responses: List[Union["bt.Synapse", Any]]) -> Any: + def process_responses(self, responses: List[Union["Synapse", Any]]) -> Any: """ Process the responses from the network. """ @@ -46,7 +52,7 @@ def process_responses(self, responses: List[Union["bt.Synapse", Any]]) -> Any: async def query_api( self, - axons: Union[bt.axon, List[bt.axon]], + axons: Union["Axon", List["Axon"]], deserialize: Optional[bool] = False, timeout: Optional[int] = 12, **kwargs: Optional[Any], @@ -64,7 +70,7 @@ async def query_api( Any: The result of the process_responses_fn. """ synapse = self.prepare_synapse(**kwargs) - bt.logging.debug(f"Querying validator axons with synapse {synapse.name}...") + logging.debug(f"Querying validator axons with synapse {synapse.name}...") responses = await self.dendrite( axons=axons, synapse=synapse, diff --git a/bittensor/utils/subtensor.py b/bittensor/utils/subtensor.py deleted file mode 100644 index 484184e77f..0000000000 --- a/bittensor/utils/subtensor.py +++ /dev/null @@ -1,139 +0,0 @@ -# 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 - -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'. -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 - } - - -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}") - - -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 - - -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", {}) diff --git a/bittensor/utils/test_utils.py b/bittensor/utils/test_utils.py deleted file mode 100644 index fdaa1bda95..0000000000 --- a/bittensor/utils/test_utils.py +++ /dev/null @@ -1,22 +0,0 @@ -import socket -from random import randint -from typing import Set - -max_tries = 10 - - -def get_random_unused_port(allocated_ports: Set = set()): - def port_in_use(port: int) -> bool: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - return s.connect_ex(("localhost", port)) == 0 - - tries = 0 - while tries < max_tries: - tries += 1 - port = randint(2**14, 2**16 - 1) - - if port not in allocated_ports and not port_in_use(port): - allocated_ports.add(port) - return port - - raise RuntimeError(f"Tried {max_tries} random ports and could not find an open one") diff --git a/bittensor/utils/version.py b/bittensor/utils/version.py index 2a5aa3cd57..f6dada5ae2 100644 --- a/bittensor/utils/version.py +++ b/bittensor/utils/version.py @@ -1,10 +1,29 @@ +# 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 typing import Optional from pathlib import Path import time from packaging.version import Version -import bittensor import requests +from .btlogging import logging +from ..core.settings import __version__ +from ..core.settings import pipaddress VERSION_CHECK_THRESHOLD = 86400 @@ -20,32 +39,30 @@ def _get_version_file_path() -> Path: def _get_version_from_file(version_file: Path) -> Optional[str]: try: mtime = version_file.stat().st_mtime - bittensor.logging.debug(f"Found version file, last modified: {mtime}") + logging.debug(f"Found version file, last modified: {mtime}") diff = time.time() - mtime if diff >= VERSION_CHECK_THRESHOLD: - bittensor.logging.debug("Version file expired") + logging.debug("Version file expired") return None return version_file.read_text() except FileNotFoundError: - bittensor.logging.debug("No bitensor version file found") + logging.debug("No bittensor version file found") return None except OSError: - bittensor.logging.exception("Failed to read version file") + logging.exception("Failed to read version file") return None def _get_version_from_pypi(timeout: int = 15) -> str: - bittensor.logging.debug( - f"Checking latest Bittensor version at: {bittensor.__pipaddress__}" - ) + logging.debug(f"Checking latest Bittensor version at: {pipaddress}") try: - response = requests.get(bittensor.__pipaddress__, timeout=timeout) + response = requests.get(pipaddress, timeout=timeout) latest_version = response.json()["info"]["version"] return latest_version except requests.exceptions.RequestException: - bittensor.logging.exception("Failed to get latest version from pypi") + logging.exception("Failed to get latest version from pypi") raise @@ -60,27 +77,28 @@ def get_and_save_latest_version(timeout: int = 15) -> str: try: version_file.write_text(latest_version) except OSError: - bittensor.logging.exception("Failed to save latest version to file") + logging.exception("Failed to save latest version to file") return latest_version def check_version(timeout: int = 15): """ - Check if the current version of Bittensor is up to date with the latest version on PyPi. + Check if the current version of Bittensor is up-to-date with the latest version on PyPi. Raises a VersionCheckError if the version check fails. """ try: latest_version = get_and_save_latest_version(timeout) - if Version(latest_version) > Version(bittensor.__version__): + if Version(latest_version) > Version(__version__): print( "\u001b[33mBittensor Version: Current {}/Latest {}\nPlease update to the latest version at your earliest convenience. " "Run the following command to upgrade:\n\n\u001b[0mpython -m pip install --upgrade bittensor".format( - bittensor.__version__, latest_version + __version__, latest_version ) ) + pass except Exception as e: raise VersionCheckError("Version check failed") from e @@ -100,4 +118,4 @@ def version_checking(timeout: int = 15): try: check_version(timeout) except VersionCheckError: - bittensor.logging.exception("Version check failed") + logging.exception("Version check failed") diff --git a/bittensor/utils/wallet_utils.py b/bittensor/utils/wallet_utils.py deleted file mode 100644 index 39218c33f0..0000000000 --- a/bittensor/utils/wallet_utils.py +++ /dev/null @@ -1,168 +0,0 @@ -# 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. - -from substrateinterface.utils import ss58 -from typing import Union, Optional - -from .. import __ss58_format__ -from substrateinterface import Keypair as Keypair - - -def get_ss58_format(ss58_address: str) -> int: - """Returns the ss58 format of the given ss58 address.""" - return ss58.get_ss58_format(ss58_address) - - -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 - - -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_format=__ss58_format__) - - ss58_addr = keypair.ss58_address - return ss58_addr is not None - - except (ValueError, IndexError): - return False - - -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 - - -def create_identity_dict( - display: str = "", - legal: str = "", - web: str = "", - riot: str = "", - email: str = "", - pgp_fingerprint: Optional[str] = None, - image: str = "", - info: str = "", - twitter: str = "", -) -> dict: - """ - Creates a dictionary with structure for identity extrinsic. Must fit within 64 bits. - - Args: - display (str): String to be converted and stored under 'display'. - legal (str): String to be converted and stored under 'legal'. - web (str): String to be converted and stored under 'web'. - riot (str): String to be converted and stored under 'riot'. - email (str): String to be converted and stored under 'email'. - pgp_fingerprint (str): String to be converted and stored under 'pgp_fingerprint'. - image (str): String to be converted and stored under 'image'. - info (str): String to be converted and stored under 'info'. - twitter (str): String to be converted and stored under 'twitter'. - - Returns: - dict: A dictionary with the specified structure and byte string conversions. - - Raises: - ValueError: If pgp_fingerprint is not exactly 20 bytes long when encoded. - """ - if pgp_fingerprint and len(pgp_fingerprint.encode()) != 20: - raise ValueError("pgp_fingerprint must be exactly 20 bytes long when encoded") - - return { - "info": { - "additional": [[]], - "display": {f"Raw{len(display.encode())}": display.encode()}, - "legal": {f"Raw{len(legal.encode())}": legal.encode()}, - "web": {f"Raw{len(web.encode())}": web.encode()}, - "riot": {f"Raw{len(riot.encode())}": riot.encode()}, - "email": {f"Raw{len(email.encode())}": email.encode()}, - "pgp_fingerprint": pgp_fingerprint.encode() if pgp_fingerprint else None, - "image": {f"Raw{len(image.encode())}": image.encode()}, - "info": {f"Raw{len(info.encode())}": info.encode()}, - "twitter": {f"Raw{len(twitter.encode())}": twitter.encode()}, - } - } - - -def decode_hex_identity_dict(info_dictionary): - for key, value in info_dictionary.items(): - if isinstance(value, dict): - item = list(value.values())[0] - if isinstance(item, str) and item.startswith("0x"): - try: - info_dictionary[key] = bytes.fromhex(item[2:]).decode() - except UnicodeDecodeError: - print(f"Could not decode: {key}: {item}") - else: - info_dictionary[key] = item - return info_dictionary diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index de26d98c02..36ef6f1c86 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -1,26 +1,25 @@ -""" -Conversion for weight between chain representation and np.array or torch.Tensor -""" - # The MIT License (MIT) -# Copyright © 2021 Yuma Rao - +# 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. +"""Conversion for weight between chain representation and np.array or torch.Tensor""" + import hashlib import logging +import typing from typing import Tuple, List, Union import numpy as np @@ -28,9 +27,14 @@ from scalecodec import ScaleBytes, U16, Vec from substrateinterface import Keypair -import bittensor +from bittensor.utils.btlogging import logging from bittensor.utils.registration import torch, use_torch, legacy_torch_api_compat +if typing.TYPE_CHECKING: + from bittensor.core.metagraph import Metagraph + from bittensor.core.subtensor import Subtensor + + U32_MAX = 4294967295 U16_MAX = 65535 @@ -39,15 +43,13 @@ def normalize_max_weight( x: Union[NDArray[np.float32], "torch.FloatTensor"], limit: float = 0.1 ) -> Union[NDArray[np.float32], "torch.FloatTensor"]: - r"""Normalizes the tensor x so that sum(x) = 1 and the max value is not greater than the limit. + """Normalizes the tensor x so that sum(x) = 1 and the max value is not greater than the limit. Args: - x (:obj:`np.float32`): - Tensor to be max_value normalized. - limit: float: - Max value after normalization. + x (:obj:`np.float32`): Tensor to be max_value normalized. + limit: float: Max value after normalization. + Returns: - y (:obj:`np.float32`): - Normalized x tensor. + y (:obj:`np.float32`): Normalized x tensor. """ epsilon = 1e-7 # For numerical stability after normalization @@ -88,17 +90,14 @@ def normalize_max_weight( def convert_weight_uids_and_vals_to_tensor( n: int, uids: List[int], weights: List[int] ) -> Union[NDArray[np.float32], "torch.FloatTensor"]: - r"""Converts weights and uids from chain representation into a np.array (inverse operation from convert_weights_and_uids_for_emit) + """Converts weights and uids from chain representation into a np.array (inverse operation from convert_weights_and_uids_for_emit) Args: - n: int: - number of neurons on network. - uids (:obj:`List[int],`): - Tensor of uids as destinations for passed weights. - weights (:obj:`List[int],`): - Tensor of weights. + n (int): number of neurons on network. + uids (List[int]): Tensor of uids as destinations for passed weights. + weights (List[int]): Tensor of weights. + Returns: - row_weights ( np.float32 or torch.FloatTensor ): - Converted row weights. + row_weights (np.float32 or torch.FloatTensor): Converted row weights. """ row_weights = ( torch.zeros([n], dtype=torch.float32) @@ -118,21 +117,16 @@ def convert_weight_uids_and_vals_to_tensor( def convert_root_weight_uids_and_vals_to_tensor( n: int, uids: List[int], weights: List[int], subnets: List[int] ) -> Union[NDArray[np.float32], "torch.FloatTensor"]: - r"""Converts root weights and uids from chain representation into a np.array or torch FloatTensor (inverse operation from convert_weights_and_uids_for_emit) + """Converts root weights and uids from chain representation into a np.array or torch FloatTensor (inverse operation from convert_weights_and_uids_for_emit) Args: - n: int: - number of neurons on network. - uids (:obj:`List[int],`): - Tensor of uids as destinations for passed weights. - weights (:obj:`List[int],`): - Tensor of weights. - subnets (:obj:`List[int],`): - list of subnets on the network + n (int): number of neurons on network. + uids (List[int]): Tensor of uids as destinations for passed weights. + weights (List[int]): Tensor of weights. + subnets (List[int]): List of subnets on the network. + Returns: - row_weights ( np.float32 ): - Converted row weights. + row_weights (np.float32): Converted row weights. """ - row_weights = ( torch.zeros([n], dtype=torch.float32) if use_torch() @@ -158,17 +152,14 @@ def convert_root_weight_uids_and_vals_to_tensor( def convert_bond_uids_and_vals_to_tensor( n: int, uids: List[int], bonds: List[int] ) -> Union[NDArray[np.int64], "torch.LongTensor"]: - r"""Converts bond and uids from chain representation into a np.array. + """Converts bond and uids from chain representation into a np.array. Args: - n: int: - number of neurons on network. - uids (:obj:`List[int],`): - Tensor of uids as destinations for passed bonds. - bonds (:obj:`List[int],`): - Tensor of bonds. + n (int): number of neurons on network. + uids (List[int]): Tensor of uids as destinations for passed bonds. + bonds (List[int]): Tensor of bonds. + Returns: - row_bonds ( np.float32 ): - Converted row bonds. + row_bonds (np.float32): Converted row bonds. """ row_bonds = ( torch.zeros([n], dtype=torch.int64) @@ -184,17 +175,14 @@ def convert_weights_and_uids_for_emit( uids: Union[NDArray[np.int64], "torch.LongTensor"], weights: Union[NDArray[np.float32], "torch.FloatTensor"], ) -> Tuple[List[int], List[int]]: - r"""Converts weights into integer u32 representation that sum to MAX_INT_WEIGHT. + """Converts weights into integer u32 representation that sum to MAX_INT_WEIGHT. Args: - uids (:obj:`np.int64,`): - Tensor of uids as destinations for passed weights. - weights (:obj:`np.float32,`): - Tensor of weights. + uids (np.int64):Tensor of uids as destinations for passed weights. + weights (np.float32):Tensor of weights. + Returns: - weight_uids (List[int]): - Uids as a list. - weight_vals (List[int]): - Weights as a list. + weight_uids (List[int]): Uids as a list. + weight_vals (List[int]): Weights as a list. """ # Checks. weights = weights.tolist() @@ -234,22 +222,23 @@ def convert_weights_and_uids_for_emit( return weight_uids, weight_vals +# The community uses / bittensor does not def process_weights_for_netuid( uids: Union[NDArray[np.int64], "torch.Tensor"], weights: Union[NDArray[np.float32], "torch.Tensor"], netuid: int, - subtensor: "bittensor.subtensor", - metagraph: "bittensor.metagraph" = None, + subtensor: "Subtensor", + metagraph: "Metagraph" = None, exclude_quantile: int = 0, ) -> Union[ Tuple["torch.Tensor", "torch.FloatTensor"], Tuple[NDArray[np.int64], NDArray[np.float32]], ]: - bittensor.logging.debug("process_weights_for_netuid()") - bittensor.logging.debug("weights", weights) - bittensor.logging.debug("netuid", netuid) - bittensor.logging.debug("subtensor", subtensor) - bittensor.logging.debug("metagraph", metagraph) + logging.debug("process_weights_for_netuid()") + logging.debug("weights", weights) + logging.debug("netuid", netuid) + logging.debug("subtensor", subtensor) + logging.debug("metagraph", metagraph) # Get latest metagraph from chain if metagraph is None. if metagraph is None: @@ -268,9 +257,9 @@ def process_weights_for_netuid( quantile = exclude_quantile / U16_MAX min_allowed_weights = subtensor.min_allowed_weights(netuid=netuid) max_weight_limit = subtensor.max_weight_limit(netuid=netuid) - bittensor.logging.debug("quantile", quantile) - bittensor.logging.debug("min_allowed_weights", min_allowed_weights) - bittensor.logging.debug("max_weight_limit", max_weight_limit) + logging.debug("quantile", quantile) + logging.debug("min_allowed_weights", min_allowed_weights) + logging.debug("max_weight_limit", max_weight_limit) # Find all non zero weights. non_zero_weight_idx = ( @@ -282,13 +271,13 @@ def process_weights_for_netuid( non_zero_weights = weights[non_zero_weight_idx] nzw_size = non_zero_weights.numel() if use_torch() else non_zero_weights.size if nzw_size == 0 or metagraph.n < min_allowed_weights: - bittensor.logging.warning("No non-zero weights returning all ones.") + logging.warning("No non-zero weights returning all ones.") final_weights = ( torch.ones((metagraph.n)).to(metagraph.n) / metagraph.n if use_torch() else np.ones((metagraph.n), dtype=np.int64) / metagraph.n ) - bittensor.logging.debug("final_weights", final_weights) + logging.debug("final_weights", final_weights) final_weights_count = ( torch.tensor(list(range(len(final_weights)))) if use_torch() @@ -301,7 +290,7 @@ def process_weights_for_netuid( ) elif nzw_size < min_allowed_weights: - bittensor.logging.warning( + logging.warning( "No non-zero weights less then min allowed weight, returning all ones." ) # ( const ): Should this be np.zeros( ( metagraph.n ) ) to reset everyone to build up weight? @@ -311,10 +300,8 @@ def process_weights_for_netuid( else np.ones((metagraph.n), dtype=np.int64) * 1e-5 ) # creating minimum even non-zero weights weights[non_zero_weight_idx] += non_zero_weights - bittensor.logging.debug("final_weights", weights) - normalized_weights = bittensor.utils.weight_utils.normalize_max_weight( - x=weights, limit=max_weight_limit - ) + logging.debug("final_weights", weights) + normalized_weights = normalize_max_weight(x=weights, limit=max_weight_limit) nw_arange = ( torch.tensor(list(range(len(normalized_weights)))) if use_torch() @@ -322,7 +309,7 @@ def process_weights_for_netuid( ) return nw_arange, normalized_weights - bittensor.logging.debug("non_zero_weights", non_zero_weights) + logging.debug("non_zero_weights", non_zero_weights) # Compute the exclude quantile and find the weights in the lowest quantile max_exclude = max(0, len(non_zero_weights) - min_allowed_weights) / len( @@ -334,21 +321,21 @@ def process_weights_for_netuid( if use_torch() else np.quantile(non_zero_weights, exclude_quantile) ) - bittensor.logging.debug("max_exclude", max_exclude) - bittensor.logging.debug("exclude_quantile", exclude_quantile) - bittensor.logging.debug("lowest_quantile", lowest_quantile) + logging.debug("max_exclude", max_exclude) + logging.debug("exclude_quantile", exclude_quantile) + logging.debug("lowest_quantile", lowest_quantile) # Exclude all weights below the allowed quantile. non_zero_weight_uids = non_zero_weight_uids[lowest_quantile <= non_zero_weights] non_zero_weights = non_zero_weights[lowest_quantile <= non_zero_weights] - bittensor.logging.debug("non_zero_weight_uids", non_zero_weight_uids) - bittensor.logging.debug("non_zero_weights", non_zero_weights) + logging.debug("non_zero_weight_uids", non_zero_weight_uids) + logging.debug("non_zero_weights", non_zero_weights) # Normalize weights and return. - normalized_weights = bittensor.utils.weight_utils.normalize_max_weight( + normalized_weights = normalize_max_weight( x=non_zero_weights, limit=max_weight_limit ) - bittensor.logging.debug("final_weights", normalized_weights) + logging.debug("final_weights", normalized_weights) return non_zero_weight_uids, normalized_weights diff --git a/requirements/prod.txt b/requirements/prod.txt index e52499a389..b9cf7aecc8 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -26,6 +26,7 @@ retry requests rich scalecodec==1.2.11 +setuptools~=70.0.0 shtab~=1.6.5 substrate-interface~=1.7.9 termcolor diff --git a/setup.py b/setup.py index 49c419724a..37d3d07b24 100644 --- a/setup.py +++ b/setup.py @@ -1,32 +1,32 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao - +# 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 setuptools import setup, find_packages -from os import path -from io import open import codecs -import re import os import pathlib +import re +from io import open +from os import path + +from setuptools import setup, find_packages def read_requirements(path): requirements = [] - git_requirements = [] with pathlib.Path(path).open() as requirements_txt: for line in requirements_txt: @@ -52,7 +52,7 @@ def read_requirements(path): # loading version from setup.py with codecs.open( - os.path.join(here, "bittensor/__init__.py"), encoding="utf-8" + os.path.join(here, "bittensor/core/settings.py"), encoding="utf-8" ) as init_file: version_match = re.search( r"^__version__ = ['\"]([^'\"]*)['\"]", init_file.read(), re.M @@ -77,7 +77,6 @@ def read_requirements(path): "dev": extra_requirements_dev, "torch": extra_requirements_torch, }, - scripts=["bin/btcli"], classifiers=[ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", diff --git a/tests/helpers/helpers.py b/tests/helpers/helpers.py index 482f59ce2d..c2ff458f25 100644 --- a/tests/helpers/helpers.py +++ b/tests/helpers/helpers.py @@ -1,14 +1,14 @@ # The MIT License (MIT) -# Copyright © 2023 Opentensor Foundation - +# 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 @@ -16,16 +16,19 @@ # DEALINGS IN THE SOFTWARE. from typing import Union -from bittensor import Balance, NeuronInfo, AxonInfo, PrometheusInfo, __ss58_format__ -from bittensor.mock.wallet_mock import MockWallet as _MockWallet -from bittensor.mock.wallet_mock import get_mock_coldkey as _get_mock_coldkey -from bittensor.mock.wallet_mock import get_mock_hotkey as _get_mock_hotkey -from bittensor.mock.wallet_mock import get_mock_keypair as _get_mock_keypair -from bittensor.mock.wallet_mock import get_mock_wallet as _get_mock_wallet + +from bittensor_wallet.mock.wallet_mock import MockWallet as _MockWallet +from bittensor_wallet.mock.wallet_mock import get_mock_coldkey as _get_mock_coldkey +from bittensor_wallet.mock.wallet_mock import get_mock_hotkey as _get_mock_hotkey +from bittensor_wallet.mock.wallet_mock import get_mock_keypair as _get_mock_keypair +from bittensor_wallet.mock.wallet_mock import get_mock_wallet as _get_mock_wallet from rich.console import Console from rich.text import Text +from bittensor.utils.balance import Balance +from bittensor.core.chain_data import AxonInfo, NeuronInfo, PrometheusInfo + def __mock_wallet_factory__(*args, **kwargs) -> _MockWallet: """Returns a mock wallet object.""" @@ -51,12 +54,8 @@ def __eq__(self, __o: Union[float, int, Balance]) -> bool: # True if __o \in [value - tolerance, value + tolerance] # or if value \in [__o - tolerance, __o + tolerance] return ( - (self.value - self.tolerance) <= __o - and __o <= (self.value + self.tolerance) - ) or ( - (__o - self.tolerance) <= self.value - and self.value <= (__o + self.tolerance) - ) + (self.value - self.tolerance) <= __o <= (self.value + self.tolerance) + ) or ((__o - self.tolerance) <= self.value <= (__o + self.tolerance)) def get_mock_neuron(**kwargs) -> NeuronInfo: diff --git a/tests/integration_tests/__init__.py b/tests/integration_tests/__init__.py index e69de29bb2..640a132503 100644 --- a/tests/integration_tests/__init__.py +++ b/tests/integration_tests/__init__.py @@ -0,0 +1,16 @@ +# 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. diff --git a/tests/integration_tests/test_cli.py b/tests/integration_tests/test_cli.py deleted file mode 100644 index 263c7c1b92..0000000000 --- a/tests/integration_tests/test_cli.py +++ /dev/null @@ -1,2738 +0,0 @@ -# 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 os -import random -import unittest -from copy import deepcopy -from types import SimpleNamespace -from typing import Dict -from unittest.mock import MagicMock, patch - -import pytest -from bittensor_wallet import Wallet -from bittensor_wallet.mock import get_mock_keypair, get_mock_wallet as generate_wallet - -import bittensor -from bittensor import Balance -from bittensor.commands.delegates import _get_coldkey_wallets_for_path -from bittensor.commands.identity import SetIdentityCommand -from bittensor.commands.wallets import _get_coldkey_ss58_addresses_for_path -from bittensor.mock import MockSubtensor -from tests.helpers import is_running_in_circleci, MockConsole - -_subtensor_mock: MockSubtensor = MockSubtensor() - - -def setUpModule(): - _subtensor_mock.reset() - - _subtensor_mock.create_subnet(netuid=1) - _subtensor_mock.create_subnet(netuid=2) - _subtensor_mock.create_subnet(netuid=3) - - # Set diff 0 - _subtensor_mock.set_difficulty(netuid=1, difficulty=0) - _subtensor_mock.set_difficulty(netuid=2, difficulty=0) - _subtensor_mock.set_difficulty(netuid=3, difficulty=0) - - -def return_mock_sub(*args, **kwargs): - return MockSubtensor - - -@patch("bittensor.subtensor", new_callable=return_mock_sub) -class TestCLIWithNetworkAndConfig(unittest.TestCase): - def setUp(self): - self._config = TestCLIWithNetworkAndConfig.construct_config() - - @property - def config(self): - copy_ = deepcopy(self._config) - return copy_ - - @staticmethod - def construct_config(): - parser = bittensor.cli.__create_parser__() - defaults = bittensor.config(parser=parser, args=[]) - # Parse commands and subcommands - for command in bittensor.ALL_COMMANDS: - if ( - command in bittensor.ALL_COMMANDS - and "commands" in bittensor.ALL_COMMANDS[command] - ): - for subcommand in bittensor.ALL_COMMANDS[command]["commands"]: - defaults.merge( - bittensor.config(parser=parser, args=[command, subcommand]) - ) - else: - defaults.merge(bittensor.config(parser=parser, args=[command])) - - defaults.netuid = 1 - # Always use mock subtensor. - defaults.subtensor.network = "finney" - # Skip version checking. - defaults.no_version_checking = True - - return defaults - - def test_overview(self, _): - if is_running_in_circleci(): - config = self.config - config.wallet.path = "/tmp/test_cli_test_overview" - config.wallet.name = "mock_wallet" - config.command = "wallet" - config.subcommand = "overview" - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - - mock_hotkeys = ["hk0", "hk1", "hk2", "hk3", "hk4"] - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - coldkeypub_file=MagicMock( - exists_on_device=MagicMock(return_value=True) # Wallet exists - ), - ) - for idx, hk in enumerate(mock_hotkeys) - ] - - mock_registrations = [ - (1, mock_wallets[0]), - (1, mock_wallets[1]), - # (1, mock_wallets[2]), Not registered on netuid 1 - (2, mock_wallets[0]), - # (2, mock_wallets[1]), Not registered on netuid 2 - (2, mock_wallets[2]), - (3, mock_wallets[0]), - (3, mock_wallets[1]), - (3, mock_wallets[2]), # All registered on netuid 3 (but hk3) - (3, mock_wallets[4]), # hk4 is only on netuid 3 - ] # hk3 is not registered on any network - - # Register each wallet to it's subnet. - print("Registering wallets to mock subtensor...") - - for netuid, wallet in mock_registrations: - _ = _subtensor_mock.force_register_neuron( - netuid=netuid, - coldkey=wallet.coldkey.ss58_address, - hotkey=wallet.hotkey.ss58_address, - ) - - def mock_get_wallet(*args, **kwargs): - hk = kwargs.get("hotkey") - name_ = kwargs.get("name") - - if not hk and kwargs.get("config"): - hk = kwargs.get("config").wallet.hotkey - if not name_ and kwargs.get("config"): - name_ = kwargs.get("config").wallet.name - - for wallet in mock_wallets: - if wallet.name == name_ and wallet.hotkey_str == hk: - return wallet - else: - for wallet in mock_wallets: - if wallet.name == name_: - return wallet - else: - return mock_wallets[0] - - mock_console = MockConsole() - with patch( - "bittensor.commands.overview.get_hotkey_wallets_for_wallet" - ) as mock_get_all_wallets: - mock_get_all_wallets.return_value = mock_wallets - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - with patch("bittensor.__console__", mock_console): - cli.run() - - # Check that the overview was printed. - self.assertIsNotNone(mock_console.captured_print) - - output_no_syntax = mock_console.remove_rich_syntax( - mock_console.captured_print - ) - - # Check that each subnet was printed. - self.assertIn("Subnet: 1", output_no_syntax) - self.assertIn("Subnet: 2", output_no_syntax) - self.assertIn("Subnet: 3", output_no_syntax) - - # Check that only registered hotkeys are printed once for each subnet. - for wallet in mock_wallets: - expected = [ - wallet.hotkey_str for _, wallet in mock_registrations - ].count(wallet.hotkey_str) - occurrences = output_no_syntax.count(wallet.hotkey_str) - self.assertEqual(occurrences, expected) - - # Check that unregistered hotkeys are not printed. - for wallet in mock_wallets: - if wallet not in [w for _, w in mock_registrations]: - self.assertNotIn(wallet.hotkey_str, output_no_syntax) - - def test_overview_not_in_first_subnet(self, _): - if is_running_in_circleci(): - config = self.config - config.wallet.path = "/tmp/test_cli_test_overview" - config.wallet.name = "mock_wallet" - config.command = "wallet" - config.subcommand = "overview" - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - - mock_hotkeys = ["hk0", "hk1", "hk2", "hk3", "hk4"] - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - coldkeypub_file=MagicMock( - exists_on_device=MagicMock(return_value=True) # Wallet exists - ), - ) - for idx, hk in enumerate(mock_hotkeys) - ] - - mock_registrations = [ - # No registrations in subnet 1 or 2 - (3, mock_wallets[4]) # hk4 is on netuid 3 - ] - - # Register each wallet to it's subnet - print("Registering mock wallets to subnets...") - - for netuid, wallet in mock_registrations: - print( - "Registering wallet {} to subnet {}".format( - wallet.hotkey_str, netuid - ) - ) - _ = _subtensor_mock.force_register_neuron( - netuid=netuid, - coldkey=wallet.coldkey.ss58_address, - hotkey=wallet.hotkey.ss58_address, - ) - - def mock_get_wallet(*args, **kwargs): - hk = kwargs.get("hotkey") - name_ = kwargs.get("name") - - if not hk and kwargs.get("config"): - hk = kwargs.get("config").wallet.hotkey - if not name_ and kwargs.get("config"): - name_ = kwargs.get("config").wallet.name - - for wallet in mock_wallets: - if wallet.name == name_ and wallet.hotkey_str == hk: - return wallet - else: - for wallet in mock_wallets: - if wallet.name == name_: - return wallet - else: - return mock_wallets[0] - - mock_console = MockConsole() - with patch( - "bittensor.commands.overview.get_hotkey_wallets_for_wallet" - ) as mock_get_all_wallets: - mock_get_all_wallets.return_value = mock_wallets - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - with patch("bittensor.__console__", mock_console): - cli.run() - - # Check that the overview was printed. - self.assertIsNotNone(mock_console.captured_print) - - output_no_syntax = mock_console.remove_rich_syntax( - mock_console.captured_print - ) - - # Check that each subnet was printed except subnet 1 and 2. - # Subnet 1 and 2 are not printed because no wallet is registered to them. - self.assertNotIn("Subnet: 1", output_no_syntax) - self.assertNotIn("Subnet: 2", output_no_syntax) - self.assertIn("Subnet: 3", output_no_syntax) - - # Check that only registered hotkeys are printed once for each subnet. - for wallet in mock_wallets: - expected = [ - wallet.hotkey_str for _, wallet in mock_registrations - ].count(wallet.hotkey_str) - occurrences = output_no_syntax.count(wallet.hotkey_str) - self.assertEqual(occurrences, expected) - - # Check that unregistered hotkeys are not printed. - for wallet in mock_wallets: - if wallet not in [w for _, w in mock_registrations]: - self.assertNotIn(wallet.hotkey_str, output_no_syntax) - - def test_overview_with_hotkeys_config(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - config.no_prompt = True - config.hotkeys = ["some_hotkey"] - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_without_hotkeys_config(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_with_sort_by_config(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - config.no_prompt = True - config.wallet.sort_by = "rank" - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_with_sort_by_bad_column_name(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - config.no_prompt = True - config.wallet.sort_by = "totallynotmatchingcolumnname" - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_without_sort_by_config(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_with_sort_order_config(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - config.wallet.sort_order = "desc" # Set descending sort order - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_with_sort_order_config_bad_sort_type(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - config.wallet.sort_order = "nowaythisshouldmatchanyorderingchoice" - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_without_sort_order_config(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - # Don't specify sort_order in config - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_with_width_config(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - config.width = 100 - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_without_width_config(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - # Don't specify width in config - config.no_prompt = True - config.all = False - config.netuid = [] # Don't set, so it tries all networks. - - cli = bittensor.cli(config) - cli.run() - - def test_overview_all(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "overview" - config.no_prompt = True - config.netuid = [] # Don't set, so it tries all networks. - - config.all = True - cli = bittensor.cli(config) - cli.run() - - def test_unstake_with_specific_hotkeys(self, _): - config = self.config - config.command = "stake" - config.subcommand = "remove" - config.no_prompt = True - config.amount = 5.0 - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk0", "hk1", "hk2"] - config.all_hotkeys = False - # Notice no max_stake specified - - mock_stakes: Dict[str, Balance] = { - # All have more than 5.0 stake - "hk0": Balance.from_float(10.0), - "hk1": Balance.from_float(11.1), - "hk2": Balance.from_float(12.2), - } - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(config.hotkeys) - ] - - # Register mock wallets and give them stakes - - for wallet in mock_wallets: - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkey.ss58_address, - stake=mock_stakes[wallet.hotkey_str].rao, - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertEqual(stake.rao, mock_stakes[wallet.hotkey_str].rao) - - cli.run() - - # Check stakes after unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertAlmostEqual( - stake.tao, - mock_stakes[wallet.hotkey_str].tao - config.amount, - places=4, - ) - - def test_unstake_with_all_hotkeys(self, _): - config = self.config - config.command = "stake" - config.subcommand = "remove" - config.no_prompt = True - config.amount = 5.0 - config.wallet.name = "fake_wallet" - # Notice wallet.hotkeys not specified - config.all_hotkeys = True - # Notice no max_stake specified - - mock_stakes: Dict[str, Balance] = { - # All have more than 5.0 stake - "hk0": Balance.from_float(10.0), - "hk1": Balance.from_float(11.1), - "hk2": Balance.from_float(12.2), - } - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(list(mock_stakes.keys())) - ] - - # Register mock wallets and give them stakes - - for wallet in mock_wallets: - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkey.ss58_address, - stake=mock_stakes[wallet.hotkey_str].rao, - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch( - "bittensor.commands.unstake.get_hotkey_wallets_for_wallet" - ) as mock_get_all_wallets: - mock_get_all_wallets.return_value = mock_wallets - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertEqual(stake.rao, mock_stakes[wallet.hotkey_str].rao) - - cli.run() - - # Check stakes after unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertAlmostEqual( - stake.tao, - mock_stakes[wallet.hotkey_str].tao - config.amount, - places=4, - ) - - def test_unstake_with_exclude_hotkeys_from_all(self, _): - config = self.config - config.command = "stake" - config.subcommand = "remove" - config.no_prompt = True - config.amount = 5.0 - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk1"] # Exclude hk1 - config.all_hotkeys = True - - mock_stakes: Dict[str, Balance] = { - # All have more than 5.0 stake - "hk0": Balance.from_float(10.0), - "hk1": Balance.from_float(11.1), - "hk2": Balance.from_float(12.2), - } - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(list(mock_stakes.keys())) - ] - - # Register mock wallets and give them stakes - - for wallet in mock_wallets: - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkey.ss58_address, - stake=mock_stakes[wallet.hotkey_str].rao, - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch( - "bittensor.commands.unstake.get_hotkey_wallets_for_wallet" - ) as mock_get_all_wallets: - mock_get_all_wallets.return_value = mock_wallets - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertEqual(stake.rao, mock_stakes[wallet.hotkey_str].rao) - - cli.run() - - # Check stakes after unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - if wallet.hotkey_str == "hk1": - # hk1 should not have been unstaked - self.assertAlmostEqual( - stake.tao, mock_stakes[wallet.hotkey_str].tao, places=4 - ) - else: - self.assertAlmostEqual( - stake.tao, - mock_stakes[wallet.hotkey_str].tao - config.amount, - places=4, - ) - - def test_unstake_with_multiple_hotkeys_max_stake(self, _): - config = self.config - config.command = "stake" - config.subcommand = "remove" - config.no_prompt = True - # Notie amount is not specified - config.max_stake = 5.0 # The keys should have at most 5.0 tao staked after - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk0", "hk1", "hk2"] - config.all_hotkeys = False - - mock_stakes: Dict[str, Balance] = { - # All have more than 5.0 stake - "hk0": Balance.from_float(10.0), - "hk1": Balance.from_float(4.9), - "hk2": Balance.from_float(12.2), - } - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(list(mock_stakes.keys())) - ] - - # Register mock wallets and give them stakes - print("Registering mock wallets...") - - for wallet in mock_wallets: - print("Registering mock wallet {}".format(wallet.hotkey_str)) - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkey.ss58_address, - stake=mock_stakes[wallet.hotkey_str].rao, - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch( - "bittensor.commands.unstake.get_hotkey_wallets_for_wallet" - ) as mock_get_all_wallets: - mock_get_all_wallets.return_value = mock_wallets - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertEqual(stake.rao, mock_stakes[wallet.hotkey_str].rao) - - cli.run() - - # Check stakes after unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # All should have been unstaked below or equal to max_stake - self.assertLessEqual( - stake.tao, config.max_stake + 0.0001 - ) # Add a small buffer for fp errors - - if wallet.hotkey_str == "hk1": - # hk1 should not have been unstaked because it was already below max_stake - self.assertAlmostEqual( - stake.tao, mock_stakes[wallet.hotkey_str].tao, places=4 - ) - - def test_unstake_with_thresholds(self, _): - config = self.config - config.command = "stake" - config.subcommand = "remove" - config.no_prompt = True - # as the minimum required stake may change, this method allows us to dynamically - # update the amount in the mock without updating the tests - min_stake: Balance = _subtensor_mock.get_minimum_required_stake() - # Must be a float - config.amount = min_stake.tao # Unstake below the minimum required stake - wallet_names = ["w0", "w1", "w2"] - config.all_hotkeys = False - # Notice no max_stake specified - - mock_stakes: Dict[str, Balance] = { - "w0": 2 * min_stake - 1, # remaining stake will be below the threshold - "w1": 2 * min_stake - 2, - "w2": 2 * min_stake - 5, - } - - mock_wallets = [ - SimpleNamespace( - name=wallet_name, - coldkey=get_mock_keypair(idx, self.id()), - coldkeypub=get_mock_keypair(idx, self.id()), - hotkey_str="hk{}".format(idx), # doesn't matter - hotkey=get_mock_keypair(idx + 100, self.id()), # doesn't matter - ) - for idx, wallet_name in enumerate(wallet_names) - ] - - delegate_hotkey = mock_wallets[0].hotkey.ss58_address - - # Register mock neuron, only for w0 - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=delegate_hotkey, - coldkey=mock_wallets[0].coldkey.ss58_address, - stake=mock_stakes["w0"], - ) - - # Become a delegate - _ = _subtensor_mock.nominate( - wallet=mock_wallets[0], - ) - - # Stake to the delegate with the other coldkeys - for wallet in mock_wallets[1:]: - # Give balance - _ = _subtensor_mock.force_set_balance( - ss58_address=wallet.coldkeypub.ss58_address, - balance=( - mock_stakes[wallet.name] + _subtensor_mock.get_existential_deposit() - ).tao - + 1.0, - ) - _ = _subtensor_mock.add_stake( - wallet=wallet, - hotkey_ss58=delegate_hotkey, - amount=mock_stakes[wallet.name], - ) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("config") and kwargs["config"].get("wallet"): - for wallet in mock_wallets: - if wallet.name == kwargs["config"].wallet.name: - return wallet - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - for wallet in mock_wallets: - # Check stakes before unstaking - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=delegate_hotkey, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertEqual(stake.rao, mock_stakes[wallet.name].rao) - - config.wallet.name = wallet.name - config.hotkey_ss58address = delegate_hotkey # Single unstake - - cli = bittensor.cli(config) - with patch.object(_subtensor_mock, "_do_unstake") as mock_unstake: - with patch( - "bittensor.__console__.print" - ) as mock_print: # Catch console print - cli.run() - - # Filter for console print calls - console_prints = [ - call[0][0] for call in mock_print.call_args_list - ] - minimum_print = filter( - lambda x: "less than minimum of" in x, console_prints - ) - - unstake_calls = mock_unstake.call_args_list - self.assertEqual(len(unstake_calls), 1) # Only one unstake call - - _, kwargs = unstake_calls[0] - # Verify delegate was unstaked - self.assertEqual(kwargs["hotkey_ss58"], delegate_hotkey) - self.assertEqual(kwargs["wallet"].name, wallet.name) - - if wallet.name == "w0": - # This wallet owns the delegate - # Should unstake specified amount - self.assertEqual( - kwargs["amount"], bittensor.Balance(config.amount) - ) - # No warning for w0 - self.assertRaises( - StopIteration, next, minimum_print - ) # No warning for w0 - else: - # Should unstake *all* the stake - staked = mock_stakes[wallet.name] - self.assertEqual(kwargs["amount"], staked) - - # Check warning was printed - _ = next( - minimum_print - ) # Doesn't raise, so the warning was printed - - def test_unstake_all(self, _): - config = self.config - config.command = "stake" - config.subcommand = "remove" - config.no_prompt = True - config.amount = 0.0 # 0 implies full unstake - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk0"] - config.all_hotkeys = False - - mock_stakes: Dict[str, Balance] = {"hk0": Balance.from_float(10.0)} - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(config.hotkeys) - ] - - # Register mock wallets and give them stakes - - for wallet in mock_wallets: - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkey.ss58_address, - stake=mock_stakes[wallet.hotkey_str].rao, - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertEqual(stake.rao, mock_stakes[wallet.hotkey_str].rao) - - cli.run() - - # Check stakes after unstaking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # because the amount is less than the threshold, none of these should unstake - self.assertEqual(stake.tao, Balance.from_tao(0)) - - def test_stake_with_specific_hotkeys(self, _): - config = self.config - config.command = "stake" - config.subcommand = "add" - config.no_prompt = True - config.amount = 5.0 - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk0", "hk1", "hk2"] - config.all_hotkeys = False - # Notice no max_stake specified - - mock_balance = Balance.from_float(22.2) - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(config.hotkeys) - ] - - # Register mock wallets and give them balances - print("Registering mock wallets...") - - for wallet in mock_wallets: - print("Registering mock wallet {}".format(wallet.hotkey_str)) - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkey.ss58_address, - ) - - success, err = _subtensor_mock.force_set_balance( - ss58_address=mock_coldkey_kp.ss58_address, balance=mock_balance.rao - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertEqual(stake.rao, 0) - - cli.run() - - # Check stakes after staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertAlmostEqual(stake.tao, config.amount, places=4) - - def test_stake_with_all_hotkeys(self, _): - config = self.config - config.command = "stake" - config.subcommand = "add" - config.no_prompt = True - config.amount = 5.0 - config.wallet.name = "fake_wallet" - # Notice wallet.hotkeys is not specified - config.all_hotkeys = True - # Notice no max_stake specified - - mock_hotkeys = ["hk0", "hk1", "hk2"] - - mock_balance = Balance.from_float(22.0) - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(mock_hotkeys) - ] - - # Register mock wallets and give them no stake - print("Registering mock wallets...") - - for wallet in mock_wallets: - print("Registering mock wallet {}".format(wallet.hotkey_str)) - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - ) - - # Set the coldkey balance - success, err = _subtensor_mock.force_set_balance( - ss58_address=mock_coldkey_kp.ss58_address, balance=mock_balance.rao - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - with patch( - "bittensor.commands.stake.get_hotkey_wallets_for_wallet" - ) as mock_get_hotkey_wallets_for_wallet: - mock_get_hotkey_wallets_for_wallet.return_value = mock_wallets - - # Check stakes before staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # Check that all stakes are 0 - self.assertEqual(stake.rao, 0) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - - self.assertAlmostEqual(balance.tao, mock_balance.tao, places=4) - - cli.run() - - # Check stakes after staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # Check that all stakes are 5.0 - self.assertAlmostEqual(stake.tao, config.amount, places=4) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - self.assertAlmostEqual( - balance.tao, - mock_balance.tao - (config.amount * len(mock_wallets)), - places=4, - ) - - def test_stake_with_exclude_hotkeys_from_all(self, _): - config = self.config - config.command = "stake" - config.subcommand = "add" - config.no_prompt = True - config.amount = 5.0 - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk1"] # exclude hk1 - config.all_hotkeys = True - # Notice no max_stake specified - - mock_hotkeys = ["hk0", "hk1", "hk2"] - - mock_balance = Balance.from_float(25.0) - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(mock_hotkeys) - ] - - # Register mock wallets and give them balances - print("Registering mock wallets...") - - for wallet in mock_wallets: - print("Registering mock wallet {}".format(wallet.hotkey_str)) - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - ) - - # Set the coldkey balance - _subtensor_mock.force_set_balance( - ss58_address=mock_coldkey_kp.ss58_address, balance=mock_balance.rao - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch( - "bittensor.commands.stake.get_hotkey_wallets_for_wallet" - ) as mock_get_all_wallets: - mock_get_all_wallets.return_value = mock_wallets - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # Check that all stakes are 0 - self.assertEqual(stake.rao, 0) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - - self.assertAlmostEqual(balance.tao, mock_balance.tao, places=4) - - cli.run() - - # Check stakes after staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - - if wallet.hotkey_str == "hk1": - # Check that hk1 stake is 0 - # We excluded it from staking - self.assertEqual(stake.tao, 0) - else: - # Check that all stakes are 5.0 - self.assertAlmostEqual(stake.tao, config.amount, places=4) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - self.assertAlmostEqual( - balance.tao, mock_balance.tao - (config.amount * 2), places=4 - ) - - def test_stake_with_multiple_hotkeys_max_stake(self, _): - config = self.config - config.command = "stake" - config.subcommand = "add" - config.no_prompt = True - # Notie amount is not specified - config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk0", "hk1", "hk2"] - config.all_hotkeys = False - - mock_balance = Balance.from_float(config.max_stake * 3) - - mock_stakes: Dict[str, Balance] = { - "hk0": Balance.from_float(0.0), - "hk1": Balance.from_float(config.max_stake * 2), - "hk2": Balance.from_float(0.0), - } - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(config.hotkeys) - ] - - # Register mock wallets and give them balances - print("Registering mock wallets...") - - for wallet in mock_wallets: - print("Registering mock wallet {}".format(wallet.hotkey_str)) - if wallet.hotkey_str == "hk1": - # Set the stake for hk1 - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - stake=mock_stakes[wallet.hotkey_str].rao, - ) - else: - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - ) - - _subtensor_mock.force_set_balance( - ss58_address=mock_coldkey_kp.ss58_address, balance=mock_balance.rao - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # Check that all stakes are correct - if wallet.hotkey_str == "hk1": - self.assertAlmostEqual(stake.tao, config.max_stake * 2, places=4) - else: - self.assertEqual(stake.rao, 0) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - - self.assertAlmostEqual(balance.tao, mock_balance.tao, places=4) - - cli.run() - - # Check stakes after staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - - # Check that all stakes at least 15.0 - self.assertGreaterEqual(stake.tao + 0.1, config.max_stake) - - if wallet.hotkey_str == "hk1": - # Check that hk1 stake was not changed - # It had more than max_stake already - self.assertAlmostEqual( - stake.tao, mock_stakes[wallet.hotkey_str].tao, places=4 - ) - - # Check that the balance decreased - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - self.assertLessEqual(balance.tao, mock_balance.tao) - - def test_stake_with_multiple_hotkeys_max_stake_not_enough_balance(self, _): - config = self.config - config.command = "stake" - config.subcommand = "add" - config.no_prompt = True - # Notie amount is not specified - config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk0", "hk1", "hk2"] - config.all_hotkeys = False - - mock_balance = Balance.from_float(15.0 * 2) # Not enough for all hotkeys - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(config.hotkeys) - ] - - # Register mock wallets and give them balances - print("Registering mock wallets...") - - for wallet in mock_wallets: - print("Registering mock wallet {}".format(wallet.hotkey_str)) - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - ) - - _subtensor_mock.force_set_balance( - ss58_address=mock_coldkey_kp.ss58_address, balance=mock_balance.rao - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # Check that all stakes are 0 - self.assertEqual(stake.rao, 0) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - - self.assertAlmostEqual(balance.tao, mock_balance.tao, places=4) - - cli.run() - - # Check stakes after staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - - if wallet.hotkey_str == "hk2": - # Check that the stake is still 0 - self.assertEqual(stake.tao, 0) - - else: - # Check that all stakes are maximum of 15.0 - self.assertLessEqual(stake.tao, config.max_stake) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - self.assertLessEqual(balance.tao, mock_balance.tao) - - def test_stake_with_single_hotkey_max_stake(self, _): - config = self.config - config.command = "stake" - config.subcommand = "add" - config.no_prompt = True - # Notie amount is not specified - config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk0"] - config.all_hotkeys = False - - mock_balance = Balance.from_float(15.0 * 3) - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(config.hotkeys) - ] - - # Register mock wallets and give them balances - print("Registering mock wallets...") - - for wallet in mock_wallets: - print("Registering mock wallet {}".format(wallet.hotkey_str)) - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - ) - - _subtensor_mock.force_set_balance( - ss58_address=mock_coldkey_kp.ss58_address, balance=mock_balance.rao - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # Check that all stakes are 0 - self.assertEqual(stake.rao, 0) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - - self.assertAlmostEqual(balance.tao, mock_balance.tao, places=4) - - cli.run() - - # Check stakes after staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - - # Check that all stakes are maximum of 15.0 - self.assertLessEqual(stake.tao, config.max_stake) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - self.assertLessEqual(balance.tao, mock_balance.tao) - - def test_stake_with_single_hotkey_max_stake_not_enough_balance(self, _): - config = self.config - config.command = "stake" - config.subcommand = "add" - config.no_prompt = True - # Notie amount is not specified - config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk0"] - config.all_hotkeys = False - - mock_balance = Balance.from_float(1.0) # Not enough balance to do max - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(config.hotkeys) - ] - - # Register mock wallets and give them balances - print("Registering mock wallets...") - - for wallet in mock_wallets: - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - ) - - _subtensor_mock.force_set_balance( - ss58_address=mock_coldkey_kp.ss58_address, balance=mock_balance.rao - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before staking - for wallet in mock_wallets: - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # Check that all stakes are 0 - self.assertEqual(stake.rao, 0) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - - self.assertAlmostEqual(balance.tao, mock_balance.tao, places=4) - - cli.run() - - wallet = mock_wallets[0] - - # Check did not stake - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - - # Check that stake is less than max_stake - 1.0 - self.assertLessEqual(stake.tao, config.max_stake - 1.0) - - # Check that the balance decreased by less than max_stake - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - self.assertGreaterEqual(balance.tao, mock_balance.tao - config.max_stake) - - def test_stake_with_single_hotkey_max_stake_enough_stake(self, _): - # tests max stake when stake >= max_stake already - config = self.config - config.command = "stake" - config.subcommand = "add" - config.no_prompt = True - # Notie amount is not specified - config.max_stake = 15.0 # The keys should have at most 15.0 tao staked after - config.wallet.name = "fake_wallet" - config.hotkeys = ["hk0"] - config.all_hotkeys = False - - mock_balance = Balance.from_float(config.max_stake * 3) - - mock_stakes: Dict[str, Balance] = { # has enough stake, more than max_stake - "hk0": Balance.from_float(config.max_stake * 2) - } - - mock_coldkey_kp = get_mock_keypair(0, self.id()) - - mock_wallets = [ - SimpleNamespace( - name=config.wallet.name, - coldkey=mock_coldkey_kp, - coldkeypub=mock_coldkey_kp, - hotkey_str=hk, - hotkey=get_mock_keypair(idx + 100, self.id()), - ) - for idx, hk in enumerate(config.hotkeys) - ] - - # Register mock wallets and give them balances - print("Registering mock wallets...") - - for wallet in mock_wallets: - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=wallet.hotkey.ss58_address, - coldkey=wallet.coldkeypub.ss58_address, - stake=mock_stakes[wallet.hotkey_str].rao, # More than max_stake - ) - - success, err = _subtensor_mock.force_set_balance( - ss58_address=mock_coldkey_kp.ss58_address, balance=mock_balance.rao - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("hotkey"): - for wallet in mock_wallets: - if wallet.hotkey_str == kwargs.get("hotkey"): - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - # Check stakes before staking - wallet = mock_wallets[0] - - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - # Check that stake is correct - self.assertAlmostEqual( - stake.tao, mock_stakes[wallet.hotkey_str].tao, places=4 - ) - # Check that the stake is greater than or equal to max_stake - self.assertGreaterEqual(stake.tao, config.max_stake) - - # Check that the balance is correct - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - - self.assertAlmostEqual(balance.tao, mock_balance.tao, places=4) - - cli.run() - - wallet = mock_wallets[0] - - # Check did not stake, since stake >= max_stake - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=wallet.hotkey.ss58_address, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - - # Check that all stake is unchanged - self.assertAlmostEqual( - stake.tao, mock_stakes[wallet.hotkey_str].tao, places=4 - ) - - # Check that the balance is the same - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - self.assertAlmostEqual(balance.tao, mock_balance.tao, places=4) - - def test_stake_with_thresholds(self, _): - config = self.config - config.command = "stake" - config.subcommand = "add" - config.no_prompt = True - - min_stake: Balance = _subtensor_mock.get_minimum_required_stake() - # Must be a float - wallet_names = ["w0", "w1", "w2"] - config.all_hotkeys = False - # Notice no max_stake specified - - mock_stakes: Dict[str, Balance] = { - "w0": min_stake - 1, # new stake will be below the threshold - "w1": min_stake - 2, - "w2": min_stake - 5, - } - - mock_wallets = [ - SimpleNamespace( - name=wallet_name, - coldkey=get_mock_keypair(idx, self.id()), - coldkeypub=get_mock_keypair(idx, self.id()), - hotkey_str="hk{}".format(idx), # doesn't matter - hotkey=get_mock_keypair(idx + 100, self.id()), # doesn't matter - ) - for idx, wallet_name in enumerate(wallet_names) - ] - - delegate_hotkey = mock_wallets[0].hotkey.ss58_address - - # Register mock neuron, only for w0 - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=delegate_hotkey, - coldkey=mock_wallets[0].coldkey.ss58_address, - balance=(mock_stakes["w0"] + _subtensor_mock.get_existential_deposit()).tao - + 1.0, - ) # No stake, but enough balance - - # Become a delegate - _ = _subtensor_mock.nominate( - wallet=mock_wallets[0], - ) - - # Give enough balance - for wallet in mock_wallets[1:]: - # Give balance - _ = _subtensor_mock.force_set_balance( - ss58_address=wallet.coldkeypub.ss58_address, - balance=( - mock_stakes[wallet.name] + _subtensor_mock.get_existential_deposit() - ).tao - + 1.0, - ) - - def mock_get_wallet(*args, **kwargs): - if kwargs.get("config") and kwargs["config"].get("wallet"): - for wallet in mock_wallets: - if wallet.name == kwargs["config"].wallet.name: - return wallet - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - for wallet in mock_wallets: - # Check balances and stakes before staking - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=delegate_hotkey, - coldkey_ss58=wallet.coldkey.ss58_address, - ) - self.assertEqual(stake.rao, 0) # No stake - - balance = _subtensor_mock.get_balance( - address=wallet.coldkeypub.ss58_address - ) - self.assertGreaterEqual( - balance, mock_stakes[wallet.name] - ) # Enough balance - - config.wallet.name = wallet.name - config.wallet.hotkey = delegate_hotkey # Single stake - config.amount = mock_stakes[ - wallet.name - ].tao # Stake an amount below the threshold - - cli = bittensor.cli(config) - with patch.object(_subtensor_mock, "_do_stake") as mock_stake: - with patch( - "bittensor.__console__.print" - ) as mock_print: # Catch console print - cli.run() - - # Filter for console print calls - console_prints = [ - call[0][0] for call in mock_print.call_args_list - ] - minimum_print = filter( - lambda x: "below the minimum required" in x, console_prints - ) - - if wallet.name == "w0": - # This wallet owns the delegate - stake_calls = mock_stake.call_args_list - # Can stake below the threshold - self.assertEqual(len(stake_calls), 1) - - _, kwargs = stake_calls[0] - - # Should stake specified amount - self.assertEqual( - kwargs["amount"], bittensor.Balance(config.amount) - ) - # No error for w0 - self.assertRaises( - StopIteration, next, minimum_print - ) # No warning for w0 - else: - # Should not call stake - self.assertEqual(len(mock_stake.call_args_list), 0) - # Should print error - self.assertIsNotNone(next(minimum_print)) - - def test_nominate(self, _): - config = self.config - config.command = "root" - config.subcommand = "nominate" - config.no_prompt = True - config.wallet.name = "w0" - config.hotkey = "hk0" - - mock_balance = Balance.from_float(100.0) - - mock_wallet = SimpleNamespace( - name="w0", - coldkey=get_mock_keypair(0, self.id()), - coldkeypub=get_mock_keypair(0, self.id()), - hotkey_str="hk0", - hotkey=get_mock_keypair(0 + 100, self.id()), - ) - - # Register mock wallet and give it a balance - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=mock_wallet.hotkey.ss58_address, - coldkey=mock_wallet.coldkey.ss58_address, - balance=mock_balance.rao, - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - hk = kwargs.get("hotkey") - name_ = kwargs.get("name") - - if not hk and kwargs.get("config"): - hk = kwargs.get("config").wallet.hotkey - if not name_ and kwargs.get("config"): - name_ = kwargs.get("config").wallet.name - - if mock_wallet.name == name_: - return mock_wallet - else: - raise ValueError("Mock wallet not found") - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - cli.run() - - # Check the nomination - is_delegate = _subtensor_mock.is_hotkey_delegate( - hotkey_ss58=mock_wallet.hotkey.ss58_address - ) - self.assertTrue(is_delegate) - - def test_delegate_stake(self, _): - config = self.config - config.command = "root" - config.subcommand = "delegate" - config.no_prompt = True - config.amount = 5.0 - config.wallet.name = "w1" - - mock_balances: Dict[str, Balance] = { - # All have more than 5.0 stake - "w0": { - "hk0": Balance.from_float(10.0), - }, - "w1": {"hk1": Balance.from_float(11.1)}, - } - - mock_stake = Balance.from_float(5.0) - - mock_wallets = [] - for idx, wallet_name in enumerate(list(mock_balances.keys())): - for idx_hk, hk in enumerate(list(mock_balances[wallet_name].keys())): - wallet = SimpleNamespace( - name=wallet_name, - coldkey=get_mock_keypair(idx, self.id()), - coldkeypub=get_mock_keypair(idx, self.id()), - hotkey_str=hk, - hotkey=get_mock_keypair(idx * 100 + idx_hk, self.id()), - ) - mock_wallets.append(wallet) - - # Set hotkey to be the hotkey from the other wallet - config.delegate_ss58key: str = mock_wallets[0].hotkey.ss58_address - - # Register mock wallets and give them balance - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=mock_wallets[0].hotkey.ss58_address, - coldkey=mock_wallets[0].coldkey.ss58_address, - balance=mock_balances["w0"]["hk0"].rao, - stake=mock_stake.rao, # Needs set stake to be a validator - ) - - # Give w1 some balance - success, err = _subtensor_mock.force_set_balance( - ss58_address=mock_wallets[1].coldkey.ss58_address, - balance=mock_balances["w1"]["hk1"].rao, - ) - - # Make the first wallet a delegate - success = _subtensor_mock.nominate(wallet=mock_wallets[0]) - self.assertTrue(success) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - hk = kwargs.get("hotkey") - name_ = kwargs.get("name") - - if not hk and kwargs.get("config"): - hk = kwargs.get("config").wallet.hotkey - if not name_ and kwargs.get("config"): - name_ = kwargs.get("config").wallet.name - - for wallet in mock_wallets: - if wallet.name == name_ and wallet.hotkey_str == hk: - return wallet - else: - for wallet in mock_wallets: - if wallet.name == name_: - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - cli.run() - - # Check the stake - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=mock_wallets[0].hotkey.ss58_address, - coldkey_ss58=mock_wallets[1].coldkey.ss58_address, - ) - self.assertAlmostEqual(stake.tao, config.amount, places=4) - - def test_undelegate_stake(self, _): - config = self.config - config.command = "root" - config.subcommand = "undelegate" - config.no_prompt = True - config.amount = 5.0 - config.wallet.name = "w1" - - mock_balances: Dict[str, Balance] = { - # All have more than 5.0 stake - "w0": { - "hk0": Balance.from_float(10.0), - }, - "w1": {"hk1": Balance.from_float(11.1)}, - } - - mock_stake = Balance.from_float(5.0) - mock_delegated = Balance.from_float(6.0) - - mock_wallets = [] - for idx, wallet_name in enumerate(list(mock_balances.keys())): - for idx_hk, hk in enumerate(list(mock_balances[wallet_name].keys())): - wallet = SimpleNamespace( - name=wallet_name, - coldkey=get_mock_keypair(idx, self.id()), - coldkeypub=get_mock_keypair(idx, self.id()), - hotkey_str=hk, - hotkey=get_mock_keypair(idx * 100 + idx_hk, self.id()), - ) - mock_wallets.append(wallet) - - # Set hotkey to be the hotkey from the other wallet - config.delegate_ss58key: str = mock_wallets[0].hotkey.ss58_address - - # Register mock wallets and give them balance - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=mock_wallets[0].hotkey.ss58_address, - coldkey=mock_wallets[0].coldkey.ss58_address, - balance=mock_balances["w0"]["hk0"].rao, - stake=mock_stake.rao, # Needs set stake to be a validator - ) - - # Give w1 some balance - success, err = _subtensor_mock.force_set_balance( - ss58_address=mock_wallets[1].coldkey.ss58_address, - balance=mock_balances["w1"]["hk1"].rao, - ) - - # Make the first wallet a delegate - success = _subtensor_mock.nominate(wallet=mock_wallets[0]) - self.assertTrue(success) - - # Stake to the delegate - success = _subtensor_mock.delegate( - wallet=mock_wallets[1], - delegate_ss58=mock_wallets[0].hotkey.ss58_address, - amount=mock_delegated, - prompt=False, - ) - self.assertTrue(success) - - # Verify the stake - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=mock_wallets[0].hotkey.ss58_address, - coldkey_ss58=mock_wallets[1].coldkey.ss58_address, - ) - self.assertAlmostEqual(stake.tao, mock_delegated.tao, places=4) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - hk = kwargs.get("hotkey") - name_ = kwargs.get("name") - - if not hk and kwargs.get("config"): - hk = kwargs.get("config").wallet.hotkey - if not name_ and kwargs.get("config"): - name_ = kwargs.get("config").wallet.name - - for wallet in mock_wallets: - if wallet.name == name_ and wallet.hotkey_str == hk: - return wallet - else: - for wallet in mock_wallets: - if wallet.name == name_: - return wallet - else: - return mock_wallets[0] - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - cli.run() - - # Check the stake - stake = _subtensor_mock.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=mock_wallets[0].hotkey.ss58_address, - coldkey_ss58=mock_wallets[1].coldkey.ss58_address, - ) - self.assertAlmostEqual( - stake.tao, mock_delegated.tao - config.amount, places=4 - ) - - def test_transfer(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "transfer" - config.no_prompt = True - config.amount = 3.2 - config.wallet.name = "w1" - - mock_balances: Dict[str, Balance] = { - "w0": Balance.from_float(10.0), - "w1": Balance.from_float(config.amount + 0.001), - } - - mock_wallets = [] - for idx, wallet_name in enumerate(list(mock_balances.keys())): - wallet = SimpleNamespace( - name=wallet_name, - coldkey=get_mock_keypair(idx, self.id()), - coldkeypub=get_mock_keypair(idx, self.id()), - ) - mock_wallets.append(wallet) - - # Set dest to w0 - config.dest = mock_wallets[0].coldkey.ss58_address - - # Give w0 and w1 balance - - for wallet in mock_wallets: - success, err = _subtensor_mock.force_set_balance( - ss58_address=wallet.coldkey.ss58_address, - balance=mock_balances[wallet.name].rao, - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - name_ = kwargs.get("name") - - if not name_ and kwargs.get("config"): - name_ = kwargs.get("config").wallet.name - - for wallet in mock_wallets: - if wallet.name == name_: - return wallet - else: - raise ValueError(f"No mock wallet found with name: {name_}") - - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - cli.run() - - # Check the balance of w0 - balance = _subtensor_mock.get_balance( - address=mock_wallets[0].coldkey.ss58_address - ) - self.assertAlmostEqual( - balance.tao, mock_balances["w0"].tao + config.amount, places=4 - ) - - # Check the balance of w1 - balance = _subtensor_mock.get_balance( - address=mock_wallets[1].coldkey.ss58_address - ) - self.assertAlmostEqual( - balance.tao, mock_balances["w1"].tao - config.amount, places=4 - ) # no fees - - def test_transfer_not_enough_balance(self, _): - config = self.config - config.command = "wallet" - config.subcommand = "transfer" - config.no_prompt = True - config.amount = 3.2 - config.wallet.name = "w1" - - mock_balances: Dict[str, Balance] = { - "w0": Balance.from_float(10.0), - "w1": Balance.from_float(config.amount - 0.1), # not enough balance - } - - mock_wallets = [] - for idx, wallet_name in enumerate(list(mock_balances.keys())): - wallet = SimpleNamespace( - name=wallet_name, - coldkey=get_mock_keypair(idx, self.id()), - coldkeypub=get_mock_keypair(idx, self.id()), - ) - mock_wallets.append(wallet) - - # Set dest to w0 - config.dest = mock_wallets[0].coldkey.ss58_address - - # Give w0 and w1 balance - - for wallet in mock_wallets: - success, err = _subtensor_mock.force_set_balance( - ss58_address=wallet.coldkey.ss58_address, - balance=mock_balances[wallet.name].rao, - ) - - cli = bittensor.cli(config) - - def mock_get_wallet(*args, **kwargs): - name_ = kwargs.get("name") - - if not name_ and kwargs.get("config"): - name_ = kwargs.get("config").wallet.name - - for wallet in mock_wallets: - if wallet.name == name_: - return wallet - else: - raise ValueError(f"No mock wallet found with name: {name_}") - - mock_console = MockConsole() - with patch("bittensor.wallet") as mock_create_wallet: - mock_create_wallet.side_effect = mock_get_wallet - - with patch("bittensor.__console__", mock_console): - cli.run() - - # Check that the overview was printed. - self.assertIsNotNone(mock_console.captured_print) - - output_no_syntax = mock_console.remove_rich_syntax( - mock_console.captured_print - ) - - self.assertIn("Not enough balance", output_no_syntax) - - # Check the balance of w0 - balance = _subtensor_mock.get_balance( - address=mock_wallets[0].coldkey.ss58_address - ) - self.assertAlmostEqual( - balance.tao, mock_balances["w0"].tao, places=4 - ) # did not transfer - - # Check the balance of w1 - balance = _subtensor_mock.get_balance( - address=mock_wallets[1].coldkey.ss58_address - ) - self.assertAlmostEqual( - balance.tao, mock_balances["w1"].tao, places=4 - ) # did not transfer - - def test_register(self, _): - config = self.config - config.command = "subnets" - config.subcommand = "register" - config.no_prompt = True - - mock_wallet = generate_wallet(hotkey=get_mock_keypair(100, self.id())) - - # Give the wallet some balance for burning - success, err = _subtensor_mock.force_set_balance( - ss58_address=mock_wallet.coldkeypub.ss58_address, - balance=Balance.from_float(200.0), - ) - - with patch("bittensor.wallet", return_value=mock_wallet) as mock_create_wallet: - cli = bittensor.cli(config) - cli.run() - mock_create_wallet.assert_called_once() - - # Verify that the wallet was registered - subtensor = bittensor.subtensor(config) - registered = subtensor.is_hotkey_registered_on_subnet( - hotkey_ss58=mock_wallet.hotkey.ss58_address, netuid=1 - ) - - self.assertTrue(registered) - - def test_pow_register(self, _): - # Not the best way to do this, but I need to finish these tests, and unittest doesn't make this - # as simple as pytest - config = self.config - config.command = "subnets" - config.subcommand = "pow_register" - config.pow_register.num_processes = 1 - config.pow_register.update_interval = 50_000 - config.no_prompt = True - - mock_wallet = generate_wallet(hotkey=get_mock_keypair(100, self.id())) - - class MockException(Exception): - pass - - with patch("bittensor.wallet", return_value=mock_wallet) as mock_create_wallet: - with patch( - "bittensor.extrinsics.registration.POWSolution.is_stale", - side_effect=MockException, - ) as mock_is_stale: - with pytest.raises(MockException): - cli = bittensor.cli(config) - cli.run() - mock_create_wallet.assert_called_once() - - self.assertEqual(mock_is_stale.call_count, 1) - - def test_stake(self, _): - amount_to_stake: Balance = Balance.from_tao(0.5) - config = self.config - config.no_prompt = True - config.command = "stake" - config.subcommand = "add" - config.amount = amount_to_stake.tao - config.stake_all = False - config.use_password = False - config.model = "core_server" - config.hotkey = "hk0" - - subtensor = bittensor.subtensor(config) - - mock_wallet = generate_wallet(hotkey=get_mock_keypair(100, self.id())) - - # Register the hotkey and give it some balance - _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=mock_wallet.hotkey.ss58_address, - coldkey=mock_wallet.coldkey.ss58_address, - balance=( - amount_to_stake + Balance.from_tao(1.0) - ).rao, # 1.0 tao extra for fees, etc - ) - - with patch("bittensor.wallet", return_value=mock_wallet) as mock_create_wallet: - old_stake = subtensor.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=mock_wallet.hotkey.ss58_address, - coldkey_ss58=mock_wallet.coldkey.ss58_address, - ) - - cli = bittensor.cli(config) - cli.run() - mock_create_wallet.assert_called() - self.assertEqual(mock_create_wallet.call_count, 2) - - new_stake = subtensor.get_stake_for_coldkey_and_hotkey( - hotkey_ss58=mock_wallet.hotkey.ss58_address, - coldkey_ss58=mock_wallet.coldkey.ss58_address, - ) - - self.assertGreater(new_stake, old_stake) - - def test_metagraph(self, _): - config = self.config - config.wallet.name = "metagraph_testwallet" - config.command = "subnets" - config.subcommand = "metagraph" - config.no_prompt = True - - # Add some neurons to the metagraph - mock_nn = [] - - def register_mock_neuron(i: int) -> int: - mock_nn.append( - SimpleNamespace( - hotkey=get_mock_keypair(i + 100, self.id()).ss58_address, - coldkey=get_mock_keypair(i, self.id()).ss58_address, - balance=Balance.from_rao(random.randint(0, 2**45)).rao, - stake=Balance.from_rao(random.randint(0, 2**45)).rao, - ) - ) - uid = _subtensor_mock.force_register_neuron( - netuid=config.netuid, - hotkey=mock_nn[i].hotkey, - coldkey=mock_nn[i].coldkey, - balance=mock_nn[i].balance, - stake=mock_nn[i].stake, - ) - return uid - - for i in range(5): - _ = register_mock_neuron(i) - - _subtensor_mock.neurons_lite(netuid=config.netuid) - - cli = bittensor.cli(config) - - mock_console = MockConsole() - with patch("bittensor.__console__", mock_console): - cli.run() - - # Check that the overview was printed. - self.assertIsNotNone(mock_console.captured_print) - - output_no_syntax = mock_console.remove_rich_syntax(mock_console.captured_print) - - self.assertIn("Metagraph", output_no_syntax) - nn = _subtensor_mock.neurons_lite(netuid=config.netuid) - self.assertIn( - str(len(nn) - 1), output_no_syntax - ) # Check that the number of neurons is output - # Check each uid is in the output - for neuron in nn: - self.assertIn(str(neuron.uid), output_no_syntax) - - def test_inspect(self, _): - config = self.config - config.wallet.name = "inspect_testwallet" - config.no_prompt = True - config.n_words = 12 - config.use_password = False - config.overwrite_coldkey = True - config.overwrite_hotkey = True - - # First create a new coldkey - config.command = "wallet" - config.subcommand = "new_coldkey" - cli = bittensor.cli(config) - cli.run() - - # Now let's give it a hotkey - config.command = "wallet" - config.subcommand = "new_hotkey" - cli.config = config - cli.run() - - # Now inspect it - config.command = "wallet" - cli.config.subcommand = "inspect" - cli.config = config - cli.run() - - config.command = "wallet" - cli.config.subcommand = "list" - cli.config = config - cli.run() - - # Run History Command to get list of transfers - config.command = "wallet" - cli.config.subcommand = "history" - cli.config = config - cli.run() - - -@patch("bittensor.subtensor", new_callable=return_mock_sub) -class TestCLIWithNetworkUsingArgs(unittest.TestCase): - """ - Test the CLI by passing args directly to the bittensor.cli factory - """ - - @unittest.mock.patch.object(MockSubtensor, "get_delegates") - def test_list_delegates(self, mocked_get_delegates, _): - # Call - cli = bittensor.cli(args=["root", "list_delegates"]) - cli.run() - - # Assertions - # make sure get_delegates called once without previous state (current only) - self.assertEqual(mocked_get_delegates.call_count, 2) - - def test_list_subnets(self, _): - cli = bittensor.cli( - args=[ - "subnets", - "list", - ] - ) - cli.run() - - def test_delegate(self, _): - """ - Test delegate add command - """ - mock_wallet = generate_wallet(hotkey=get_mock_keypair(100, self.id())) - delegate_wallet = generate_wallet(hotkey=get_mock_keypair(100 + 1, self.id())) - - # register the wallet - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=mock_wallet.hotkey.ss58_address, - coldkey=mock_wallet.coldkey.ss58_address, - ) - - # register the delegate - _ = _subtensor_mock.force_register_neuron( - netuid=1, - hotkey=delegate_wallet.hotkey.ss58_address, - coldkey=delegate_wallet.coldkey.ss58_address, - ) - - # make the delegate a delegate - _subtensor_mock.nominate(delegate_wallet, wait_for_finalization=True) - self.assertTrue( - _subtensor_mock.is_hotkey_delegate(delegate_wallet.hotkey.ss58_address) - ) - - # Give the wallet some TAO - _, err = _subtensor_mock.force_set_balance( - ss58_address=mock_wallet.coldkey.ss58_address, - balance=Balance.from_tao(20.0), - ) - self.assertEqual(err, None) - - # Check balance - old_balance = _subtensor_mock.get_balance(mock_wallet.coldkey.ss58_address) - self.assertEqual(old_balance.tao, 20.0) - - # Check delegate stake - old_delegate_stake = _subtensor_mock.get_total_stake_for_hotkey( - delegate_wallet.hotkey.ss58_address - ) - - # Check wallet stake - old_wallet_stake = _subtensor_mock.get_total_stake_for_coldkey( - mock_wallet.coldkey.ss58_address - ) - - with patch( - "bittensor.wallet", return_value=mock_wallet - ): # Mock wallet creation. SHOULD NOT BE REGISTERED - cli = bittensor.cli( - args=[ - "root", - "delegate", - "--subtensor.network", - "mock", # Mock network - "--wallet.name", - "mock", - "--delegate_ss58key", - delegate_wallet.hotkey.ss58_address, - "--amount", - "10.0", # Delegate 10 TAO - "--no_prompt", - ] - ) - cli.run() - - # Check delegate stake - new_delegate_stake = _subtensor_mock.get_total_stake_for_hotkey( - delegate_wallet.hotkey.ss58_address - ) - - # Check wallet stake - new_wallet_stake = _subtensor_mock.get_total_stake_for_coldkey( - mock_wallet.coldkey.ss58_address - ) - - # Check that the delegate stake increased by 10 TAO - self.assertAlmostEqual( - new_delegate_stake.tao, old_delegate_stake.tao + 10.0, delta=1e-6 - ) - - # Check that the wallet stake increased by 10 TAO - self.assertAlmostEqual( - new_wallet_stake.tao, old_wallet_stake.tao + 10.0, delta=1e-6 - ) - - new_balance = _subtensor_mock.get_balance(mock_wallet.coldkey.ss58_address) - self.assertAlmostEqual(new_balance.tao, old_balance.tao - 10.0, delta=1e-6) - - -@pytest.fixture(scope="function") -def wallets_dir_path(tmp_path): - wallets_dir = tmp_path / "wallets" - wallets_dir.mkdir() - yield wallets_dir - - -@pytest.mark.parametrize( - "test_id, wallet_names, expected_wallet_count", - [ - ("happy_path_single_wallet", ["wallet1"], 1), # Single wallet - ( - "happy_path_multiple_wallets", - ["wallet1", "wallet2", "wallet3"], - 3, - ), # Multiple wallets - ("happy_path_no_wallets", [], 0), # No wallets - ], -) -def test_get_coldkey_wallets_for_path( - test_id, wallet_names, expected_wallet_count, wallets_dir_path -): - # Arrange: Create mock wallet directories - for name in wallet_names: - (wallets_dir_path / name).mkdir() - - # Act: Call the function with the test directory - wallets = _get_coldkey_wallets_for_path(str(wallets_dir_path)) - - # Assert: Check if the correct number of wallet objects are returned - assert len(wallets) == expected_wallet_count - for wallet in wallets: - assert isinstance( - wallet, Wallet - ), "The returned object should be an instance of bittensor.wallet" - - -@pytest.mark.parametrize( - "test_id, exception, mock_path, expected_result", - [ - ( - "error_case_invalid_path", - StopIteration, - "/invalid/path", - [], - ), # Invalid path causing StopIteration - ], -) -def test_get_coldkey_wallets_for_path_errors( - test_id, exception, mock_path, expected_result -): - # Arrange: Patch os.walk to raise an exception - with patch("os.walk", side_effect=exception): - # Act: Call the function with an invalid path - wallets = _get_coldkey_wallets_for_path(mock_path) - - # Assert: Check if an empty list is returned - assert ( - wallets == expected_result - ), "Function should return an empty list on error" - - -@pytest.mark.parametrize( - "test_id, display, legal, web, riot, email, pgp_fingerprint, image, info, twitter, expected_exception, expected_message", - [ - ( - "test-run-happy-path-1", - "Alice", - "Alice Doe", - "https://alice.example.com", - "@alice:matrix", - "alice@example.com", - "ABCD1234ABCD1234ABCD", - "https://alice.image", - "Alice in Wonderland", - "@liceTwitter", - None, - "", - ), - # Edge cases - ( - "test_run_edge_case_002", - "", - "", - "", - "", - "", - "", - "", - "", - "", - None, - "", - ), # Empty strings as input - # Error cases - # Each field has a maximum size of 64 bytes, PGP fingerprint has a maximum size of 20 bytes - ( - "test_run_error_case_003", - "A" * 65, - "B" * 65, - "C" * 65, - "D" * 65, - "E" * 65, - "F" * 21, - "G" * 65, - "H" * 65, - "I" * 65, - ValueError, - "Identity value `display` must be <= 64 raw bytes", - ), - ], -) -def test_set_identity_command( - test_id, - display, - legal, - web, - riot, - email, - pgp_fingerprint, - image, - info, - twitter, - expected_exception, - expected_message, -): - # Arrange - mock_cli = MagicMock() - mock_cli.config = MagicMock( - display=display, - legal=legal, - web=web, - riot=riot, - email=email, - pgp_fingerprint=pgp_fingerprint, - image=image, - info=info, - twitter=twitter, - ) - mock_subtensor = MagicMock() - mock_subtensor.update_identity = MagicMock() - mock_subtensor.query_identity = MagicMock(return_value={}) - mock_subtensor.close = MagicMock() - mock_wallet = MagicMock() - mock_wallet.hotkey.ss58_address = "fake_ss58_address" - mock_wallet.coldkey.ss58_address = "fake_coldkey_ss58_address" - mock_wallet.coldkey = MagicMock() - - with patch("bittensor.subtensor", return_value=mock_subtensor), patch( - "bittensor.wallet", return_value=mock_wallet - ), patch("bittensor.__console__", MagicMock()), patch( - "rich.prompt.Prompt.ask", side_effect=["y", "y"] - ), patch("sys.exit") as mock_exit: - # Act - if expected_exception: - with pytest.raises(expected_exception) as exc_info: - SetIdentityCommand._run(mock_cli, mock_subtensor) - # Assert - assert str(exc_info.value) == expected_message - else: - SetIdentityCommand._run(mock_cli, mock_subtensor) - # Assert - mock_subtensor.update_identity.assert_called_once() - assert mock_exit.call_count == 0 - - -@pytest.fixture -def setup_files(tmp_path): - def _setup_files(files): - for file_path, content in files.items(): - full_path = tmp_path / file_path - os.makedirs(full_path.parent, exist_ok=True) - with open(full_path, "w") as f: - f.write(content) - return tmp_path - - return _setup_files - - -@pytest.mark.parametrize( - "test_id, setup_data, expected", - [ - # Error cases - ( - "error_case_nonexistent_dir", - {"just_a_file.txt": ""}, - ([], []), - ), # Nonexistent dir - ], -) -def test_get_coldkey_ss58_addresses_for_path( - setup_files, test_id, setup_data, expected -): - path = setup_files(setup_data) - - # Arrange - # Setup done in setup_files fixture and parametrize - - # Act - result = _get_coldkey_ss58_addresses_for_path(str(path)) - - # Assert - assert ( - result == expected - ), f"Test ID: {test_id} failed. Expected {expected}, got {result}" - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/integration_tests/test_cli_no_network.py b/tests/integration_tests/test_cli_no_network.py deleted file mode 100644 index e3a3d6a49c..0000000000 --- a/tests/integration_tests/test_cli_no_network.py +++ /dev/null @@ -1,1533 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2022 Yuma Rao -# Copyright © 2022-2023 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 unittest -from unittest.mock import MagicMock, patch -from typing import Any, Optional -import pytest -from copy import deepcopy -import re - -from tests.helpers import _get_mock_coldkey, __mock_wallet_factory__, MockConsole - -import bittensor -from bittensor import Balance -from rich.table import Table - - -class MockException(Exception): - pass - - -mock_delegate_info = { - "hotkey_ss58": "", - "total_stake": bittensor.Balance.from_rao(0), - "nominators": [], - "owner_ss58": "", - "take": 0.18, - "validator_permits": [], - "registrations": [], - "return_per_1000": bittensor.Balance.from_rao(0), - "total_daily_return": bittensor.Balance.from_rao(0), -} - - -def return_mock_sub_1(*args, **kwargs): - return MagicMock( - return_value=MagicMock( - get_subnets=MagicMock(return_value=[1]), # Mock subnet 1 ONLY. - block=10_000, - get_delegates=MagicMock( - return_value=[bittensor.DelegateInfo(**mock_delegate_info)] - ), - ) - ) - - -def return_mock_wallet_factory(*args, **kwargs): - return MagicMock( - return_value=__mock_wallet_factory__(*args, **kwargs), - add_args=bittensor.wallet.add_args, - ) - - -@patch( - "bittensor.subtensor", - new_callable=return_mock_sub_1, -) -@patch("bittensor.wallet", new_callable=return_mock_wallet_factory) -class TestCLINoNetwork(unittest.TestCase): - def setUp(self): - self._config = TestCLINoNetwork.construct_config() - - def config(self): - copy_ = deepcopy(self._config) - return copy_ - - @staticmethod - def construct_config(): - parser = bittensor.cli.__create_parser__() - defaults = bittensor.config(parser=parser, args=["subnets", "metagraph"]) - - # Parse commands and subcommands - for command in bittensor.ALL_COMMANDS: - if ( - command in bittensor.ALL_COMMANDS - and "commands" in bittensor.ALL_COMMANDS[command] - ): - for subcommand in bittensor.ALL_COMMANDS[command]["commands"]: - defaults.merge( - bittensor.config(parser=parser, args=[command, subcommand]) - ) - else: - defaults.merge(bittensor.config(parser=parser, args=[command])) - - defaults.netuid = 1 - defaults.subtensor.network = "mock" - defaults.no_version_checking = True - - return defaults - - def test_check_configs(self, _, __): - config = self.config() - config.no_prompt = True - config.model = "core_server" - config.dest = "no_prompt" - config.amount = 1 - config.mnemonic = "this is a mnemonic" - config.seed = None - config.uids = [1, 2, 3] - config.weights = [0.25, 0.25, 0.25, 0.25] - config.no_version_checking = True - config.ss58_address = bittensor.Keypair.create_from_seed(b"0" * 32).ss58_address - config.public_key_hex = None - config.proposal_hash = "" - - cli_instance = bittensor.cli - - # Define the response function for rich.prompt.Prompt.ask - def ask_response(prompt: str) -> Any: - if "delegate index" in prompt: - return 0 - elif "wallet name" in prompt: - return "mock" - elif "hotkey" in prompt: - return "mock" - - # Patch the ask response - with patch("rich.prompt.Prompt.ask", ask_response): - # Loop through all commands and their subcommands - for command, command_data in bittensor.ALL_COMMANDS.items(): - config.command = command - if isinstance(command_data, dict): - for subcommand in command_data["commands"].keys(): - config.subcommand = subcommand - cli_instance.check_config(config) - else: - config.subcommand = None - cli_instance.check_config(config) - - def test_new_coldkey(self, _, __): - config = self.config() - config.wallet.name = "new_coldkey_testwallet" - - config.command = "wallet" - config.subcommand = "new_coldkey" - config.amount = 1 - config.dest = "no_prompt" - config.model = "core_server" - config.n_words = 12 - config.use_password = False - config.no_prompt = True - config.overwrite_coldkey = True - - cli = bittensor.cli(config) - cli.run() - - def test_new_hotkey(self, _, __): - config = self.config() - config.wallet.name = "new_hotkey_testwallet" - config.command = "wallet" - config.subcommand = "new_hotkey" - config.amount = 1 - config.dest = "no_prompt" - config.model = "core_server" - config.n_words = 12 - config.use_password = False - config.no_prompt = True - config.overwrite_hotkey = True - - cli = bittensor.cli(config) - cli.run() - - def test_regen_coldkey(self, _, __): - config = self.config() - config.wallet.name = "regen_coldkey_testwallet" - config.command = "wallet" - config.subcommand = "regen_coldkey" - config.amount = 1 - config.dest = "no_prompt" - config.model = "core_server" - config.mnemonic = "faculty decade seven jelly gospel axis next radio grain radio remain gentle" - config.seed = None - config.n_words = 12 - config.use_password = False - config.no_prompt = True - config.overwrite_coldkey = True - - cli = bittensor.cli(config) - cli.run() - - def test_regen_coldkeypub(self, _, __): - config = self.config() - config.wallet.name = "regen_coldkeypub_testwallet" - config.command = "wallet" - config.subcommand = "regen_coldkeypub" - config.ss58_address = "5DD26kC2kxajmwfbbZmVmxhrY9VeeyR1Gpzy9i8wxLUg6zxm" - config.public_key = None - config.use_password = False - config.no_prompt = True - config.overwrite_coldkeypub = True - - cli = bittensor.cli(config) - cli.run() - - def test_regen_hotkey(self, _, __): - config = self.config() - config.wallet.name = "regen_hotkey_testwallet" - config.command = "wallet" - config.subcommand = "regen_hotkey" - config.amount = 1 - config.model = "core_server" - config.mnemonic = "faculty decade seven jelly gospel axis next radio grain radio remain gentle" - config.seed = None - config.n_words = 12 - config.use_password = False - config.no_prompt = True - config.overwrite_hotkey = True - - cli = bittensor.cli(config) - cli.run() - - def test_list(self, _, __): - # Mock IO for wallet - with patch( - "bittensor.wallet", - side_effect=[ - MagicMock( - coldkeypub_file=MagicMock( - exists_on_device=MagicMock(return_value=True), # Wallet exists - is_encrypted=MagicMock( - return_value=False # Wallet is not encrypted - ), - ), - coldkeypub=MagicMock( - ss58_address=bittensor.Keypair.create_from_mnemonic( - bittensor.Keypair.generate_mnemonic() - ).ss58_address - ), - ), - MagicMock( - hotkey_file=MagicMock( - exists_on_device=MagicMock(return_value=True), # Wallet exists - is_encrypted=MagicMock( - return_value=False # Wallet is not encrypted - ), - ), - hotkey=MagicMock( - ss58_address=bittensor.Keypair.create_from_mnemonic( - bittensor.Keypair.generate_mnemonic() - ).ss58_address - ), - ), - ], - ): - config = self.config() - config.wallet.path = "tmp/walletpath" - config.wallet.name = "mock_wallet" - config.no_prompt = True - config.command = "wallet" - config.subcommand = "list" - - cli = bittensor.cli(config) - with patch( - "os.walk", - side_effect=[ - iter([("/tmp/walletpath", ["mock_wallet"], [])]), # 1 wallet dir - iter( - [ - ("/tmp/walletpath/mock_wallet/hotkeys", [], ["hk0"]) - ] # 1 hotkey file - ), - ], - ): - cli.run() - - def test_list_no_wallet(self, _, __): - with patch( - "bittensor.wallet", - side_effect=[ - MagicMock( - coldkeypub_file=MagicMock( - exists_on_device=MagicMock(return_value=True) - ) - ) - ], - ): - config = self.config() - config.wallet.path = "/tmp/test_cli_test_list_no_wallet" - config.no_prompt = True - config.command = "wallet" - config.subcommand = "list" - - cli = bittensor.cli(config) - # This shouldn't raise an error anymore - cli.run() - - def test_btcli_help(self, _, __): - with pytest.raises(SystemExit) as pytest_wrapped_e: - with patch( - "argparse.ArgumentParser._print_message", return_value=None - ) as mock_print_message: - args = ["--help"] - bittensor.cli(args=args).run() - - mock_print_message.assert_called_once() - - call_args = mock_print_message.call_args - help_out = call_args[0][0] - - # Extract commands from the help text. - commands_section = re.search( - r"positional arguments:.*?{(.+?)}", help_out, re.DOTALL - ).group(1) - extracted_commands = [cmd.strip() for cmd in commands_section.split(",")] - - # Get expected commands - parser = bittensor.cli.__create_parser__() - expected_commands = [command for command in parser._actions[-1].choices] - - # Validate each expected command is in extracted commands - for command in expected_commands: - assert ( - command in extracted_commands - ), f"Command {command} not found in help output" - - # Check for duplicates - assert len(extracted_commands) == len( - set(extracted_commands) - ), "Duplicate commands found in help output" - - @patch("torch.cuda.is_available", return_value=True) - def test_register_cuda_use_cuda_flag(self, _, __, patched_sub): - base_args = [ - "subnets", - "pow_register", - "--wallet.path", - "tmp/walletpath", - "--wallet.name", - "mock", - "--wallet.hotkey", - "hk0", - "--no_prompt", - "--cuda.dev_id", - "0", - ] - - patched_sub.return_value = MagicMock( - get_subnets=MagicMock(return_value=[1]), - subnet_exists=MagicMock(return_value=True), - register=MagicMock(side_effect=MockException), - ) - - # Should be able to set true without argument - args = base_args + [ - "--pow_register.cuda.use_cuda", # should be True without any arugment - ] - with pytest.raises(MockException): - cli = bittensor.cli(args=args) - cli.run() - - self.assertEqual(cli.config.pow_register.cuda.get("use_cuda"), True) - - # Should be able to set to false with no argument - - args = base_args + [ - "--pow_register.cuda.no_cuda", - ] - with pytest.raises(MockException): - cli = bittensor.cli(args=args) - cli.run() - - self.assertEqual(cli.config.pow_register.cuda.get("use_cuda"), False) - - -def return_mock_sub_2(*args, **kwargs): - return MagicMock( - return_value=MagicMock( - get_subnet_burn_cost=MagicMock(return_value=0.1), - get_subnets=MagicMock(return_value=[1]), # Need to pass check config - get_delegates=MagicMock( - return_value=[ - bittensor.DelegateInfo( - hotkey_ss58="", - total_stake=Balance.from_rao(0), - nominators=[], - owner_ss58="", - take=0.18, - validator_permits=[], - registrations=[], - return_per_1000=Balance(0.0), - total_daily_return=Balance(0.0), - ) - ] - ), - block=10_000, - ), - add_args=bittensor.subtensor.add_args, - ) - - -@patch("bittensor.wallet", new_callable=return_mock_wallet_factory) -@patch("bittensor.subtensor", new_callable=return_mock_sub_2) -class TestEmptyArgs(unittest.TestCase): - """ - Test that the CLI doesn't crash when no args are passed - """ - - @patch("rich.prompt.PromptBase.ask", side_effect=MockException) - def test_command_no_args(self, _, __, patched_prompt_ask): - # Get argparser - parser = bittensor.cli.__create_parser__() - # Get all commands from argparser - commands = [ - command - for command in parser._actions[-1].choices # extract correct subparser keys - if len(command) > 1 # Skip singleton aliases - and command - not in [ - "subnet", - "sudos", - "stakes", - "roots", - "wallets", - "weight", - "st", - "wt", - "su", - ] # Skip duplicate aliases - ] - # Test that each command and its subcommands can be run with no args - for command in commands: - command_data = bittensor.ALL_COMMANDS.get(command) - - # If command is dictionary, it means it has subcommands - if isinstance(command_data, dict): - for subcommand in command_data["commands"].keys(): - try: - # Run each subcommand - bittensor.cli(args=[command, subcommand]).run() - except MockException: - pass # Expected exception - else: - try: - # If no subcommands, just run the command - bittensor.cli(args=[command]).run() - except MockException: - pass # Expected exception - - # Should not raise any other exceptions - - -mock_delegate_info = { - "hotkey_ss58": "", - "total_stake": bittensor.Balance.from_rao(0), - "nominators": [], - "owner_ss58": "", - "take": 0.18, - "validator_permits": [], - "registrations": [], - "return_per_1000": bittensor.Balance.from_rao(0), - "total_daily_return": bittensor.Balance.from_rao(0), -} - - -def return_mock_sub_3(*args, **kwargs): - return MagicMock( - return_value=MagicMock( - get_subnets=MagicMock(return_value=[1]), # Mock subnet 1 ONLY. - block=10_000, - get_delegates=MagicMock( - return_value=[bittensor.DelegateInfo(**mock_delegate_info)] - ), - ), - block=10_000, - ) - - -@patch("bittensor.subtensor", new_callable=return_mock_sub_3) -class TestCLIDefaultsNoNetwork(unittest.TestCase): - def test_inspect_prompt_wallet_name(self, _): - # Patch command to exit early - with patch("bittensor.commands.inspect.InspectCommand.run", return_value=None): - # Test prompt happens when no wallet name is passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=[ - "wallet", - "inspect", - # '--wallet.name', 'mock', - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called_once() - - # Test NO prompt happens when wallet name is passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=[ - "wallet", - "inspect", - "--wallet.name", - "coolwalletname", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - # Test NO prompt happens when wallet name 'default' is passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=[ - "wallet", - "inspect", - "--wallet.name", - "default", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - def test_overview_prompt_wallet_name(self, _): - # Patch command to exit early - with patch( - "bittensor.commands.overview.OverviewCommand.run", return_value=None - ): - # Test prompt happens when no wallet name is passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=[ - "wallet", - "overview", - # '--wallet.name', 'mock', - "--netuid", - "1", - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called_once() - - # Test NO prompt happens when wallet name is passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=[ - "wallet", - "overview", - "--wallet.name", - "coolwalletname", - "--netuid", - "1", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - # Test NO prompt happens when wallet name 'default' is passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=[ - "wallet", - "overview", - "--wallet.name", - "default", - "--netuid", - "1", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - def test_stake_prompt_wallet_name_and_hotkey_name(self, _): - base_args = [ - "stake", - "add", - "--all", - ] - # Patch command to exit early - with patch("bittensor.commands.stake.StakeCommand.run", return_value=None): - # Test prompt happens when - # - wallet name IS NOT passed, AND - # - hotkey name IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = ["mock", "mock_hotkey"] - - cli = bittensor.cli( - args=base_args - + [ - # '--wallet.name', 'mock', - #'--wallet.hotkey', 'mock_hotkey', - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 2, - msg="Prompt should have been called twice", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in [val for val in kwargs0.values()] - ] - # check that prompt was called for wallet name - self.assertTrue( - any( - filter( - lambda x: "wallet name" in x.lower(), combined_args_kwargs0 - ) - ), - msg=f"Prompt should have been called for wallet name: {combined_args_kwargs0}", - ) - - args1, kwargs1 = mock_ask_prompt.call_args_list[1] - combined_args_kwargs1 = [arg for arg in args1] + [ - val for val in kwargs1.values() - ] - # check that prompt was called for hotkey - - self.assertTrue( - any(filter(lambda x: "hotkey" in x.lower(), combined_args_kwargs1)), - msg=f"Prompt should have been called for hotkey: {combined_args_kwargs1}", - ) - - # Test prompt happens when - # - wallet name IS NOT passed, AND - # - hotkey name IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = ["mock", "mock_hotkey"] - - cli = bittensor.cli( - args=base_args - + [ - #'--wallet.name', 'mock', - "--wallet.hotkey", - "mock_hotkey", - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called ONCE", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for wallet name - self.assertTrue( - any( - filter( - lambda x: "wallet name" in x.lower(), combined_args_kwargs0 - ) - ), - msg=f"Prompt should have been called for wallet name: {combined_args_kwargs0}", - ) - - # Test prompt happens when - # - wallet name IS passed, AND - # - hotkey name IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = ["mock", "mock_hotkey"] - - cli = bittensor.cli( - args=base_args - + [ - "--wallet.name", - "mock", - #'--wallet.hotkey', 'mock_hotkey', - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called ONCE", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for hotkey - self.assertTrue( - any(filter(lambda x: "hotkey" in x.lower(), combined_args_kwargs0)), - msg=f"Prompt should have been called for hotkey {combined_args_kwargs0}", - ) - - # Test NO prompt happens when - # - wallet name IS passed, AND - # - hotkey name IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--wallet.name", - "coolwalletname", - "--wallet.hotkey", - "coolwalletname_hotkey", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - # Test NO prompt happens when - # - wallet name 'default' IS passed, AND - # - hotkey name 'default' IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--wallet.name", - "default", - "--wallet.hotkey", - "default", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - def test_unstake_prompt_wallet_name_and_hotkey_name(self, _): - base_args = [ - "stake", - "remove", - "--all", - ] - # Patch command to exit early - with patch("bittensor.commands.unstake.UnStakeCommand.run", return_value=None): - # Test prompt happens when - # - wallet name IS NOT passed, AND - # - hotkey name IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = ["mock", "mock_hotkey"] - - cli = bittensor.cli( - args=base_args - + [ - # '--wallet.name', 'mock', - #'--wallet.hotkey', 'mock_hotkey', - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 2, - msg="Prompt should have been called twice", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for wallet name - self.assertTrue( - any( - filter( - lambda x: "wallet name" in x.lower(), combined_args_kwargs0 - ) - ), - msg=f"Prompt should have been called for wallet name: {combined_args_kwargs0}", - ) - - args1, kwargs1 = mock_ask_prompt.call_args_list[1] - combined_args_kwargs1 = [arg for arg in args1] + [ - val for val in kwargs1.values() - ] - # check that prompt was called for hotkey - self.assertTrue( - any(filter(lambda x: "hotkey" in x.lower(), combined_args_kwargs1)), - msg=f"Prompt should have been called for hotkey {combined_args_kwargs1}", - ) - - # Test prompt happens when - # - wallet name IS NOT passed, AND - # - hotkey name IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = ["mock", "mock_hotkey"] - - cli = bittensor.cli( - args=base_args - + [ - #'--wallet.name', 'mock', - "--wallet.hotkey", - "mock_hotkey", - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called ONCE", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for wallet name - self.assertTrue( - any( - filter( - lambda x: "wallet name" in x.lower(), combined_args_kwargs0 - ) - ), - msg=f"Prompt should have been called for wallet name: {combined_args_kwargs0}", - ) - - # Test prompt happens when - # - wallet name IS passed, AND - # - hotkey name IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = ["mock", "mock_hotkey"] - - cli = bittensor.cli( - args=base_args - + [ - "--wallet.name", - "mock", - #'--wallet.hotkey', 'mock_hotkey', - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called ONCE", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for hotkey - self.assertTrue( - any(filter(lambda x: "hotkey" in x.lower(), combined_args_kwargs0)), - msg=f"Prompt should have been called for hotkey {combined_args_kwargs0}", - ) - - # Test NO prompt happens when - # - wallet name IS passed, AND - # - hotkey name IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--wallet.name", - "coolwalletname", - "--wallet.hotkey", - "coolwalletname_hotkey", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - # Test NO prompt happens when - # - wallet name 'default' IS passed, AND - # - hotkey name 'default' IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--wallet.name", - "default", - "--wallet.hotkey", - "default", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - def test_delegate_prompt_wallet_name(self, _): - base_args = [ - "root", - "delegate", - "--all", - "--delegate_ss58key", - _get_mock_coldkey(0), - ] - # Patch command to exit early - with patch( - "bittensor.commands.delegates.DelegateStakeCommand.run", return_value=None - ): - # Test prompt happens when - # - wallet name IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = ["mock"] - - cli = bittensor.cli( - args=base_args - + [ - # '--wallet.name', 'mock', - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called ONCE", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for wallet name - self.assertTrue( - any( - filter( - lambda x: "wallet name" in x.lower(), combined_args_kwargs0 - ) - ), - msg=f"Prompt should have been called for wallet name: {combined_args_kwargs0}", - ) - - # Test NO prompt happens when - # - wallet name IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--wallet.name", - "coolwalletname", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - def test_undelegate_prompt_wallet_name(self, _): - base_args = [ - "root", - "undelegate", - "--all", - "--delegate_ss58key", - _get_mock_coldkey(0), - ] - # Patch command to exit early - with patch( - "bittensor.commands.delegates.DelegateUnstakeCommand.run", return_value=None - ): - # Test prompt happens when - # - wallet name IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = ["mock"] - - cli = bittensor.cli( - args=base_args - + [ - # '--wallet.name', 'mock', - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called ONCE", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for wallet name - self.assertTrue( - any( - filter( - lambda x: "wallet name" in x.lower(), combined_args_kwargs0 - ) - ), - msg=f"Prompt should have been called for wallet name: {combined_args_kwargs0}", - ) - - # Test NO prompt happens when - # - wallet name IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--wallet.name", - "coolwalletname", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - def test_history_prompt_wallet_name(self, _): - base_args = [ - "wallet", - "history", - ] - # Patch command to exit early - with patch( - "bittensor.commands.wallets.GetWalletHistoryCommand.run", return_value=None - ): - # Test prompt happens when - # - wallet name IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = ["mock"] - - cli = bittensor.cli( - args=base_args - + [ - # '--wallet.name', 'mock', - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called ONCE", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for wallet name - self.assertTrue( - any( - filter( - lambda x: "wallet name" in x.lower(), combined_args_kwargs0 - ) - ), - msg=f"Prompt should have been called for wallet name: {combined_args_kwargs0}", - ) - - # Test NO prompt happens when - # - wallet name IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--wallet.name", - "coolwalletname", - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - def test_delegate_prompt_hotkey(self, _): - # Tests when - # - wallet name IS passed, AND - # - delegate hotkey IS NOT passed - base_args = [ - "root", - "delegate", - "--all", - "--wallet.name", - "mock", - ] - - delegate_ss58 = _get_mock_coldkey(0) - with patch("bittensor.commands.delegates.show_delegates"): - with patch( - "bittensor.subtensor.Subtensor.get_delegates", - return_value=[ - bittensor.DelegateInfo( - hotkey_ss58=delegate_ss58, # return delegate with mock coldkey - total_stake=bittensor.Balance.from_float(0.1), - nominators=[], - owner_ss58="", - take=0.18, - validator_permits=[], - registrations=[], - return_per_1000=bittensor.Balance.from_float(0.1), - total_daily_return=bittensor.Balance.from_float(0.1), - ) - ], - ): - # Patch command to exit early - with patch( - "bittensor.commands.delegates.DelegateStakeCommand.run", - return_value=None, - ): - # Test prompt happens when - # - delegate hotkey IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = [ - "0" - ] # select delegate with mock coldkey - - cli = bittensor.cli( - args=base_args - + [ - # '--delegate_ss58key', delegate_ss58, - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called ONCE", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for delegate hotkey - self.assertTrue( - any( - filter( - lambda x: "delegate" in x.lower(), - combined_args_kwargs0, - ) - ), - msg=f"Prompt should have been called for delegate: {combined_args_kwargs0}", - ) - - # Test NO prompt happens when - # - delegate hotkey IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--delegate_ss58key", - delegate_ss58, - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - def test_undelegate_prompt_hotkey(self, _): - # Tests when - # - wallet name IS passed, AND - # - delegate hotkey IS NOT passed - base_args = [ - "root", - "undelegate", - "--all", - "--wallet.name", - "mock", - ] - - delegate_ss58 = _get_mock_coldkey(0) - with patch("bittensor.commands.delegates.show_delegates"): - with patch( - "bittensor.subtensor.Subtensor.get_delegates", - return_value=[ - bittensor.DelegateInfo( - hotkey_ss58=delegate_ss58, # return delegate with mock coldkey - total_stake=bittensor.Balance.from_float(0.1), - nominators=[], - owner_ss58="", - take=0.18, - validator_permits=[], - registrations=[], - return_per_1000=bittensor.Balance.from_float(0.1), - total_daily_return=bittensor.Balance.from_float(0.1), - ) - ], - ): - # Patch command to exit early - with patch( - "bittensor.commands.delegates.DelegateUnstakeCommand.run", - return_value=None, - ): - # Test prompt happens when - # - delegate hotkey IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = [ - "0" - ] # select delegate with mock coldkey - - cli = bittensor.cli( - args=base_args - + [ - # '--delegate_ss58key', delegate_ss58, - ] - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called ONCE", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for delegate hotkey - self.assertTrue( - any( - filter( - lambda x: "delegate" in x.lower(), - combined_args_kwargs0, - ) - ), - msg=f"Prompt should have been called for delegate: {combined_args_kwargs0}", - ) - - # Test NO prompt happens when - # - delegate hotkey IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--delegate_ss58key", - delegate_ss58, - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - def test_vote_command_prompt_proposal_hash(self, _): - """Test that the vote command prompts for proposal_hash when it is not passed""" - base_args = [ - "root", - "senate_vote", - "--wallet.name", - "mock", - "--wallet.hotkey", - "mock_hotkey", - ] - - mock_proposal_hash = "mock_proposal_hash" - - with patch("bittensor.subtensor.Subtensor.is_senate_member", return_value=True): - with patch( - "bittensor.subtensor.Subtensor.get_vote_data", - return_value={"index": 1}, - ): - # Patch command to exit early - with patch( - "bittensor.commands.senate.VoteCommand.run", - return_value=None, - ): - # Test prompt happens when - # - proposal_hash IS NOT passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - mock_ask_prompt.side_effect = [ - mock_proposal_hash # Proposal hash - ] - - cli = bittensor.cli( - args=base_args - # proposal_hash not added - ) - cli.run() - - # Prompt happened - mock_ask_prompt.assert_called() - self.assertEqual( - mock_ask_prompt.call_count, - 1, - msg="Prompt should have been called once", - ) - args0, kwargs0 = mock_ask_prompt.call_args_list[0] - combined_args_kwargs0 = [arg for arg in args0] + [ - val for val in kwargs0.values() - ] - # check that prompt was called for proposal_hash - self.assertTrue( - any( - filter( - lambda x: "proposal" in x.lower(), - combined_args_kwargs0, - ) - ), - msg=f"Prompt should have been called for proposal: {combined_args_kwargs0}", - ) - - # Test NO prompt happens when - # - proposal_hash IS passed - with patch("rich.prompt.Prompt.ask") as mock_ask_prompt: - cli = bittensor.cli( - args=base_args - + [ - "--proposal_hash", - mock_proposal_hash, - ] - ) - cli.run() - - # NO prompt happened - mock_ask_prompt.assert_not_called() - - @patch("bittensor.wallet", new_callable=return_mock_wallet_factory) - def test_commit_reveal_weights_enabled_parse_boolean_argument(self, mock_sub, __): - param = "commit_reveal_weights_enabled" - - def _test_value_parsing(parsed_value: bool, modified: str): - cli = bittensor.cli( - args=[ - "sudo", - "set", - "--netuid", - "1", - "--param", - param, - "--value", - modified, - "--wallet.name", - "mock", - ] - ) - cli.run() - - _, kwargs = mock_sub.call_args - passed_config = kwargs["config"] - self.assertEqual(passed_config.param, param, msg="Incorrect param") - self.assertEqual( - passed_config.value, - parsed_value, - msg=f"Boolean argument not correctly for {modified}", - ) - - for boolean_value in [True, False, 1, 0]: - as_str = str(boolean_value) - - _test_value_parsing(boolean_value, as_str) - _test_value_parsing(boolean_value, as_str.capitalize()) - _test_value_parsing(boolean_value, as_str.upper()) - _test_value_parsing(boolean_value, as_str.lower()) - - @patch("bittensor.wallet", new_callable=return_mock_wallet_factory) - def test_hyperparameter_allowed_values( - self, - mock_sub, - __, - ): - params = ["alpha_values"] - - def _test_value_parsing(param: str, value: str): - cli = bittensor.cli( - args=[ - "sudo", - "set", - "hyperparameters", - "--netuid", - "1", - "--param", - param, - "--value", - value, - "--wallet.name", - "mock", - ] - ) - should_raise_error = False - error_message = "" - - try: - alpha_low_str, alpha_high_str = value.strip("[]").split(",") - alpha_high = float(alpha_high_str) - alpha_low = float(alpha_low_str) - if alpha_high <= 52428 or alpha_high >= 65535: - should_raise_error = True - error_message = "between 52428 and 65535" - elif alpha_low < 0 or alpha_low > 52428: - should_raise_error = True - error_message = "between 0 and 52428" - except ValueError: - should_raise_error = True - error_message = "a number or a boolean" - except TypeError: - should_raise_error = True - error_message = "a number or a boolean" - - if isinstance(value, bool): - should_raise_error = True - error_message = "a number or a boolean" - - if should_raise_error: - with pytest.raises(ValueError) as exc_info: - cli.run() - assert ( - f"Hyperparameter {param} value is not within bounds. Value is {value} but must be {error_message}" - in str(exc_info.value) - ) - else: - cli.run() - _, kwargs = mock_sub.call_args - passed_config = kwargs["config"] - self.assertEqual(passed_config.param, param, msg="Incorrect param") - self.assertEqual( - passed_config.value, - value, - msg=f"Value argument not set correctly for {param}", - ) - - for param in params: - for value in [ - [0.8, 11], - [52429, 52428], - [52427, 53083], - [6553, 53083], - [-123, None], - [1, 0], - [True, "Some string"], - ]: - as_str = str(value).strip("[]") - _test_value_parsing(param, as_str) - - @patch("bittensor.wallet", new_callable=return_mock_wallet_factory) - def test_network_registration_allowed_parse_boolean_argument(self, mock_sub, __): - param = "network_registration_allowed" - - def _test_value_parsing(parsed_value: bool, modified: str): - cli = bittensor.cli( - args=[ - "sudo", - "set", - "--netuid", - "1", - "--param", - param, - "--value", - modified, - "--wallet.name", - "mock", - ] - ) - cli.run() - - _, kwargs = mock_sub.call_args - passed_config = kwargs["config"] - self.assertEqual(passed_config.param, param, msg="Incorrect param") - self.assertEqual( - passed_config.value, - parsed_value, - msg=f"Boolean argument not correctly for {modified}", - ) - - for boolean_value in [True, False, 1, 0]: - as_str = str(boolean_value) - - _test_value_parsing(boolean_value, as_str) - _test_value_parsing(boolean_value, as_str.capitalize()) - _test_value_parsing(boolean_value, as_str.upper()) - _test_value_parsing(boolean_value, as_str.lower()) - - @patch("bittensor.wallet", new_callable=return_mock_wallet_factory) - def test_network_pow_registration_allowed_parse_boolean_argument( - self, mock_sub, __ - ): - param = "network_pow_registration_allowed" - - def _test_value_parsing(parsed_value: bool, modified: str): - cli = bittensor.cli( - args=[ - "sudo", - "set", - "--netuid", - "1", - "--param", - param, - "--value", - modified, - "--wallet.name", - "mock", - ] - ) - cli.run() - - _, kwargs = mock_sub.call_args - passed_config = kwargs["config"] - self.assertEqual(passed_config.param, param, msg="Incorrect param") - self.assertEqual( - passed_config.value, - parsed_value, - msg=f"Boolean argument not correctly for {modified}", - ) - - for boolean_value in [True, False, 1, 0]: - as_str = str(boolean_value) - - _test_value_parsing(boolean_value, as_str) - _test_value_parsing(boolean_value, as_str.capitalize()) - _test_value_parsing(boolean_value, as_str.upper()) - _test_value_parsing(boolean_value, as_str.lower()) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/integration_tests/test_metagraph_integration.py b/tests/integration_tests/test_metagraph_integration.py index 5dbb9ddfc1..e1cf924cd1 100644 --- a/tests/integration_tests/test_metagraph_integration.py +++ b/tests/integration_tests/test_metagraph_integration.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Foundation - +# 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 @@ -19,8 +18,8 @@ import bittensor import torch import os -from bittensor.mock import MockSubtensor -from bittensor.metagraph import METAGRAPH_STATE_DICT_NDARRAY_KEYS, get_save_dir +from bittensor.utils.mock import MockSubtensor +from bittensor.core.metagraph import METAGRAPH_STATE_DICT_NDARRAY_KEYS, get_save_dir _subtensor_mock: MockSubtensor = MockSubtensor() diff --git a/tests/integration_tests/test_subtensor_integration.py b/tests/integration_tests/test_subtensor_integration.py index 407dee848c..3152d74d56 100644 --- a/tests/integration_tests/test_subtensor_integration.py +++ b/tests/integration_tests/test_subtensor_integration.py @@ -1,15 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2023 Opentensor Technologies Inc - +# 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 @@ -26,7 +25,7 @@ from substrateinterface import Keypair import bittensor -from bittensor.mock import MockSubtensor +from bittensor.utils.mock import MockSubtensor from bittensor.utils import weight_utils from bittensor.utils.balance import Balance from tests.helpers import ( @@ -35,6 +34,7 @@ _get_mock_keypair, _get_mock_wallet, ) +from bittensor.core import settings class TestSubtensor(unittest.TestCase): @@ -55,7 +55,9 @@ def setUp(self): def setUpClass(cls) -> None: # mock rich console status mock_console = MockConsole() - cls._mock_console_patcher = patch("bittensor.__console__", mock_console) + cls._mock_console_patcher = patch( + "bittensor.core.settings.bt_console", mock_console + ) cls._mock_console_patcher.start() # Keeps the same mock network for all tests. This stops the network from being re-setup for each test. @@ -79,8 +81,8 @@ def test_network_overrides(self): # Argument importance: chain_endpoint (arg) > network (arg) > config.subtensor.chain_endpoint > config.subtensor.network config0 = bittensor.subtensor.config() config0.subtensor.network = "finney" - config0.subtensor.chain_endpoint = "wss://finney.subtensor.io" # Should not match bittensor.__finney_entrypoint__ - assert config0.subtensor.chain_endpoint != bittensor.__finney_entrypoint__ + config0.subtensor.chain_endpoint = "wss://finney.subtensor.io" # Should not match bittensor.core.settings.finney_entrypoint + assert config0.subtensor.chain_endpoint != settings.finney_entrypoint config1 = bittensor.subtensor.config() config1.subtensor.network = "local" @@ -94,7 +96,7 @@ def test_network_overrides(self): sub1 = bittensor.subtensor(config=config1, network="local") self.assertEqual( sub1.chain_endpoint, - bittensor.__local_entrypoint__, + settings.local_entrypoint, msg="Explicit network arg should override config.network", ) @@ -102,14 +104,14 @@ def test_network_overrides(self): sub2 = bittensor.subtensor(config=config0) self.assertNotEqual( sub2.chain_endpoint, - bittensor.__finney_entrypoint__, # Here we expect the endpoint corresponding to the network "finney" + settings.finney_entrypoint, # Here we expect the endpoint corresponding to the network "finney" msg="config.network should override config.chain_endpoint", ) sub3 = bittensor.subtensor(config=config1) # Should pick local instead of finney (default) assert sub3.network == "local" - assert sub3.chain_endpoint == bittensor.__local_entrypoint__ + assert sub3.chain_endpoint == settings.local_entrypoint def test_get_current_block(self): block = self.subtensor.get_current_block() @@ -710,7 +712,9 @@ def test_registration_multiprocessed_already_registered(self): ) self.subtensor._do_pow_register = MagicMock(return_value=(True, None)) - with patch("bittensor.__console__.status") as mock_set_status: + with patch( + "bittensor.core.settings.bt_console.status" + ) as mock_set_status: # Need to patch the console status to avoid opening a parallel live display mock_set_status.__enter__ = MagicMock(return_value=True) mock_set_status.__exit__ = MagicMock(return_value=True) @@ -769,7 +773,7 @@ def test_registration_failed(self): mock_neuron.is_null = True with patch( - "bittensor.extrinsics.registration.create_pow", return_value=None + "bittensor.api.extrinsics.registration.create_pow", return_value=None ) as mock_create_pow: wallet = _get_mock_wallet( hotkey=_get_mock_keypair(0, self.id()), @@ -821,7 +825,7 @@ class ExitEarly(Exception): mock_create_pow = MagicMock(return_value=MagicMock(is_stale=mock_is_stale)) - with patch("bittensor.extrinsics.registration.create_pow", mock_create_pow): + with patch("bittensor.api.extrinsics.registration.create_pow", mock_create_pow): # should create a pow and check if it is stale # then should create a new pow and check if it is stale # then should enter substrate and exit early because of test @@ -843,7 +847,7 @@ class ExitEarly(Exception): def test_defaults_to_finney(self): sub = bittensor.subtensor() assert sub.network == "finney" - assert sub.chain_endpoint == bittensor.__finney_entrypoint__ + assert sub.chain_endpoint == settings.finney_entrypoint if __name__ == "__main__": diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 90b6f25748..a5503f8961 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -3,11 +3,11 @@ @pytest.fixture -def force_legacy_torch_compat_api(monkeypatch): +def force_legacy_torch_compatible_api(monkeypatch): monkeypatch.setenv("USE_TORCH", "1") @pytest.fixture -def mock_aioresponse(): +def mock_aio_response(): with aioresponses() as m: yield m diff --git a/tests/unit_tests/extrinsics/test_delegation.py b/tests/unit_tests/extrinsics/test_delegation.py index 729f5baf8b..1b1200f7aa 100644 --- a/tests/unit_tests/extrinsics/test_delegation.py +++ b/tests/unit_tests/extrinsics/test_delegation.py @@ -20,18 +20,18 @@ import pytest from bittensor_wallet.wallet import Wallet -from bittensor.errors import ( +from bittensor.core.errors import ( NominationError, NotDelegateError, NotRegisteredError, StakeError, ) -from bittensor.extrinsics.delegation import ( +from bittensor.api.extrinsics.delegation import ( nominate_extrinsic, delegate_extrinsic, undelegate_extrinsic, ) -from bittensor.subtensor import Subtensor +from bittensor.core.subtensor import Subtensor from bittensor.utils.balance import Balance diff --git a/tests/unit_tests/extrinsics/test_network.py b/tests/unit_tests/extrinsics/test_network.py index ede01bf5fc..e4589bc1b4 100644 --- a/tests/unit_tests/extrinsics/test_network.py +++ b/tests/unit_tests/extrinsics/test_network.py @@ -20,11 +20,11 @@ import pytest from bittensor_wallet import Wallet -from bittensor.extrinsics.network import ( +from bittensor.api.extrinsics.network import ( set_hyperparameter_extrinsic, register_subnetwork_extrinsic, ) -from bittensor.subtensor import Subtensor +from bittensor.core.subtensor import Subtensor # Mock the bittensor and related modules to avoid real network calls and wallet operations diff --git a/tests/unit_tests/extrinsics/test_prometheus.py b/tests/unit_tests/extrinsics/test_prometheus.py index 237e2051be..6b45b7fc22 100644 --- a/tests/unit_tests/extrinsics/test_prometheus.py +++ b/tests/unit_tests/extrinsics/test_prometheus.py @@ -20,21 +20,21 @@ import pytest from bittensor_wallet import Wallet -import bittensor -from bittensor.extrinsics.prometheus import prometheus_extrinsic -from bittensor.subtensor import Subtensor +from bittensor.api.extrinsics.prometheus import prometheus_extrinsic +from bittensor.core.subtensor import Subtensor +from bittensor.core.settings import version_as_int # Mocking the bittensor and networking modules @pytest.fixture def mock_bittensor(): - with patch("bittensor.subtensor") as mock: + with patch("bittensor.core.subtensor.Subtensor") as mock: yield mock @pytest.fixture def mock_wallet(): - with patch("bittensor.wallet") as mock: + with patch("bittensor_wallet.Wallet") as mock: yield mock @@ -74,7 +74,7 @@ def test_prometheus_extrinsic_happy_path( mock_net.ip_version.return_value = 4 neuron = MagicMock() neuron.is_null = False - neuron.prometheus_info.version = bittensor.__version_as_int__ + neuron.prometheus_info.version = version_as_int neuron.prometheus_info.ip = 3232235521 neuron.prometheus_info.port = port neuron.prometheus_info.ip_type = 4 diff --git a/tests/unit_tests/extrinsics/test_registration.py b/tests/unit_tests/extrinsics/test_registration.py index b06f8b5ec9..00addfd5ac 100644 --- a/tests/unit_tests/extrinsics/test_registration.py +++ b/tests/unit_tests/extrinsics/test_registration.py @@ -20,14 +20,14 @@ import pytest from bittensor_wallet import Wallet -from bittensor.extrinsics.registration import ( +from bittensor.api.extrinsics.registration import ( MaxSuccessException, MaxAttemptsException, swap_hotkey_extrinsic, burned_register_extrinsic, register_extrinsic, ) -from bittensor.subtensor import Subtensor +from bittensor.core.subtensor import Subtensor from bittensor.utils.registration import POWSolution @@ -97,7 +97,7 @@ def test_run_faucet_extrinsic_happy_path( "bittensor.utils.registration._solve_for_difficulty_fast", return_value=mock_pow_solution, ) as mock_create_pow, patch("rich.prompt.Confirm.ask", return_value=True): - from bittensor.extrinsics.registration import run_faucet_extrinsic + from bittensor.api.extrinsics.registration import run_faucet_extrinsic # Arrange mock_subtensor.get_balance.return_value = 100 @@ -153,7 +153,7 @@ def test_run_faucet_extrinsic_edge_cases( with patch("torch.cuda.is_available", return_value=torch_cuda_available), patch( "rich.prompt.Confirm.ask", return_value=prompt_response ): - from bittensor.extrinsics.registration import run_faucet_extrinsic + from bittensor.api.extrinsics.registration import run_faucet_extrinsic # Act result = run_faucet_extrinsic( @@ -187,7 +187,7 @@ def test_run_faucet_extrinsic_error_cases( "bittensor.utils.registration.create_pow", side_effect=[mock_pow_solution, exception], ): - from bittensor.extrinsics.registration import run_faucet_extrinsic + from bittensor.api.extrinsics.registration import run_faucet_extrinsic # Act result = run_faucet_extrinsic( diff --git a/tests/unit_tests/extrinsics/test_root.py b/tests/unit_tests/extrinsics/test_root.py index b801f7b4e1..7e1137b7b4 100644 --- a/tests/unit_tests/extrinsics/test_root.py +++ b/tests/unit_tests/extrinsics/test_root.py @@ -1,10 +1,12 @@ -import pytest from unittest.mock import MagicMock, patch -from bittensor.subtensor import Subtensor -from bittensor.extrinsics.root import ( + +import pytest + +from bittensor.api.extrinsics.root import ( root_register_extrinsic, set_root_weights_extrinsic, ) +from bittensor.core.subtensor import Subtensor @pytest.fixture @@ -293,7 +295,7 @@ def test_set_root_weights_extrinsic_torch( prompt, user_response, expected_success, - force_legacy_torch_compat_api, + force_legacy_torch_compatible_api, ): test_set_root_weights_extrinsic( mock_subtensor, diff --git a/tests/unit_tests/extrinsics/test_senate.py b/tests/unit_tests/extrinsics/test_senate.py index 66849efc5c..32bb7bdffe 100644 --- a/tests/unit_tests/extrinsics/test_senate.py +++ b/tests/unit_tests/extrinsics/test_senate.py @@ -1,7 +1,8 @@ import pytest from unittest.mock import MagicMock, patch -from bittensor import subtensor, wallet -from bittensor.extrinsics.senate import ( +from bittensor.core.subtensor import Subtensor +from bittensor_wallet import Wallet +from bittensor.api.extrinsics.senate import ( leave_senate_extrinsic, register_senate_extrinsic, vote_senate_extrinsic, @@ -11,14 +12,14 @@ # Mocking external dependencies @pytest.fixture def mock_subtensor(): - mock = MagicMock(spec=subtensor) + mock = MagicMock(spec=Subtensor) mock.substrate = MagicMock() return mock @pytest.fixture def mock_wallet(): - mock = MagicMock(spec=wallet) + mock = MagicMock(spec=Wallet) mock.coldkey = MagicMock() mock.hotkey = MagicMock() mock.hotkey.ss58_address = "fake_hotkey_address" @@ -55,8 +56,8 @@ def test_register_senate_extrinsic( ): # Arrange with patch( - "bittensor.extrinsics.senate.Confirm.ask", return_value=not prompt - ), patch("bittensor.extrinsics.senate.time.sleep"), patch.object( + "bittensor.api.extrinsics.senate.Confirm.ask", return_value=not prompt + ), patch("bittensor.api.extrinsics.senate.time.sleep"), patch.object( mock_subtensor.substrate, "compose_call" ), patch.object(mock_subtensor.substrate, "create_signed_extrinsic"), patch.object( mock_subtensor.substrate, @@ -67,7 +68,7 @@ def test_register_senate_extrinsic( error_message="error", ), ) as mock_submit_extrinsic, patch.object( - mock_wallet, "is_senate_member", return_value=is_registered + mock_subtensor, "is_senate_member", return_value=is_registered ): # Act result = register_senate_extrinsic( @@ -148,8 +149,8 @@ def test_vote_senate_extrinsic( proposal_idx = 123 with patch( - "bittensor.extrinsics.senate.Confirm.ask", return_value=not prompt - ), patch("bittensor.extrinsics.senate.time.sleep"), patch.object( + "bittensor.api.extrinsics.senate.Confirm.ask", return_value=not prompt + ), patch("bittensor.api.extrinsics.senate.time.sleep"), patch.object( mock_subtensor.substrate, "compose_call" ), patch.object(mock_subtensor.substrate, "create_signed_extrinsic"), patch.object( mock_subtensor.substrate, @@ -212,8 +213,8 @@ def test_leave_senate_extrinsic( ): # Arrange with patch( - "bittensor.extrinsics.senate.Confirm.ask", return_value=not prompt - ), patch("bittensor.extrinsics.senate.time.sleep"), patch.object( + "bittensor.api.extrinsics.senate.Confirm.ask", return_value=not prompt + ), patch("bittensor.api.extrinsics.senate.time.sleep"), patch.object( mock_subtensor.substrate, "compose_call" ), patch.object(mock_subtensor.substrate, "create_signed_extrinsic"), patch.object( mock_subtensor.substrate, @@ -223,7 +224,7 @@ def test_leave_senate_extrinsic( process_events=MagicMock(), error_message="error", ), - ), patch.object(mock_wallet, "is_senate_member", return_value=is_registered): + ), patch.object(mock_subtensor, "is_senate_member", return_value=is_registered): # Act result = leave_senate_extrinsic( subtensor=mock_subtensor, diff --git a/tests/unit_tests/extrinsics/test_serving.py b/tests/unit_tests/extrinsics/test_serving.py index 3269397a28..26f25af0b6 100644 --- a/tests/unit_tests/extrinsics/test_serving.py +++ b/tests/unit_tests/extrinsics/test_serving.py @@ -20,13 +20,13 @@ import pytest from bittensor_wallet import Wallet -from bittensor.axon import axon as Axon -from bittensor.extrinsics.serving import ( +from bittensor.core.axon import Axon +from bittensor.api.extrinsics.serving import ( serve_extrinsic, publish_metadata, serve_axon_extrinsic, ) -from bittensor.subtensor import Subtensor +from bittensor.core.subtensor import Subtensor @pytest.fixture @@ -119,7 +119,7 @@ def test_serve_extrinsic_happy_path( ): # Arrange mock_subtensor._do_serve_axon.return_value = (True, "") - with patch("bittensor.extrinsics.serving.Confirm.ask", return_value=True): + with patch("bittensor.api.extrinsics.serving.Confirm.ask", return_value=True): # Act result = serve_extrinsic( mock_subtensor, @@ -176,7 +176,7 @@ def test_serve_extrinsic_edge_cases( ): # Arrange mock_subtensor._do_serve_axon.return_value = (True, "") - with patch("bittensor.extrinsics.serving.Confirm.ask", return_value=True): + with patch("bittensor.api.extrinsics.serving.Confirm.ask", return_value=True): # Act result = serve_extrinsic( mock_subtensor, @@ -233,7 +233,7 @@ def test_serve_extrinsic_error_cases( ): # Arrange mock_subtensor._do_serve_axon.return_value = (False, "Error serving axon") - with patch("bittensor.extrinsics.serving.Confirm.ask", return_value=True): + with patch("bittensor.api.extrinsics.serving.Confirm.ask", return_value=True): # Act result = serve_extrinsic( mock_subtensor, diff --git a/tests/unit_tests/extrinsics/test_set_weights.py b/tests/unit_tests/extrinsics/test_set_weights.py index 68ce7acae9..38d4a69445 100644 --- a/tests/unit_tests/extrinsics/test_set_weights.py +++ b/tests/unit_tests/extrinsics/test_set_weights.py @@ -1,20 +1,22 @@ import torch import pytest from unittest.mock import MagicMock, patch -from bittensor import subtensor, wallet -from bittensor.extrinsics.set_weights import set_weights_extrinsic + +from bittensor.core.subtensor import Subtensor +from bittensor_wallet import Wallet +from bittensor.api.extrinsics.set_weights import set_weights_extrinsic @pytest.fixture def mock_subtensor(): - mock = MagicMock(spec=subtensor) + mock = MagicMock(spec=Subtensor) mock.network = "mock_network" return mock @pytest.fixture def mock_wallet(): - mock = MagicMock(spec=wallet) + mock = MagicMock(spec=Wallet) return mock diff --git a/tests/unit_tests/extrinsics/test_staking.py b/tests/unit_tests/extrinsics/test_staking.py index c3b888520b..c91fda1713 100644 --- a/tests/unit_tests/extrinsics/test_staking.py +++ b/tests/unit_tests/extrinsics/test_staking.py @@ -1,25 +1,43 @@ +# 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 pytest from unittest.mock import patch, MagicMock -import bittensor from bittensor.utils.balance import Balance -from bittensor.extrinsics.staking import ( +from bittensor.api.extrinsics.staking import ( add_stake_extrinsic, add_stake_multiple_extrinsic, ) -from bittensor.errors import NotDelegateError +from bittensor.core.errors import NotDelegateError +from bittensor.core.subtensor import Subtensor +from bittensor_wallet import Wallet # Mocking external dependencies @pytest.fixture def mock_subtensor(): - mock = MagicMock(spec=bittensor.subtensor) + mock = MagicMock(spec=Subtensor) mock.network = "mock_network" return mock @pytest.fixture def mock_wallet(): - mock = MagicMock(spec=bittensor.wallet) + mock = MagicMock(spec=Wallet) mock.hotkey.ss58_address = "5FHneW46..." mock.coldkeypub.ss58_address = "5Gv8YYFu8..." mock.hotkey_str = "mock_hotkey_str" @@ -29,7 +47,7 @@ def mock_wallet(): @pytest.fixture def mock_other_owner_wallet(): - mock = MagicMock(spec=bittensor.wallet) + mock = MagicMock(spec=Wallet) mock.hotkey.ss58_address = "11HneC46..." mock.coldkeypub.ss58_address = "6Gv9ZZFu8..." mock.hotkey_str = "mock_hotkey_str_other_owner" @@ -109,9 +127,7 @@ def test_add_stake_extrinsic( staking_balance = amount if amount else Balance.from_tao(100) else: staking_balance = ( - Balance.from_tao(amount) - if not isinstance(amount, bittensor.Balance) - else amount + Balance.from_tao(amount) if not isinstance(amount, Balance) else amount ) with patch.object( @@ -135,11 +151,11 @@ def test_add_stake_extrinsic( ) as mock_confirm, patch.object( mock_subtensor, "get_minimum_required_stake", - return_value=bittensor.Balance.from_tao(0.01), + return_value=Balance.from_tao(0.01), ), patch.object( mock_subtensor, "get_existential_deposit", - return_value=bittensor.Balance.from_rao(100_000), + return_value=Balance.from_rao(100_000), ): mock_balance = mock_subtensor.get_balance() existential_deposit = mock_subtensor.get_existential_deposit() @@ -447,7 +463,7 @@ def test_add_stake_extrinsic( None, 0, TypeError, - "amounts must be a [list of bittensor.Balance or float] or None", + "amounts must be a [list of Balance or float] or None", ), ], ids=[ diff --git a/tests/unit_tests/extrinsics/test_unstaking.py b/tests/unit_tests/extrinsics/test_unstaking.py index 0fa6ba84c4..107757fd04 100644 --- a/tests/unit_tests/extrinsics/test_unstaking.py +++ b/tests/unit_tests/extrinsics/test_unstaking.py @@ -1,22 +1,26 @@ -import bittensor -import pytest - from unittest.mock import patch, MagicMock +import pytest +from bittensor_wallet import Wallet + +from bittensor.api.extrinsics.unstaking import ( + unstake_extrinsic, + unstake_multiple_extrinsic, +) +from bittensor.core.subtensor import Subtensor from bittensor.utils.balance import Balance -from bittensor.extrinsics.unstaking import unstake_extrinsic, unstake_multiple_extrinsic @pytest.fixture def mock_subtensor(): - mock = MagicMock(spec=bittensor.subtensor) + mock = MagicMock(spec=Subtensor) mock.network = "mock_network" return mock @pytest.fixture def mock_wallet(): - mock = MagicMock(spec=bittensor.wallet) + mock = MagicMock(spec=Wallet) mock.hotkey.ss58_address = "5FHneW46..." mock.coldkeypub.ss58_address = "5Gv8YYFu8..." mock.hotkey_str = "mock_hotkey_str" @@ -105,9 +109,7 @@ def test_unstake_extrinsic( mock_subtensor._do_unstake.assert_called_once_with( wallet=mock_wallet, hotkey_ss58=hotkey_ss58 or mock_wallet.hotkey.ss58_address, - amount=bittensor.Balance.from_tao(amount) - if amount - else mock_current_stake, + amount=Balance.from_tao(amount) if amount else mock_current_stake, wait_for_inclusion=wait_for_inclusion, wait_for_finalization=wait_for_finalization, ) @@ -241,7 +243,7 @@ def test_unstake_extrinsic( None, 0, TypeError, - "amounts must be a [list of bittensor.Balance or float] or None", + "amounts must be a [list of Balance or float] or None", ), ], ids=[ diff --git a/tests/unit_tests/factories/neuron_factory.py b/tests/unit_tests/factories/neuron_factory.py index 4ad70c5dca..f99a084acd 100644 --- a/tests/unit_tests/factories/neuron_factory.py +++ b/tests/unit_tests/factories/neuron_factory.py @@ -1,6 +1,6 @@ import factory -from bittensor.chain_data import AxonInfo, NeuronInfoLite, PrometheusInfo +from bittensor.core.chain_data import AxonInfo, NeuronInfoLite, PrometheusInfo from bittensor.utils.balance import Balance diff --git a/tests/unit_tests/test_axon.py b/tests/unit_tests/test_axon.py index c25fc2e54e..8d450c4c64 100644 --- a/tests/unit_tests/test_axon.py +++ b/tests/unit_tests/test_axon.py @@ -1,16 +1,14 @@ # The MIT License (MIT) -# Copyright © 2021 Yuma Rao -# Copyright © 2022 Opentensor Foundation -# Copyright © 2023 Opentensor Technologies Inc - +# 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 @@ -18,63 +16,63 @@ # DEALINGS IN THE SOFTWARE. -# Standard Lib import re import time from dataclasses import dataclass - -from typing import Any +from typing import Any, Tuple from unittest import IsolatedAsyncioTestCase from unittest.mock import AsyncMock, MagicMock, patch -# Third Party import netaddr - import pytest -from starlette.requests import Request from fastapi.testclient import TestClient +from starlette.requests import Request -# Bittensor -import bittensor -from bittensor import Synapse, RunException -from bittensor.axon import AxonMiddleware -from bittensor.axon import axon as Axon -from bittensor.utils.axon_utils import allowed_nonce_window_ns, calculate_diff_seconds -from bittensor.constants import ALLOWED_DELTA, NANOSECONDS_IN_SECOND +from bittensor.core.axon import Axon, AxonMiddleware +from bittensor.core.errors import RunException +from bittensor.core.settings import version_as_int +from bittensor.core.synapse import Synapse +from bittensor.core.threadpool import PriorityThreadPoolExecutor +from bittensor.utils.axon_utils import ( + allowed_nonce_window_ns, + calculate_diff_seconds, + ALLOWED_DELTA, + NANOSECONDS_IN_SECOND, +) -def test_attach(): +def test_attach_initial(): # Create a mock AxonServer instance - server = bittensor.axon() + server = Axon() # Define the Synapse type - class Synapse(bittensor.Synapse): + class TestSynapse(Synapse): pass # Define the functions with the correct signatures - def forward_fn(synapse: Synapse) -> Any: + def forward_fn(synapse: TestSynapse) -> Any: pass - def blacklist_fn(synapse: Synapse) -> bool: - return True + def blacklist_fn(synapse: TestSynapse) -> Tuple[bool, str]: + return True, "" - def priority_fn(synapse: Synapse) -> float: + def priority_fn(synapse: TestSynapse) -> float: return 1.0 - def verify_fn(synapse: Synapse) -> None: + def verify_fn(synapse: TestSynapse) -> None: pass # Test attaching with correct signatures server.attach(forward_fn, blacklist_fn, priority_fn, verify_fn) # Define functions with incorrect signatures - def wrong_blacklist_fn(synapse: Synapse) -> int: + def wrong_blacklist_fn(synapse: TestSynapse) -> int: return 1 - def wrong_priority_fn(synapse: Synapse) -> int: + def wrong_priority_fn(synapse: TestSynapse) -> int: return 1 - def wrong_verify_fn(synapse: Synapse) -> bool: + def wrong_verify_fn(synapse: TestSynapse) -> bool: return True # Test attaching with incorrect signatures @@ -90,14 +88,14 @@ def wrong_verify_fn(synapse: Synapse) -> bool: def test_attach(): # Create a mock AxonServer instance - server = bittensor.axon() + server = Axon() # Define the Synapse type - class Synapse: + class FakeSynapse: pass # Define a class that inherits from Synapse - class InheritedSynapse(bittensor.Synapse): + class InheritedSynapse(Synapse): pass # Define a function with the correct signature @@ -121,7 +119,7 @@ def wrong_forward_fn(synapse: NonInheritedSynapse) -> Any: def test_log_and_handle_error(): - from bittensor.axon import log_and_handle_error + from bittensor.core.axon import log_and_handle_error synapse = SynapseMock() @@ -132,7 +130,7 @@ def test_log_and_handle_error(): def test_create_error_response(): - from bittensor.axon import create_error_response + from bittensor.core.axon import create_error_response synapse = SynapseMock() synapse.axon.status_code = 500 @@ -199,10 +197,10 @@ def __init__(self): self.priority_fns = {} self.forward_fns = {} self.verify_fns = {} - self.thread_pool = bittensor.PriorityThreadPoolExecutor(max_workers=1) + self.thread_pool = PriorityThreadPoolExecutor(max_workers=1) -class SynapseMock(bittensor.Synapse): +class SynapseMock(Synapse): pass @@ -265,6 +263,7 @@ async def test_blacklist_fail(middleware): @pytest.mark.asyncio +@pytest.mark.skip("middleware.priority runs infinitely") async def test_priority_pass(middleware): synapse = SynapseMock() middleware.axon.priority_fns = {"SynapseMock": priority_fn_pass} @@ -473,7 +472,7 @@ def setUp(self): self.mock_axon = MagicMock() self.mock_axon.uuid = "1234" self.mock_axon.forward_class_types = { - "request_name": bittensor.Synapse, + "request_name": Synapse, } self.mock_axon.wallet.hotkey.sign.return_value = bytes.fromhex("aabbccdd") # Create an instance of AxonMiddleware @@ -492,7 +491,7 @@ async def test_preprocess(self): synapse = await self.axon_middleware.preprocess(request) # Check if the preprocess function fills the axon information into the synapse - assert synapse.axon.version == str(bittensor.__version_as_int__) + assert synapse.axon.version == str(version_as_int) assert synapse.axon.uuid == "1234" assert synapse.axon.nonce is not None assert synapse.axon.status_message is None @@ -548,7 +547,7 @@ async def test_unknown_path(self, http_client): ) async def test_ping__no_dendrite(self, http_client): - response = http_client.post_synapse(bittensor.Synapse()) + response = http_client.post_synapse(Synapse()) assert (response.status_code, response.json()) == ( 401, { diff --git a/tests/unit_tests/test_chain_data.py b/tests/unit_tests/test_chain_data.py index a6474bbee9..e2d7616454 100644 --- a/tests/unit_tests/test_chain_data.py +++ b/tests/unit_tests/test_chain_data.py @@ -1,9 +1,25 @@ +# 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 pytest -import bittensor import torch -from bittensor.chain_data import AxonInfo, ChainDataType, DelegateInfo, NeuronInfo -SS58_FORMAT = bittensor.__ss58_format__ +from bittensor.core.chain_data import AxonInfo, ChainDataType, DelegateInfo, NeuronInfo + RAOPERTAO = 10**18 @@ -224,7 +240,7 @@ def test_to_parameter_dict(axon_info, test_case): def test_to_parameter_dict_torch( axon_info, test_case, - force_legacy_torch_compat_api, + force_legacy_torch_compatible_api, ): result = axon_info.to_parameter_dict() @@ -294,7 +310,7 @@ def test_from_parameter_dict(parameter_dict, expected, test_case): ], ) def test_from_parameter_dict_torch( - parameter_dict, expected, test_case, force_legacy_torch_compat_api + parameter_dict, expected, test_case, force_legacy_torch_compatible_api ): # Act result = AxonInfo.from_parameter_dict(parameter_dict) @@ -511,13 +527,14 @@ def test_fix_decoded_values_error_cases( @pytest.fixture def mock_from_scale_encoding(mocker): - return mocker.patch("bittensor.chain_data.from_scale_encoding") + return mocker.patch("bittensor.core.chain_data.from_scale_encoding") @pytest.fixture def mock_fix_decoded_values(mocker): return mocker.patch( - "bittensor.DelegateInfo.fix_decoded_values", side_effect=lambda x: x + "bittensor.core.chain_data.DelegateInfo.fix_decoded_values", + side_effect=lambda x: x, ) diff --git a/tests/unit_tests/test_dendrite.py b/tests/unit_tests/test_dendrite.py index 0146bb7782..e7ccc691f4 100644 --- a/tests/unit_tests/test_dendrite.py +++ b/tests/unit_tests/test_dendrite.py @@ -17,24 +17,26 @@ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER # DEALINGS IN THE SOFTWARE. -# Standard Lib import asyncio import typing from unittest.mock import MagicMock, Mock -# Third Party import aiohttp import pytest -# Application -import bittensor -from bittensor.constants import DENDRITE_ERROR_MAPPING, DENDRITE_DEFAULT_ERROR -from bittensor.dendrite import dendrite as Dendrite -from bittensor.synapse import TerminalInfo +from bittensor.core.axon import Axon +from bittensor.core.dendrite import ( + DENDRITE_ERROR_MAPPING, + DENDRITE_DEFAULT_ERROR, + Dendrite, +) +from bittensor.core.synapse import TerminalInfo from tests.helpers import _get_mock_wallet +from bittensor.core.synapse import Synapse +from bittensor.core.chain_data import AxonInfo -class SynapseDummy(bittensor.Synapse): +class SynapseDummy(Synapse): input: int output: typing.Optional[int] = None @@ -46,10 +48,9 @@ def dummy(synapse: SynapseDummy) -> SynapseDummy: @pytest.fixture def setup_dendrite(): - user_wallet = ( - _get_mock_wallet() - ) # assuming bittensor.wallet() returns a wallet object - dendrite_obj = bittensor.dendrite(user_wallet) + # Assuming bittensor.wallet() returns a wallet object + user_wallet = _get_mock_wallet() + dendrite_obj = Dendrite(user_wallet) return dendrite_obj @@ -60,7 +61,7 @@ def dendrite_obj(setup_dendrite): @pytest.fixture def axon_info(): - return bittensor.AxonInfo( + return AxonInfo( version=1, ip="127.0.0.1", port=666, @@ -72,7 +73,7 @@ def axon_info(): @pytest.fixture(scope="session") def setup_axon(): - axon = bittensor.axon() + axon = Axon() axon.attach(forward_fn=dummy) axon.start() yield axon @@ -81,7 +82,7 @@ def setup_axon(): def test_init(setup_dendrite): dendrite_obj = setup_dendrite - assert isinstance(dendrite_obj, bittensor.dendrite) + assert isinstance(dendrite_obj, Dendrite) assert dendrite_obj.keypair == setup_dendrite.keypair @@ -127,16 +128,16 @@ def __await__(self): def test_dendrite_create_wallet(): - d = bittensor.dendrite(_get_mock_wallet()) - d = bittensor.dendrite(_get_mock_wallet().hotkey) - d = bittensor.dendrite(_get_mock_wallet().coldkeypub) + d = Dendrite(_get_mock_wallet()) + d = Dendrite(_get_mock_wallet().hotkey) + d = Dendrite(_get_mock_wallet().coldkeypub) assert d.__str__() == d.__repr__() @pytest.mark.asyncio async def test_forward_many(): n = 10 - d = bittensor.dendrite(wallet=_get_mock_wallet()) + d = Dendrite(wallet=_get_mock_wallet()) d.call = AsyncMock() axons = [MagicMock() for _ in range(n)] @@ -152,10 +153,10 @@ async def test_forward_many(): def test_pre_process_synapse(): - d = bittensor.dendrite(wallet=_get_mock_wallet()) - s = bittensor.Synapse() + d = Dendrite(wallet=_get_mock_wallet()) + s = Synapse() synapse = d.preprocess_synapse_for_request( - target_axon_info=bittensor.axon(wallet=_get_mock_wallet()).info(), + target_axon_info=Axon(wallet=_get_mock_wallet()).info(), synapse=s, timeout=12, ) @@ -296,7 +297,7 @@ def test_terminal_info_error_cases( @pytest.mark.asyncio async def test_dendrite__call__success_response( - axon_info, dendrite_obj, mock_aioresponse + axon_info, dendrite_obj, mock_aio_response ): input_synapse = SynapseDummy(input=1) expected_synapse = SynapseDummy( @@ -312,7 +313,7 @@ async def test_dendrite__call__success_response( ) ) ) - mock_aioresponse.post( + mock_aio_response.post( f"http://127.0.0.1:666/SynapseDummy", body=expected_synapse.json(), ) @@ -327,13 +328,13 @@ async def test_dendrite__call__success_response( @pytest.mark.asyncio async def test_dendrite__call__handles_http_error_response( - axon_info, dendrite_obj, mock_aioresponse + axon_info, dendrite_obj, mock_aio_response ): status_code = 414 message = "Custom Error" - mock_aioresponse.post( - f"http://127.0.0.1:666/SynapseDummy", + mock_aio_response.post( + "http://127.0.0.1:666/SynapseDummy", status=status_code, payload={"message": message}, ) diff --git a/tests/unit_tests/test_logging.py b/tests/unit_tests/test_logging.py index 1822fc86ef..6c39d7b5fd 100644 --- a/tests/unit_tests/test_logging.py +++ b/tests/unit_tests/test_logging.py @@ -2,9 +2,12 @@ import multiprocessing import logging as stdlogging from unittest.mock import MagicMock, patch -from bittensor.btlogging import LoggingMachine -from bittensor.btlogging.defines import DEFAULT_LOG_FILE_NAME, BITTENSOR_LOGGER_NAME -from bittensor.btlogging.loggingmachine import LoggingConfig +from bittensor.utils.btlogging import LoggingMachine +from bittensor.utils.btlogging.defines import ( + DEFAULT_LOG_FILE_NAME, + BITTENSOR_LOGGER_NAME, +) +from bittensor.utils.btlogging.loggingmachine import LoggingConfig @pytest.fixture(autouse=True, scope="session") @@ -75,7 +78,9 @@ def test_state_transitions(logging_machine, mock_config): Test state transitions and the associated logging level changes. """ config, log_file_path = mock_config - with patch("bittensor.btlogging.loggingmachine.all_loggers") as mocked_all_loggers: + with patch( + "bittensor.utils.btlogging.loggingmachine.all_loggers" + ) as mocked_all_loggers: # mock the main bittensor logger, identified by its `name` field mocked_bt_logger = MagicMock() mocked_bt_logger.name = BITTENSOR_LOGGER_NAME diff --git a/tests/unit_tests/test_metagraph.py b/tests/unit_tests/test_metagraph.py index af0dbdba76..826fa527fe 100644 --- a/tests/unit_tests/test_metagraph.py +++ b/tests/unit_tests/test_metagraph.py @@ -1,27 +1,28 @@ # The MIT License (MIT) -# Copyright © 2023 Opentensor Technologies Inc - +# 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 unittest.mock import MagicMock from unittest.mock import Mock -import pytest + import numpy as np -import bittensor +import pytest -from bittensor.metagraph import metagraph as Metagraph -from unittest.mock import MagicMock +from bittensor.core import settings +from bittensor.core.metagraph import Metagraph @pytest.fixture @@ -57,7 +58,7 @@ def mock_environment(): def test_set_metagraph_attributes(mock_environment): subtensor, neurons = mock_environment - metagraph = bittensor.metagraph(1, sync=False) + metagraph = Metagraph(1, sync=False) metagraph.neurons = neurons metagraph._set_metagraph_attributes(block=5, subtensor=subtensor) @@ -95,7 +96,7 @@ def test_set_metagraph_attributes(mock_environment): def test_process_weights_or_bonds(mock_environment): _, neurons = mock_environment - metagraph = bittensor.metagraph(1, sync=False) + metagraph = Metagraph(1, sync=False) metagraph.neurons = neurons # Test weights processing @@ -128,7 +129,7 @@ def test_process_weights_or_bonds(mock_environment): @pytest.fixture def mock_subtensor(): subtensor = MagicMock() - subtensor.chain_endpoint = bittensor.__finney_entrypoint__ + subtensor.chain_endpoint = settings.finney_entrypoint subtensor.network = "finney" subtensor.get_current_block.return_value = 601 return subtensor diff --git a/tests/unit_tests/test_overview.py b/tests/unit_tests/test_overview.py deleted file mode 100644 index 638ab4df4c..0000000000 --- a/tests/unit_tests/test_overview.py +++ /dev/null @@ -1,266 +0,0 @@ -# Standard Lib -from copy import deepcopy -from unittest.mock import MagicMock, patch - -# Pytest -import pytest - -# Bittensor -import bittensor -from bittensor.commands.overview import OverviewCommand -from tests.unit_tests.factories.neuron_factory import NeuronInfoLiteFactory - - -@pytest.fixture -def mock_subtensor(): - mock = MagicMock() - mock.get_balance = MagicMock(return_value=100) - return mock - - -def fake_config(**kwargs): - config = deepcopy(construct_config()) - for key, value in kwargs.items(): - setattr(config, key, value) - return config - - -def construct_config(): - parser = bittensor.cli.__create_parser__() - defaults = bittensor.config(parser=parser, args=[]) - # Parse commands and subcommands - for command in bittensor.ALL_COMMANDS: - if ( - command in bittensor.ALL_COMMANDS - and "commands" in bittensor.ALL_COMMANDS[command] - ): - for subcommand in bittensor.ALL_COMMANDS[command]["commands"]: - defaults.merge( - bittensor.config(parser=parser, args=[command, subcommand]) - ) - else: - defaults.merge(bittensor.config(parser=parser, args=[command])) - - defaults.netuid = 1 - # Always use mock subtensor. - defaults.subtensor.network = "finney" - # Skip version checking. - defaults.no_version_checking = True - - return defaults - - -@pytest.fixture -def mock_wallet(): - mock = MagicMock() - mock.coldkeypub_file.exists_on_device = MagicMock(return_value=True) - mock.coldkeypub_file.is_encrypted = MagicMock(return_value=False) - mock.coldkeypub.ss58_address = "fake_address" - return mock - - -class MockHotkey: - def __init__(self, hotkey_str): - self.hotkey_str = hotkey_str - - -class MockCli: - def __init__(self, config): - self.config = config - - -@pytest.mark.parametrize( - "config_all, exists_on_device, is_encrypted, expected_balance, test_id", - [ - (True, True, False, 100, "happy_path_all_wallets"), - (False, True, False, 100, "happy_path_single_wallet"), - (True, False, False, 0, "edge_case_no_wallets_found"), - (True, True, True, 0, "edge_case_encrypted_wallet"), - ], -) -def test_get_total_balance( - mock_subtensor, - mock_wallet, - config_all, - exists_on_device, - is_encrypted, - expected_balance, - test_id, -): - # Arrange - cli = MockCli(fake_config(all=config_all)) - mock_wallet.coldkeypub_file.exists_on_device.return_value = exists_on_device - mock_wallet.coldkeypub_file.is_encrypted.return_value = is_encrypted - - with patch( - "bittensor.wallet", return_value=mock_wallet - ) as mock_wallet_constructor, patch( - "bittensor.commands.overview.get_coldkey_wallets_for_path", - return_value=[mock_wallet] if config_all else [], - ), patch( - "bittensor.commands.overview.get_all_wallets_for_path", - return_value=[mock_wallet], - ), patch( - "bittensor.commands.overview.get_hotkey_wallets_for_wallet", - return_value=[mock_wallet], - ): - # Act - result_hotkeys, result_balance = OverviewCommand._get_total_balance( - 0, mock_subtensor, cli - ) - - # Assert - assert result_balance == expected_balance, f"Test ID: {test_id}" - assert all( - isinstance(hotkey, MagicMock) for hotkey in result_hotkeys - ), f"Test ID: {test_id}" - - -@pytest.mark.parametrize( - "config, all_hotkeys, expected_result, test_id", - [ - # Happy path tests - ( - {"all_hotkeys": False, "hotkeys": ["abc123", "xyz456"]}, - [MockHotkey("abc123"), MockHotkey("xyz456"), MockHotkey("mno567")], - ["abc123", "xyz456"], - "test_happy_path_included", - ), - ( - {"all_hotkeys": True, "hotkeys": ["abc123", "xyz456"]}, - [MockHotkey("abc123"), MockHotkey("xyz456"), MockHotkey("mno567")], - ["mno567"], - "test_happy_path_excluded", - ), - # Edge cases - ( - {"all_hotkeys": False, "hotkeys": []}, - [MockHotkey("abc123"), MockHotkey("xyz456")], - [], - "test_edge_no_hotkeys_specified", - ), - ( - {"all_hotkeys": True, "hotkeys": []}, - [MockHotkey("abc123"), MockHotkey("xyz456")], - ["abc123", "xyz456"], - "test_edge_all_hotkeys_excluded", - ), - ( - {"all_hotkeys": False, "hotkeys": ["abc123", "xyz456"]}, - [], - [], - "test_edge_no_hotkeys_available", - ), - ( - {"all_hotkeys": True, "hotkeys": ["abc123", "xyz456"]}, - [], - [], - "test_edge_no_hotkeys_available_excluded", - ), - ], -) -def test_get_hotkeys(config, all_hotkeys, expected_result, test_id): - # Arrange - cli = MockCli( - fake_config( - hotkeys=config.get("hotkeys"), all_hotkeys=config.get("all_hotkeys") - ) - ) - - # Act - result = OverviewCommand._get_hotkeys(cli, all_hotkeys) - - # Assert - assert [ - hotkey.hotkey_str for hotkey in result - ] == expected_result, f"Failed {test_id}" - - -def test_get_hotkeys_error(): - # Arrange - cli = MockCli(fake_config(hotkeys=["abc123", "xyz456"], all_hotkeys=False)) - all_hotkeys = None - - # Act - with pytest.raises(TypeError): - OverviewCommand._get_hotkeys(cli, all_hotkeys) - - -@pytest.fixture -def neuron_info(): - return [ - (1, [NeuronInfoLiteFactory(netuid=1)], None), - (2, [NeuronInfoLiteFactory(netuid=2)], None), - ] - - -@pytest.fixture -def neurons_dict(): - return { - "1": [NeuronInfoLiteFactory(netuid=1)], - "2": [NeuronInfoLiteFactory(netuid=2)], - } - - -@pytest.fixture -def netuids_list(): - return [1, 2] - - -# Test cases -@pytest.mark.parametrize( - "test_id, results, expected_neurons, expected_netuids", - [ - # Test ID: 01 - Happy path, all neurons processed correctly - ( - "01", - [ - (1, [NeuronInfoLiteFactory(netuid=1)], None), - (2, [NeuronInfoLiteFactory(netuid=2)], None), - ], - { - "1": [NeuronInfoLiteFactory(netuid=1)], - "2": [NeuronInfoLiteFactory(netuid=2)], - }, - [1, 2], - ), - # Test ID: 02 - Error message present, should skip processing for that netuid - ( - "02", - [ - (1, [NeuronInfoLiteFactory(netuid=1)], None), - (2, [], "Error fetching data"), - ], - {"1": [NeuronInfoLiteFactory()]}, - [1], - ), - # Test ID: 03 - No neurons found for a netuid, should remove the netuid - ( - "03", - [(1, [NeuronInfoLiteFactory()], None), (2, [], None)], - {"1": [NeuronInfoLiteFactory()]}, - [1], - ), - # Test ID: 04 - Mixed conditions - ( - "04", - [ - (1, [NeuronInfoLiteFactory(netuid=1)], None), - (2, [], None), - ], - {"1": [NeuronInfoLiteFactory()]}, - [1], - ), - ], -) -def test_process_neuron_results( - test_id, results, expected_neurons, expected_netuids, neurons_dict, netuids_list -): - # Act - actual_neurons = OverviewCommand._process_neuron_results( - results, neurons_dict, netuids_list - ) - - # Assert - assert actual_neurons.keys() == expected_neurons.keys(), f"Failed test {test_id}" - assert netuids_list == expected_netuids, f"Failed test {test_id}" diff --git a/tests/unit_tests/test_subtensor.py b/tests/unit_tests/test_subtensor.py index 731285c225..08a919ff74 100644 --- a/tests/unit_tests/test_subtensor.py +++ b/tests/unit_tests/test_subtensor.py @@ -1,38 +1,33 @@ # The MIT License (MIT) -# Copyright © 2022 Opentensor Foundation - +# 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. -# Standard Lib import argparse import unittest.mock as mock +from typing import List, Tuple from unittest.mock import MagicMock -# 3rd Party import pytest +from bittensor_wallet import Wallet -# Application -import bittensor -from bittensor.subtensor import ( - Subtensor, - _logger, - Balance, -) -from bittensor.chain_data import SubnetHyperparameters -from bittensor.commands.utils import normalize_hyperparameters -from bittensor import subtensor_module +from bittensor.core import subtensor as subtensor_module, settings +from bittensor.core.axon import Axon +from bittensor.core.chain_data import SubnetHyperparameters +from bittensor.core.subtensor import Subtensor, logging +from bittensor.utils import U16_NORMALIZED_FLOAT, U64_NORMALIZED_FLOAT from bittensor.utils.balance import Balance U16_MAX = 65535 @@ -45,11 +40,11 @@ def test_serve_axon_with_external_ip_set(): mock_serve_axon = MagicMock(return_value=True) - mock_subtensor = MagicMock(spec=bittensor.subtensor, serve_axon=mock_serve_axon) + mock_subtensor = MagicMock(spec=Subtensor, serve_axon=mock_serve_axon) mock_add_insecure_port = mock.MagicMock(return_value=None) mock_wallet = MagicMock( - spec=bittensor.wallet, + spec=Wallet, coldkey=MagicMock(), coldkeypub=MagicMock( # mock ss58 address @@ -60,8 +55,8 @@ def test_serve_axon_with_external_ip_set(): ), ) - mock_config = bittensor.axon.config() - mock_axon_with_external_ip_set = bittensor.axon( + mock_config = Axon.config() + mock_axon_with_external_ip_set = Axon( wallet=mock_wallet, ip=internal_ip, external_ip=external_ip, @@ -92,13 +87,13 @@ def test_serve_axon_with_external_port_set(): mock_serve_axon = MagicMock(return_value=True) mock_subtensor = MagicMock( - spec=bittensor.subtensor, + spec=Subtensor, serve=mock_serve, serve_axon=mock_serve_axon, ) mock_wallet = MagicMock( - spec=bittensor.wallet, + spec=Wallet, coldkey=MagicMock(), coldkeypub=MagicMock( # mock ss58 address @@ -109,9 +104,9 @@ def test_serve_axon_with_external_port_set(): ), ) - mock_config = bittensor.axon.config() + mock_config = Axon.config() - mock_axon_with_external_port_set = bittensor.axon( + mock_axon_with_external_port_set = Axon( wallet=mock_wallet, port=internal_port, external_port=external_port, @@ -141,10 +136,10 @@ class ExitEarly(Exception): def test_stake_multiple(): - mock_amount: bittensor.Balance = bittensor.Balance.from_tao(1.0) + mock_amount: Balance = Balance.from_tao(1.0) mock_wallet = MagicMock( - spec=bittensor.wallet, + spec=Wallet, coldkey=MagicMock(), coldkeypub=MagicMock( # mock ss58 address @@ -166,17 +161,17 @@ def test_stake_multiple(): mock_do_stake = MagicMock(side_effect=ExitEarly) mock_subtensor = MagicMock( - spec=bittensor.subtensor, + spec=Subtensor, network="mock_net", get_balance=MagicMock( - return_value=bittensor.Balance.from_tao(mock_amount.tao + 20.0) + return_value=Balance.from_tao(mock_amount.tao + 20.0) ), # enough balance to stake get_neuron_for_pubkey_and_subnet=MagicMock(return_value=mock_neuron), _do_stake=mock_do_stake, ) with pytest.raises(ExitEarly): - bittensor.subtensor.add_stake_multiple( + Subtensor.add_stake_multiple( mock_subtensor, wallet=mock_wallet, hotkey_ss58s=mock_hotkey_ss58s, @@ -230,40 +225,40 @@ def mock_add_argument(*args, **kwargs): "network, expected_network, expected_endpoint", [ # Happy path tests - ("finney", "finney", bittensor.__finney_entrypoint__), - ("local", "local", bittensor.__local_entrypoint__), - ("test", "test", bittensor.__finney_test_entrypoint__), - ("archive", "archive", bittensor.__archive_entrypoint__), + ("finney", "finney", settings.finney_entrypoint), + ("local", "local", settings.local_entrypoint), + ("test", "test", settings.finney_test_entrypoint), + ("archive", "archive", settings.archive_entrypoint), # Endpoint override tests ( - bittensor.__finney_entrypoint__, + settings.finney_entrypoint, "finney", - bittensor.__finney_entrypoint__, + settings.finney_entrypoint, ), ( "entrypoint-finney.opentensor.ai", "finney", - bittensor.__finney_entrypoint__, + settings.finney_entrypoint, ), ( - bittensor.__finney_test_entrypoint__, + settings.finney_test_entrypoint, "test", - bittensor.__finney_test_entrypoint__, + settings.finney_test_entrypoint, ), ( "test.finney.opentensor.ai", "test", - bittensor.__finney_test_entrypoint__, + settings.finney_test_entrypoint, ), ( - bittensor.__archive_entrypoint__, + settings.archive_entrypoint, "archive", - bittensor.__archive_entrypoint__, + settings.archive_entrypoint, ), ( "archive.chain.opentensor.ai", "archive", - bittensor.__archive_entrypoint__, + settings.archive_entrypoint, ), ("127.0.0.1", "local", "127.0.0.1"), ("localhost", "local", "localhost"), @@ -296,39 +291,15 @@ class MockSubstrate: @pytest.fixture def subtensor(substrate): - mock.patch.object( - subtensor_module, - "get_subtensor_errors", - return_value={ - "1": ("ErrorOne", "Description one"), - "2": ("ErrorTwo", "Description two"), - }, - ).start() return Subtensor() -def test_get_error_info_by_index_known_error(subtensor): - name, description = subtensor.get_error_info_by_index(1) - assert name == "ErrorOne" - assert description == "Description one" - - @pytest.fixture def mock_logger(): - with mock.patch.object(_logger, "warning") as mock_warning: + with mock.patch.object(logging, "warning") as mock_warning: yield mock_warning -def test_get_error_info_by_index_unknown_error(subtensor, mock_logger): - fake_index = 999 - name, description = subtensor.get_error_info_by_index(fake_index) - assert name == "Unknown Error" - assert description == "" - mock_logger.assert_called_once_with( - f"Subtensor returned an error with an unknown index: {fake_index}" - ) - - # Subtensor()._get_hyperparameter tests def test_hyperparameter_subnet_does_not_exist(subtensor, mocker): """Tests when the subnet does not exist.""" @@ -491,6 +462,52 @@ def sample_hyperparameters(): return MagicMock(spec=SubnetHyperparameters) +def normalize_hyperparameters( + subnet: "SubnetHyperparameters", +) -> List[Tuple[str, str, str]]: + """ + Normalizes the hyperparameters of a subnet. + + Args: + subnet: The subnet hyperparameters object. + + Returns: + A list of tuples containing the parameter name, value, and normalized value. + """ + param_mappings = { + "adjustment_alpha": U64_NORMALIZED_FLOAT, + "min_difficulty": U64_NORMALIZED_FLOAT, + "max_difficulty": U64_NORMALIZED_FLOAT, + "difficulty": U64_NORMALIZED_FLOAT, + "bonds_moving_avg": U64_NORMALIZED_FLOAT, + "max_weight_limit": U16_NORMALIZED_FLOAT, + "kappa": U16_NORMALIZED_FLOAT, + "alpha_high": U16_NORMALIZED_FLOAT, + "alpha_low": U16_NORMALIZED_FLOAT, + "min_burn": Balance.from_rao, + "max_burn": Balance.from_rao, + } + + normalized_values: List[Tuple[str, str, str]] = [] + subnet_dict = subnet.__dict__ + + for param, value in subnet_dict.items(): + try: + if param in param_mappings: + norm_value = param_mappings[param](value) + if isinstance(norm_value, float): + norm_value = f"{norm_value:.{10}g}" + else: + norm_value = value + except Exception as e: + logging.warning(f"Error normalizing parameter '{param}': {e}") + norm_value = "-" + + normalized_values.append((param, str(value), str(norm_value))) + + return normalized_values + + def get_normalized_value(normalized_data, param_name): return next( ( @@ -534,7 +551,7 @@ def test_hyperparameter_normalization( # Mid-value test if is_balance: - numeric_value = float(str(norm_value).lstrip(bittensor.__tao_symbol__)) + numeric_value = float(str(norm_value).lstrip(settings.tao_symbol)) expected_tao = mid_value / 1e9 assert ( numeric_value == expected_tao @@ -548,7 +565,7 @@ def test_hyperparameter_normalization( norm_value = get_normalized_value(normalized, param_name) if is_balance: - numeric_value = float(str(norm_value).lstrip(bittensor.__tao_symbol__)) + numeric_value = float(str(norm_value).lstrip(settings.tao_symbol)) expected_tao = max_value / 1e9 assert ( numeric_value == expected_tao @@ -562,7 +579,7 @@ def test_hyperparameter_normalization( norm_value = get_normalized_value(normalized, param_name) if is_balance: - numeric_value = float(str(norm_value).lstrip(bittensor.__tao_symbol__)) + numeric_value = float(str(norm_value).lstrip(settings.tao_symbol)) expected_tao = zero_value / 1e9 assert ( numeric_value == expected_tao diff --git a/tests/unit_tests/test_synapse.py b/tests/unit_tests/test_synapse.py index b0ce4f1325..80c127c587 100644 --- a/tests/unit_tests/test_synapse.py +++ b/tests/unit_tests/test_synapse.py @@ -1,28 +1,31 @@ # The MIT License (MIT) -# Copyright © 2022 Opentensor Foundation - +# 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 json + import base64 -import pytest -import bittensor +import json from typing import Optional, ClassVar +import pytest + +from bittensor.core.synapse import Synapse + def test_parse_headers_to_inputs(): - class Test(bittensor.Synapse): + class Test(Synapse): key1: list[int] # Define a mock headers dictionary to use for testing @@ -57,7 +60,7 @@ class Test(bittensor.Synapse): def test_from_headers(): - class Test(bittensor.Synapse): + class Test(Synapse): key1: list[int] # Define a mock headers dictionary to use for testing @@ -93,10 +96,10 @@ class Test(bittensor.Synapse): def test_synapse_create(): # Create an instance of Synapse - synapse = bittensor.Synapse() + synapse = Synapse() # Ensure the instance created is of type Synapse - assert isinstance(synapse, bittensor.Synapse) + assert isinstance(synapse, Synapse) # Check default properties of a newly created Synapse assert synapse.name == "Synapse" @@ -125,7 +128,7 @@ def test_synapse_create(): def test_custom_synapse(): # Define a custom Synapse subclass - class Test(bittensor.Synapse): + class Test(Synapse): a: int # Carried through because required. b: int = None # Not carried through headers c: Optional[int] # Required, carried through headers, cannot be None @@ -177,7 +180,7 @@ class Test(bittensor.Synapse): def test_body_hash_override(): # Create a Synapse instance - synapse_instance = bittensor.Synapse() + synapse_instance = Synapse() # Try to set the body_hash property and expect an AttributeError with pytest.raises( @@ -188,7 +191,7 @@ def test_body_hash_override(): def test_default_instance_fields_dict_consistency(): - synapse_instance = bittensor.Synapse() + synapse_instance = Synapse() assert synapse_instance.model_dump() == { "name": "Synapse", "timeout": 12.0, @@ -222,7 +225,7 @@ def test_default_instance_fields_dict_consistency(): } -class LegacyHashedSynapse(bittensor.Synapse): +class LegacyHashedSynapse(Synapse): """Legacy Synapse subclass that serialized `required_hash_fields`.""" a: int @@ -232,7 +235,7 @@ class LegacyHashedSynapse(bittensor.Synapse): required_hash_fields: Optional[list[str]] = ["b", "a", "d"] -class HashedSynapse(bittensor.Synapse): +class HashedSynapse(Synapse): a: int b: int c: Optional[int] = None diff --git a/tests/unit_tests/test_tensor.py b/tests/unit_tests/test_tensor.py index 9939b397e7..8c189c6af3 100644 --- a/tests/unit_tests/test_tensor.py +++ b/tests/unit_tests/test_tensor.py @@ -1,25 +1,27 @@ # The MIT License (MIT) -# Copyright © 2022 Opentensor Foundation - +# 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 pytest -import numpy as np -import bittensor + import numpy +import numpy as np +import pytest import torch +from bittensor.core.tensor import tensor as tensor_class, Tensor + # This is a fixture that creates an example tensor for testing @pytest.fixture @@ -28,16 +30,16 @@ def example_tensor(): data = np.array([1, 2, 3, 4]) # Serialize the tensor into a Tensor instance and return it - return bittensor.tensor(data) + return tensor_class(data) @pytest.fixture -def example_tensor_torch(force_legacy_torch_compat_api): +def example_tensor_torch(force_legacy_torch_compatible_api): # Create a tensor from a list using PyTorch data = torch.tensor([1, 2, 3, 4]) # Serialize the tensor into a Tensor instance and return it - return bittensor.tensor(data) + return tensor_class(data) def test_deserialize(example_tensor): @@ -49,7 +51,7 @@ def test_deserialize(example_tensor): assert tensor.tolist() == [1, 2, 3, 4] -def test_deserialize_torch(example_tensor_torch, force_legacy_torch_compat_api): +def test_deserialize_torch(example_tensor_torch, force_legacy_torch_compatible_api): tensor = example_tensor_torch.deserialize() # Check that the result is a PyTorch tensor with the correct values assert isinstance(tensor, torch.Tensor) @@ -58,7 +60,7 @@ def test_deserialize_torch(example_tensor_torch, force_legacy_torch_compat_api): def test_serialize(example_tensor): # Check that the serialized tensor is an instance of Tensor - assert isinstance(example_tensor, bittensor.Tensor) + assert isinstance(example_tensor, Tensor) # Check that the Tensor instance has the correct buffer, dtype, and shape assert example_tensor.buffer == example_tensor.buffer @@ -87,9 +89,9 @@ def test_serialize(example_tensor): assert example_tensor.shape == example_tensor.shape -def test_serialize_torch(example_tensor_torch, force_legacy_torch_compat_api): +def test_serialize_torch(example_tensor_torch, force_legacy_torch_compatible_api): # Check that the serialized tensor is an instance of Tensor - assert isinstance(example_tensor_torch, bittensor.Tensor) + assert isinstance(example_tensor_torch, Tensor) # Check that the Tensor instance has the correct buffer, dtype, and shape assert example_tensor_torch.buffer == example_tensor_torch.buffer @@ -120,7 +122,7 @@ def test_serialize_torch(example_tensor_torch, force_legacy_torch_compat_api): def test_buffer_field(): # Create a Tensor instance with a specified buffer, dtype, and shape - tensor = bittensor.Tensor( + tensor = Tensor( buffer="0x321e13edqwds231231231232131", dtype="float32", shape=[3, 3] ) @@ -128,9 +130,9 @@ def test_buffer_field(): assert tensor.buffer == "0x321e13edqwds231231231232131" -def test_buffer_field_torch(force_legacy_torch_compat_api): +def test_buffer_field_torch(force_legacy_torch_compatible_api): # Create a Tensor instance with a specified buffer, dtype, and shape - tensor = bittensor.Tensor( + tensor = Tensor( buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3] ) @@ -140,7 +142,7 @@ def test_buffer_field_torch(force_legacy_torch_compat_api): def test_dtype_field(): # Create a Tensor instance with a specified buffer, dtype, and shape - tensor = bittensor.Tensor( + tensor = Tensor( buffer="0x321e13edqwds231231231232131", dtype="float32", shape=[3, 3] ) @@ -148,8 +150,8 @@ def test_dtype_field(): assert tensor.dtype == "float32" -def test_dtype_field_torch(force_legacy_torch_compat_api): - tensor = bittensor.Tensor( +def test_dtype_field_torch(force_legacy_torch_compatible_api): + tensor = Tensor( buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3] ) assert tensor.dtype == "torch.float32" @@ -157,7 +159,7 @@ def test_dtype_field_torch(force_legacy_torch_compat_api): def test_shape_field(): # Create a Tensor instance with a specified buffer, dtype, and shape - tensor = bittensor.Tensor( + tensor = Tensor( buffer="0x321e13edqwds231231231232131", dtype="float32", shape=[3, 3] ) @@ -165,79 +167,79 @@ def test_shape_field(): assert tensor.shape == [3, 3] -def test_shape_field_torch(force_legacy_torch_compat_api): - tensor = bittensor.Tensor( +def test_shape_field_torch(force_legacy_torch_compatible_api): + tensor = Tensor( buffer="0x321e13edqwds231231231232131", dtype="torch.float32", shape=[3, 3] ) assert tensor.shape == [3, 3] def test_serialize_all_types(): - bittensor.tensor(np.array([1], dtype=np.float16)) - bittensor.tensor(np.array([1], dtype=np.float32)) - bittensor.tensor(np.array([1], dtype=np.float64)) - bittensor.tensor(np.array([1], dtype=np.uint8)) - bittensor.tensor(np.array([1], dtype=np.int32)) - bittensor.tensor(np.array([1], dtype=np.int64)) - bittensor.tensor(np.array([1], dtype=bool)) + tensor_class(np.array([1], dtype=np.float16)) + tensor_class(np.array([1], dtype=np.float32)) + tensor_class(np.array([1], dtype=np.float64)) + tensor_class(np.array([1], dtype=np.uint8)) + tensor_class(np.array([1], dtype=np.int32)) + tensor_class(np.array([1], dtype=np.int64)) + tensor_class(np.array([1], dtype=bool)) -def test_serialize_all_types_torch(force_legacy_torch_compat_api): - bittensor.tensor(torch.tensor([1], dtype=torch.float16)) - bittensor.tensor(torch.tensor([1], dtype=torch.float32)) - bittensor.tensor(torch.tensor([1], dtype=torch.float64)) - bittensor.tensor(torch.tensor([1], dtype=torch.uint8)) - bittensor.tensor(torch.tensor([1], dtype=torch.int32)) - bittensor.tensor(torch.tensor([1], dtype=torch.int64)) - bittensor.tensor(torch.tensor([1], dtype=torch.bool)) +def test_serialize_all_types_torch(force_legacy_torch_compatible_api): + tensor_class(torch.tensor([1], dtype=torch.float16)) + tensor_class(torch.tensor([1], dtype=torch.float32)) + tensor_class(torch.tensor([1], dtype=torch.float64)) + tensor_class(torch.tensor([1], dtype=torch.uint8)) + tensor_class(torch.tensor([1], dtype=torch.int32)) + tensor_class(torch.tensor([1], dtype=torch.int64)) + tensor_class(torch.tensor([1], dtype=torch.bool)) def test_serialize_all_types_equality(): rng = np.random.default_rng() tensor = rng.standard_normal((100,), dtype=np.float32) - assert np.all(bittensor.tensor(tensor).tensor() == tensor) + assert np.all(tensor_class(tensor).tensor() == tensor) tensor = rng.standard_normal((100,), dtype=np.float64) - assert np.all(bittensor.tensor(tensor).tensor() == tensor) + assert np.all(tensor_class(tensor).tensor() == tensor) tensor = np.random.randint(255, 256, (1000,), dtype=np.uint8) - assert np.all(bittensor.tensor(tensor).tensor() == tensor) + assert np.all(tensor_class(tensor).tensor() == tensor) tensor = np.random.randint(2_147_483_646, 2_147_483_647, (1000,), dtype=np.int32) - assert np.all(bittensor.tensor(tensor).tensor() == tensor) + assert np.all(tensor_class(tensor).tensor() == tensor) tensor = np.random.randint( 9_223_372_036_854_775_806, 9_223_372_036_854_775_807, (1000,), dtype=np.int64 ) - assert np.all(bittensor.tensor(tensor).tensor() == tensor) + assert np.all(tensor_class(tensor).tensor() == tensor) tensor = rng.standard_normal((100,), dtype=np.float32) < 0.5 - assert np.all(bittensor.tensor(tensor).tensor() == tensor) + assert np.all(tensor_class(tensor).tensor() == tensor) -def test_serialize_all_types_equality_torch(force_legacy_torch_compat_api): +def test_serialize_all_types_equality_torch(force_legacy_torch_compatible_api): torchtensor = torch.randn([100], dtype=torch.float16) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + assert torch.all(tensor_class(torchtensor).tensor() == torchtensor) torchtensor = torch.randn([100], dtype=torch.float32) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + assert torch.all(tensor_class(torchtensor).tensor() == torchtensor) torchtensor = torch.randn([100], dtype=torch.float64) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + assert torch.all(tensor_class(torchtensor).tensor() == torchtensor) torchtensor = torch.randint(255, 256, (1000,), dtype=torch.uint8) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + assert torch.all(tensor_class(torchtensor).tensor() == torchtensor) torchtensor = torch.randint( 2_147_483_646, 2_147_483_647, (1000,), dtype=torch.int32 ) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + assert torch.all(tensor_class(torchtensor).tensor() == torchtensor) torchtensor = torch.randint( 9_223_372_036_854_775_806, 9_223_372_036_854_775_807, (1000,), dtype=torch.int64 ) - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + assert torch.all(tensor_class(torchtensor).tensor() == torchtensor) torchtensor = torch.randn([100], dtype=torch.float32) < 0.5 - assert torch.all(bittensor.tensor(torchtensor).tensor() == torchtensor) + assert torch.all(tensor_class(torchtensor).tensor() == torchtensor) diff --git a/tests/unit_tests/utils/test_balance.py b/tests/unit_tests/utils/test_balance.py index b99bc111f2..bf9ac10bb3 100644 --- a/tests/unit_tests/utils/test_balance.py +++ b/tests/unit_tests/utils/test_balance.py @@ -3,7 +3,7 @@ from hypothesis import strategies as st from typing import Union -from bittensor import Balance +from bittensor.utils.balance import Balance from tests.helpers import CLOSE_IN_VALUE """ diff --git a/tests/unit_tests/utils/test_registration.py b/tests/unit_tests/utils/test_registration.py index d0c4fc743b..c85608b5f3 100644 --- a/tests/unit_tests/utils/test_registration.py +++ b/tests/unit_tests/utils/test_registration.py @@ -1,3 +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. + import pytest from bittensor.utils.registration import LazyLoadedTorch @@ -14,7 +31,7 @@ def error(self, message): @pytest.fixture def mock_bittensor_logging(monkeypatch): mock_logger = MockBittensorLogging() - monkeypatch.setattr("bittensor.logging", mock_logger) + monkeypatch.setattr("bittensor.utils.registration.logging", mock_logger) return mock_logger diff --git a/tests/unit_tests/utils/test_subtensor.py b/tests/unit_tests/utils/test_subtensor.py deleted file mode 100644 index 1c1220bcea..0000000000 --- a/tests/unit_tests/utils/test_subtensor.py +++ /dev/null @@ -1,99 +0,0 @@ -# The MIT License (MIT) -# Copyright © 2022 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 json - -import pytest - -import bittensor.utils.subtensor as st_utils - - -class MockPallet: - def __init__(self, errors): - self.errors = errors - - -@pytest.fixture -def pallet_with_errors(): - """Provide a mock pallet with sample errors.""" - return MockPallet( - [ - {"index": 1, "name": "ErrorOne", "docs": ["Description one."]}, - { - "index": 2, - "name": "ErrorTwo", - "docs": ["Description two.", "Continued."], - }, - ] - ) - - -@pytest.fixture -def empty_pallet(): - """Provide a mock pallet with no errors.""" - return MockPallet([]) - - -def test_get_errors_from_pallet_with_errors(pallet_with_errors): - """Ensure errors are correctly parsed from pallet.""" - expected = { - "1": {"name": "ErrorOne", "description": "Description one."}, - "2": {"name": "ErrorTwo", "description": "Description two. Continued."}, - } - assert st_utils._get_errors_from_pallet(pallet_with_errors) == expected - - -def test_get_errors_from_pallet_empty(empty_pallet): - """Test behavior with an empty list of errors.""" - assert st_utils._get_errors_from_pallet(empty_pallet) is None - - -def test_save_errors_to_cache(tmp_path): - """Ensure that errors are correctly saved to a file.""" - test_file = tmp_path / "subtensor_errors_map.json" - errors = {"1": {"name": "ErrorOne", "description": "Description one."}} - st_utils._ERRORS_FILE_PATH = test_file - st_utils._save_errors_to_cache("0x123", errors) - - with open(test_file, "r") as file: - data = json.load(file) - assert data["subtensor_build_id"] == "0x123" - assert data["errors"] == errors - - -def test_get_errors_from_cache(tmp_path): - """Test retrieval of errors from cache.""" - test_file = tmp_path / "subtensor_errors_map.json" - errors = {"1": {"name": "ErrorOne", "description": "Description one."}} - - st_utils._ERRORS_FILE_PATH = test_file - with open(test_file, "w") as file: - json.dump({"subtensor_build_id": "0x123", "errors": errors}, file) - assert st_utils._get_errors_from_cache() == { - "subtensor_build_id": "0x123", - "errors": errors, - } - - -def test_get_errors_no_cache(mocker, empty_pallet): - """Test get_errors function when no cache is available.""" - mocker.patch("bittensor.utils.subtensor._get_errors_from_cache", return_value=None) - mocker.patch("bittensor.utils.subtensor.SubstrateInterface") - substrate_mock = mocker.MagicMock() - substrate_mock.metadata.get_metadata_pallet.return_value = empty_pallet - substrate_mock.metadata[0].value = "0x123" - assert st_utils.get_subtensor_errors(substrate_mock) == {} diff --git a/tests/unit_tests/utils/test_version.py b/tests/unit_tests/utils/test_version.py index f9760933f3..fa96bddad3 100644 --- a/tests/unit_tests/utils/test_version.py +++ b/tests/unit_tests/utils/test_version.py @@ -22,13 +22,16 @@ from freezegun import freeze_time from datetime import datetime, timedelta, timezone -from bittensor.utils.version import ( - VERSION_CHECK_THRESHOLD, - VersionCheckError, - get_and_save_latest_version, - check_version, - version_checking, -) +# from bittensor.utils.version import ( +# VERSION_CHECK_THRESHOLD, +# VersionCheckError, +# get_and_save_latest_version, +# check_version, +# version_checking, +# __version__ +# ) +from bittensor.utils import version + from unittest.mock import MagicMock from pytest_mock import MockerFixture @@ -62,14 +65,14 @@ def test_get_and_save_latest_version_no_file( ): assert not version_file_path.exists() - assert get_and_save_latest_version() == pypi_version + assert version.get_and_save_latest_version() == pypi_version mock_get_version_from_pypi.assert_called_once() assert version_file_path.exists() assert version_file_path.read_text() == pypi_version -@pytest.mark.parametrize("elapsed", [0, VERSION_CHECK_THRESHOLD - 5]) +@pytest.mark.parametrize("elapsed", [0, version.VERSION_CHECK_THRESHOLD - 5]) def test_get_and_save_latest_version_file_fresh_check( mock_get_version_from_pypi: MagicMock, version_file_path: Path, elapsed: int ): @@ -78,7 +81,7 @@ def test_get_and_save_latest_version_file_fresh_check( version_file_path.write_text("6.9.5") with freeze_time(now + timedelta(seconds=elapsed)): - assert get_and_save_latest_version() == "6.9.5" + assert version.get_and_save_latest_version() == "6.9.5" mock_get_version_from_pypi.assert_not_called() @@ -90,8 +93,8 @@ def test_get_and_save_latest_version_file_expired_check( version_file_path.write_text("6.9.5") - with freeze_time(now + timedelta(seconds=VERSION_CHECK_THRESHOLD + 1)): - assert get_and_save_latest_version() == pypi_version + with freeze_time(now + timedelta(seconds=version.VERSION_CHECK_THRESHOLD + 1)): + assert version.get_and_save_latest_version() == pypi_version mock_get_version_from_pypi.assert_called_once() assert version_file_path.read_text() == pypi_version @@ -111,13 +114,13 @@ def test_get_and_save_latest_version_file_expired_check( def test_check_version_newer_available( mocker: MockerFixture, current_version: str, latest_version: str, capsys ): - mocker.patch("bittensor.utils.version.bittensor.__version__", current_version) + version.__version__ = current_version mocker.patch( "bittensor.utils.version.get_and_save_latest_version", return_value=latest_version, ) - check_version() + version.check_version() captured = capsys.readouterr() @@ -137,13 +140,13 @@ def test_check_version_newer_available( def test_check_version_up_to_date( mocker: MockerFixture, current_version: str, latest_version: str, capsys ): - mocker.patch("bittensor.utils.version.bittensor.__version__", current_version) + version.__version__ = current_version mocker.patch( "bittensor.utils.version.get_and_save_latest_version", return_value=latest_version, ) - check_version() + version.check_version() captured = capsys.readouterr() @@ -153,16 +156,16 @@ def test_check_version_up_to_date( def test_version_checking(mocker: MockerFixture): mock = mocker.patch("bittensor.utils.version.check_version") - version_checking() + version.version_checking() mock.assert_called_once() def test_version_checking_exception(mocker: MockerFixture): mock = mocker.patch( - "bittensor.utils.version.check_version", side_effect=VersionCheckError + "bittensor.utils.version.check_version", side_effect=version.VersionCheckError ) - version_checking() + version.version_checking() mock.assert_called_once() diff --git a/tests/unit_tests/utils/test_weight_utils.py b/tests/unit_tests/utils/test_weight_utils.py index 66f3c8127a..fccf2276ba 100644 --- a/tests/unit_tests/utils/test_weight_utils.py +++ b/tests/unit_tests/utils/test_weight_utils.py @@ -57,7 +57,7 @@ def test_convert_weight_and_uids(): weight_utils.convert_weights_and_uids_for_emit(uids, weights) -def test_convert_weight_and_uids_torch(force_legacy_torch_compat_api): +def test_convert_weight_and_uids_torch(force_legacy_torch_compatible_api): uids = torch.tensor(list(range(10))) weights = torch.rand(10) weight_utils.convert_weights_and_uids_for_emit(uids, weights) @@ -144,7 +144,7 @@ def test_normalize_with_max_weight(): def test_normalize_with_max_weight__legacy_torch_api_compat( - force_legacy_torch_compat_api, + force_legacy_torch_compatible_api, ): weights = torch.rand(1000) wn = weight_utils.normalize_max_weight(weights, limit=0.01) @@ -240,7 +240,7 @@ def test_convert_weight_uids_and_vals_to_tensor_happy_path( ], ) def test_convert_weight_uids_and_vals_to_tensor_happy_path_torch( - test_id, n, uids, weights, subnets, expected, force_legacy_torch_compat_api + test_id, n, uids, weights, subnets, expected, force_legacy_torch_compatible_api ): # Act result = weight_utils.convert_weight_uids_and_vals_to_tensor(n, uids, weights) @@ -338,7 +338,7 @@ def test_convert_root_weight_uids_and_vals_to_tensor_happy_paths( ], ) def test_convert_root_weight_uids_and_vals_to_tensor_edge_cases( - test_id, n, uids, weights, subnets, expected, force_legacy_torch_compat_api + test_id, n, uids, weights, subnets, expected, force_legacy_torch_compatible_api ): # Act result = weight_utils.convert_root_weight_uids_and_vals_to_tensor( @@ -468,7 +468,7 @@ def test_happy_path(test_id, n, uids, bonds, expected_output): ], ) def test_happy_path_torch( - test_id, n, uids, bonds, expected_output, force_legacy_torch_compat_api + test_id, n, uids, bonds, expected_output, force_legacy_torch_compatible_api ): # Act result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds) @@ -512,7 +512,7 @@ def test_edge_cases(test_id, n, uids, bonds, expected_output): ], ) def test_edge_cases_torch( - test_id, n, uids, bonds, expected_output, force_legacy_torch_compat_api + test_id, n, uids, bonds, expected_output, force_legacy_torch_compatible_api ): # Act result = weight_utils.convert_bond_uids_and_vals_to_tensor(n, uids, bonds)