Skip to content

Commit

Permalink
Merge pull request #296 from opentensor/release/8.4.4
Browse files Browse the repository at this point in the history
Release/8.4.4
  • Loading branch information
ibraheem-opentensor authored Feb 8, 2025
2 parents 9f538ea + ef86a46 commit 29c0cea
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 29 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 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
Expand Down
2 changes: 1 addition & 1 deletion bittensor_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,6 @@
from .cli import CLIManager


__version__ = "8.4.3"
__version__ = "8.4.4"

__all__ = ["CLIManager", "__version__"]
6 changes: 4 additions & 2 deletions bittensor_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class GitError(Exception):
pass


__version__ = "8.4.3"
__version__ = "8.4.4"


_core_version = re.match(r"^\d+\.\d+\.\d+", __version__).group(0)
Expand Down Expand Up @@ -2798,7 +2798,9 @@ def root_proposals(
[green]$[/green] btcli root proposals
"""
self.verbosity_handler(quiet, verbose)
return self._run_command(root.proposals(self.initialize_chain(network)))
return self._run_command(
root.proposals(self.initialize_chain(network), verbose)
)

def root_set_take(
self,
Expand Down
29 changes: 29 additions & 0 deletions bittensor_cli/src/bittensor/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1010,3 +1010,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"
69 changes: 52 additions & 17 deletions bittensor_cli/src/commands/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
update_metadata_table,
group_subnets,
unlock_key,
blocks_to_duration,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -82,14 +83,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})"


async def _get_senate_members(
Expand Down Expand Up @@ -1210,24 +1219,30 @@ async def register(wallet: Wallet, subtensor: SubtensorInterface, prompt: bool):
)


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",
Expand All @@ -1242,25 +1257,45 @@ 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,
width=None,
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 set_take(wallet: Wallet, subtensor: SubtensorInterface, take: float) -> bool:
Expand Down
21 changes: 12 additions & 9 deletions tests/e2e_tests/test_senate.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,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
Expand All @@ -110,7 +111,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"
Expand Down Expand Up @@ -143,19 +144,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
Expand Down Expand Up @@ -204,18 +206,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

Expand Down

0 comments on commit 29c0cea

Please sign in to comment.