diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a7ffbbae..3af5badae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,11 +1,52 @@ # Changelog -## 9.0.0rc4 /2025-02-12 -* Adds sort option to metagraph/show -* Updates metagraph/show to use direct dividends instead of relative -* Updates subnets list to use working tao emissions +## 9.0.0 /2025-02-13 -## 9.0.0rc3 /2025-02-11 +## What's Changed +* Btcli ported to Rao by @ibraheem-opentensor & @thewhaleking in https://github.com/opentensor/btcli/tree/rao-games/pools +* fix netuid from str to int by @roman-opentensor in https://github.com/opentensor/btcli/pull/195 +* add runtime apis to reg by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/196 +* Updated tables (st list, s list) by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/200 +* Modifying descriptions and links in stake and subnets dot py files by @rajkaramchedu in https://github.com/opentensor/btcli/pull/246 +* Fixes Identity Lookup (Rao Games Pools) by @thewhaleking in https://github.com/opentensor/btcli/pull/279 +* Show encrypted hotkeys in w list by @thewhaleking in https://github.com/opentensor/btcli/pull/288 +* Backmerge rao branch to decoding branch by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/290 +* Updates identity, sn identity, and other chain stuff by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/292 +* Updates Rao to decode using chain by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/286 +* Fix/rao remove mention of cost by @camfairchild in https://github.com/opentensor/btcli/pull/293 +* Uses uvloop if it's installed by @thewhaleking in https://github.com/opentensor/btcli/pull/294 +* Feat: Safe staking by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/299 +* Removes stake from w balances by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/301 +* Updates docstrings for commands by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/303 +* Release/9.0.0rc4 by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/306 +* Rao to staging merge (new branch) by @thewhaleking in https://github.com/opentensor/btcli/pull/305 +* [WIP] Rao by @thewhaleking in https://github.com/opentensor/btcli/pull/129 +* Updates e2e tests for rao by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/307 +* Update dividends, adds sort by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/308 +* Final cleanups for Rao by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/309 + +## New Contributors +* @camfairchild made their first contribution in https://github.com/opentensor/btcli/pull/293 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v8.4.4...v9.0.0 + +## 8.4.4 /2025-02-07 - 18:30 PST + +## What's Changed +* Proposals info fix (for dtao governance vote) by @ibraheem-opentensor + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v8.4.3...v8.4.4 + +## 8.4.3 /2025-01-23 + +## What's Changed +* Backmerge main to staging 842 by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/273 +* Fix arg order for set-identity by @thewhaleking in https://github.com/opentensor/btcli/pull/282 +* Adds updates to btwallet3, adds overwrite flag and updates tests by @ibraheem-opentensor in https://github.com/opentensor/btcli/pull/275 + +**Full Changelog**: https://github.com/opentensor/btcli/compare/v8.4.2...v8.4.3 + +## 8.4.2 /2024-12-12 ## What's Changed * Removes the `.value` checks as we no longer use SCALE objects. by @thewhaleking in https://github.com/opentensor/btcli/pull/270 diff --git a/bittensor_cli/__init__.py b/bittensor_cli/__init__.py index b8718f44a..55ce167e0 100644 --- a/bittensor_cli/__init__.py +++ b/bittensor_cli/__init__.py @@ -18,6 +18,6 @@ from .cli import CLIManager -__version__ = "9.0.0rc4" +__version__ = "9.0.0" __all__ = ["CLIManager", "__version__"] diff --git a/bittensor_cli/cli.py b/bittensor_cli/cli.py index cd3356f2f..66aa4036a 100755 --- a/bittensor_cli/cli.py +++ b/bittensor_cli/cli.py @@ -72,7 +72,7 @@ class GitError(Exception): pass -__version__ = "9.0.0rc4" +__version__ = "9.0.0" _core_version = re.match(r"^\d+\.\d+\.\d+", __version__).group(0) @@ -4072,7 +4072,9 @@ def sudo_proposals( [green]$[/green] btcli sudo proposals """ self.verbosity_handler(quiet, verbose) - return self._run_command(sudo.proposals(self.initialize_chain(network))) + return self._run_command( + sudo.proposals(self.initialize_chain(network), verbose) + ) def sudo_senate_vote( self, diff --git a/bittensor_cli/src/bittensor/utils.py b/bittensor_cli/src/bittensor/utils.py index 2ac4019e8..de113e1c3 100644 --- a/bittensor_cli/src/bittensor/utils.py +++ b/bittensor_cli/src/bittensor/utils.py @@ -1355,3 +1355,32 @@ def hex_to_bytes(hex_str: str) -> bytes: else: bytes_result = bytes.fromhex(hex_str) return bytes_result + + +def blocks_to_duration(blocks: int) -> str: + """Convert blocks to human readable duration string using two largest units. + + Args: + blocks (int): Number of blocks (12s per block) + + Returns: + str: Duration string like '2d 5h', '3h 45m', '2m 10s', or '0s' + """ + if blocks <= 0: + return "0s" + + seconds = blocks * 12 + intervals = [ + ("d", 86400), # 60 * 60 * 24 + ("h", 3600), # 60 * 60 + ("m", 60), + ("s", 1), + ] + results = [] + for unit, seconds_per_unit in intervals: + unit_count = seconds // seconds_per_unit + seconds %= seconds_per_unit + if unit_count > 0: + results.append(f"{unit_count}{unit}") + # Return only the first two non-zero units + return " ".join(results[:2]) or "0s" diff --git a/bittensor_cli/src/commands/stake/add.py b/bittensor_cli/src/commands/stake/add.py index 70b02ae83..d400ce967 100644 --- a/bittensor_cli/src/commands/stake/add.py +++ b/bittensor_cli/src/commands/stake/add.py @@ -108,7 +108,7 @@ async def safe_stake_extrinsic( return else: err_out( - f"\n{failure_prelude} with error: {format_error_message(e, subtensor.substrate)}" + f"\n{failure_prelude} with error: {format_error_message(e)}" ) return else: @@ -181,7 +181,7 @@ async def stake_extrinsic( ) except SubstrateRequestException as e: err_out( - f"\n{failure_prelude} with error: {format_error_message(e, subtensor.substrate)}" + f"\n{failure_prelude} with error: {format_error_message(e)}" ) return else: diff --git a/bittensor_cli/src/commands/stake/remove.py b/bittensor_cli/src/commands/stake/remove.py index 1f092ea88..25f53e08e 100644 --- a/bittensor_cli/src/commands/stake/remove.py +++ b/bittensor_cli/src/commands/stake/remove.py @@ -667,7 +667,7 @@ async def _safe_unstake_extrinsic( return else: err_out( - f"\n{failure_prelude} with error: {format_error_message(e, subtensor.substrate)}" + f"\n{failure_prelude} with error: {format_error_message(e)}" ) return diff --git a/bittensor_cli/src/commands/sudo.py b/bittensor_cli/src/commands/sudo.py index 58c6a4b15..dbbdf5376 100644 --- a/bittensor_cli/src/commands/sudo.py +++ b/bittensor_cli/src/commands/sudo.py @@ -17,6 +17,7 @@ print_verbose, normalize_hyperparameters, unlock_key, + blocks_to_duration, ) if TYPE_CHECKING: @@ -269,14 +270,22 @@ def format_call_data(call_data: dict) -> str: call_info = call_details[0] call_function, call_args = next(iter(call_info.items())) - # Extract the argument, handling tuple values - formatted_args = ", ".join( - str(arg[0]) if isinstance(arg, tuple) else str(arg) - for arg in call_args.values() - ) + # Format arguments, handle nested/large payloads + formatted_args = [] + for arg_name, arg_value in call_args.items(): + if isinstance(arg_value, (tuple, list, dict)): + # For large nested, show abbreviated version + content_str = str(arg_value) + if len(content_str) > 20: + formatted_args.append(f"{arg_name}: ... [{len(content_str)}] ...") + else: + formatted_args.append(f"{arg_name}: {arg_value}") + else: + formatted_args.append(f"{arg_name}: {arg_value}") # Format the final output string - return f"{call_function}({formatted_args})" + args_str = ", ".join(formatted_args) + return f"{module}.{call_function}({args_str})" def _validate_proposal_hash(proposal_hash: str) -> bool: @@ -576,24 +585,30 @@ async def get_senate(subtensor: "SubtensorInterface"): return console.print(table) -async def proposals(subtensor: "SubtensorInterface"): +async def proposals(subtensor: "SubtensorInterface", verbose: bool): console.print( ":satellite: Syncing with chain: [white]{}[/white] ...".format( subtensor.network ) ) - print_verbose("Fetching senate members & proposals") block_hash = await subtensor.substrate.get_chain_head() - senate_members, all_proposals = await asyncio.gather( + senate_members, all_proposals, current_block = await asyncio.gather( _get_senate_members(subtensor, block_hash), _get_proposals(subtensor, block_hash), + subtensor.substrate.get_block_number(block_hash), ) - print_verbose("Fetching member information from Chain") registered_delegate_info: dict[ str, DelegatesDetails ] = await subtensor.get_delegate_identities() + title = ( + f"[bold #4196D6]Bittensor Governance Proposals[/bold #4196D6]\n" + f"[steel_blue3]Current Block:[/steel_blue3] {current_block}\t" + f"[steel_blue3]Network:[/steel_blue3] {subtensor.network}\n\n" + f"[steel_blue3]Active Proposals:[/steel_blue3] {len(all_proposals)}\t" + f"[steel_blue3]Senate Size:[/steel_blue3] {len(senate_members)}\n" + ) table = Table( Column( "[white]HASH", @@ -608,8 +623,8 @@ async def proposals(subtensor: "SubtensorInterface"): style="rgb(50,163,219)", ), Column("[white]END", style="bright_cyan"), - Column("[white]CALLDATA", style="dark_sea_green"), - title=f"\n[dark_orange]Proposals\t\t\nActive Proposals: {len(all_proposals)}\t\tSenate Size: {len(senate_members)}\nNetwork: {subtensor.network}", + Column("[white]CALLDATA", style="dark_sea_green", width=30), + title=title, show_footer=True, box=box.SIMPLE_HEAVY, pad_edge=False, @@ -617,16 +632,36 @@ async def proposals(subtensor: "SubtensorInterface"): border_style="bright_black", ) for hash_, (call_data, vote_data) in all_proposals.items(): + blocks_remaining = vote_data.end - current_block + if blocks_remaining > 0: + duration_str = blocks_to_duration(blocks_remaining) + vote_end_cell = f"{vote_data.end} [dim](in {duration_str})[/dim]" + else: + vote_end_cell = f"{vote_data.end} [red](expired)[/red]" + + ayes_threshold = ( + (len(vote_data.ayes) / vote_data.threshold * 100) + if vote_data.threshold > 0 + else 0 + ) + nays_threshold = ( + (len(vote_data.nays) / vote_data.threshold * 100) + if vote_data.threshold > 0 + else 0 + ) table.add_row( - hash_, + hash_ if verbose else f"{hash_[:4]}...{hash_[-4:]}", str(vote_data.threshold), - str(len(vote_data.ayes)), - str(len(vote_data.nays)), + f"{len(vote_data.ayes)} ({ayes_threshold:.2f}%)", + f"{len(vote_data.nays)} ({nays_threshold:.2f}%)", display_votes(vote_data, registered_delegate_info), - str(vote_data.end), + vote_end_cell, format_call_data(call_data), ) - return console.print(table) + console.print(table) + console.print( + "\n[dim]* Both Ayes and Nays percentages are calculated relative to the proposal's threshold.[/dim]" + ) async def senate_vote( diff --git a/requirements.txt b/requirements.txt index b9f56c3a0..521e94944 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ wheel async-property==0.2.2 -async-substrate-interface>=1.0.0rc14 +async-substrate-interface>=1.0.0 aiohttp~=3.10.2 backoff~=2.2.1 GitPython>=3.0.0 @@ -16,7 +16,7 @@ rich~=13.7 scalecodec==1.2.11 typer~=0.12 websockets>=14.1 -bittensor-wallet>=3.0.2 +bittensor-wallet>=3.0.3 plotille pywry plotly \ No newline at end of file diff --git a/tests/e2e_tests/test_senate.py b/tests/e2e_tests/test_senate.py index a0c53d759..7d8a52418 100644 --- a/tests/e2e_tests/test_senate.py +++ b/tests/e2e_tests/test_senate.py @@ -100,9 +100,10 @@ def test_senate(local_chain, wallet_setup): extra_args=[ "--chain", "ws://127.0.0.1:9945", + "--verbose", ], ) - proposals_output = proposals.stdout.splitlines()[8].split() + proposals_output = proposals.stdout.splitlines()[9].split() # Assert the hash is of correct format assert len(proposals_output[0]) == 66 @@ -112,7 +113,7 @@ def test_senate(local_chain, wallet_setup): assert proposals_output[2] == "0" # 0 Nayes for the proposal - assert proposals_output[3] == "0" + assert proposals_output[4] == "0" # Assert initial threshold is 3 assert proposals_output[1] == "3" @@ -145,19 +146,20 @@ def test_senate(local_chain, wallet_setup): extra_args=[ "--chain", "ws://127.0.0.1:9945", + "--verbose", ], ) - proposals_after_aye_output = proposals_after_aye.stdout.splitlines()[8].split() + proposals_after_aye_output = proposals_after_aye.stdout.splitlines()[9].split() # Assert Bob's vote is shown as aye - assert proposals_after_aye_output[4].strip(":") == wallet_bob.hotkey.ss58_address - assert proposals_after_aye_output[5] == "Aye" + assert proposals_after_aye_output[6].strip(":") == wallet_bob.hotkey.ss58_address + assert proposals_after_aye_output[7] == "Aye" # Aye votes increased to 1 assert proposals_after_aye_output[2] == "1" # Nay votes remain 0 - assert proposals_after_aye_output[3] == "0" + assert proposals_after_aye_output[4] == "0" # Register Alice to the root network (0) # Registering to root automatically makes you a senator if eligible @@ -208,18 +210,19 @@ def test_senate(local_chain, wallet_setup): extra_args=[ "--chain", "ws://127.0.0.1:9945", + "--verbose", ], ) proposals_after_nay_output = proposals_after_nay.stdout.splitlines() # Total Ayes to remain 1 - proposals_after_nay_output[8].split()[2] == "1" + proposals_after_nay_output[9].split()[2] == "1" # Total Nays increased to 1 - proposals_after_nay_output[8].split()[3] == "1" + proposals_after_nay_output[9].split()[4] == "1" # Assert Alice has voted Nay - proposals_after_nay_output[9].split()[0].strip( + proposals_after_nay_output[10].split()[0].strip( ":" ) == wallet_alice.hotkey.ss58_address