diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index d10221388..ce74d1c72 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -218,6 +218,11 @@ class Options: "--quiet", help="Display only critical information on the console.", ) + live = typer.Option( + False, + "--live", + help="Display live view of the table", + ) def list_prompt(init_var: list, list_type: type, help_text: str) -> list: @@ -3391,6 +3396,7 @@ def subnets_list( # html_output: bool = Options.html_output, quiet: bool = Options.quiet, verbose: bool = Options.verbose, + live_mode: bool = Options.live, ): """ List all subnets and their detailed information. @@ -3427,6 +3433,7 @@ def subnets_list( False, # reuse-last False, # html-output not self.config.get("use_cache", True), + live_mode, ) ) diff --git a/bittensor_cli/src/commands/stake/stake.py b/bittensor_cli/src/commands/stake/stake.py index 2b8caf42f..33198509e 100644 --- a/bittensor_cli/src/commands/stake/stake.py +++ b/bittensor_cli/src/commands/stake/stake.py @@ -8,6 +8,7 @@ from bittensor_wallet.errors import KeyFileError from rich.prompt import Confirm, FloatPrompt, Prompt from rich.table import Table +from rich import box from substrateinterface.exceptions import SubstrateRequestException from bittensor_cli.src.bittensor.balances import Balance @@ -1543,8 +1544,9 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): else hotkey_ ) rows = [] - total_global_tao = Balance(0) + total_tao_ownership = Balance(0) total_tao_value = Balance(0) + total_swapped_tao_value = Balance(0) for substake_ in substakes: netuid = substake_.netuid pool = dynamic_info[netuid] @@ -1563,6 +1565,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage( substake_.stake ) + total_swapped_tao_value += swapped_tao_value if pool.is_dynamic: slippage_percentage_ = ( 100 * float(slippage) / float(slippage + swapped_tao_value) @@ -1583,7 +1586,7 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): tao_ownership = Balance.from_tao( (alpha_value.tao / issuance.tao) * tao_locked.tao ) - total_global_tao += tao_ownership + total_tao_ownership += tao_ownership else: # TODO what's this var for? alpha_ownership = "0.0000" @@ -1592,27 +1595,25 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): [ str(netuid), # Number symbol, # Symbol - # f"[medium_purple]{tao_ownership}[/medium_purple] ([light_salmon3]{ alpha_ownership }[/light_salmon3][white]%[/white])", # Tao ownership. - f"[medium_purple]{tao_ownership}[/medium_purple]", # Tao ownership. - # f"[dark_sea_green]{ alpha_value }", # Alpha value - f"{substake_.stake.tao:,.4f} {symbol}", - f"{pool.price.tao:.4f} τ/{symbol}", - f"[light_slate_blue]{tao_value}[/light_slate_blue]", # Tao equiv - f"[cadet_blue]{swapped_tao_value}[/cadet_blue] ({slippage_percentage})", # Swap amount. - # f"[light_salmon3]{ alpha_ownership }%[/light_salmon3]", # Ownership. + f"{substake_.stake.tao:,.4f} {symbol}", # Stake (a) + f"{pool.tao_in.tao:,.4f} τ", # TAO pool (t_in) + f"{pool.alpha_in.tao:,.4f} {symbol}", # Alpha Pool a_in + f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a) + f"{pool.alpha_out.tao:,.4f} {symbol}", # Alpha out (a_out) + f"[medium_purple]{tao_ownership}[/medium_purple]", # TAO equiv + f"[light_slate_blue]{tao_value}[/light_slate_blue]", # Exchange Value (α x τ/α) + f"[cadet_blue]{swapped_tao_value}[/cadet_blue] ({slippage_percentage})", # Swap(α) -> τ "[bold cadet_blue]YES[/bold cadet_blue]" if substake_.is_registered - else "[dark_red]NO[/dark_red]", - # Registered. + else "[hot_pink3]NO[/hot_pink3]", # Registered str(Balance.from_tao(per_block_emission).set_unit(netuid)) if substake_.is_registered - else "[dark_red]N/A[/dark_red]", # emission per block. - # f"[light_slate_blue]{locked_value}[/light_slate_blue]", # Locked value + else "[hot_pink3]N/A[/hot_pink3]", # Emission(α/block) ] ) # table = Table(show_footer=True, pad_edge=False, box=None, expand=False, title=f"{name}") table = Table( - title=f"\n[dark_orange]Hotkey: {name}[/dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n", + title=f"\n[dark_orange]Hotkey: {name}[/dark_orange]\n[dark_orange]Network: {subtensor.network}[/dark_orange]\n\nSee below for an explanation of the columns\n", show_footer=True, show_edge=False, header_style="bold white", @@ -1631,47 +1632,62 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): no_wrap=True, ) table.add_column( - f"[white]TAO({Balance.unit})", + f"[white]Stake ({Balance.get_unit(1)})", + footer_style="overline white", + style="rgb(42,161,152)", + justify="center", + ) + table.add_column( + f"[white]TAO pool ({Balance.unit}_in)", style="medium_purple", justify="right", - footer=f"{total_global_tao}", ) table.add_column( - f"[white]Stake({Balance.get_unit(1)})", - footer_style="overline white", - style="rgb(42,161,152)", - justify="center", + f"[white]Alpha pool ({Balance.get_unit(1)}_in)", + style="medium_purple", + justify="right", ) table.add_column( - f"[white]Rate({Balance.unit}/{Balance.get_unit(1)})", + f"[white]Rate \n({Balance.unit}_in/{Balance.get_unit(1)}_in)", footer_style="white", style="light_goldenrod2", justify="center", ) table.add_column( - f"[white]Value({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", + f"[white]Alpha out ({Balance.get_unit(1)}_out)", + style="medium_purple", + justify="right", + ) + table.add_column( + f"[white]TAO equiv \n({Balance.unit}_in x {Balance.get_unit(1)}/{Balance.get_unit(1)}_out)", + style="medium_purple", + justify="right", + footer=f"{total_tao_ownership}", + ) + table.add_column( + f"[white]Exchange Value \n({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})", footer_style="overline white", style="blue", justify="right", footer=f"{total_tao_value}", ) table.add_column( - f"[white]Swap({Balance.get_unit(1)}) -> {Balance.unit}", + f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})", footer_style="overline white", style="white", justify="right", + footer=f"{total_swapped_tao_value}", ) - # table.add_column(f"[white]Control({bittensor.Balance.get_unit(1)})", style="aquamarine3", justify="right") table.add_column("[white]Registered", style="red", justify="right") table.add_column( - f"[white]Emission({Balance.get_unit(1)}/block)", + f"[white]Emission \n({Balance.get_unit(1)}/block)", style="light_goldenrod2", justify="right", ) for row in rows: table.add_row(*row) console.print(table) - return total_global_tao, total_tao_value + return total_tao_ownership, total_tao_value for substake in sub_stakes: hotkey = substake.hotkey_ss58 @@ -1682,13 +1698,20 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): hotkeys_to_substakes[hotkey].append(substake) # Iterate over each hotkey and make a table + counter = 0 + num_hotkeys = len(hotkeys_to_substakes) all_hotkeys_total_global_tao = Balance(0) all_hotkeys_total_tao_value = Balance(0) for hotkey in hotkeys_to_substakes.keys(): + counter += 1 stake, value = table_substakes(hotkey, hotkeys_to_substakes[hotkey]) all_hotkeys_total_global_tao += stake all_hotkeys_total_tao_value += value + if num_hotkeys > 1 and counter < num_hotkeys: + console.print("\nPress Enter to continue to the next hotkey...") + input() + console.print("\n\n") console.print( f"Wallet:\n" @@ -1697,23 +1720,74 @@ def table_substakes(hotkey_: str, substakes: list[StakeInfo]): f" Total TAO ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_global_tao}[/dark_sea_green]\n" f" Total Value ({Balance.unit}): [dark_sea_green]{all_hotkeys_total_tao_value}[/dark_sea_green]" ) - console.print( - """ -[bold white]Description[/bold white]: -Each table displays information about your coldkey's staking accounts with a hotkey. -The header of the table displays the hotkey and the footer displays the total stake and total value of all your staking accounts. -The columns of the table are as follows: - - [bold white]Netuid[/bold white]: The unique identifier for the subnet (its index). - - [bold white]Symbol[/bold white]: The symbol representing the subnet stake's unit. - - [bold white]TAO[/bold white]: The hotkey's TAO balance on this subnet. This is this hotkey's proportion of total TAO staked into the subnet divided by the hotkey's share of outstanding stake. - - [bold white]Stake[/bold white]: The hotkey's stake balance in subnets staking unit. - - [bold white]Rate[/bold white]: The rate of exchange between the subnet's staking unit and the subnet's TAO. - - [bold white]Value[/bold white]: The price of the hotkey's stake in TAO computed via the exchange rate. - - [bold white]Swap[/bold white]: The amount of TAO received when unstaking all of the hotkey's stake (with slippage). - - [bold white]Registered[/bold white]: Whether the hotkey is registered on this subnet. - - [bold white]Emission[/bold white]: If registered, the emission (in stake) attained by this hotkey on this subnet per block. + + console.print("\nPress Enter to continue to column descriptions...") + input() + header = """ +[bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows: """ + console.print(header) + description_table = Table( + show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + ) + + fields = [ + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), + ( + "[bold tan]Symbol[/bold tan]", + "The symbol for the subnet's dynamic TAO token.", + ), + ( + "[bold tan]Stake (α)[/bold tan]", + "Stake this hotkey holds in the subnet, expressed in subnet's dynamic TAO currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]TAO Pool (τ_in)[/bold tan]", + 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "ALPHA Pool (α_in)" description. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Alpha Pool (α_in)[/bold tan]", + 'Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with "TAO Pool(τ_in)", form the subnet pool for every subnet. This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]RATE (τ_in/α_in)[/bold tan]", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool(τ_in) / ALPHA Pool (α_in)). This can change every block when staking or unstaking or emissions occur on this subnet. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Alpha out (α_out)[/bold tan]", + "Total stake in the subnet, expressed in subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]", + 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Pool(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Exchange Value (α x τ/α)[/bold tan]", + "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Swap (α → τ)[/bold tan]", + "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Registered[/bold tan]", + "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Emission (α/block)[/bold tan]", + "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ] + + description_table.add_column( + "Field", + no_wrap=True, + style="bold tan", ) + description_table.add_column("Description", overflow="fold") + for field_name, description in fields: + description_table.add_row(field_name, description) + console.print(description_table) async def move_stake( diff --git a/bittensor_cli/src/commands/subnets.py b/bittensor_cli/src/commands/subnets.py index e311b8fd7..48b07e0c6 100644 --- a/bittensor_cli/src/commands/subnets.py +++ b/bittensor_cli/src/commands/subnets.py @@ -1,13 +1,17 @@ import asyncio import json import sqlite3 -from textwrap import dedent from typing import TYPE_CHECKING, Optional, cast from bittensor_wallet import Wallet from bittensor_wallet.errors import KeyFileError from rich.prompt import Confirm +from rich.console import Console, Group +from rich.spinner import Spinner +from rich.text import Text +from rich.progress import Progress, BarColumn, TextColumn from rich.table import Column, Table +from rich import box from bittensor_cli.src.bittensor.balances import Balance from bittensor_cli.src.bittensor.chain_data import SubnetState @@ -15,6 +19,7 @@ register_extrinsic, burned_register_extrinsic, ) +from rich.live import Live from bittensor_cli.src.bittensor.minigraph import MiniGraph from bittensor_cli.src.commands.wallets import set_id, set_id_prompts from bittensor_cli.src.bittensor.utils import ( @@ -154,112 +159,380 @@ async def _find_event_attributes_in_extrinsic_receipt( async def subnets_list( - subtensor: "SubtensorInterface", reuse_last: bool, html_output: bool, no_cache: bool + subtensor: "SubtensorInterface", + reuse_last: bool, + html_output: bool, + no_cache: bool, + live: bool, ): """List all subnet netuids in the network.""" - # TODO add reuse-last and html-output and no-cache - rows = [] - subnets = await subtensor.get_all_subnet_dynamic_info() - global_weights = await subtensor.get_global_weights( - [subnet.netuid for subnet in subnets] - ) + async def fetch_subnet_data(): + subnets = await subtensor.get_all_subnet_dynamic_info() + global_weights = await subtensor.get_global_weights( + [subnet.netuid for subnet in subnets] + ) + return subnets, global_weights - for subnet in subnets: - netuid = subnet.netuid - global_weight = global_weights.get(netuid) - symbol = f"{subnet.symbol}\u200e" + def define_table(total_emissions: float, total_rate: float): + table = Table( + title=f"\n[underline navajo_white1]Subnets[/underline navajo_white1]" + f"\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n\n", + show_footer=True, + show_edge=False, + header_style="bold white", + border_style="bright_black", + style="bold", + title_justify="center", + show_lines=False, + pad_edge=True, + ) - if netuid == 0: - emission_tao = 0.0 - else: - emission_tao = subnet.emission.tao + table.add_column("[bold white]NETUID", style="white", justify="center") + table.add_column("[bold white]SYMBOL", style="bright_cyan", justify="right") + table.add_column( + f"[bold white]EMISSION ({Balance.get_unit(0)})", + style="tan", + justify="left", + footer=f"τ {total_emissions:.4f}", + ) + table.add_column( + f"[bold white]STAKE ({Balance.get_unit(1)}_out)", + style="light_salmon3", + justify="left", + ) + table.add_column( + f"[bold white]TAO Pool ({Balance.get_unit(0)}_in)", + style="rgb(42,161,152)", + justify="left", + ) + table.add_column( + f"[bold white]Alpha Pool ({Balance.get_unit(1)}_in)", + style="rgb(42,161,152)", + justify="left", + ) + table.add_column( + f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)", + style="medium_purple", + justify="left", + footer=f"τ {total_rate:.4f}", + ) + table.add_column( + "[bold white]Tempo (k/n)", + style="plum2", + justify="left", + overflow="fold", + ) + table.add_column( + "[bold white]Local weight coeff. (γ)", style="steel_blue", justify="left" + ) + return table - rows.append( - ( - str(netuid), - f"[light_goldenrod1]{subnet.symbol}[light_goldenrod1]", - f"τ {emission_tao:.4f}", - f"τ {subnet.tao_in.tao:,.4f}", - f"{subnet.alpha_out.tao:,.4f} {symbol}", - f"{subnet.price.tao:.4f} τ/{symbol}", - f"{subnet.blocks_since_last_step}/{subnet.tempo}", - f"{global_weight:.4f}" if global_weight is not None else "N/A", + # Non-live mode + def create_table(subnets, global_weights): + rows = [] + for subnet in subnets: + netuid = subnet.netuid + global_weight = global_weights.get(netuid) + symbol = f"{subnet.symbol}\u200e" + + if netuid == 0: + emission_tao = 0.0 + else: + emission_tao = subnet.emission.tao + + # Prepare content + netuid_cell = str(netuid) + symbol_cell = f"{subnet.symbol}" + emission_cell = f"{emission_tao:,.4f}" + alpha_out_cell = f"{subnet.alpha_out.tao:,.5f} {symbol}" + tao_in_cell = f"{subnet.tao_in.tao:,.4f} τ" + alpha_in_cell = f"{subnet.alpha_in.tao:,.4f} {symbol}" + price_cell = f"{subnet.price.tao:.4f} τ/{symbol}" + tempo_cell = f"{subnet.blocks_since_last_step}/{subnet.tempo}" + global_weight_cell = ( + f"{global_weight:.4f}" if global_weight is not None else "N/A" ) + + rows.append( + ( + netuid_cell, # Netuid + symbol_cell, # Symbol + emission_cell, # Emission (τ) + alpha_out_cell, # Stake α_out + tao_in_cell, # TAO Pool τ_in + alpha_in_cell, # Alpha Pool α_in + price_cell, # Rate τ_in/α_in + tempo_cell, # Tempo k/n + global_weight_cell, # Local weight coeff. (γ) + ) + ) + + total_emissions = sum( + float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0 ) - total_emissions = sum( - float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0 - ) + total_rate = sum( + float(subnet.price.tao) for subnet in subnets if subnet.netuid != 0 + ) + table = define_table(total_emissions, total_rate) - table = Table( - title=f"\n[underline navajo_white1]Subnets[/underline navajo_white1]\n[navajo_white1]Network: {subtensor.network}[/navajo_white1]\n", - show_footer=True, - show_edge=False, - header_style="bold white", - border_style="bright_black", - style="bold", - title_justify="center", - show_lines=False, - pad_edge=True, - ) + # Sort rows by emission, keeping the root subnet in the first position + sorted_rows = [rows[0]] + sorted( + rows[1:], key=lambda x: float(str(x[2]).replace(",", "")), reverse=True + ) - table.add_column("[bold white]NETUID", style="white", justify="center") - table.add_column("[bold white]SYMBOL", style="bright_cyan", justify="right") - table.add_column( - f"[bold white]EMISSION ({Balance.get_unit(0)})", - style="tan", - justify="right", - footer=f"τ {total_emissions:.4f}", - ) - table.add_column( - f"[bold white]TAO ({Balance.get_unit(0)})", - style="rgb(42,161,152)", - justify="right", - ) - table.add_column( - f"[bold white]STAKE ({Balance.get_unit(1)})", - style="light_salmon3", - justify="right", - ) - table.add_column( - f"[bold white]RATE ({Balance.get_unit(1)}/{Balance.get_unit(0)})", - style="medium_purple", - justify="right", - ) - table.add_column( - "[bold white]Tempo (k/n)", - style="plum2", - justify="right", - overflow="fold", - ) - table.add_column("[bold white]Global weight (γ)", style="dark_sea_green3", justify="center") + # Add rows to the table + for row in sorted_rows: + table.add_row(*row) + return table + + # Live mode + def create_table_live(subnets, global_weights, previous_data): + def format_cell(value, previous_value, unit="", precision=4): + if previous_value is not None: + change = value - previous_value + if change > 0: + change_text = ( + f" [pale_green3](+{change:.{precision}f}{unit})[/pale_green3]" + ) + elif change < 0: + change_text = ( + f" [hot_pink3]({change:.{precision}f}{unit})[/hot_pink3]" + ) + else: + change_text = "" + else: + change_text = "" + return f"{value:,.{precision}f}{unit}{change_text}" - # Sort rows by subnet.emission.tao, keeping the first subnet in the first position - sorted_rows = [rows[0]] + sorted(rows[1:], key=lambda x: x[2], reverse=True) + rows = [] + current_data = {} # To store current values for comparison in the next update + + for subnet in subnets: + netuid = subnet.netuid + global_weight = global_weights.get(netuid) + symbol = f"{subnet.symbol}\u200e" + + if netuid == 0: + emission_tao = 0.0 + else: + emission_tao = subnet.emission.tao + + # Store current values for comparison + current_data[netuid] = { + "emission_tao": emission_tao, + "alpha_out": subnet.alpha_out.tao, + "tao_in": subnet.tao_in.tao, + "alpha_in": subnet.alpha_in.tao, + "price": subnet.price.tao, + "blocks_since_last_step": subnet.blocks_since_last_step, + "global_weight": global_weight, + } + + # Retrieve previous data if available + prev = previous_data.get(netuid) if previous_data else {} + + # Prepare content + netuid_cell = str(netuid) + symbol_cell = f"{subnet.symbol}" + emission_cell = format_cell( + emission_tao, prev.get("emission_tao"), unit="", precision=4 + ) + alpha_out_cell = format_cell( + subnet.alpha_out.tao, + prev.get("alpha_out"), + unit=f" {symbol}", + precision=5, + ) + tao_in_cell = format_cell( + subnet.tao_in.tao, prev.get("tao_in"), unit=" τ", precision=4 + ) + alpha_in_cell = format_cell( + subnet.alpha_in.tao, + prev.get("alpha_in"), + unit=f" {symbol}", + precision=4, + ) + price_cell = format_cell( + subnet.price.tao, prev.get("price"), unit=f" τ/{symbol}", precision=4 + ) + + # Content: Blocks_since_last_step + prev_blocks_since_last_step = prev.get("blocks_since_last_step") + if prev_blocks_since_last_step is not None: + if subnet.blocks_since_last_step >= prev_blocks_since_last_step: + block_change = ( + subnet.blocks_since_last_step - prev_blocks_since_last_step + ) + else: + # Tempo restarted + block_change = ( + subnet.blocks_since_last_step + subnet.tempo + 1 + ) - prev_blocks_since_last_step + if block_change > 0: + block_change_text = f" [pale_green3](+{block_change})[/pale_green3]" + elif block_change < 0: + block_change_text = f" [hot_pink3]({block_change})[/hot_pink3]" + else: + block_change_text = "" + else: + block_change_text = "" + tempo_cell = ( + f"{subnet.blocks_since_last_step}/{subnet.tempo}{block_change_text}" + ) - # Add rows to the table - for row in sorted_rows: - table.add_row(*row) + # Content: Global_weight + prev_global_weight = prev.get("global_weight") + if prev_global_weight is not None and global_weight is not None: + weight_change = float(global_weight) - float(prev_global_weight) + if weight_change > 0: + weight_change_text = ( + f" [pale_green3](+{weight_change:.6f})[/pale_green3]" + ) + elif weight_change < 0: + weight_change_text = ( + f" [hot_pink3]({weight_change:.6f})[/hot_pink3]" + ) + else: + weight_change_text = "" + else: + weight_change_text = "" + + global_weight_cell = ( + f"{global_weight:.4f}{weight_change_text}" + if global_weight is not None + else "N/A" + ) - # Print the table - console.print(table) + rows.append( + ( + netuid_cell, # Netuid + symbol_cell, # Symbol + emission_cell, # Emission (τ) + alpha_out_cell, # Stake α_out + tao_in_cell, # TAO Pool τ_in + alpha_in_cell, # Alpha Pool α_in + price_cell, # Rate τ_in/α_in + tempo_cell, # Tempo k/n + global_weight_cell, # Local weight coeff. (γ) + ) + ) - # TODO: Add description for global weights - console.print( - """ -[bold white]Description[/bold white]: -The table displays relevant information about each subnet on the network. -The columns are as follows: - - [bold white]Netuid[/bold white]: The unique identifier for the subnet (its index). - - [bold white]Symbol[/bold white]: The symbol representing the subnet's stake. - - [bold white]Emission[/bold white]: The amount of TAO added to the subnet every block. Calculated by dividing the TAO (t) column values by the sum of the TAO (t) column. - - [bold white]TAO[/bold white]: The TAO staked into the subnet ( which dynamically changes during stake, unstake and emission events ). - - [bold white]Stake[/bold white]: The outstanding supply of stake across all staking accounts on this subnet. - - [bold white]Rate[/bold white]: The rate of conversion between TAO and the subnet's staking unit. - - [bold white]Tempo[/bold white]: The number of blocks between epochs. Represented as (k/n) where k is the blocks since the last epoch and n is the total blocks in the epoch. - - [bold white]Global weight[/bold white]: The global weight of the subnet across all subnets. + total_emissions = sum( + float(subnet.emission.tao) for subnet in subnets if subnet.netuid != 0 + ) + total_rate = sum( + float(subnet.price.tao) for subnet in subnets if subnet.netuid != 0 + ) + table = define_table(total_emissions, total_rate) + + # Sort rows by emission, keeping the first subnet in the first position + sorted_rows = [rows[0]] + sorted( + rows[1:], + key=lambda x: float(str(x[2]).split()[0].replace(",", "")), + reverse=True, + ) + + # Add rows to the table + for row in sorted_rows: + table.add_row(*row) + return table, current_data + + # Live mode + if live: + refresh_interval = 15 # seconds + + progress = Progress( + TextColumn("[progress.description]{task.description}"), + BarColumn(bar_width=20, style="green", complete_style="green"), + TextColumn("[progress.percentage]{task.percentage:>3.0f}%"), + console=console, + auto_refresh=True, + ) + progress_task = progress.add_task("Updating:", total=refresh_interval) + + previous_data = None + with Live(console=console, screen=True, auto_refresh=True) as live: + try: + while True: + subnets, global_weights = await fetch_subnet_data() + table, current_data = create_table_live( + subnets, global_weights, previous_data + ) + previous_data = current_data + progress.reset(progress_task) + start_time = asyncio.get_event_loop().time() + + # Create the message + message = "\nLive view active. Press [bold red]Ctrl + C[/bold red] to exit" + + # Include the message in the live render group + live_render = Group(table, progress, message) + live.update(live_render) + while not progress.finished: + await asyncio.sleep(0.1) + elapsed = asyncio.get_event_loop().time() - start_time + progress.update(progress_task, completed=elapsed) + + except KeyboardInterrupt: + pass # Ctrl + C + else: + # Non-live mode + subnets, global_weights = await fetch_subnet_data() + table = create_table(subnets, global_weights) + console.print(table) + + console.print("\nPress Enter to continue to column descriptions...") + input() + header = """ +[bold white]Description[/bold white]: The table displays information about each subnet. The columns are as follows: """ - ) + console.print(header) + description_table = Table( + show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True + ) + + fields = [ + ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."), + ( + "[bold tan]Symbol[/bold tan]", + "The symbol for the subnet's dynamic TAO token.", + ), + ( + "[bold tan]Emission (τ)[/bold tan]", + "Shows how the one τ/block emission is distributed among all the subnet pools. For each subnet, this fraction is first calculated by dividing the subnet's TAO Pool (τ_in) by the sum of all TAO Pool (τ_in) across all the subnets. This fraction is then added to the TAO Pool (τ_in) of the subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]STAKE (α_out)[/bold tan]", + "Total stake in the subnet, expressed in the subnet's dynamic TAO currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]TAO Pool (τ_in)[/bold tan]", + 'Units of TAO in the TAO pool reserves for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Alpha Pool (α_in)[/bold tan]", + "Units of subnet dTAO token in the dTAO pool reserves for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]RATE (τ_in/α_in)[/bold tan]", + "Exchange rate between TAO and subnet dTAO token. Calculated as (TAO Pool (τ_in) / Alpha Pool (α_in)). This can change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ( + "[bold tan]Tempo (k/n)[/bold tan]", + 'The tempo status of the subnet. Represented as (k/n) where "k" is the number of blocks elapsed since the last tempo and "n" is the total number of blocks in the tempo. The number "n" is a subnet hyperparameter and does not change every block. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].', + ), + ( + "[bold tan]Local weight coeff (γ)[/bold tan]", + "A multiplication factor between 0 and 1, applied to the relative proportion of a validator's stake in this subnet. Applied as (γ) × (a validator's α stake in this subnet) / (Total α stake in this subnet, i.e., Stake (α_out)). This γ-weighted relative proportion is used, in addition to other factors, in determining the overall stake weight of a validator in this subnet. This is a subnet parameter. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].", + ), + ] + + description_table.add_column("Field", no_wrap=True, style="bold tan") + description_table.add_column("Description", overflow="fold") + for field_name, description in fields: + description_table.add_row(field_name, description) + console.print(description_table) async def show(subtensor: "SubtensorInterface", netuid: int, prompt: bool = True): @@ -455,9 +728,15 @@ async def show_subnet(netuid_: int): no_wrap=True, justify="center", ) - table.add_column("Dividends", style="#8787d7", no_wrap=True, justify="center", footer=f"{relative_emissions_sum:.3f}",) + table.add_column( + "Dividends", + style="#8787d7", + no_wrap=True, + justify="center", + footer=f"{relative_emissions_sum:.3f}", + ) table.add_column("Incentive", style="#5fd7ff", no_wrap=True, justify="center") - + # Hiding relative emissions for now # table.add_column( # "Emissions", @@ -473,12 +752,8 @@ async def show_subnet(netuid_: int): justify="center", footer=str(Balance.from_tao(emission_sum).set_unit(subnet_info.netuid)), ) - table.add_column( - "Hotkey", style="plum2", no_wrap=True, justify="center" - ) - table.add_column( - "Coldkey", style="plum2", no_wrap=True, justify="center" - ) + table.add_column("Hotkey", style="plum2", no_wrap=True, justify="center") + table.add_column("Coldkey", style="plum2", no_wrap=True, justify="center") for row in rows: table.add_row(*row)