2017-09-27 05:30:09 +00:00
|
|
|
# Copyright (c) 2014-2017 esotericnonsense (Daniel Edgecumbe)
|
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file COPYING or https://opensource.org/licenses/mit-license.php
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
import math
|
|
|
|
import curses
|
|
|
|
import asyncio
|
|
|
|
from decimal import Decimal
|
|
|
|
|
2017-09-27 16:18:09 +00:00
|
|
|
from macros import MIN_WINDOW_SIZE
|
|
|
|
|
2017-09-27 05:30:09 +00:00
|
|
|
|
|
|
|
class MonitorView(object):
|
|
|
|
def __init__(self, client):
|
|
|
|
self._client = client
|
|
|
|
|
|
|
|
self._pad = None
|
|
|
|
|
|
|
|
self._visible = False
|
|
|
|
|
|
|
|
self._lock = asyncio.Lock()
|
|
|
|
self._bestblockhash = None
|
|
|
|
self._bestblockheader = None # raw json blockheader
|
|
|
|
self._bestblock = None # raw json block
|
|
|
|
self._bestcoinbase = None # raw json tx
|
2017-09-27 18:09:16 +00:00
|
|
|
self._mempoolinfo = None # raw mempoolinfo
|
2017-09-27 18:19:54 +00:00
|
|
|
self._estimatesmartfee = {} # blocks -> feerate/kB
|
2017-09-27 05:30:09 +00:00
|
|
|
self._dt = None
|
2017-09-27 19:42:19 +00:00
|
|
|
self._uptime = None # raw uptime from bitcoind (seconds)
|
2017-09-27 05:30:09 +00:00
|
|
|
|
2017-09-27 16:18:09 +00:00
|
|
|
self._window_size = MIN_WINDOW_SIZE
|
|
|
|
|
2017-09-28 02:06:10 +00:00
|
|
|
async def _draw(self):
|
2017-09-27 05:30:09 +00:00
|
|
|
# TODO: figure out window width etc.
|
|
|
|
|
|
|
|
if self._pad is not None:
|
|
|
|
self._pad.clear()
|
|
|
|
else:
|
|
|
|
self._pad = curses.newpad(20, 100)
|
|
|
|
|
|
|
|
if self._bestblockheader:
|
|
|
|
bbh = self._bestblockheader
|
|
|
|
self._pad.addstr(0, 1, "Height: {: 8d}".format(bbh["height"]))
|
|
|
|
self._pad.addstr(0, 36, bbh["hash"])
|
|
|
|
|
|
|
|
if self._bestblock:
|
|
|
|
bb = self._bestblock
|
|
|
|
self._pad.addstr(1, 1, "Size: {: 8d} bytes Weight: {: 8d} WU".format(
|
|
|
|
bb["size"],
|
|
|
|
bb["weight"]
|
|
|
|
))
|
|
|
|
|
|
|
|
self._pad.addstr(1, 64, "Block timestamp: {}".format(
|
|
|
|
datetime.datetime.utcfromtimestamp(bb["time"]),
|
|
|
|
))
|
|
|
|
|
|
|
|
if self._dt:
|
|
|
|
stampdelta = int(
|
|
|
|
(self._dt - datetime.datetime.utcfromtimestamp(bb["time"]))
|
|
|
|
.total_seconds())
|
|
|
|
|
|
|
|
if stampdelta > 3600*3: # probably syncing
|
|
|
|
stampdelta_string = " (syncing)"
|
|
|
|
|
|
|
|
elif stampdelta > 0:
|
|
|
|
m, s = divmod(stampdelta, 60)
|
|
|
|
h, m = divmod(m, 60)
|
|
|
|
d, h = divmod(h, 24)
|
|
|
|
stampdelta_string = "({:d}d {:02d}:{:02d}:{:02d} by stamp)".format(d,h,m,s)
|
|
|
|
|
|
|
|
else:
|
|
|
|
stampdelta_string = " (stamp in future)"
|
|
|
|
|
|
|
|
self._pad.addstr(2, 64, "Age: {}".format(
|
|
|
|
stampdelta_string))
|
|
|
|
|
|
|
|
self._pad.addstr(2, 1, "Transactions: {} ({} bytes/tx, {} WU/tx)".format(
|
|
|
|
len(bb["tx"]),
|
|
|
|
bb["size"] // len(bb["tx"]),
|
|
|
|
bb["weight"] // len(bb["tx"]),
|
|
|
|
))
|
|
|
|
|
|
|
|
if self._bestcoinbase:
|
|
|
|
bcb = self._bestcoinbase
|
|
|
|
reward = sum(vout["value"] for vout in bcb["vout"])
|
|
|
|
|
|
|
|
# TODO: if chain is regtest, this is different
|
|
|
|
halvings = bb["height"] // 210000
|
|
|
|
block_subsidy = Decimal(50 * (0.5 ** halvings))
|
|
|
|
|
|
|
|
total_fees = Decimal(reward) - block_subsidy
|
|
|
|
|
|
|
|
self._pad.addstr(4, 1, "Block reward: {:.6f} BTC".format(
|
|
|
|
reward))
|
|
|
|
|
|
|
|
if len(bb["tx"]) > 1:
|
|
|
|
if reward > 0:
|
|
|
|
fee_pct = total_fees * 100 / Decimal(reward)
|
|
|
|
else:
|
|
|
|
fee_pct = 0
|
|
|
|
mbtc_per_tx = (total_fees / (len(bb["tx"]) - 1)) * 1000
|
|
|
|
|
|
|
|
# 80 bytes for the block header.
|
|
|
|
total_tx_size = bb["size"] - 80 - bcb["size"]
|
|
|
|
if total_tx_size > 0:
|
|
|
|
sat_per_kb = ((total_fees * 1024) / total_tx_size) * 100000000
|
|
|
|
else:
|
|
|
|
sat_per_kb = 0
|
|
|
|
self._pad.addstr(4, 34, "Fees: {: 8.6f} BTC ({: 6.2f}%, avg {: 6.2f} mBTC/tx, ~{: 7.0f} sat/kB)".format(total_fees, fee_pct, mbtc_per_tx, sat_per_kb))
|
|
|
|
|
|
|
|
self._pad.addstr(6, 1, "Diff: {:,}".format(
|
|
|
|
int(bb["difficulty"]),
|
|
|
|
))
|
|
|
|
self._pad.addstr(7, 1, "Chain work: 2**{:.6f}".format(
|
|
|
|
math.log(int(bb["chainwork"], 16), 2),
|
|
|
|
))
|
|
|
|
|
2017-09-27 18:09:16 +00:00
|
|
|
if self._mempoolinfo:
|
|
|
|
self._pad.addstr(9, 1, "Mempool transactions: {: 6d} ({: 5.2f} MiB)".format(
|
|
|
|
self._mempoolinfo["size"],
|
|
|
|
self._mempoolinfo["bytes"] / 1048576,
|
|
|
|
))
|
|
|
|
|
2017-09-27 18:19:54 +00:00
|
|
|
if self._estimatesmartfee:
|
|
|
|
estimates = " ".join(
|
|
|
|
"({: 2d}: {: 8.0f} sat/kB)".format(b, fr*10**8)
|
|
|
|
for b, fr in sorted(self._estimatesmartfee.items())
|
|
|
|
)
|
|
|
|
self._pad.addstr(11, 1, "estimatesmartfee: {}".format(estimates))
|
|
|
|
|
2017-09-27 19:42:19 +00:00
|
|
|
if self._uptime:
|
|
|
|
self._pad.addstr(13, 1, "uptime: {}".format(datetime.timedelta(seconds=self._uptime)))
|
|
|
|
|
2017-09-28 02:06:10 +00:00
|
|
|
await self._draw_pad_to_screen()
|
2017-09-27 16:18:09 +00:00
|
|
|
|
2017-09-28 02:06:10 +00:00
|
|
|
async def _draw_pad_to_screen(self):
|
2017-09-27 16:18:09 +00:00
|
|
|
maxy, maxx = self._window_size
|
|
|
|
if maxy < 8 or maxx < 3:
|
|
|
|
return # Can't do it
|
|
|
|
|
|
|
|
self._pad.refresh(0, 0, 4, 0, min(maxy-3, 24), min(maxx-1, 100))
|
2017-09-27 05:30:09 +00:00
|
|
|
|
|
|
|
async def draw(self):
|
|
|
|
with await self._lock:
|
2017-09-28 02:06:10 +00:00
|
|
|
if self._visible:
|
|
|
|
await self._draw()
|
2017-09-27 05:30:09 +00:00
|
|
|
|
|
|
|
async def on_bestblockhash(self, key, obj):
|
|
|
|
try:
|
|
|
|
bestblockhash = obj["result"]
|
|
|
|
except KeyError:
|
|
|
|
return
|
|
|
|
|
|
|
|
draw = False
|
|
|
|
with await self._lock:
|
|
|
|
if bestblockhash != self._bestblockhash:
|
|
|
|
draw = True
|
|
|
|
self._bestblockhash = bestblockhash
|
|
|
|
|
|
|
|
j = await self._client.request("getblockheader", [bestblockhash])
|
|
|
|
self._bestblockheader = j["result"]
|
|
|
|
|
|
|
|
j = await self._client.request("getblock", [bestblockhash])
|
|
|
|
self._bestblock = j["result"]
|
|
|
|
|
|
|
|
j = await self._client.request("getrawtransaction", [j["result"]["tx"][0], 1])
|
|
|
|
self._bestcoinbase = j["result"]
|
|
|
|
|
2017-09-28 02:06:10 +00:00
|
|
|
if draw:
|
2017-09-27 05:30:09 +00:00
|
|
|
await self.draw()
|
|
|
|
|
2017-09-27 18:09:16 +00:00
|
|
|
async def on_mempoolinfo(self, key, obj):
|
|
|
|
try:
|
|
|
|
self._mempoolinfo = obj["result"]
|
|
|
|
except KeyError:
|
|
|
|
return
|
|
|
|
|
2017-09-28 02:06:10 +00:00
|
|
|
await self.draw()
|
2017-09-27 18:09:16 +00:00
|
|
|
|
2017-09-27 18:19:54 +00:00
|
|
|
async def on_estimatesmartfee(self, key, obj):
|
|
|
|
try:
|
|
|
|
estimatesmartfee = obj["result"]
|
|
|
|
except KeyError:
|
|
|
|
return
|
|
|
|
|
2017-09-27 22:41:06 +00:00
|
|
|
try:
|
|
|
|
b, fr = estimatesmartfee["blocks"], estimatesmartfee["feerate"]
|
|
|
|
self._estimatesmartfee[b] = fr
|
|
|
|
except KeyError:
|
|
|
|
self._estimatesmartfee = None
|
2017-09-27 18:19:54 +00:00
|
|
|
|
2017-09-28 02:06:10 +00:00
|
|
|
await self.draw()
|
2017-09-27 18:19:54 +00:00
|
|
|
|
2017-09-27 05:30:09 +00:00
|
|
|
async def on_tick(self, dt):
|
|
|
|
with await self._lock:
|
|
|
|
self._dt = dt
|
|
|
|
|
2017-09-28 02:06:10 +00:00
|
|
|
await self.draw()
|
2017-09-27 05:30:09 +00:00
|
|
|
|
2017-09-27 19:42:19 +00:00
|
|
|
async def on_uptime(self, key, obj):
|
|
|
|
try:
|
|
|
|
self._uptime = obj["result"]
|
|
|
|
except KeyError:
|
|
|
|
return
|
|
|
|
|
2017-09-28 02:06:10 +00:00
|
|
|
await self.draw()
|
2017-09-27 19:42:19 +00:00
|
|
|
|
2017-09-27 05:30:09 +00:00
|
|
|
async def on_mode_change(self, newmode):
|
|
|
|
if newmode != "monitor":
|
|
|
|
self._visible = False
|
|
|
|
return
|
|
|
|
|
|
|
|
self._visible = True
|
|
|
|
await self.draw()
|
2017-09-27 16:18:09 +00:00
|
|
|
|
|
|
|
async def on_window_resize(self, y, x):
|
|
|
|
# At the moment we ignore the x size and limit to 100.
|
|
|
|
self._window_size = (y, x)
|
2017-09-28 02:06:10 +00:00
|
|
|
await self.draw()
|