From 6ad8c16c47fac55152091786cf0dcdbc9a110e6a Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Mon, 17 Oct 2016 20:39:14 +0900 Subject: [PATCH] Handle a couple more requests. --- lib/hash.py | 5 ++++- server/controller.py | 36 +++++++++++++++++++++++++++++++++--- server/protocol.py | 14 ++++++++++++++ 3 files changed, 51 insertions(+), 4 deletions(-) diff --git a/lib/hash.py b/lib/hash.py index 2cae68b..bf0aed6 100644 --- a/lib/hash.py +++ b/lib/hash.py @@ -30,13 +30,16 @@ def hmac_sha512(key, msg): def hash160(x): return ripemd160(sha256(x)) - def hash_to_str(x): '''Converts a big-endian binary hash to a little-endian hex string, as shown in block explorers, etc. ''' return bytes(reversed(x)).hex() +def hex_str_to_hash(x): + '''Converts a little-endian hex string as shown to a big-endian binary + hash.''' + return bytes(reversed(bytes.fromhex(x))) class InvalidBase58String(Exception): pass diff --git a/server/controller.py b/server/controller.py index cdd5e6e..9925335 100644 --- a/server/controller.py +++ b/server/controller.py @@ -11,7 +11,8 @@ import aiohttp from server.db import DB from server.protocol import ElectrumX, LocalRPC -from lib.hash import sha256, hash_to_str, Base58 +from lib.hash import (sha256, double_sha256, hash_to_str, + Base58, hex_str_to_hash) from lib.util import LoggedClass @@ -116,6 +117,29 @@ class Controller(LoggedClass): return status + async def get_merkle(self, tx_hash, height): + '''tx_hash is a hex string.''' + daemon_send = self.block_cache.send_single + block_hash = await daemon_send('getblockhash', (height,)) + block = await daemon_send('getblock', (block_hash, True)) + tx_hashes = block['tx'] + # This will throw if the tx_hash is bad + pos = tx_hashes.index(tx_hash) + + idx = pos + hashes = [hex_str_to_hash(txh) for txh in tx_hashes] + merkle_branch = [] + while len(hashes) > 1: + if len(hashes) & 1: + hashes.append(hashes[-1]) + idx = idx - 1 if (idx & 1) else idx + 1 + merkle_branch.append(hash_to_str(hashes[idx])) + idx //= 2 + hashes = [double_sha256(hashes[n] + hashes[n + 1]) + for n in range(0, len(hashes), 2)] + + return {"block_height": height, "merkle": merkle_branch, "pos": pos} + def get_peers(self): '''Returns a dictionary of IRC nick to (ip, host, ports) tuples, one per peer.''' @@ -127,6 +151,9 @@ class BlockCache(LoggedClass): block chain reorganisations. ''' + class DaemonError: + pass + def __init__(self, env, db): super().__init__() self.db = db @@ -165,7 +192,10 @@ class BlockCache(LoggedClass): '''Loops forever polling for more blocks.''' self.logger.info('prefetching blocks...') while True: - await self.maybe_prefetch() + try: + await self.maybe_prefetch() + except self.DaemonError: + pass await asyncio.sleep(2) def cache_used(self): @@ -246,7 +276,7 @@ class BlockCache(LoggedClass): secs = 30 else: msg = 'daemon errors: {}'.format(errs) - secs = 3 + raise self.DaemonError(msg) self.logger.error('{}. Sleeping {:d}s and trying again...' .format(msg, secs)) diff --git a/server/protocol.py b/server/protocol.py index f42410e..cf72aa8 100644 --- a/server/protocol.py +++ b/server/protocol.py @@ -148,6 +148,20 @@ class ElectrumX(JSONRPC): net_info = await self.BC.send_single('getnetworkinfo') return net_info['relayfee'] + async def handle_blockchain_transaction_get(self, params): + if len(params) != 1: + raise Error(Error.BAD_REQUEST, + 'params should contain a transaction hash') + tx_hash = params[0] + return await self.BC.send_single('getrawtransaction', (tx_hash, 0)) + + async def handle_blockchain_transaction_get_merkle(self, params): + if len(params) != 2: + raise Error(Error.BAD_REQUEST, + 'params should contain a transaction hash and height') + tx_hash, height = params + return await self.controller.get_merkle(tx_hash, height) + async def handle_server_banner(self, params): '''Return the server banner.''' banner = 'Welcome to Electrum!'