diff --git a/bittensor/__init__.py b/bittensor/__init__.py index d1a06b1e64..18397c7032 100644 --- a/bittensor/__init__.py +++ b/bittensor/__init__.py @@ -78,7 +78,7 @@ def turn_console_off(): # Needs to use wss:// __bellagene_entrypoint__ = "wss://parachain.opentensor.ai:443" -__local_entrypoint__ = "ws://127.0.0.1:9945" +__local_entrypoint__ = "ws://127.0.0.1:9944" __tao_symbol__: str = chr(0x03C4) @@ -87,11 +87,10 @@ def turn_console_off(): # Block Explorers map network to explorer url ## Must all be polkadotjs explorer urls __network_explorer_map__ = { - 'local': "https://explorer.nakamoto.opentensor.ai/#/explorer", + 'local': "https://explorer.finney.opentensor.ai/#/explorer", 'nakamoto': "https://explorer.nakamoto.opentensor.ai/#/explorer", - 'endpoint': "https://explorer.nakamoto.opentensor.ai/#/", - 'nobunaga': "https://staging.opentensor.ai/#/explorer", - 'finney': "https://explorer.opentensor.ai/" + 'endpoint': "https://explorer.finney.opentensor.ai/#/explorer", + 'finney': "https://explorer.finney.opentensor.ai/#/explorer" } # Avoid collisions with other processes @@ -101,9 +100,12 @@ def turn_console_off(): __mock_chain_db__ = './tmp/mock_chain_db' -# Delegate Profiles -__delegate_profiles_url__: str = 'https://raw.githubusercontent.com/opentensor/delegate_profiles/master/DELEGATES.md' - +# --- Type Registry --- +__type_registry__ = { + 'types': { + 'Balance': 'u64', # Need to override default u128 + }, +} # --- Prometheus --- __prometheus_version__ = "0.1.0" @@ -207,4 +209,4 @@ def turn_console_off(): wandb.add_defaults( defaults ) logging.add_defaults( defaults ) -from substrateinterface import Keypair as Keypair +from substrateinterface import Keypair as Keypair diff --git a/bittensor/_axon/__init__.py b/bittensor/_axon/__init__.py index 068e31ca08..df8fab339a 100644 --- a/bittensor/_axon/__init__.py +++ b/bittensor/_axon/__init__.py @@ -318,7 +318,7 @@ def check_config(cls, config: 'bittensor.Config' ): bittensor.wallet.check_config( config ) @classmethod - def default_synapse_check(cls, synapse, hotkey ): + def default_synapse_check(cls, synapse, hotkey, inputs ): """ default synapse check function """ if len(hotkey) == bittensor.__ss58_address_length__: @@ -412,8 +412,12 @@ def parse_signature( ) -> Tuple[int, str, str, str, int]: r"""Attempts to parse a signature from the metadata""" signature = metadata.get("bittensor-signature") + version = metadata.get('bittensor-version') if signature is None: raise Exception("Request signature missing") + if int(version) < 370: + raise Exception("Incorrect Version") + for parser in [self.parse_signature_v2, self.parse_legacy_signature]: parts = parser(signature) if parts is not None: diff --git a/bittensor/_cli/commands/__init__.py b/bittensor/_cli/commands/__init__.py index 208c230426..770230692c 100644 --- a/bittensor/_cli/commands/__init__.py +++ b/bittensor/_cli/commands/__init__.py @@ -11,4 +11,4 @@ from .list import ListCommand from .weights import SetWeightsCommand, WeightsCommand from .query import QueryCommand -from .misc import HelpCommand, UpdateCommand, ListSubnetsCommand \ No newline at end of file +from .misc import HelpCommand, UpdateCommand, ListSubnetsCommand diff --git a/bittensor/_cli/commands/delegates.py b/bittensor/_cli/commands/delegates.py index 715919b440..e1e7f377db 100644 --- a/bittensor/_cli/commands/delegates.py +++ b/bittensor/_cli/commands/delegates.py @@ -16,6 +16,7 @@ # DEALINGS IN THE SOFTWARE. import sys +import os import json import argparse import bittensor @@ -24,26 +25,55 @@ from rich.prompt import Prompt from rich.prompt import Confirm from rich.console import Text +from tqdm import tqdm + +import os +import bittensor +from typing import List + +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__ # Uses rich console to pretty print a table of delegates. -def show_delegates( delegates: List['bittensor.DelegateInfo'], width: Optional[int] = None): +def show_delegates( delegates: List['bittensor.DelegateInfo'], prev_delegates: List['bittensor.DelegateInfo'], width: Optional[int] = None): """ Pretty prints a table of delegates sorted by total stake. """ delegates.sort(key=lambda delegate: delegate.total_stake, reverse=True) - registered_delegate_info = json.load( open("delegates.json") ) + prev_delegates_dict = {} + for prev_delegate in prev_delegates: + prev_delegates_dict[prev_delegate.hotkey_ss58] = prev_delegate + try: + package_dir = os.path.dirname(bittensor.__file__) + root_dir = os.path.dirname(package_dir) + filename = os.path.join(root_dir, 'delegates.json') + if os.path.exists(filename): + registered_delegate_info = json.load( open(filename, 'r') ) + else: + registered_delegate_info = {} + except: + 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]OWNER", style='rgb(50,163,219)', no_wrap=True, justify='left') + 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]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]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]SUBNETS", justify='right', style='white', no_wrap=True) table.add_column("[overline white]VPERMIT", justify='right', no_wrap=True) #table.add_column("[overline white]TAKE", style='white', no_wrap=True) - table.add_column("[overline white]24h/k\u03C4", style='green', justify='center') + 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)') #table.add_column("[overline white]DESCRIPTION", style='white') @@ -63,17 +93,34 @@ def show_delegates( delegates: List['bittensor.DelegateInfo'], width: Optional[i 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]0%[/grey0]" + table.add_row( str(i), Text(delegate_name, style=f'link {delegate_url}'), f'{delegate.hotkey_ss58:8.8}...', - str(len(delegate.nominators)), + str(len([nom for nom in delegate.nominators if nom[1].rao > 0])), f'{owner_stake!s:13.13}', f'{delegate.total_stake!s:13.13}', + rate_change_in_stake_str, str(delegate.registrations), str(['*' if subnet in delegate.validator_permits else '' for subnet in delegate.registrations]), #f'{delegate.take * 100:.1f}%', - f'{delegate.return_per_1000!s:6.6}', + f'{bittensor.Balance.from_tao( delegate.total_daily_return.tao * (1000/ ( 0.001 + delegate.total_stake.tao ) ))!s:6.6}', + f'{bittensor.Balance.from_tao( delegate.total_daily_return.tao * (0.18) ) !s:6.6}', str(delegate_description) #f'{delegate_profile.description:140.140}', ) @@ -143,13 +190,14 @@ def check_config( config: 'bittensor.Config' ): with bittensor.__console__.status(":satellite: Loading delegates..."): subtensor = bittensor.subtensor( config = config ) delegates: List[bittensor.DelegateInfo] = subtensor.get_delegates() + prev_delegates = subtensor.get_delegates(max(0, subtensor.block - 1200)) 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 ) + 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)) @@ -241,13 +289,14 @@ def check_config( config: 'bittensor.Config' ): with bittensor.__console__.status(":satellite: Loading delegates..."): subtensor = bittensor.subtensor( config = config ) delegates: List[bittensor.DelegateInfo] = subtensor.get_delegates() + prev_delegates = subtensor.get_delegates(max(0, subtensor.block - 1200)) 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 ) + 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)) @@ -274,7 +323,8 @@ def run( cli ): subtensor = bittensor.subtensor( config = cli.config ) with bittensor.__console__.status(":satellite: Loading delegates..."): delegates: bittensor.DelegateInfo = subtensor.get_delegates() - show_delegates( delegates, width = cli.config.get('width', None) ) + prev_delegates = subtensor.get_delegates(max(0, subtensor.block - 1200)) + show_delegates( delegates, prev_delegates = prev_delegates, width = cli.config.get('width', None) ) @staticmethod def add_args( parser: argparse.ArgumentParser ): @@ -351,11 +401,6 @@ def check_config( config: 'bittensor.Config' ): if config.wallet.get('hotkey') == bittensor.defaults.wallet.hotkey and not config.no_prompt: hotkey = Prompt.ask("Enter hotkey name", default = bittensor.defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) - - - - - class MyDelegatesCommand: @@ -364,28 +409,18 @@ class MyDelegatesCommand: def run( cli ): '''Delegates stake to a chain delegate.''' config = cli.config.copy() - wallet = bittensor.wallet( config = config ) + if config.all == True: + wallets = _get_coldkey_wallets_for_path( config.wallet.path ) + else: + wallets = [bittensor.wallet( config = config )] subtensor: bittensor.Subtensor = bittensor.subtensor( config = config ) - 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) - - try: - registered_delegate_info = json.load( open("delegates.json") ) - except: - registered_delegate_info = {} table = Table(show_footer=True, 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]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", str(len(delegates)), footer_style = "overline white", style='bold yellow') - table.add_column("[overline green]My Delegation", str(len(delegates)), footer_style = "overline green", style='bold green') + 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) @@ -394,38 +429,63 @@ def run( cli ): table.add_column("[overline white]24h/k\u03C4", 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[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( - str(i), - 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}', - 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}%', - f'{delegate[0].return_per_1000!s:6.6}', - str(delegate_description) - #f'{delegate_profile.description:140.140}', + 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) + + try: + package_dir = os.path.dirname(bittensor.__file__) + root_dir = os.path.dirname(package_dir) + filename = os.path.join(root_dir, 'delegates.json') + if os.path.exists(filename): + registered_delegate_info = json.load( open(filename, 'r') ) + else: + registered_delegate_info = {} + except: + 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) @staticmethod @@ -440,6 +500,12 @@ def add_args( parser: argparse.ArgumentParser ): help='''Set false to stop cli version checking''', default = False ) + delegate_stake_parser.add_argument( + '--all', + action='store_true', + help='''Check all coldkey wallets.''', + default = False + ) delegate_stake_parser.add_argument( '--no_prompt', dest='no_prompt', @@ -452,7 +518,7 @@ def add_args( parser: argparse.ArgumentParser ): @staticmethod def check_config( config: 'bittensor.Config' ): - if config.wallet.get('name') == bittensor.defaults.wallet.name and not config.no_prompt: + if not config.all 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) diff --git a/bittensor/_cli/commands/inspect.py b/bittensor/_cli/commands/inspect.py index cd79a51e8a..84a41d6fdc 100644 --- a/bittensor/_cli/commands/inspect.py +++ b/bittensor/_cli/commands/inspect.py @@ -15,137 +15,152 @@ # 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 json import argparse import bittensor +from tqdm import tqdm +from rich.table import Table from rich.prompt import Prompt from .utils import check_netuid_set console = bittensor.__console__ +import os +import bittensor +from typing import List, Tuple + +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: @staticmethod def run (cli): r""" Inspect a cold, hot pair. """ - wallet = bittensor.wallet(config = cli.config) + if cli.config.all == True: + wallets = _get_coldkey_wallets_for_path( cli.config.wallet.path ) + else: + wallets = [bittensor.wallet( config = cli.config )] subtensor = bittensor.subtensor( config = cli.config ) - if cli.config.netuid != None: - # 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) - - - with bittensor.__console__.status(":satellite: Looking up account on: [white]{}[/white] ...".format(cli.config.subtensor.get('network', bittensor.defaults.subtensor.network))): - - if cli.config.wallet.get('hotkey', bittensor.defaults.wallet.hotkey) is None: - # If no hotkey is provided, inspect just the coldkey - wallet.coldkeypub - cold_balance = wallet.get_balance( subtensor = subtensor ) - bittensor.__console__.print("\n[bold white]{}[/bold white]:\n {}[bold white]{}[/bold white]\n {} {}\n".format( wallet, "coldkey:".ljust(15), wallet.coldkeypub.ss58_address, " balance:".ljust(15), cold_balance.__rich__()), highlight=True) + netuids = subtensor.get_all_subnet_netuids() + try: + package_dir = os.path.dirname(bittensor.__file__) + root_dir = os.path.dirname(package_dir) + filename = os.path.join(root_dir, 'delegates.json') + if os.path.exists(filename): + registered_delegate_info = json.load( open(filename, 'r') ) else: - wallet.hotkey - wallet.coldkeypub + registered_delegate_info = {} + except: + registered_delegate_info = {} - if cli.config.netuid != None: - # If a netuid is provided, inspect the hotkey and the neuron - dendrite = bittensor.dendrite( wallet = wallet ) - neuron = subtensor.get_neuron_for_pubkey_and_subnet( hotkey_ss58 = wallet.hotkey.ss58_address, netuid = cli.config.netuid ) - if neuron.is_null: - registered = '[bold white]No[/bold white]' - stake = bittensor.Balance.from_tao( 0 ) - emission = bittensor.Balance.from_rao( 0 ) - latency = 'N/A' - else: - endpoint = bittensor.endpoint.from_neuron( neuron ) - registered = '[bold white]Yes[/bold white]' - stake = neuron.total_stake - emission = bittensor.Balance.from_rao( neuron.emission * 1000000000 ) - synapses = [bittensor.synapse.TextLastHiddenState()] - _, c, t = dendrite.text( endpoints = endpoint, inputs = 'hello world', synapses=synapses) - latency = "{}".format((t[0]).tolist()[0]) if (c[0]).tolist()[0] == 1 else 'N/A' + neuron_state_dict = {} + for netuid in tqdm( netuids ): + neuron_state_dict[netuid] = subtensor.neurons_lite( netuid ) - cold_balance = subtensor.get_balance( wallet.coldkeypub.ss58_address ) - bittensor.__console__.print(( - "\n[bold white]{}[/bold white]:\n [bold grey]{}[bold white]{}[/bold white]\n" + \ - " {}[bold white]{}[/bold white]\n {}{}\n {}{}\n {}{}\n {}{}\n {}{}[/bold grey]" - ) - .format( - wallet, - "coldkey:".ljust(15), - wallet.coldkeypub.ss58_address, - "hotkey:".ljust(15), - wallet.hotkey.ss58_address, - "registered:".ljust(15), - registered, - "balance:".ljust(15), - cold_balance.__rich__(), - "stake:".ljust(15), - stake.__rich__(), - "emission:".ljust(15), - emission.__rich_rao__(), - "latency:".ljust(15), - latency - ), highlight=True) + 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 = wallet.get_balance( subtensor = subtensor ) + 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: - # Otherwise, print all subnets the hotkey is registered on. - # If a netuid is provided, inspect the hotkey and the neuron - stake = subtensor.get_stake_for_coldkey_and_hotkey( hotkey_ss58 = wallet.hotkey.ss58_address, coldkey_ss58 = wallet.coldkeypub.ss58_address ) - if stake == None: - # Not registered on any subnets - subnets = "[bold white][][/bold white]" - stake = bittensor.Balance.from_tao( 0 ) - else: - # Registered on subnets - subnets_registered = subtensor.get_netuids_for_hotkey( hotkey_ss58 = wallet.hotkey.ss58_address ) - subnets = f'[bold white]{subnets_registered}[/bold white]' - - emission = bittensor.Balance.from_rao( 0 ) - for netuid in subnets_registered: - neuron = subtensor.neuron_for_pubkey( hotkey_ss58 = wallet.hotkey.ss58_address, netuid = netuid ) - emission += bittensor.Balance.from_rao( neuron.emission * 1000000000 ) - - cold_balance = subtensor.get_balance( wallet.coldkeypub.ss58_address ) - bittensor.__console__.print(( - "\n[bold white]{}[/bold white]:\n [bold grey]{}[bold white]{}[/bold white]\n" + \ - " {}[bold white]{}[/bold white]\n {}{}\n {}{}\n {}{}\n {}{}\n {}{}[/bold grey]" - ) - .format( - wallet, - "coldkey:".ljust(15), - wallet.coldkeypub.ss58_address, - "hotkey:".ljust(15), - wallet.hotkey.ss58_address, - "subnets:".ljust(15), - subnets, - "balance:".ljust(15), - cold_balance.__rich__(), - "stake:".ljust(15), - stake.__rich__(), - "emission:".ljust(15), - emission.__rich_rao__(), - ), highlight=True) + 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: + table.add_row( + '', + '', + '', + '', + '', + str( netuid ), + str( neuron.hotkey ), + str( neuron.stake ), + str( bittensor.Balance.from_tao(neuron.emission) ) + ) + + bittensor.__console__.print(table) + + @staticmethod def check_config( config: 'bittensor.Config' ): - check_netuid_set( config, subtensor = bittensor.subtensor( config = config ),allow_none = True ) - if config.wallet.get('name') == bittensor.defaults.wallet.name and not config.no_prompt: + if not config.all 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) - if config.wallet.get('hotkey') == bittensor.defaults.wallet.hotkey and not config.no_prompt: - hotkey = Prompt.ask("Enter hotkey name (optional)", default = None) - config.wallet.hotkey = hotkey - @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( '--no_prompt', dest='no_prompt', diff --git a/bittensor/_cli/commands/overview.py b/bittensor/_cli/commands/overview.py index a9f41342ad..38a0a8e38f 100644 --- a/bittensor/_cli/commands/overview.py +++ b/bittensor/_cli/commands/overview.py @@ -73,7 +73,7 @@ def run( cli ): return # Pull neuron info for all keys. - neurons: Dict[str, List[bittensor.NeuronInfo, bittensor.Wallet]] = {} + neurons: Dict[str, List[bittensor.NeuronInfoLite, bittensor.Wallet]] = {} block = subtensor.block netuids = subtensor.get_all_subnet_netuids() @@ -115,6 +115,7 @@ def run( cli ): 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 @@ -126,7 +127,7 @@ def run( cli ): total_emission = 0 for nn, hotwallet in neurons[str(netuid)]: - nn: bittensor.NeuronInfo + nn: bittensor.NeuronInfoLite uid = nn.uid active = nn.active stake = nn.total_stake.tao @@ -136,7 +137,7 @@ def run( cli ): validator_trust = nn.validator_trust incentive = nn.incentive dividends = nn.dividends - emission = nn.emission / (subtensor.tempo(netuid=netuid) + 1) + emission = int(nn.emission / (subnet_tempo + 1) * 1e9) last_update = int(block - nn.last_update) validator_permit = nn.validator_permit row = [ @@ -150,7 +151,7 @@ def run( cli ): '{:.5f}'.format(consensus), '{:.5f}'.format(incentive), '{:.5f}'.format(dividends), - '{:.5f}'.format(emission), + '{:_}'.format(emission), '{:.5f}'.format(validator_trust), '*' if validator_permit else '', str(last_update), @@ -196,7 +197,7 @@ def run( cli ): 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(\u03C4)", '\u03C4{}'.format(int(total_emission)), 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) diff --git a/bittensor/_cli/commands/unstake.py b/bittensor/_cli/commands/unstake.py index 15b565b420..d08e67d676 100644 --- a/bittensor/_cli/commands/unstake.py +++ b/bittensor/_cli/commands/unstake.py @@ -32,12 +32,12 @@ def check_config( cls, config: 'bittensor.Config' ): wallet_name = Prompt.ask("Enter wallet name", default = bittensor.defaults.wallet.name) config.wallet.name = str(wallet_name) - if config.wallet.get('hotkey') == bittensor.defaults.wallet.hotkey and not config.no_prompt and not config.get('all_hotkeys') and not config.get('hotkeys'): + if not config.hotkey_ss58address and config.wallet.get('hotkey') == bittensor.defaults.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 = bittensor.defaults.wallet.hotkey) config.wallet.hotkey = str(hotkey) # Get amount. - if not config.get('amount') and not config.get('unstake_all') and not config.get('max_stake'): + if not config.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" @@ -80,6 +80,12 @@ def add_args( command_parser ): 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", @@ -129,7 +135,10 @@ def run( cli ): # Get the hotkey_names (if any) and the hotkey_ss58s. hotkeys_to_unstake_from: List[Tuple[Optional[str], str]] = [] - if cli.config.get('all_hotkeys'): + 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. diff --git a/bittensor/_endpoint/__init__.py b/bittensor/_endpoint/__init__.py index 8de7747455..2d21d04f4b 100644 --- a/bittensor/_endpoint/__init__.py +++ b/bittensor/_endpoint/__init__.py @@ -18,7 +18,7 @@ # DEALINGS IN THE SOFTWARE. import json -from types import SimpleNamespace +from typing import Union import torch import bittensor @@ -63,7 +63,7 @@ def __new__( @staticmethod - def from_neuron( neuron: 'bittensor.NeuronInfo' ) -> 'bittensor.Endpoint': + def from_neuron( neuron: Union['bittensor.NeuronInfo', 'bittensor.NeuronInfoLite'] ) -> 'bittensor.Endpoint': """ endpoint.assert_format( version = neuron.version, diff --git a/bittensor/_logging/__init__.py b/bittensor/_logging/__init__.py index 6958a97d4a..13636849ed 100644 --- a/bittensor/_logging/__init__.py +++ b/bittensor/_logging/__init__.py @@ -304,25 +304,12 @@ def rpc_log( synapse = synapse ) - - @classmethod - def create_receptor_log( cls, endpoint: 'bittensor.Endpoint' ): - """ Debug logging for the connection between endpoints - """ - logger.debug( 'endpoint', receptor=True, action = '' + 'Connect'.center(16) + '', uid=str(endpoint.uid).center(4), hotkey=endpoint.hotkey, coldkey=endpoint.coldkey, ip_str=endpoint.ip_str().center(27) ) - @classmethod def update_receptor_log( cls, endpoint: 'bittensor.Endpoint' ): """ Debug logging for updating the connection with endpoint """ logger.debug( 'endpoint', receptor=True, action = '' + 'Update'.center(16) + '', uid=str(endpoint.uid).center(4), hotkey=endpoint.hotkey, coldkey=endpoint.coldkey, ip_str=endpoint.ip_str().center(27) ) - @classmethod - def destroy_receptor_log( cls, endpoint: 'bittensor.Endpoint' ): - """ Debug logging for destroying connection with endpoint - """ - logger.debug( 'endpoint', receptor=True, action = '' + 'Destroy'.center(16) + '', uid=str(endpoint.uid).center(4), hotkey=endpoint.hotkey, coldkey=endpoint.coldkey, ip_str=endpoint.ip_str().center(27) ) - @classmethod def success( cls, prefix:str, sufix:str ): """ Success logging diff --git a/bittensor/_metagraph/__init__.py b/bittensor/_metagraph/__init__.py index 3b0d9fd244..435affc954 100644 --- a/bittensor/_metagraph/__init__.py +++ b/bittensor/_metagraph/__init__.py @@ -24,7 +24,7 @@ import bittensor from . import metagraph_impl from . import metagraph_mock -from typing import Optional, List +from typing import Optional, List, Union import bittensor.utils.weight_utils as weight_utils from .naka_metagraph_impl import Metagraph as naka_metagraph @@ -77,11 +77,11 @@ def __new__( if network == None: network = config.subtensor.get('network', bittensor.defaults.subtensor.network) - if network =='finney': - return metagraph_impl.Metagraph( network = network, netuid = netuid ) - elif network =='nakamoto': + if network =='nakamoto': config.subtensor.network = 'nakamoto' return naka_metagraph(config = config, subtensor = subtensor) + else: + return metagraph_impl.Metagraph( network = network, netuid = netuid ) @classmethod def config(cls) -> 'bittensor.Config': @@ -123,7 +123,7 @@ def check_config( cls, config: 'bittensor.Config' ): pass @staticmethod - def from_neurons( network: str, netuid: int, info: 'bittensor.SubnetInfo', neurons: List['bittensor.NeuronInfo'], block: int ) -> 'bittensor.Metagraph': + def from_neurons( network: str, netuid: int, info: 'bittensor.SubnetInfo', neurons: Union[List['bittensor.NeuronInfo'], List['bittensor.NeuronInfoLite']], block: int ) -> 'bittensor.Metagraph': r""" Creates a metagraph from a list of neurons. Args: network: (:obj:`str`, required): @@ -132,7 +132,7 @@ def from_neurons( network: str, netuid: int, info: 'bittensor.SubnetInfo', neuro netuid of the subnet for the metagraph. info: (:obj:`SubnetInfo`, required): SubnetInfo object for the metagraph, including the subnet's hyperparameters. - neurons: (:obj:`List[NeuronInfo]`, required): + neurons: (:obj:`Union[List[NeuronInfo], List[NeuronInfoLite]]`, required): List of neurons to create metagraph from. block: (:obj:`int`, required): Block number at time of the metagraph. @@ -179,6 +179,9 @@ def from_neurons( network: str, netuid: int, info: 'bittensor.SubnetInfo', neuro endpoint = bittensor.endpoint.from_neuron(n) metagraph._endpoint_objs[n.uid] = endpoint endpoints[n.uid] = endpoint.to_tensor().tolist() + if isinstance(n, bittensor.NeuronInfoLite): + continue + # Weights and bonds only for full neurons. if len(n.weights) > 0: w_uids, w_weights = zip(*n.weights) weights[n.uid] = weight_utils.convert_weight_uids_and_vals_to_tensor( n_total, w_uids, w_weights ).tolist() diff --git a/bittensor/_metagraph/metagraph_impl.py b/bittensor/_metagraph/metagraph_impl.py index 5b5e2ab4f6..5496a0d5b0 100644 --- a/bittensor/_metagraph/metagraph_impl.py +++ b/bittensor/_metagraph/metagraph_impl.py @@ -342,7 +342,7 @@ def load_from_state_dict(self, state_dict: dict ) -> 'Metagraph': self.info = bittensor.SubnetInfo.from_parameter_dict( state_dict['info'] ) if 'info' in state_dict else None return self - def sync ( self, netuid: Optional[int] = None, subtensor: 'bittensor.Subtensor' = None, block: Optional[int] = None ) -> 'Metagraph': + def sync ( self, netuid: Optional[int] = None, subtensor: 'bittensor.Subtensor' = None, block: Optional[int] = None, lite: bool = True ) -> 'Metagraph': r""" Synchronizes this metagraph with the chain state. Args: subtensor: (:obj:`bittensor.Subtensor`, optional, defaults to None): @@ -353,6 +353,9 @@ def sync ( self, netuid: Optional[int] = None, subtensor: 'bittensor.Subtensor' Defaults to the netuid of the metagraph object. block: (:obj:`int`, optional, defaults to None): block to sync with. If None, syncs with the current block. + lite: (:obj:`bool`, defaults to True): + If true, syncs using the lite version of the metagraph. + Note: lite version does not include weights, bonds Returns: self: (:obj:`Metagraph`, required): Returns self. @@ -365,7 +368,7 @@ def sync ( self, netuid: Optional[int] = None, subtensor: 'bittensor.Subtensor' if netuid == None: raise ValueError('Metagraph.sync() requires a netuid to sync with.') # Pull metagraph from chain using subtensor. - metagraph = subtensor.metagraph( netuid = netuid, block = block ) + metagraph = subtensor.metagraph( netuid = netuid, block = block, lite = lite ) # Update self with new values. self.__dict__.update(metagraph.__dict__) return self diff --git a/bittensor/_neuron/text/core_server/__init__.py b/bittensor/_neuron/text/core_server/__init__.py index 2440d9edc9..1e4d56d4e2 100644 --- a/bittensor/_neuron/text/core_server/__init__.py +++ b/bittensor/_neuron/text/core_server/__init__.py @@ -214,7 +214,7 @@ def run( # Load/Create our bittensor wallet. self.wallet.reregister(subtensor=self.subtensor, netuid = self.config.netuid) - self.metagraph.load().sync(netuid = self.config.netuid, subtensor=self.subtensor).save() + self.metagraph.sync(netuid = self.config.netuid, subtensor=self.subtensor).save() # Create our optimizer. optimizer = torch.optim.SGD( diff --git a/bittensor/_neuron/text/core_validator/__init__.py b/bittensor/_neuron/text/core_validator/__init__.py index 1e5136669e..b20638da79 100644 --- a/bittensor/_neuron/text/core_validator/__init__.py +++ b/bittensor/_neuron/text/core_validator/__init__.py @@ -76,8 +76,6 @@ class neuron: bittensor dendrite object dataset (:obj:bittensor.dendrite, `optional`): bittensor dendrite object - axon (:obj:bittensor.axon, `optional`): - bittensor axon object Examples:: >>> subtensor = bittensor.subtensor(network='nakamoto') >>> validator = bittensor.neuron.text.core_validator.neuron(subtensor=subtensor) @@ -91,7 +89,6 @@ def __init__( metagraph: 'bittensor.Metagraph' = None, dendrite: 'bittensor.Dendrite' = None, dataset: 'bittensor.dataset' = None, - axon: 'bittensor.axon' = None, netuid: int = None ): @@ -123,7 +120,6 @@ def __init__( self.config.dendrite._mock = True self.config.metagraph._mock = True self.config.subtensor._mock = True - self.config.axon._mock = True print ( self.config ) # === Logging + prometheus === @@ -139,8 +135,7 @@ def __init__( self.wallet = bittensor.wallet ( config = self.config ) if wallet == None else wallet self.subtensor = subtensor self.metagraph = bittensor.metagraph ( config = self.config ) if metagraph == None else metagraph - self.dendrite = bittensor.dendrite ( config = self.config, wallet = self.wallet, max_active_receptors = 0 ) if dendrite == None else dendrite # Dendrite should not store receptor in validator. - self.axon = bittensor.axon ( netuid=self.config.netuid, config = self.config, wallet = self.wallet ) if axon == None else axon + self.dendrite = bittensor.dendrite ( config = self.config, wallet = self.wallet, max_active_receptors = 0 ) if dendrite == None else dendrite # Dendrite should not store receptor in validator. self.device = torch.device ( device = self.config.neuron.device ) self.nucleus = nucleus ( config = self.config, device = self.device, subtensor = self.subtensor, vlogger = self.vlogger ).to( self.device ) if self.config.subtensor.network == 'nakamoto': @@ -192,7 +187,6 @@ def check_config( cls, config: 'bittensor.Config' ): bittensor.dataset.check_config( config ) bittensor.dendrite.check_config( config ) bittensor.wandb.check_config( config ) - bittensor.axon.check_config( config ) bittensor.prometheus.check_config( config ) full_path = os.path.expanduser('{}/{}/{}/netuid{}/{}'.format( config.logging.logging_dir, config.wallet.name, config.wallet.hotkey, config.netuid, config.neuron.name )) config.neuron.full_path = os.path.expanduser(full_path) @@ -236,7 +230,6 @@ def config ( cls ): bittensor.logging.add_args( parser ) bittensor.dataset.add_args( parser ) bittensor.wandb.add_args(parser) - bittensor.axon.add_args( parser ) bittensor.prometheus.add_args( parser ) return bittensor.config( parser ) @@ -286,15 +279,13 @@ def __enter__(self): ) # === Set prometheus run info === - # Serve the prometheus with axon so we can determine where the prometheus server port is (the axon is only served for this reason.) - # TODO (Cameron) this should be it's own storage map on-chain. + # Serve the prometheus bittensor.prometheus( config = self.config, wallet = self.wallet, netuid = self.config.netuid, - port = self.config.prometheus.port if self.config.prometheus.port == bittensor.defaults.axon.port else self.config.axon.port - 1000 + port = self.config.prometheus.port ) - self.axon.serve( subtensor = self.subtensor ) self.vlogger.prometheus.log_run_info( parameters = self.nucleus.parameters(), @@ -844,7 +835,7 @@ def add_args( cls, parser ): parser.add_argument('--nucleus.dropout', type=float, help='the dropout value', default=0.2) parser.add_argument('--nucleus.importance', type=float, help='hyperparameter for the importance loss', default=3) parser.add_argument('--nucleus.noise_multiplier', type=float, help='Standard deviation multipler on weights', default=2 ) - parser.add_argument('--nucleus.no_dendrite_backward', action='store_true', help='Pass backward request to the server side or not', default=False ) + parser.add_argument('--nucleus.dendrite_backward', action='store_true', help='Pass backward request to the server side or not', default=False ) parser.add_argument('--nucleus.scaling_law_power', type=float, help='Power for modified scaling law, powered down to improve dynamic range, e.g. 3 → 6 nats for 0.5. (default value: -1, pulling from subtensor directly)', default=-1) parser.add_argument('--nucleus.synergy_scaling_law_power', type=float, help='Power for synergy modified scaling law, powered down to improve dynamic range, e.g. 3 → 6 nats for 0.5. (default value: -1, pulling from subtensor directly)', default=-1) parser.add_argument('--nucleus.logits_divergence', type=float, help=' the divergence value for logit anomaly detection (default value: -1, pulling from subtensor directly)', default=-1) @@ -985,10 +976,11 @@ def forward( timeout=bittensor.__blocktime__ ) - if self.config.nucleus.no_dendrite_backward: - query_responses = [[syn.detach().to(self.device) for syn in res] for res in query_responses] - return_ops = [ops.detach().to(self.device) for ops in return_ops] - times = [t.detach().to(self.device) for t in times] + if not self.config.nucleus.dendrite_backward: + query_responses = [[syn.detach() for syn in res] for res in query_responses] + return_ops = [ops.detach() for ops in return_ops] + times = [t.detach() for t in times] + # Send responses to device. This is required to ensure we move the responses # Onto the correct device. diff --git a/bittensor/_receptor/receptor_pool_impl.py b/bittensor/_receptor/receptor_pool_impl.py index db76bb3c5a..04ae83c368 100644 --- a/bittensor/_receptor/receptor_pool_impl.py +++ b/bittensor/_receptor/receptor_pool_impl.py @@ -338,7 +338,6 @@ def _destroy_receptors_over_max_allowed( self ): if receptor_to_remove != None: try: - bittensor.logging.destroy_receptor_log(receptor_to_remove.endpoint) self.receptors[ receptor_to_remove.endpoint.hotkey ].close() del self.receptors[ receptor_to_remove.endpoint.hotkey ] except KeyError: @@ -370,7 +369,6 @@ def _get_or_create_receptor_for_endpoint( self, endpoint: 'bittensor.Endpoint' ) # ---- Or: Create a new receptor ---- else: - bittensor.logging.create_receptor_log( endpoint ) receptor = bittensor.receptor ( endpoint = endpoint, wallet = self.wallet, diff --git a/bittensor/_subtensor/__init__.py b/bittensor/_subtensor/__init__.py index e20f86c266..a17f61e3cc 100644 --- a/bittensor/_subtensor/__init__.py +++ b/bittensor/_subtensor/__init__.py @@ -112,15 +112,16 @@ def __new__( # make sure formatting is good endpoint_url = bittensor.utils.networking.get_formatted_ws_endpoint_url(endpoint_url) - substrate = SubstrateInterface( - ss58_format = bittensor.__ss58_format__, - use_remote_preset=True, - url = endpoint_url, - ) + subtensor.check_config( config ) network = config.subtensor.get('network', bittensor.defaults.subtensor.network) if network == 'nakamoto': + substrate = SubstrateInterface( + ss58_format = bittensor.__ss58_format__, + use_remote_preset=True, + url = endpoint_url, + ) # Use nakamoto-specific subtensor. return Nakamoto_subtensor( substrate = substrate, @@ -128,6 +129,12 @@ def __new__( chain_endpoint = config.subtensor.chain_endpoint, ) else: + substrate = SubstrateInterface( + ss58_format = bittensor.__ss58_format__, + use_remote_preset=True, + url = endpoint_url, + type_registry=bittensor.__type_registry__ + ) return subtensor_impl.Subtensor( substrate = substrate, network = config.subtensor.get('network', bittensor.defaults.subtensor.network), diff --git a/bittensor/_subtensor/extrinsics/set_weights.py b/bittensor/_subtensor/extrinsics/set_weights.py index a6286e94e3..06164337fa 100644 --- a/bittensor/_subtensor/extrinsics/set_weights.py +++ b/bittensor/_subtensor/extrinsics/set_weights.py @@ -90,7 +90,8 @@ def set_weights_extrinsic( 'version_key': version_key, } ) - extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey ) + # Period dictates how long the extrinsic will stay as part of waiting pool + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.hotkey, era={'period':100}) response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion = wait_for_inclusion, wait_for_finalization = wait_for_finalization ) # We only wait here if we expect finalization. if not wait_for_finalization and not wait_for_inclusion: diff --git a/bittensor/_subtensor/extrinsics/staking.py b/bittensor/_subtensor/extrinsics/staking.py index 37caab6afa..214101a30a 100644 --- a/bittensor/_subtensor/extrinsics/staking.py +++ b/bittensor/_subtensor/extrinsics/staking.py @@ -289,7 +289,7 @@ def add_stake_multiple_extrinsic ( block = subtensor.get_current_block() new_stake = subtensor.get_stake_for_coldkey_and_hotkey( coldkey_ss58 = wallet.coldkeypub.ss58_address, hotkey_ss58 = hotkey_ss58, block = block ) new_balance = subtensor.get_balance( wallet.coldkeypub.ss58_address, block = block ) - bittensor.__console__.print("Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( wallet.hotkey.ss58_address, old_stake, new_stake )) + bittensor.__console__.print("Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( hotkey_ss58, old_stake, new_stake )) old_balance = new_balance successful_stakes += 1 if staking_all: @@ -301,7 +301,7 @@ def add_stake_multiple_extrinsic ( continue except NotRegisteredError as e: - bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered.[/red]".format(wallet.hotkey_str)) + bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered.[/red]".format(hotkey_ss58)) continue except StakeError as e: bittensor.__console__.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) diff --git a/bittensor/_subtensor/extrinsics/transfer.py b/bittensor/_subtensor/extrinsics/transfer.py index 10266e537a..8b5ed9c7a7 100644 --- a/bittensor/_subtensor/extrinsics/transfer.py +++ b/bittensor/_subtensor/extrinsics/transfer.py @@ -31,6 +31,7 @@ def transfer_extrinsic( amount: Union[Balance, float], wait_for_inclusion: bool = True, wait_for_finalization: bool = False, + keep_alive: bool = True, prompt: bool = False, ) -> bool: r""" Transfers funds from this wallet to the destination public key address @@ -47,6 +48,8 @@ def transfer_extrinsic( 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: @@ -75,14 +78,42 @@ def transfer_extrinsic( # Check balance. with bittensor.__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 subtensor.substrate as substrate: + call = substrate.compose_call( + call_module='Balances', + call_function='transfer', + call_params={ + 'dest': dest, + 'value': transfer_balance.rao + } + ) + + try: + payment_info = substrate.get_payment_info( call = call, keypair = wallet.coldkey ) + except Exception as e: + bittensor.__console__.print(":cross_mark: [red]Failed to get payment info[/red]:[bold white]\n {}[/bold white]".format(e)) + payment_info = { + 'partialFee': 2e7, # assume 0.02 Tao + } + + fee = bittensor.Balance.from_rao( payment_info['partialFee'] ) - if account_balance < transfer_balance: - bittensor.__console__.print(":cross_mark: [red]Not enough balance[/red]:[bold white]\n balance: {}\n amount: {}[/bold white]".format( account_balance, transfer_balance )) + if not keep_alive: + # Check if the transfer should keep_alive the account + existential_deposit = bittensor.Balance(0) + + # Check if we have enough balance. + if account_balance < (transfer_balance + fee + existential_deposit): + bittensor.__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 )) return False # Ask before moving on. if prompt: - if not Confirm.ask("Do you want to transfer:[bold white]\n amount: {}\n from: {}:{}\n to: {}[/bold white]".format( transfer_balance, wallet.name, wallet.coldkey.ss58_address, dest )): + if not Confirm.ask("Do you want to transfer:[bold white]\n amount: {}\n from: {}:{}\n to: {}\n for fee: {}[/bold white]".format( transfer_balance, wallet.name, wallet.coldkey.ss58_address, dest, fee )): return False with bittensor.__console__.status(":satellite: Transferring..."): @@ -95,6 +126,7 @@ def transfer_extrinsic( 'value': transfer_balance.rao } ) + extrinsic = substrate.create_signed_extrinsic( call = call, keypair = wallet.coldkey ) response = substrate.submit_extrinsic( extrinsic, wait_for_inclusion = wait_for_inclusion, wait_for_finalization = wait_for_finalization ) # We only wait here if we expect finalization. diff --git a/bittensor/_subtensor/extrinsics/unstaking.py b/bittensor/_subtensor/extrinsics/unstaking.py index 447c85baf4..82e01151b0 100644 --- a/bittensor/_subtensor/extrinsics/unstaking.py +++ b/bittensor/_subtensor/extrinsics/unstaking.py @@ -288,14 +288,14 @@ def unstake_multiple_extrinsic ( with bittensor.__console__.status(":satellite: Checking Balance on: [white]{}[/white] ...".format(subtensor.network)): block = subtensor.get_current_block() new_stake = subtensor.get_stake_for_coldkey_and_hotkey( coldkey_ss58 = wallet.coldkeypub.ss58_address, hotkey_ss58 = hotkey_ss58, block = block ) - bittensor.__console__.print("Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format( wallet.hotkey.ss58_address, stake_on_uid, new_stake )) + bittensor.__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]: Error unknown.") continue except NotRegisteredError as e: - bittensor.__console__.print(":cross_mark: [red]Hotkey: {} is not registered.[/red]".format(wallet.hotkey_str)) + bittensor.__console__.print(":cross_mark: [red]{} is not registered.[/red]".format(hotkey_ss58)) continue except StakeError as e: bittensor.__console__.print(":cross_mark: [red]Stake Error: {}[/red]".format(e)) diff --git a/bittensor/_subtensor/subtensor_impl.py b/bittensor/_subtensor/subtensor_impl.py index 62618e1418..88e12a2b7a 100644 --- a/bittensor/_subtensor/subtensor_impl.py +++ b/bittensor/_subtensor/subtensor_impl.py @@ -241,6 +241,22 @@ def transfer( wait_for_finalization = wait_for_finalization, prompt = prompt ) + + def get_existential_deposit( + self, + block: Optional[int] = None, + ) -> Optional[Balance]: + """ Returns the existential deposit for the chain. """ + result = self.query_constant( + module_name='Balances', + constant_name='ExistentialDeposit', + block = block, + ) + + if result is None: + return None + + return Balance.from_rao(result.value) ################# #### Serving #### @@ -373,6 +389,18 @@ def make_substrate_call_with_retry(): block_hash = None if block == None else substrate.get_block_hash(block) ) return make_substrate_call_with_retry() + + """ Gets a constant from subtensor with module_name, constant_name, and block. """ + def query_constant( self, module_name: str, constant_name: str, block: Optional[int] = None ) -> Optional[object]: + @retry(delay=2, tries=3, backoff=2, max_delay=4) + def make_substrate_call_with_retry(): + with self.substrate as substrate: + return substrate.get_constant( + module_name=module_name, + constant_name=constant_name, + block_hash = None if block == None else substrate.get_block_hash(block) + ) + return make_substrate_call_with_retry() ##################################### #### Hyper parameter calls. #### @@ -484,7 +512,7 @@ def tempo (self, netuid: int, block: Optional[int] = None) -> int: return self.query_subtensor('Tempo', block, [netuid] ).value ########################## - #### Account fucntions ### + #### Account functions ### ########################## """ Returns the total stake held on a hotkey including delegative """ @@ -621,7 +649,7 @@ def make_substrate_call_with_retry(): block_hash = None if block == None else substrate.get_block_hash( block ) params = [] if block_hash: - params = [block_hash] + params + params = params + [block_hash] return substrate.rpc_request( method="subnetInfo_getSubnetsInfo", # custom rpc method params=params @@ -642,7 +670,7 @@ def make_substrate_call_with_retry(): block_hash = None if block == None else substrate.get_block_hash( block ) params = [netuid] if block_hash: - params = [block_hash] + params + params = params + [block_hash] return substrate.rpc_request( method="subnetInfo_getSubnetInfo", # custom rpc method params=params @@ -679,7 +707,7 @@ def make_substrate_call_with_retry(encoded_hotkey: List[int]): block_hash = None if block == None else substrate.get_block_hash( block ) params = [encoded_hotkey] if block_hash: - params = [block_hash] + params + params = params + [block_hash] return substrate.rpc_request( method="delegateInfo_getDelegate", # custom rpc method params=params @@ -702,7 +730,7 @@ def make_substrate_call_with_retry(): block_hash = None if block == None else substrate.get_block_hash( block ) params = [] if block_hash: - params = [block_hash] + params + params = params + [block_hash] return substrate.rpc_request( method="delegateInfo_getDelegates", # custom rpc method params=params @@ -724,7 +752,7 @@ def make_substrate_call_with_retry(encoded_coldkey: List[int]): block_hash = None if block == None else substrate.get_block_hash( block ) params = [encoded_coldkey] if block_hash: - params = [block_hash] + params + params = params + [block_hash] return substrate.rpc_request( method="delegateInfo_getDelegated", # custom rpc method params=params @@ -802,7 +830,7 @@ def make_substrate_call_with_retry(): block_hash = None if block == None else substrate.get_block_hash( block ) params = [netuid, uid] if block_hash: - params = [block_hash] + params + params = params + [block_hash] return substrate.rpc_request( method="neuronInfo_getNeuron", # custom rpc method params=params @@ -832,7 +860,7 @@ def make_substrate_call_with_retry(): block_hash = None if block == None else substrate.get_block_hash( block ) params = [netuid] if block_hash: - params = [block_hash] + params + params = params + [block_hash] return substrate.rpc_request( method="neuronInfo_getNeurons", # custom rpc method params=params @@ -866,7 +894,7 @@ def make_substrate_call_with_retry(): block_hash = None if block == None else substrate.get_block_hash( block ) params = [netuid, uid] if block_hash: - params = [block_hash] + params + params = params + [block_hash] return substrate.rpc_request( method="neuronInfo_getNeuronLite", # custom rpc method params=params @@ -896,7 +924,7 @@ def make_substrate_call_with_retry(): block_hash = None if block == None else substrate.get_block_hash( block ) params = [netuid] if block_hash: - params = [block_hash] + params + params = params + [block_hash] return substrate.rpc_request( method="neuronInfo_getNeuronsLite", # custom rpc method params=params @@ -910,7 +938,7 @@ def make_substrate_call_with_retry(): return NeuronInfoLite.list_from_vec_u8( result ) - def metagraph( self, netuid: int, block: Optional[int] = None ) -> 'bittensor.Metagraph': + def metagraph( self, netuid: int, block: Optional[int] = None, lite: bool = True ) -> 'bittensor.Metagraph': r""" Returns the metagraph for the subnet. Args: netuid ( int ): @@ -918,6 +946,8 @@ def metagraph( self, netuid: int, block: Optional[int] = None ) -> 'bittensor.Me block (Optional[int]): The block to create the metagraph for. Defaults to latest. + lite (bool, default=True): + If true, returns a metagraph using the lite sync (no weights, no bonds) Returns: metagraph ( `bittensor.Metagraph` ): The metagraph for the subnet at the block. @@ -928,7 +958,11 @@ def metagraph( self, netuid: int, block: Optional[int] = None ) -> 'bittensor.Me status.start() # Get neurons. - neurons = self.neurons( netuid = netuid, block = block ) + if lite: + neurons = self.neurons_lite( netuid = netuid, block = block ) + else: + neurons = self.neurons( netuid = netuid, block = block ) + # Get subnet info. subnet_info: Optional[bittensor.SubnetInfo] = self.get_subnet_info( netuid = netuid, block = block ) if subnet_info == None: @@ -939,11 +973,17 @@ def metagraph( self, netuid: int, block: Optional[int] = None ) -> 'bittensor.Me # Create metagraph. block_number = self.block - + metagraph = bittensor.metagraph.from_neurons( network = self.network, netuid = netuid, info = subnet_info, neurons = neurons, block = block_number ) print("Metagraph subtensor: ", self.network) return metagraph + ################ + #### Transfer ## + ################ + + + ################ #### Legacy #### diff --git a/bittensor/utils/weight_utils.py b/bittensor/utils/weight_utils.py index 3a66baa2dc..c89d6a9c34 100644 --- a/bittensor/utils/weight_utils.py +++ b/bittensor/utils/weight_utils.py @@ -138,4 +138,4 @@ def convert_weights_and_uids_for_emit( uids: torch.LongTensor, weights: torch.Fl weight_vals.append( uint16_val ) weight_uids.append( uid_i ) - return weight_uids, weight_vals + return weight_uids, weight_vals \ No newline at end of file diff --git a/delegates.json b/delegates.json index 492db831f5..ccbc5f33da 100644 --- a/delegates.json +++ b/delegates.json @@ -1,21 +1,21 @@ { "5F4tQyWrhfGVcNhoqeiNsR6KjD4wMZ2kfhLj4oHYuyHbZAc3": { - "name": "Opentensor Foundation", + "name": "Openτensor Foundaτion", "url": "https://opentensor.ai/", "description": "Founded, maintain and advance Bittensor" }, "5HNQURvmjjYhTSksi8Wfsw676b4owGwfLR2BFAQzG7H3HhYf": { - "name": "Neural Internet", + "name": "Neural Interneτ", "url": "https://neuralinternet.ai/", "description": "An AI research and development decentralized autonomous organization (DAO)." }, "5FLKnbMjHY8LarHZvk2q2RY9drWFbpxjAcR5x8tjr3GqtU6F": { - "name": "Tao Bridge", + "name": "τao Bridge", "url": "https://taobridge.xyz", "description": "A community bridge between Bittensor and Ethereum" }, "5HeKSHGdsRCwVgyrHchijnZJnq4wiv6GqoDLNah8R5WMfnLB": { - "name": "TaoStation", + "name": "τaoStation", "url": "https://taostation.com", "description": "The go to validator for maximal TAO returns to stakers." }, @@ -25,7 +25,7 @@ "description": "Vune is a dev at Opentensor and a BSc CS student at UofT." }, "5H6BgKkAr2Anmm9Xw5BVDE4VaQmFEVMkJUHeT7Gki4J7yF4x": { - "name": "TaoPolishNode", + "name": "τaoPolishNode", "url": "https://taonode.io", "description": "This node is a collective effort of the polish community. We are engaged in evangelizing the project, educating and sharing the knowledge." }, @@ -35,12 +35,12 @@ "description": "GPU Cloud built for AI. We plan to introduce perks for those who stake." }, "5CPzGD8sxyv8fKKXNvKem4qJRhCXABRmpUgC1wb1V4YAXLc3": { - "name": "Tao Staking", + "name": "τao Staking", "url": "https://www.taostaking.com", "description": "Empowering innovation on the Bittensor network by helping upcoming projects grow and thrive on Bittensor." }, "5FFApaS75bv5pJHfAp2FVLBj9ZaXuFDjEypsaBNc1wCfe52v": { - "name": "RoundTable21", + "name": "Roundτable21", "url": "https://roundtable21.com", "description": "RoundTable21 is an International, multi-disciplinary team of consultants and advisors partnering alongside leading blockchain startups to offer guidance, expertise, investment and hands-on assistance in every aspect of development." }, @@ -50,7 +50,7 @@ "description": "Foundry works to empower a decentralized infrastructure. We are protocol-agnostic and seek to support like-minded blockchain entrepreneurs who share our mission to advance the industry." }, "5DCc5oHA6c1Lpt9R6T1xU8jJGTMvvwBqD1yGX67sL8dHUcga": { - "name": "WaveTensor", + "name": "Waveτensor", "url": "https://twitter.com/wavetensor", "description": "A new Wave is coming, join the AI revolution on top of Bittensor by staking with us." }, @@ -59,14 +59,79 @@ "url": "https://taostats.io", "description": "Supporting the bittensor eco-system through data provision, statistics and analytics." }, + "5CXRfP2ekFhe62r7q3vppRajJmGhTi7vwvb2yr79jveZ282w": { + "name": "Rizzo", + "url": "", + "description": "Validator built for performance and uptime. Data center housed, redundancies include dual physical failover servers (HA), power, internet, tested DR Plan." + }, + "5DRZr3d3twF8SzqB9jBof3a1vPnAkgkxeo2E8yUKJAnE2rSZ": { + "name": "Humble AI-Loving Anon", + "url": "", + "description": "Doing our best to support the Bittensor ecosystem." + }, "5GcBK8PDrVifV1xAf4Qkkk6KsbsmhDdX9atvk8vyKU8xdU63": { - "name": "Tensor.Exchange", + "name": "τensor.exchange", "url": "www.tensor.exchange", "description": "Bittensor's first community OTC exchange" }, + "5EhvL1FVkQPpMjZX4MAADcW42i3xPSF1KiCpuaxTYVr28sux": { + "name": "TAO-Validator.com", + "url": "www.tao-validator.com", + "description": "Maximize your return when staking with TAO-Validator.com. TAO-Validator.com is a highly secure validator that aims to become one of the top contributing entities to Bittensor." + }, + "5FvhvCWLbu2VgotT5obC9E6S9nskerJUrVsWqkWXCbuD8veW": { + "name": "The Lost Cove", + "url": "https://lostcove.tech/", + "description": "Australia and New Zealand community. We're in it for the gains." + }, + "5Dyi5e2QqnWn2RN9X6r8A8Q1QBjYD536H75mxNye193oeCJ4": { + "name": "Makoto AI", + "url": "https://www.linkedin.com/in/henry-thrasher-17b320239/", + "description": "An interdisciplinary research institute committed to discovering and accelerating innovative solutions for climate change, social inequality, and mental and physical illness." + }, + "5Ehv5XMriPZwNBtYHdQV7VrdbN8MBTDTmQhWprZJXxSiMapR": { + "name": "Dale Cooper", + "url": "", + "description": "I have no idea where this will lead us, but I have a definite feeling it will be a place both wonderful and strange." + }, + "5FP9miYmgjAP8Wt3747M2Y6Kk7PrXf6zG7a3EjokQiFFcmUu": { + "name": "Elm Place", + "url": "", + "description": "Run by individuals passionate about creating decentralised digital infrastructure. Background in fiduciary funds management managing institutional investors' capital in real assets, energy and infrastructure" + }, + "5E6oB7h5wtWPbqtPxtSoZeo11fpvDjPuY13SobAMxqEUjqkQ": { + "name": "StakeTensor.com-3", + "url": "www.staketensor.com", + "description": "We run multiple, parallel validators to support Bittensor decentralization & achieve maximum returns" + }, + "5DnWFhKfeu6gXMydzrv8bkwxFegAC6bMWsC4Z2XtaotAeB6S": { + "name": "Bittensor Greece", + "url": "", + "description": "The Greek / Cypriot validator supporting the development of decentralised AI" + }, + "5GBxDYkDp8eJZHGT89wcZJKcMc4ytSqnqqVSpeuGeqtGfqxK": { + "name": "τao Stake", + "url": "www.taostake.io", + "description": "We have been mining since the start of bittensor and want to maintain a long term solid validator to help people get some value from thier investment and keep TAO within the ecosystem." + }, + "5FcXnzNo3mrqReTEY4ftkg5iXRBi61iyvM4W1bywZLRqfxAY": { + "name": "Lucrosus Capiτal", + "url": "https://lucrosuspool.io/", + "description": "Decentralized VC focused on the most thriving blockchain ideas. Join our pool to receive early entrance into promising projects!" + }, + "5CAW5xpgPbxGUzkE3dgmM2XasaygpbvtSoTh3UeR8RMfHQZU": { + "name": "Vogue τensor", + "url": "https://voguetensor.webflow.io/", + "description": "Designing branded clothing for the Bittensor community." + }, "5CsvRJXuR955WojnGMdok1hbhffZyB4N5ocrv82f3p5A2zVp": { "name": "Owl Ventures", "url": "https://owlventures.co.uk", "description": "Owl Ventures Bittensor Validator" + }, + "5D9wuLzoWSbucNx6NQRo7wdNfvW2poMy3NumSiggsEStPc9X": { + "name": "T A O A L M A N A C H", + "url": "", + "description": "TAO ALMANACH is the validator of bittensor mod blardo and polkadot mod mister_cole" } } diff --git a/requirements/prod.txt b/requirements/prod.txt index 88bedfb26a..6bdfb67e1a 100644 --- a/requirements/prod.txt +++ b/requirements/prod.txt @@ -31,7 +31,7 @@ pyyaml==6.0 rich==12.5.1 retry==0.9.2 requests==2.25.0 -scalecodec>=1.2,<1.3 +scalecodec==1.2.0 sentencepiece==0.1.97 termcolor==2.1.1 torch==1.13.1 @@ -43,4 +43,5 @@ qqdm==0.0.7 wandb>=0.11.1,<0.13.4 ansible_vault>=2.1 substrate-interface==1.5.0 +jsonschema[format-nongpl]>=4.14.0,<=4.17.0 markupsafe==2.0.1