Very simple initial implementation of Moe's new `dashboard` command

remotes/upstream/doubtfire
derekpierre 2019-11-14 11:57:09 -05:00 committed by Kieran R. Prasch
parent 83ca610c6a
commit 2e6029dfd7
5 changed files with 157 additions and 96 deletions

View File

@ -39,7 +39,7 @@ from nucypher.crypto.powers import SigningPower, TransactingPower
from nucypher.keystore.keypairs import HostingKeypair
from nucypher.keystore.threading import ThreadedSession
from nucypher.network.server import TLSHostingPower
from nucypher.network.status_app.moe import MoeStatusApp
class Moe(Character):

View File

@ -1,4 +1,6 @@
import click
from flask import Flask
from umbral.keys import UmbralPrivateKey
from nucypher.blockchain.eth.interfaces import BlockchainInterfaceFactory
from nucypher.blockchain.eth.registry import InMemoryContractRegistry, LocalContractRegistry
@ -7,11 +9,15 @@ from nucypher.characters.chaotic import Moe
from nucypher.cli import actions
from nucypher.cli.config import nucypher_click_config
from nucypher.cli.types import NETWORK_PORT, EXISTING_READABLE_FILE
from nucypher.keystore.keypairs import HostingKeypair
from nucypher.network.middleware import RestMiddleware
from cryptography.hazmat.primitives.asymmetric import ec
from twisted.internet import reactor
from nucypher.network.server import TLSHostingPower
from nucypher.network.status_app.crawler import NetworkCrawler
from nucypher.network.status_app.moe import MoeDashboardApp
@click.group()
@ -26,7 +32,7 @@ def moe():
@click.option('--teacher', 'teacher_uri', help="An Ursula URI to start learning from (seednode)", type=click.STRING)
@click.option('--registry-filepath', help="Custom contract registry filepath", type=EXISTING_READABLE_FILE)
@click.option('--min-stake', help="The minimum stake the teacher must have to be a teacher", type=click.INT, default=0)
@click.option('--network', help="Network Domain Name", type=click.STRING)
@click.option('--network', help="Network Domain Name", type=click.STRING, default='goerli')
@click.option('--host', help="The host to run Moe services on", type=click.STRING, default='127.0.0.1')
@click.option('--certificate-filepath', help="Pre-signed TLS certificate filepath")
@click.option('--tls-key-filepath', help="TLS private key filepath")
@ -51,12 +57,9 @@ def network_crawler(click_config,
emitter = click_config.emitter
emitter.clear()
emitter.banner(MOE_BANNER)
emitter.echo("> NETWORK CRAWLER")
BlockchainInterfaceFactory.initialize_interface(provider_uri=provider_uri)
if registry_filepath:
registry = LocalContractRegistry.from_latest_publication()
else:
registry = InMemoryContractRegistry.from_latest_publication()
registry = __get_registry(provider_uri, registry_filepath)
# Teacher Ursula
teacher_uris = [teacher_uri] if teacher_uri else None
@ -90,26 +93,84 @@ def network_crawler(click_config,
@moe.command()
def dashboard():
@click.option('--host', help="The host to run Moe services on", type=click.STRING, default='127.0.0.1')
@click.option('--http-port', help="The network port to run Moe services on", type=NETWORK_PORT, default=12500)
@click.option('--registry-filepath', help="Custom contract registry filepath", type=EXISTING_READABLE_FILE)
@click.option('--certificate-filepath', help="Pre-signed TLS certificate filepath")
@click.option('--tls-key-filepath', help="TLS private key filepath")
@click.option('--provider', 'provider_uri', help="Blockchain provider's URI", type=click.STRING)
@click.option('--network', help="Network Domain Name", type=click.STRING, default='goerli')
@click.option('--dry-run', '-x', help="Execute normally without actually starting the node", is_flag=True)
@nucypher_click_config
def dashboard(click_config,
host,
http_port,
registry_filepath,
certificate_filepath,
tls_key_filepath,
provider_uri,
network,
dry_run,
):
"""
Run UI dashboard of NuCypher network.
"""
# #
# # WSGI Service
# #
# self.rest_app = Flask("fleet-monitor")
# rest_app = self.rest_app
# MoeStatusApp(moe=self,
# title='Moe Monitoring Application',
# flask_server=self.rest_app,
# route_url='/')
# Banner
emitter = click_config.emitter
emitter.clear()
emitter.banner(MOE_BANNER)
emitter.echo("> UI DASHBOARD")
registry = __get_registry(provider_uri, registry_filepath)
#
# #
# # Server
# #
# deployer = self._crypto_power.power_ups(TLSHostingPower).get_deployer(rest_app=rest_app, port=self.http_port)
# if not dry_run:
# deployer.run()
# WSGI Service
#
# pass
pass
rest_app = Flask("moe-dashboard")
MoeDashboardApp(title='Moe Dashboard Application',
flask_server=rest_app,
route_url='/',
registry=registry,
network=network)
#
# Server
#
tls_hosting_power = __get_tls_hosting_power(host=host,
tls_certificate_filepath=certificate_filepath,
tls_private_key_filepath=tls_key_filepath)
emitter.message(f"Running Moe Dashboard - https://{host}:{http_port}")
deployer = tls_hosting_power.get_deployer(rest_app=rest_app, port=http_port)
if not dry_run:
deployer.run()
def __get_registry(provider_uri, registry_filepath):
BlockchainInterfaceFactory.initialize_interface(provider_uri=provider_uri)
if registry_filepath:
registry = LocalContractRegistry.from_latest_publication()
else:
registry = InMemoryContractRegistry.from_latest_publication()
return registry
def __get_tls_hosting_power(host: str = None,
tls_certificate_filepath: str = None,
tls_private_key_filepath: str = None):
# Pre-Signed
if tls_certificate_filepath and tls_private_key_filepath:
with open(tls_private_key_filepath, 'rb') as file:
tls_private_key = UmbralPrivateKey.from_bytes(file.read())
tls_hosting_keypair = HostingKeypair(curve=ec.SECP384R1,
host=host,
certificate_filepath=tls_certificate_filepath,
private_key=tls_private_key)
# Self-Sign
else:
tls_hosting_keypair = HostingKeypair(curve=ec.SECP384R1, host=host)
tls_hosting_power = TLSHostingPower(keypair=tls_hosting_keypair, host=host)
return tls_hosting_power

View File

@ -10,7 +10,7 @@ from nucypher.characters.chaotic import Moe
from influxdb import InfluxDBClient
from maya import MayaDT
from nucypher.network.status_app.db import InfluxCrawlerClient
from nucypher.network.status_app.db import BlockchainCrawlerClient
import sqlite3
import os
@ -235,8 +235,9 @@ class NetworkCrawler:
and self._locked_tokens_learning_task.running
and self._moe_known_nodes_learning_task.running)
def get_blockchain_crawler_client(self):
return InfluxCrawlerClient(host='localhost', port=8086, database=self.BLOCKCHAIN_DB_NAME)
@staticmethod
def get_blockchain_crawler_client():
return BlockchainCrawlerClient(host='localhost', port=8086, database=NetworkCrawler.BLOCKCHAIN_DB_NAME)
def get_node_db_row_information(self, node_info, teacher_address, current_period):
# Staker address

View File

@ -5,7 +5,7 @@ from influxdb import InfluxDBClient
from maya import MayaDT
class InfluxCrawlerClient:
class BlockchainCrawlerClient:
"""
Performs operations on data in the MoeBlockchainCrawler DB.
@ -70,4 +70,4 @@ class InfluxCrawlerClient:
return num_stakers_dict
def close(self):
self._client.close()
self._client.close()

View File

@ -6,11 +6,13 @@ import plotly.graph_objs as go
from dash.dependencies import Output, Input
from maya import MayaDT
from nucypher.blockchain.eth.agents import StakingEscrowAgent, ContractAgency
from nucypher.blockchain.eth.token import NU
from nucypher.network.status_app.base import NetworkStatusPage
from nucypher.network.status_app.crawler import NetworkCrawler
class MoeStatusApp(NetworkStatusPage):
class MoeDashboardApp(NetworkStatusPage):
"""
Status application for 'Moe' monitoring node.
"""
@ -24,15 +26,14 @@ class MoeStatusApp(NetworkStatusPage):
'fillFrame': False,
'displayModeBar': False}
def __init__(self, moe, *args, **kwargs):
def __init__(self, registry, network, *args, **kwargs):
super().__init__(*args, **kwargs)
self.moe = moe
from nucypher.network.status_app.crawler import NetworkCrawler
self.moe_crawler = NetworkCrawler(moe=moe)
self.moe_crawler.start()
self.blockchain_db_client = NetworkCrawler.get_blockchain_crawler_client()
self.registry = registry
self.staking_agent = ContractAgency.get_agent(StakingEscrowAgent, registry=self.registry)
self.moe_db_client = self.moe_crawler.get_blockchain_crawler_client()
self.network = network
self.dash_app.layout = html.Div([
dcc.Location(id='url', refresh=False),
@ -57,7 +58,7 @@ class MoeStatusApp(NetworkStatusPage):
html.Div(id='time-remaining'),
html.Div(id='domains'),
html.Div(id='active-stakers'),
html.Div(id='stakers-breakdown'),
html.Div(id='staker-breakdown'),
html.Div(id='staked-tokens'),
], id='stats'),
@ -65,15 +66,15 @@ class MoeStatusApp(NetworkStatusPage):
html.Div([
html.Div(id='prev-num-stakers-graph'),
html.Div(id='prev-locked-stake-graph'),
html.Div(id='locked-stake-graph'),
#html.Div(id='locked-stake-graph'),
], id='widgets'),
# States and Known Nodes Table
html.Div([
html.Div(id='prev-states'),
html.Br(),
html.Div(id='known-nodes'),
])
# html.Div([
# html.Div(id='prev-states'),
# html.Br(),
# html.Div(id='known-nodes'),
# ])
]),
], id='main'),
@ -96,42 +97,41 @@ class MoeStatusApp(NetworkStatusPage):
def header(pathname):
return self.header()
@self.dash_app.callback(Output('prev-states', 'children'),
[Input('state-update-button', 'n_clicks'),
Input('minute-interval', 'n_intervals')])
def state(n_clicks, n_intervals):
return self.previous_states(moe)
# @self.dash_app.callback(Output('prev-states', 'children'),
# [Input('state-update-button', 'n_clicks'),
# Input('minute-interval', 'n_intervals')])
# def state(n_clicks, n_intervals):
# return self.previous_states(moe)
@self.dash_app.callback(Output('known-nodes', 'children'),
[Input('node-update-button', 'n_clicks'),
Input('minute-interval', 'n_intervals')])
def known_nodes(n_clicks, n_intervals):
return self.known_nodes(moe)
# @self.dash_app.callback(Output('known-nodes', 'children'),
# [Input('node-update-button', 'n_clicks'),
# Input('minute-interval', 'n_intervals')])
# def known_nodes(n_clicks, n_intervals):
# return self.known_nodes(moe)
@self.dash_app.callback(Output('active-stakers', 'children'),
[Input('minute-interval', 'n_intervals')])
def active_stakers(n):
confirmed, pending, inactive = moe.staking_agent.partition_stakers_by_activity()
confirmed, pending, inactive = self.staking_agent.partition_stakers_by_activity()
total_stakers = len(confirmed) + len(pending) + len(inactive)
return html.Div([html.H4("Active Ursulas"),
html.H5(f"{len(confirmed)}/{total_stakers}")])
@self.dash_app.callback(Output('stakers-breakdown', 'children'),
@self.dash_app.callback(Output('staker-breakdown', 'children'),
[Input('minute-interval', 'n_intervals')])
def stakers_breakdown(n):
confirmed, pending, inactive = moe.staking_agent.partition_stakers_by_activity()
confirmed, pending, inactive = self.staking_agent.partition_stakers_by_activity()
stakers = dict()
stakers['Active'] = len(confirmed)
stakers['Pending'] = len(pending)
stakers['Inactive'] = len(inactive)
staker_type_numbers = list(stakers.values())
staker_breakdown = list(stakers.values())
fig = go.Figure(
data=[
go.Pie(
labels=list(stakers.keys()),
values=staker_type_numbers,
name='Stakers',
marker=go.bar.Marker(color=staker_type_numbers, colorscale='Viridis')
values=staker_breakdown,
name='Stakers'
)
],
layout=go.Layout(
@ -146,7 +146,7 @@ class MoeStatusApp(NetworkStatusPage):
[Input('minute-interval', 'n_intervals')])
def current_period(pathname):
return html.Div([html.H4("Current Period"),
html.H5(moe.staking_agent.get_current_period())])
html.H5(self.staking_agent.get_current_period())])
@self.dash_app.callback(Output('time-remaining', 'children'),
[Input('minute-interval', 'n_intervals')])
@ -161,16 +161,15 @@ class MoeStatusApp(NetworkStatusPage):
@self.dash_app.callback(Output('domains', 'children'),
[Input('url', 'pathname')]) # on page-load
def domains(pathname):
domains = ' | '.join(moe.learning_domains)
return html.Div([
html.H4('Learning Domains'),
html.H5(domains),
html.H4('Domain'),
html.H5(self.network),
])
@self.dash_app.callback(Output('staked-tokens', 'children'),
[Input('minute-interval', 'n_intervals')])
def staked_tokens(n):
nu = NU.from_nunits(moe.staking_agent.get_global_locked_tokens())
nu = NU.from_nunits(self.staking_agent.get_global_locked_tokens())
return html.Div([
html.H4('Staked Tokens'),
html.H5(f"{nu}"),
@ -180,7 +179,7 @@ class MoeStatusApp(NetworkStatusPage):
[Input('daily-interval', 'n_intervals')])
def prev_locked_tokens(n):
prior_periods = 30
locked_tokens_dict = self.moe_db_client.get_historical_locked_tokens_over_range(prior_periods)
locked_tokens_dict = self.blockchain_db_client.get_historical_locked_tokens_over_range(prior_periods)
token_values = list(locked_tokens_dict.values())
fig = go.Figure(data=[
go.Bar(
@ -207,7 +206,7 @@ class MoeStatusApp(NetworkStatusPage):
[Input('daily-interval', 'n_intervals')])
def historical_known_nodes(n):
prior_periods = 30
num_stakers_dict = self.moe_db_client.get_historical_num_stakers_over_range(prior_periods)
num_stakers_dict = self.blockchain_db_client.get_historical_num_stakers_over_range(prior_periods)
marker_color = 'rgb(0, 163, 239)'
fig = go.Figure(data=[
go.Scatter(
@ -230,31 +229,31 @@ class MoeStatusApp(NetworkStatusPage):
fig['layout'].update(autosize=True, width=None, height=None)
return dcc.Graph(figure=fig, id='prev-stakers-graph', config=self.GRAPH_CONFIG)
@self.dash_app.callback(Output('locked-stake-graph', 'children'),
[Input('daily-interval', 'n_intervals')])
def future_locked_tokens(n):
token_counter = self.moe_crawler.snapshot['future_locked_tokens']
periods = len(token_counter)
period_range = list(range(1, periods + 1))
token_counter_values = list(token_counter.values())
fig = go.Figure(data=[
go.Bar(
textposition='auto',
x=period_range,
y=token_counter_values,
name='Stake',
marker=go.bar.Marker(color=token_counter_values, colorscale='Viridis')
)
],
layout=go.Layout(
title=f'Staked NU over the next {periods} days.',
xaxis={'title': 'Days'},
yaxis={'title': 'NU Tokens', 'rangemode': 'tozero'},
showlegend=False,
legend=go.layout.Legend(x=0, y=1.0),
paper_bgcolor='rgba(0,0,0,0)',
plot_bgcolor='rgba(0,0,0,0)'
))
fig['layout'].update(autosize=True, width=None, height=None)
return dcc.Graph(figure=fig, id='locked-graph', config=self.GRAPH_CONFIG)
# @self.dash_app.callback(Output('locked-stake-graph', 'children'),
# [Input('daily-interval', 'n_intervals')])
# def future_locked_tokens(n):
# token_counter = self.moe_crawler.snapshot['future_locked_tokens']
# periods = len(token_counter)
# period_range = list(range(1, periods + 1))
# token_counter_values = list(token_counter.values())
# fig = go.Figure(data=[
# go.Bar(
# textposition='auto',
# x=period_range,
# y=token_counter_values,
# name='Stake',
# marker=go.bar.Marker(color=token_counter_values, colorscale='Viridis')
# )
# ],
# layout=go.Layout(
# title=f'Staked NU over the next {periods} days.',
# xaxis={'title': 'Days'},
# yaxis={'title': 'NU Tokens', 'rangemode': 'tozero'},
# showlegend=False,
# legend=go.layout.Legend(x=0, y=1.0),
# paper_bgcolor='rgba(0,0,0,0)',
# plot_bgcolor='rgba(0,0,0,0)'
# ))
#
# fig['layout'].update(autosize=True, width=None, height=None)
# return dcc.Graph(figure=fig, id='locked-graph', config=self.GRAPH_CONFIG)