From 228481d2ac4e482ca779f7c9442345435503f2b9 Mon Sep 17 00:00:00 2001 From: Daniel Edgecumbe Date: Thu, 28 Sep 2017 11:07:11 +0100 Subject: [PATCH] [block] Add rudimentary transaction list / browser --- block.py | 180 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 148 insertions(+), 32 deletions(-) diff --git a/block.py b/block.py index 4ed87e7..404a93a 100644 --- a/block.py +++ b/block.py @@ -128,47 +128,97 @@ class BlockView(object): self._visible = False self._hash = None # currently browsed hash. + self._selected_tx = None # (index, blockhash) + self._tx_offset = None # (offset, blockhash) self._window_size = MIN_WINDOW_SIZE - async def _draw(self, block, bestblockhash): - # TODO: figure out window width etc. - - if self._pad is not None: - self._pad.clear() - else: - self._pad = curses.newpad(20, 100) + async def _set_hash(self, newhash): + # TODO: lock? + self._hash = newhash + self._selected_tx = (0, newhash) + self._tx_offset = (0, newhash) + async def _draw_block(self, block, bestblockhash): CGREEN = curses.color_pair(1) CRED = curses.color_pair(3) CYELLOW = curses.color_pair(5) CBOLD = curses.A_BOLD - if block: - self._pad.addstr(0, 59, "[J/K: browse, HOME/END: quicker, L: best]", CYELLOW) + self._pad.addstr(0, 59, "[J/K: browse, HOME/END: quicker, L: best]", CYELLOW) - self._pad.addstr(0, 1, "Time {}".format( - datetime.datetime.utcfromtimestamp(block["time"]).isoformat(timespec="seconds") - ), CBOLD) - self._pad.addstr(0, 31, "Height {}".format(block["height"]), CBOLD) + self._pad.addstr(0, 1, "Time {}".format( + datetime.datetime.utcfromtimestamp(block["time"]).isoformat(timespec="seconds") + ), CBOLD) + self._pad.addstr(0, 31, "Height {}".format(block["height"]), 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(3, 1, "Diff {:,d}".format(int(block["difficulty"])), CBOLD) - self._pad.addstr(4, 1, "Version 0x{}".format(block["versionHex"]), 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(3, 1, "Diff {:,d}".format(int(block["difficulty"])), CBOLD) + self._pad.addstr(4, 1, "Version 0x{}".format(block["versionHex"]), CBOLD) - self._pad.addstr(1, 31, "Hash {}".format(block["hash"]), CBOLD) - if "previousblockhash" in block: - self._pad.addstr(2, 31, "Prev {}".format(block["previousblockhash"]), CBOLD) + self._pad.addstr(1, 31, "Hash {}".format(block["hash"]), CBOLD) + if "previousblockhash" in block: + self._pad.addstr(2, 31, "Prev {}".format(block["previousblockhash"]), CBOLD) + else: + self._pad.addstr(2, 60, "genesis block!", CBOLD + CRED) + + if "nextblockhash" in block: + self._pad.addstr(3, 31, "Next {}".format(block["nextblockhash"]), CBOLD) + elif block["hash"] == bestblockhash: + self._pad.addstr(3, 60, "best block!", CBOLD + CGREEN) + + self._pad.addstr(4, 31, "Root {}".format(block["merkleroot"]), CBOLD) + + async def _draw_transactions(self, block, bestblockhash): + CGREEN = curses.color_pair(1) + CRED = curses.color_pair(3) + CYELLOW = curses.color_pair(5) + CBOLD = curses.A_BOLD + CREVERSE = curses.A_REVERSE + + self._pad.addstr(6, 36, "Transactions: {}".format( + len(block["tx"])), CBOLD) + self._pad.addstr(6, 68, "[UP/DOWN: browse, ENTER: select]".format( + len(block["tx"])), CYELLOW) + + if self._selected_tx is None or self._tx_offset is None: + # Shouldn't happen + raise Exception + + if self._selected_tx[1] != block["hash"] or self._tx_offset[1] != block["hash"]: + # Shouldn't happen + raise Exception + + offset = self._tx_offset[0] + if offset > 0: + self._pad.addstr(7, 36, "... ^ ...", CBOLD) + if offset < len(block["tx"]) - 9: + self._pad.addstr(17, 36, "... v ...", CBOLD) + for i, txid in enumerate(block["tx"]): + if i < offset: # this is lazy + continue + if i > offset+8: # this is lazy + break + + if i == self._selected_tx[0] and self._hash == self._selected_tx[1]: + self._pad.addstr(8+i-offset, 36, "{}".format(txid), CBOLD + CREVERSE) else: - self._pad.addstr(2, 60, "genesis block!", CBOLD + CRED) + self._pad.addstr(8+i-offset, 36, "{}".format(txid)) - if "nextblockhash" in block: - self._pad.addstr(3, 31, "Next {}".format(block["nextblockhash"]), CBOLD) - elif block["hash"] == bestblockhash: - self._pad.addstr(3, 60, "best block!", CBOLD + CGREEN) - self._pad.addstr(4, 31, "Root {}".format(block["merkleroot"]), CBOLD) + async def _draw(self, block, bestblockhash): + # TODO: figure out window width etc. + + if self._pad is not None: + self._pad.clear() + else: + self._pad = curses.newpad(20, 100) + + + if block: + await self._draw_block(block, bestblockhash) + await self._draw_transactions(block, bestblockhash) await self._draw_pad_to_screen() @@ -179,15 +229,64 @@ class BlockView(object): self._pad.refresh(0, 0, 4, 0, min(maxy-3, 24), min(maxx-1, 100)) + async def _select_previous_transaction(self): + if self._hash is None: + return # Can't do anything + + if self._selected_tx == None or self._selected_tx[1] != self._hash: + return # Can't do anything + + if self._tx_offset == None or self._tx_offset[1] != self._hash: + return # Can't do anything + + if self._selected_tx[0] == 0: + return # At the beginning already. + + if self._selected_tx[0] == self._tx_offset[0]: + self._tx_offset = (self._tx_offset[0] - 1, self._tx_offset[1]) + + self._selected_tx = (self._selected_tx[0] - 1, self._selected_tx[1]) + + await self.draw() + + async def _select_next_transaction(self): + if self._hash is None: + return # Can't do anything + + if self._selected_tx == None or self._selected_tx[1] != self._hash: + return # Can't do anything + + if self._tx_offset == None or self._tx_offset[1] != self._hash: + return # Can't do anything + + try: + block = await self._blockstore.get_block(self._hash) + except KeyError: + return # Can't do anything + + if self._selected_tx[0] == len(block["tx"]) - 1: + return # At the end already + + if self._selected_tx[0] == self._tx_offset[0] + 8: + self._tx_offset = (self._tx_offset[0] + 1, self._tx_offset[1]) + + self._selected_tx = (self._selected_tx[0] + 1, self._selected_tx[1]) + + await self.draw() + + async def _enter_transaction_view(self): + raise NotImplementedError("Transaction view is not yet implemented") + async def _select_previous_block(self): if self._hash is None: return # Can't do anything try: - self._hash = await self._blockstore.get_previousblockhash(self._hash) + newhash = await self._blockstore.get_previousblockhash(self._hash) except KeyError: return # Can't do anything + await self._set_hash(newhash) await self.draw() async def _select_next_block(self): @@ -195,10 +294,11 @@ class BlockView(object): return # Can't do anything try: - self._hash = await self._blockstore.get_nextblockhash(self._hash) + newhash = await self._blockstore.get_nextblockhash(self._hash) except KeyError: return # Can't do anything + await self._set_hash(newhash) await self.draw() async def _select_previous_block_n(self, n): @@ -206,10 +306,11 @@ class BlockView(object): return # Can't do anything try: - self._hash = await self._blockstore.get_previousblockhash_n(self._hash, n) + newhash = await self._blockstore.get_previousblockhash_n(self._hash, n) except KeyError: return # Can't do anything + await self._set_hash(newhash) await self.draw() async def _select_next_block_n(self, n): @@ -217,10 +318,11 @@ class BlockView(object): return # Can't do anything try: - self._hash = await self._blockstore.get_nextblockhash_n(self._hash, n) + newhash = await self._blockstore.get_nextblockhash_n(self._hash, n) except KeyError: return # Can't do anything + await self._set_hash(newhash) await self.draw() async def _select_best_block(self): @@ -228,10 +330,11 @@ class BlockView(object): return # Can't do anything try: - self._hash = await self._blockstore.get_bestblockhash() + newhash = await self._blockstore.get_bestblockhash() except KeyError: return # Can't do anything + await self._set_hash(newhash) await self.draw() async def draw(self): @@ -256,7 +359,8 @@ class BlockView(object): # If we have no browse hash, set it to the best. if self._hash is None: - self._hash = bestblockhash + newhash = bestblockhash + await self._set_hash(newhash) # Redraw so that we know if it's the best await self.draw() @@ -294,6 +398,18 @@ class BlockView(object): await self._select_next_block_n(1000) return None + if key == "KEY_UP": + await self._select_previous_transaction() + return None + + if key == "KEY_DOWN": + await self._select_next_transaction() + return None + + if key == "KEY_RETURN" or key == "\n": + await self._enter_transaction_view() + return None + if key.lower() == "l": await self._select_best_block() return None