Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 71 additions & 4 deletions bittensor_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@
from bittensor_cli.src.bittensor import utils
from bittensor_cli.src.bittensor.balances import Balance
from bittensor_cli.src.bittensor.chain_data import SubnetHyperparameters
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
from bittensor_cli.src.bittensor.subtensor_interface import (
SubtensorInterface,
best_connection,
)
from bittensor_cli.src.bittensor.utils import (
console,
err_console,
Expand Down Expand Up @@ -619,7 +622,7 @@ class CLIManager:
wallet_app: typer.Typer
subnets_app: typer.Typer
weights_app: typer.Typer
utils_app = typer.Typer(epilog=_epilog)
utils_app: typer.Typer
view_app: typer.Typer
asyncio_runner = asyncio

Expand Down Expand Up @@ -692,6 +695,7 @@ def __init__(self):
self.weights_app = typer.Typer(epilog=_epilog)
self.view_app = typer.Typer(epilog=_epilog)
self.liquidity_app = typer.Typer(epilog=_epilog)
self.utils_app = typer.Typer(epilog=_epilog)

# config alias
self.app.add_typer(
Expand Down Expand Up @@ -766,7 +770,7 @@ def __init__(self):

# utils app
self.app.add_typer(
self.utils_app, name="utils", no_args_is_help=True, hidden=True
self.utils_app, name="utils", no_args_is_help=True, hidden=False
)

# view app
Expand Down Expand Up @@ -1048,6 +1052,10 @@ def __init__(self):
"remove", rich_help_panel=HELP_PANELS["LIQUIDITY"]["LIQUIDITY_MGMT"]
)(self.liquidity_remove)

# utils app
self.utils_app.command("convert")(self.convert)
self.utils_app.command("latency")(self.best_connection)

def generate_command_tree(self) -> Tree:
"""
Generates a rich.Tree of the commands, subcommands, and groups of this app
Expand Down Expand Up @@ -6296,7 +6304,6 @@ def liquidity_modify(
)

@staticmethod
@utils_app.command("convert")
def convert(
from_rao: Optional[str] = typer.Option(
None, "--rao", help="Convert amount from Rao"
Expand Down Expand Up @@ -6326,6 +6333,66 @@ def convert(
f"{Balance.from_tao(tao).rao}{Balance.rao_unit}",
)

def best_connection(
self,
additional_networks: Optional[list[str]] = typer.Option(
None,
"--network",
help="Network(s) to test for the best connection",
),
):
"""
This command will give you the latency of all finney-like network in additional to any additional networks you specify via the '--network' flag

The results are three-fold. One column is the overall time to initialise a connection, send the requests, and wait for the results. The second column measures single ping-pong speed once connected. The third makes a real world call to fetch the chain head.

EXAMPLE

[green]$[/green] btcli utils latency --network ws://189.234.12.45 --network wss://mysubtensor.duckdns.org

"""
additional_networks = additional_networks or []
if any(not x.startswith("ws") for x in additional_networks):
err_console.print(
"Invalid network endpoint. Ensure you are specifying a valid websocket endpoint"
f" (starting with [{COLORS.G.LINKS}]ws://[/{COLORS.G.LINKS}] or "
f"[{COLORS.G.LINKS}]wss://[/{COLORS.G.LINKS}]).",
)
return False
results: dict[str, list[float]] = self._run_command(
best_connection(Constants.lite_nodes + additional_networks)
)
sorted_results = {
k: v for k, v in sorted(results.items(), key=lambda item: item[1][0])
}
table = Table(
Column("Network"),
Column("End to End Latency", style="cyan"),
Column("Single Request Ping", style="cyan"),
Column("Chain Head Request Latency", style="cyan"),
title="Connection Latencies (seconds)",
caption="lower value is faster",
)
for n_name, (
overall_latency,
single_request,
chain_head,
) in sorted_results.items():
table.add_row(
n_name, str(overall_latency), str(single_request), str(chain_head)
)
console.print(table)
fastest = next(iter(sorted_results.keys()))
if conf_net := self.config.get("network", ""):
if not conf_net.startswith("ws") and conf_net in Constants.networks:
conf_net = Constants.network_map[conf_net]
if conf_net != fastest:
console.print(
f"The fastest network is {fastest}. You currently have {conf_net} selected as your default network."
f"\nYou can update this with {arg__(f'btcli config set --network {fastest}')}"
)
return True

def run(self):
self.app()

Expand Down
1 change: 1 addition & 0 deletions bittensor_cli/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class Constants:
dev_entrypoint = "wss://dev.chain.opentensor.ai:443"
local_entrypoint = "ws://127.0.0.1:9944"
latent_lite_entrypoint = "wss://lite.sub.latent.to:443"
lite_nodes = [finney_entrypoint, subvortex_entrypoint, latent_lite_entrypoint]
network_map = {
"finney": finney_entrypoint,
"test": finney_test_entrypoint,
Expand Down
42 changes: 36 additions & 6 deletions bittensor_cli/src/bittensor/subtensor_interface.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import asyncio
import os
import time
from typing import Optional, Any, Union, TypedDict, Iterable

import aiohttp
from async_substrate_interface.async_substrate import (
DiskCachedAsyncSubstrateInterface,
AsyncSubstrateInterface,
)
from async_substrate_interface.errors import SubstrateRequestException
from async_substrate_interface.utils.storage import StorageKey
from bittensor_wallet import Wallet
from bittensor_wallet.bittensor_wallet import Keypair
from bittensor_wallet.utils import SS58_FORMAT
from scalecodec import GenericCall
from async_substrate_interface.errors import SubstrateRequestException
import typer
import websockets


from async_substrate_interface.async_substrate import (
DiskCachedAsyncSubstrateInterface,
AsyncSubstrateInterface,
)
from bittensor_cli.src.bittensor.chain_data import (
DelegateInfo,
StakeInfo,
Expand Down Expand Up @@ -1654,3 +1655,32 @@ async def get_subnet_prices(
map_[netuid_] = Balance.from_rao(int(current_price * 1e9))

return map_


async def best_connection(networks: list[str]):
"""
Basic function to compare the latency of a given list of websocket endpoints
Args:
networks: list of network URIs

Returns:
{network_name: [end_to_end_latency, single_request_latency, chain_head_request_latency]}

"""
results = {}
for network in networks:
try:
t1 = time.monotonic()
async with websockets.connect(network) as websocket:
pong = await websocket.ping()
latency = await pong
pt1 = time.monotonic()
await websocket.send(
"{'jsonrpc': '2.0', 'method': 'chain_getHead', 'params': [], 'id': '82'}"
)
await websocket.recv()
t2 = time.monotonic()
results[network] = [t2 - t1, latency, t2 - pt1]
except Exception as e:
err_console.print(f"Error attempting network {network}: {e}")
return results
Loading