Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Commander (BTTUI) Backend Server #1861

Draft
wants to merge 28 commits into
base: staging
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
77dd3c6
First command implemented in server.
thewhaleking May 9, 2024
f447ba6
[WIP] check-in
thewhaleking May 10, 2024
f4871de
[WIP] check-in
thewhaleking May 10, 2024
fbd4ecc
[WIP] check-in
thewhaleking May 10, 2024
56323b1
[WIP] check-in
thewhaleking May 13, 2024
28f649c
Initial removal of nest_asyncio, define uvicorn[standard] so that uvl…
thewhaleking May 13, 2024
ffe9b43
[WIP] Check-in. Optimised some of IO-bound code in the commands, made…
thewhaleking May 14, 2024
6a4f205
[WIP] Check-in.
thewhaleking May 15, 2024
b83aeaa
[WIP] Check-in.
thewhaleking May 15, 2024
9c8e854
[WIP] Check-in.
thewhaleking May 16, 2024
8bd2801
[WIP] Fixed wallet overview
thewhaleking May 16, 2024
9d141d5
[WIP] Unlock cold key and transfer working.
thewhaleking May 16, 2024
d1f4931
[WIP] Wallet Inspect working
thewhaleking May 17, 2024
d7456a6
[WIP] Add check_config to setup_get and unlock cold key
thewhaleking May 17, 2024
ab987e9
[WIP] Fixed Wallet Inspection
thewhaleking May 17, 2024
77b3084
[WIP] Convert dataclasses to actual dataclasses
thewhaleking May 17, 2024
aff9980
[WIP] Added comments for the future.
thewhaleking May 20, 2024
cf455fa
[WIP] Wallet Balance working.
thewhaleking May 22, 2024
e00dc04
[WIP] better error
thewhaleking May 22, 2024
dfd685b
[WIP] Non-POW Wallet functions ported.
thewhaleking May 22, 2024
94f6aed
[WIP] New async RPC call method (experimental)
thewhaleking May 23, 2024
48937ba
TODO
thewhaleking May 24, 2024
a1e769c
Merge branch 'staging' into feature/bittui-backend-server/thewhaleking
thewhaleking May 28, 2024
c390f3a
Merge remote-tracking branch 'origin/feature/bittui-backend-server/th…
thewhaleking May 28, 2024
37017d9
[WIP] Check-in
thewhaleking May 28, 2024
1b06239
[WIP] Stake Show command working, black formatting
thewhaleking May 29, 2024
22e7e73
Merge branch 'staging' into feature/bittui-backend-server/thewhaleking
thewhaleking May 29, 2024
cd050f1
[WIP] Stake Add done
thewhaleking May 29, 2024
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
3 changes: 0 additions & 3 deletions bittensor/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,6 @@

# Install and apply nest asyncio to allow the async functions
# to run in a .ipynb
import nest_asyncio

nest_asyncio.apply()

# Bittensor code and protocol version.
__version__ = "7.0.0"
Expand Down
34 changes: 34 additions & 0 deletions bittensor/commander/data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import Optional

from pydantic import BaseModel

import bittensor


class Config:
netuid = 0
wallet = None
initialized = False
json_encrypted_path = None
json_encrypted_pw = None

def __bool__(self):
return self.initialized

def setup(self, conf: "ConfigBody"):
self.initialized = True
self.netuid = conf.netuid
self.wallet = bittensor.wallet(
name=conf.wallet["name"],
hotkey=conf.wallet["hotkey"],
path=conf.wallet["path"],
) # maybe config
self.json_encrypted_path = conf.json_encrypted_path
self.json_encrypted_pw = conf.json_encrypted_pw


class ConfigBody(BaseModel):
netuid: int = 0
wallet: dict
json_encrypted_path: Optional[str] = None
json_encrypted_pw: Optional[str] = None
116 changes: 116 additions & 0 deletions bittensor/commander/server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import asyncio
from functools import partial
import time

# import sys

from fastapi import FastAPI, Request, Response, HTTPException, Depends
from fastapi.responses import JSONResponse
from pydantic import BaseModel

# import uvicorn

import bittensor
from bittensor.commands import network
from bittensor.commands import metagraph
from bittensor.commands import register
from bittensor.commands import list as list_commands
from bittensor.commands import overview
from bittensor.commands import transfer
from bittensor.commands import inspect
from bittensor.commands import wallets
from bittensor.commander import data


# TODO app-wide error-handling

sub = bittensor.subtensor("test")
app = FastAPI(debug=True)
config = data.Config()


@app.post("/setup")
async def setup(conf: data.ConfigBody):
config.setup(conf)
return JSONResponse(status_code=200, content={"success": True})


async def check_config():
if not config:
raise HTTPException(status_code=401, detail="Config missing")


async def run_fn(command_class, *params):
start = time.time()
try:
if hasattr(command_class, "commander_run"):
# Offload synchronous execution to the threadpool
response_content = await command_class.commander_run(sub, config=config)
print(command_class, time.time() - start)
return JSONResponse(content=response_content)
else:
raise HTTPException(
status_code=501, detail="Command implementation missing"
)
except Exception as e:
raise
# raise HTTPException(status_code=500, detail=str(e))


# Subnets
@app.get("/subnets/{sub_cmd}", dependencies=[Depends(check_config)])
async def get_subnet(sub_cmd: str):
routing_list = {
"list": network.SubnetListCommand,
"metagraph": metagraph.MetagraphCommand,
"lock_cost": network.SubnetLockCostCommand,
"create": network.RegisterSubnetworkCommand,
"pow_register": register.PowRegisterCommand,
"register": register.RegisterCommand,
"hyperparameters": network.SubnetHyperparamsCommand,
}
return await run_fn(routing_list[sub_cmd])


# Wallet
@app.get("/wallet/hotkey/{sub_cmd}", dependencies=[Depends(check_config)])
async def wallet_hotkey(sub_cmd: str):
routing_list = {
"new": wallets.NewHotkeyCommand,
"regen": wallets.RegenHotkeyCommand,
"swap": register.SwapHotkeyCommand,
}
return await run_fn(routing_list[sub_cmd])


@app.get("/wallet/coldkey/{sub_cmd}", dependencies=[Depends(check_config)])
async def wallet_coldkey(sub_cmd: str):
routing_list = {
"new": wallets.NewColdkeyCommand,
"regen": wallets.RegenColdkeyCommand,
"regen/pub": wallets.RegenColdkeypubCommand,
}
return await run_fn(routing_list[sub_cmd])


@app.get("/wallet/{sub_cmd}", dependencies=[Depends(check_config)])
async def wallet(sub_cmd: str):
routing_list = {
"list": list_commands.ListCommand,
"overview": overview.OverviewCommand,
"transfer": transfer.TransferCommand,
"inspect": inspect.InspectCommand,
"balance": wallets.WalletBalanceCommand,
"create": wallets.WalletCreateCommand,
"faucet": register.RunFaucetCommand,
"update": wallets.UpdateWalletCommand,
"history": wallets.GetWalletHistoryCommand,
}
return await run_fn(routing_list[sub_cmd])


# Root

# Sudo

# Stake
84 changes: 84 additions & 0 deletions bittensor/commands/metagraph.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@
# DEALINGS IN THE SOFTWARE.

import argparse
import asyncio
from collections import namedtuple
from functools import reduce


import bittensor
from rich.table import Table

from .utils import check_netuid_set

console = bittensor.__console__ # type: ignore
Expand Down Expand Up @@ -82,6 +88,84 @@ def run(cli: "bittensor.cli"):
subtensor.close()
bittensor.logging.debug("closing subtensor connection")

@staticmethod
async def commander_run(
subtensor: "bittensor.subtensor", config
) -> dict: # find out what netuid is
def reducer(x, uid):
ep = metagraph.axons[uid]
new_row = [
str(metagraph.neurons[uid].uid),
"{:.5f}".format(metagraph.total_stake[uid]),
"{:.5f}".format(metagraph.ranks[uid]),
"{:.5f}".format(metagraph.trust[uid]),
"{:.5f}".format(metagraph.consensus[uid]),
"{:.5f}".format(metagraph.incentive[uid]),
"{:.5f}".format(metagraph.dividends[uid]),
"{}".format(int(metagraph.emission[uid] * 1000000000)),
"{:.5f}".format(metagraph.validator_trust[uid]),
"*" if metagraph.validator_permit[uid] else "",
str((metagraph.block.item() - metagraph.last_update[uid].item())),
str(metagraph.active[uid].item()),
(
ep.ip + ":" + str(ep.port)
if ep.is_serving
else "[yellow]none[/yellow]"
),
ep.hotkey[:10],
ep.coldkey[:10],
]
return [
x[0] + [new_row] if x[0] else [new_row], # rows
x[1] + metagraph.total_stake[uid], # total_stake
x[2] + metagraph.ranks[uid], # total_rank
x[3] + metagraph.validator_trust[uid], # total_validator_trust
x[4] + metagraph.trust[uid], # total_trust
x[5] + metagraph.consensus[uid], # total_consensus
x[6] + metagraph.incentive[uid], # total_incentive
x[7] + metagraph.dividends[uid], # total_dividends
x[8] + int(metagraph.emission[uid] * 1000000000), # total_emission
]

netuid = config.netuid
metagraph: bittensor.metagraph = await asyncio.get_event_loop().run_in_executor(
None, subtensor.metagraph, netuid
)
metagraph.save()
TableData = namedtuple(
"TableData",
[
"rows",
"total_stake",
"total_rank",
"total_validator_trust",
"total_trust",
"total_consensus",
"total_incentive",
"total_dividends",
"total_emission",
"total_neurons",
"difficulty",
"subnet_emission",
"total_issuance",
],
)
table = TableData(
*(
reduce(
reducer, metagraph.uids, [[], 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0]
)
),
len(metagraph.uids),
subtensor.difficulty(netuid),
bittensor.Balance.from_tao(
subtensor.get_emission_value_by_subnet(netuid)
).to_dict(),
bittensor.Balance.from_rao(subtensor.total_issuance().rao).to_dict()
)
return table._asdict()

@staticmethod
def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
r"""Prints an entire metagraph."""
console = bittensor.__console__
Expand Down
59 changes: 59 additions & 0 deletions bittensor/commands/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# 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 asyncio
import argparse
import bittensor
from . import defaults
Expand Down Expand Up @@ -72,6 +73,19 @@ def run(cli: "bittensor.cli"):
subtensor.close()
bittensor.logging.debug("closing subtensor connection")

@staticmethod
async def commander_run(subtensor: "bittensor.subtensor", config):
success = subtensor.register_subnetwork(wallet=config.wallet, prompt=False)
# todo investigate why this isn't working
if success:
# todo params
# if config.get("set_identity"):
# subtensor.close()
# # TODO set identity when set_identity is true
return {"success": True}
else:
return {"success": False}

@staticmethod
def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
r"""Register a subnetwork"""
Expand Down Expand Up @@ -159,6 +173,12 @@ def run(cli: "bittensor.cli"):
subtensor.close()
bittensor.logging.debug("closing subtensor connection")

@staticmethod
async def commander_run(subtensor: "bittensor.subtensor", config):
return bittensor.utils.balance.Balance(
subtensor.get_subnet_burn_cost()
).to_dict()

@staticmethod
def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
r"""View locking cost of creating a new subnetwork"""
Expand Down Expand Up @@ -235,6 +255,38 @@ def run(cli: "bittensor.cli"):
subtensor.close()
bittensor.logging.debug("closing subtensor connection")

@staticmethod
async def commander_run(
subtensor: "bittensor.subtensor", config
) -> List[List[str]]:
subnets: List[bittensor.SubnetInfo]
delegate_info: Optional[Dict[str, DelegatesDetails]]
event_loop = asyncio.get_event_loop()
subnets, delegate_info = await asyncio.gather(
event_loop.run_in_executor(None, subtensor.get_all_subnets_info),
event_loop.run_in_executor(
None,
lambda: get_delegates_details(url=bittensor.__delegates_details_url__),
),
)
structure = [
["NETUID", "N", "MAX_N", "EMISSION", "TEMPO", "BURN", "POW", "SUDO"]
+ [
[
str(s.netuid),
str(s.subnetwork_n),
bittensor.utils.formatting.millify(s.max_n),
f"{s.emission_value / bittensor.utils.RAOPERTAO * 100:0.2f}%",
str(s.tempo),
str(s.burn),
str(bittensor.utils.formatting.millify(s.difficulty)),
f"{delegate_info[s.owner_ss58].name if s.owner_ss58 in delegate_info else s.owner_ss58}",
]
for s in subnets
]
]
return structure

@staticmethod
def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
r"""List all subnet netuids in the network."""
Expand Down Expand Up @@ -470,6 +522,13 @@ def run(cli: "bittensor.cli"):
subtensor.close()
bittensor.logging.debug("closing subtensor connection")

@staticmethod
async def commander_run(subtensor: "bittensor.subtensor", config) -> dict:
subnet: bittensor.SubnetHyperparameters = subtensor.get_subnet_hyperparameters(
config.netuid
)
return subnet.__dict__

@staticmethod
def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
r"""View hyperparameters of a subnetwork."""
Expand Down
21 changes: 21 additions & 0 deletions bittensor/commands/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,27 @@ def run(cli: "bittensor.cli"):
subtensor.close()
bittensor.logging.debug("closing subtensor connection")

@staticmethod
async def commander_run(subtensor: "bittensor.subtensor", config):
# TODO figure out what tpb is
if not subtensor.subnet_exists(netuid=config.get("netuid")):
return {"success": False, "msg": f"Subnet {netuid} does not exist"}

registered = subtensor.register(
wallet=config.get("wallet"),
netuid=config.get("netuid"),
prompt=False,
tpb=config.get("tpb"),
update_interval=config.get("update_interval"),
num_processes=config.get(
"num_processes", None
), # TODO look over these as they need to come from POW reg
cuda=config.get("cuda"),
dev_id=config.get("dev_id"),
# TODO output in place and log verbose
)
return {"success": True, "msg": registered}

@staticmethod
def _run(cli: "bittensor.cli", subtensor: "bittensor.subtensor"):
r"""Register neuron."""
Expand Down
Loading