From cc567aa599bc25d280e9ee650838df1530f0a71e Mon Sep 17 00:00:00 2001 From: Daniel Edgecumbe Date: Wed, 27 Sep 2017 21:15:26 +0100 Subject: [PATCH] [block] Add embryonic block view --- block.py | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ macros.py | 2 +- main.py | 13 +++++- 3 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 block.py diff --git a/block.py b/block.py new file mode 100644 index 0000000..719dfcc --- /dev/null +++ b/block.py @@ -0,0 +1,120 @@ +# 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 + +from macros import MIN_WINDOW_SIZE + + +class BlockStore(object): + def __init__(self, client): + self._client = client + + self._lock = asyncio.Lock() + + self._blocks = {} # hash -> raw block (full details) + + async def get_block(self, blockhash): + with await self._lock: + try: + return self._blocks[blockhash] + except KeyError: + # TODO: handle error if the block doesn't exist at all. + j = await self._client.request("getblock", [blockhash]) + self._blocks[blockhash] = j["result"] + return j["result"] + + async def get_previousblockhash(self, blockhash): + with await self._lock: + try: + return self._blocks[blockhash]["previousblockhash"] + except KeyError: + raise + + +class BlockView(object): + def __init__(self, blockstore): + self._blockstore = blockstore + + self._pad = None + + self._visible = False + + self._hash = None # currently browsed hash. + + self._window_size = MIN_WINDOW_SIZE + + def _draw(self, block): + # TODO: figure out window width etc. + + if self._pad is not None: + self._pad.clear() + else: + self._pad = curses.newpad(20, 100) + + CGREEN = curses.color_pair(1) + CCYAN = curses.color_pair(2) + CRED = curses.color_pair(3) + CYELLOW = curses.color_pair(5) + CBOLD = curses.A_BOLD + + if block: + #self._pad.addstr(0, 1, "height: " + str(block["height"]).zfill(6) + " (J/K: browse, HOME/END: quicker, L: latest, G: seek)", CBOLD) + self._pad.addstr(0, 1, "Height: {}".format(block["height"]), CBOLD) + self._pad.addstr(0, 30, "Hash: {}".format(block["hash"]), CBOLD) + self._pad.addstr(1, 28, "Merkle: {}".format(block["merkleroot"]), CBOLD) + self._pad.addstr(1, 1, "Size: {} bytes".format(block["size"]), CBOLD) + self._pad.addstr(2, 1, "Weight: {} WU".format(block["weight"]), CBOLD) + self._pad.addstr(2, 28, "Difficulty: {:,d}".format(int(block["difficulty"])), CBOLD) + self._pad.addstr(2, 70, "Timestamp: {}".format( + datetime.datetime.utcfromtimestamp(block["time"]).isoformat(timespec="seconds") + ), CBOLD) + self._pad.addstr(3, 81, "Version: 0x{}".format(block["versionHex"]), CBOLD) + + self._draw_pad_to_screen() + + def _draw_pad_to_screen(self): + 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)) + + async def draw(self): + block = None + if self._hash: + block = await self._blockstore.get_block(self._hash) + + self._draw(block) + + async def on_bestblockhash(self, key, obj): + try: + bestblockhash = obj["result"] + except KeyError: + return + + # If we have no browse hash, set it to the best. + if self._hash is None: + self._hash = bestblockhash + + if self._visible: + await self.draw() + + async def on_mode_change(self, newmode): + if newmode != "block": + self._visible = False + return + + self._visible = True + await self.draw() + + 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) + if self._visible: + await self.draw() diff --git a/macros.py b/macros.py index 94e1a62..4e81ccd 100644 --- a/macros.py +++ b/macros.py @@ -8,7 +8,7 @@ VERSION_STRING = "bitcoind-ncurses v0.2.0-dev" # "monitor", "wallet", "peers", "block", # "tx", "console", "net", "forks", # ] -MODES = ["monitor", "peers"] +MODES = ["monitor", "peers", "block"] DEFAULT_MODE = "monitor" MIN_WINDOW_SIZE = (10, 20) diff --git a/main.py b/main.py index 6c3c846..d4c2bc9 100644 --- a/main.py +++ b/main.py @@ -13,6 +13,7 @@ import header import footer import monitor import peers +import block from macros import MODES, DEFAULT_MODE @@ -117,8 +118,17 @@ def create_tasks(client, window): monitorview = monitor.MonitorView(client) peerview = peers.PeersView() + + blockstore = block.BlockStore(client) + blockview = block.BlockView(blockstore) + footerview.add_callback(monitorview.on_mode_change) footerview.add_callback(peerview.on_mode_change) + footerview.add_callback(blockview.on_mode_change) + + async def on_bestblockhash(key, obj): + await monitorview.on_bestblockhash(key, obj) + await blockview.on_bestblockhash(key, obj) async def on_peerinfo(key, obj): await headerview.on_peerinfo(key, obj) @@ -135,6 +145,7 @@ def create_tasks(client, window): await footerview.on_window_resize(y, x) await monitorview.on_window_resize(y, x) await peerview.on_window_resize(y, x) + await blockview.on_window_resize(y, x) # Set the initial window sizes ty, tx = window.getmaxyx() @@ -144,7 +155,7 @@ def create_tasks(client, window): tasks = [ poll_client(client, "getbestblockhash", - monitorview.on_bestblockhash, 1.0), + on_bestblockhash, 1.0), poll_client(client, "getblockchaininfo", headerview.on_blockchaininfo, 5.0), poll_client(client, "getnetworkinfo",