diff --git a/monitor.py b/monitor.py index 9b4a0a4..83cf980 100644 --- a/monitor.py +++ b/monitor.py @@ -9,6 +9,7 @@ import asyncio from decimal import Decimal import view +from rpc import RPCError class MonitorView(view.View): @@ -21,7 +22,7 @@ class MonitorView(view.View): self._bestblockhash = None self._bestblockheader = None # raw json blockheader self._bestblock = None # raw json block - self._bestcoinbase = None # raw json tx + self._bestcoinbase = (None, None) # (blockhash, raw json tx) self._mempoolinfo = None # raw mempoolinfo self._estimatesmartfee = {} # blocks -> feerate/kB self._dt = None @@ -32,83 +33,6 @@ class MonitorView(view.View): async def _draw(self): self._clear_init_pad() - 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), - )) - if self._mempoolinfo: self._pad.addstr(9, 1, "Mempool transactions: {: 6d} ({: 5.2f} MiB)".format( self._mempoolinfo["size"], @@ -125,6 +49,100 @@ class MonitorView(view.View): if self._uptime: self._pad.addstr(13, 1, "uptime: {}".format(datetime.timedelta(seconds=self._uptime))) + bbh = self._bestblockhash + if not bbh: + self._draw_pad_to_screen() + return + + self._pad.addstr(0, 36, bbh) + + bbhd = self._bestblockheader + if not bbhd or bbhd["hash"] != bbh: + self._draw_pad_to_screen() + return + + self._pad.addstr(0, 1, "Height: {: 8d}".format(bbhd["height"])) + + bb = self._bestblock + if not bb or bb["hash"] != bbh: + self._draw_pad_to_screen() + return + + 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"]), + )) + + self._pad.addstr(2, 1, "Transactions: {} ({} bytes/tx, {} WU/tx)".format( + len(bb["tx"]), + bb["size"] // len(bb["tx"]), + bb["weight"] // len(bb["tx"]), + )) + + 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), + )) + + 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)) + + # TODO: check the coinbase is associated with this block. + (h, bcb) = self._bestcoinbase + if not bcb or h != bbh: + self._draw_pad_to_screen() + return + + 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._draw_pad_to_screen() async def _draw_if_visible(self): @@ -133,6 +151,26 @@ class MonitorView(view.View): if self._visible: await self._draw() + async def _request_bestblockhash_info(self, bestblockhash): + try: + j = await self._client.request("getblockheader", [bestblockhash]) + self._bestblockheader = j["result"] + except RPCError: + return + + try: + j = await self._client.request("getblock", [bestblockhash]) + h = j["result"]["hash"] + self._bestblock = j["result"] + except (RPCError, KeyError): + return + + try: + j = await self._client.request("getrawtransaction", [j["result"]["tx"][0], 1]) + self._bestcoinbase = (h, j["result"]) + except RPCError: + return + async def on_bestblockhash(self, key, obj): try: bestblockhash = obj["result"] @@ -144,15 +182,7 @@ class MonitorView(view.View): 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"] + await self._request_bestblockhash_info(bestblockhash) if draw: await self._draw_if_visible()