def handle_query(sock, line, rpc, txmonitor): debug("=> " + line) try: query = json.loads(line) except json.decoder.JSONDecodeError as e: raise IOError(e) method = query["method"] #protocol documentation #https://github.com/kyuupichan/electrumx/blob/master/docs/PROTOCOL.rst if method == "blockchain.transaction.get": tx = rpc.call("gettransaction", [query["params"][0]]) send_response(sock, query, tx["hex"]) elif method == "blockchain.transaction.get_merkle": txid = query["params"][0] try: tx = rpc.call("gettransaction", [txid]) core_proof = rpc.call("gettxoutproof", [[txid], tx["blockhash"]]) electrum_proof = merkleproof.convert_core_to_electrum_merkle_proof( core_proof) implied_merkle_root = hashes.hash_merkle_root( electrum_proof["merkle"], txid, electrum_proof["pos"]) if implied_merkle_root != electrum_proof["merkleroot"]: raise ValueError txheader = get_block_header(rpc, tx["blockhash"]) reply = { "block_height": txheader["block_height"], "pos": electrum_proof["pos"], "merkle": electrum_proof["merkle"] } except (ValueError, JsonRpcError) as e: log("WARNING: merkle proof failed for " + txid + " err=" + repr(e)) #so reply with an invalid proof which electrum handles without # disconnecting us #https://github.com/spesmilo/electrum/blob/c8e67e2bd07efe042703bc1368d499c5e555f854/lib/verifier.py#L74 reply = {"block_height": 1, "pos": 0, "merkle": [txid]} send_response(sock, query, reply) elif method == "blockchain.scripthash.subscribe": scrhash = query["params"][0] if txmonitor.subscribe_address(scrhash): history_hash = txmonitor.get_electrum_history_hash(scrhash) else: log("WARNING: address scripthash not known to server: " + scrhash) history_hash = hashes.get_status_electrum([]) send_response(sock, query, history_hash) elif method == "blockchain.scripthash.get_history": scrhash = query["params"][0] history = txmonitor.get_electrum_history(scrhash) if history == None: history = [] log("WARNING: address scripthash history not known to server: " + scrhash) send_response(sock, query, history) elif method == "blockchain.headers.subscribe": subscribed_to_headers[0] = True if len(query["params"]) > 0: are_headers_raw[0] = query["params"][0] new_bestblockhash, header = get_current_header(rpc, are_headers_raw[0]) send_response(sock, query, header) elif method == "blockchain.block.get_header": height = query["params"][0] try: blockhash = rpc.call("getblockhash", [height]) header = get_block_header(rpc, blockhash) send_response(sock, query, header) except JsonRpcError: error = { "message": "height " + str(height) + " out of range", "code": -1 } send_error(sock, query["id"], error) elif method == "blockchain.block.headers": MAX_CHUNK_SIZE = 2016 start_height = query["params"][0] count = query["params"][1] count = min(count, MAX_CHUNK_SIZE) headers_hex, n = get_block_headers_hex(rpc, start_height, count) send_response(sock, query, { 'hex': headers_hex, 'count': n, 'max': MAX_CHUNK_SIZE }) elif method == "blockchain.block.get_chunk": RETARGET_INTERVAL = 2016 index = query["params"][0] tip_height = rpc.call("getblockchaininfo", [])["headers"] #logic copied from kyuupichan's electrumx get_chunk() in controller.py next_height = tip_height + 1 start_height = min(index * RETARGET_INTERVAL, next_height) count = min(next_height - start_height, RETARGET_INTERVAL) headers_hex, n = get_block_headers_hex(rpc, start_height, count) send_response(sock, query, headers_hex) elif method == "blockchain.transaction.broadcast": try: result = rpc.call("sendrawtransaction", [query["params"][0]]) except JsonRpcError as e: result = str(e) debug("tx broadcast result = " + str(result)) send_response(sock, query, result) elif method == "mempool.get_fee_histogram": mempool = rpc.call("getrawmempool", [True]) #algorithm copied from the relevant place in ElectrumX #https://github.com/kyuupichan/electrumx/blob/e92c9bd4861c1e35989ad2773d33e01219d33280/server/mempool.py fee_hist = defaultdict(int) for txid, details in mempool.items(): fee_rate = 1e8 * details["fee"] // details["size"] fee_hist[fee_rate] += details["size"] l = list(reversed(sorted(fee_hist.items()))) out = [] size = 0 r = 0 binsize = 100000 for fee, s in l: size += s if size + r > binsize: out.append((fee, size)) r += size - binsize size = 0 binsize *= 1.1 result = out send_response(sock, query, result) elif method == "blockchain.estimatefee": estimate = rpc.call("estimatesmartfee", [query["params"][0]]) feerate = 0.0001 if "feerate" in estimate: feerate = estimate["feerate"] send_response(sock, query, feerate) elif method == "blockchain.relayfee": networkinfo = rpc.call("getnetworkinfo", []) send_response(sock, query, networkinfo["relayfee"]) elif method == "server.banner": networkinfo = rpc.call("getnetworkinfo", []) blockchaininfo = rpc.call("getblockchaininfo", []) uptime = rpc.call("uptime", []) nettotals = rpc.call("getnettotals", []) send_response( sock, query, BANNER.format( detwallets=len(txmonitor.deterministic_wallets), addr=len(txmonitor.address_history), useragent=networkinfo["subversion"], peers=networkinfo["connections"], uptime=str(datetime.timedelta(seconds=uptime)), blocksonly=not networkinfo["localrelay"], pruning=blockchaininfo["pruned"], recvbytes=hashes.bytes_fmt(nettotals["totalbytesrecv"]), sentbytes=hashes.bytes_fmt(nettotals["totalbytessent"]), donationaddr=DONATION_ADDR)) elif method == "server.donation_address": send_response(sock, query, DONATION_ADDR) elif method == "server.version": send_response( sock, query, ["ElectrumPersonalServer " + VERSION_NUMBER, VERSION_NUMBER]) elif method == "server.peers.subscribe": send_response(sock, query, []) #no peers to report else: log("*** BUG! Not handling method: " + method + " query=" + str(query))
def handle_query(sock, line, rpc, txmonitor): debug("=> " + line) try: query = json.loads(line) except json.decoder.JSONDecodeError as e: raise IOError(e) method = query["method"] #protocol documentation #https://github.com/kyuupichan/electrumx/blob/master/docs/PROTOCOL.rst if method == "blockchain.transaction.get": tx = rpc.call("gettransaction", [query["params"][0]]) send_response(sock, query, tx["hex"]) elif method == "blockchain.transaction.get_merkle": txid = query["params"][0] try: tx = rpc.call("gettransaction", [txid]) core_proof = rpc.call("gettxoutproof", [[txid], tx["blockhash"]]) electrum_proof = merkleproof.convert_core_to_electrum_merkle_proof( core_proof) implied_merkle_root = hashes.hash_merkle_root( electrum_proof["merkle"], txid, electrum_proof["pos"]) if implied_merkle_root != electrum_proof["merkleroot"]: raise ValueError txheader = get_block_header(rpc, tx["blockhash"]) reply = {"block_height": txheader["block_height"], "pos": electrum_proof["pos"], "merkle": electrum_proof["merkle"]} except (ValueError, JsonRpcError) as e: log("WARNING: merkle proof failed for " + txid + " err=" + repr(e)) #so reply with an invalid proof which electrum handles without # disconnecting us #https://github.com/spesmilo/electrum/blob/c8e67e2bd07efe042703bc1368d499c5e555f854/lib/verifier.py#L74 reply = {"block_height": 1, "pos": 0, "merkle": [txid]} send_response(sock, query, reply) elif method == "blockchain.scripthash.subscribe": scrhash = query["params"][0] if txmonitor.subscribe_address(scrhash): history_hash = txmonitor.get_electrum_history_hash(scrhash) else: log("WARNING: address scripthash not known to us: " + scrhash) history_hash = hashes.get_status_electrum([]) send_response(sock, query, history_hash) elif method == "blockchain.scripthash.get_history": scrhash = query["params"][0] history = txmonitor.get_electrum_history(scrhash) if history == None: history = [] log("WARNING: address scripthash history not known to us: " + scrhash) send_response(sock, query, history) elif method == "blockchain.headers.subscribe": subscribed_to_headers[0] = True new_bestblockhash, header = get_current_header(rpc) send_response(sock, query, header) elif method == "blockchain.block.get_header": blockhash = rpc.call("getblockhash", [query["params"][0]]) header = get_block_header(rpc, blockhash) send_response(sock, query, header) elif method == "blockchain.block.get_chunk": RETARGET_INTERVAL = 2016 index = query["params"][0] tip_height = rpc.call("getblockchaininfo", [])["headers"] #logic copied from kyuupichan's electrumx get_chunk() in controller.py next_height = tip_height + 1 start_height = min(index*RETARGET_INTERVAL, next_height) count = min(next_height - start_height, RETARGET_INTERVAL) #read count number of headers starting from start_height result = bytearray() the_hash = rpc.call("getblockhash", [start_height]) for i in range(count): header = rpc.call("getblockheader", [the_hash]) #add header hex to result if "previousblockhash" in header: prevblockhash = header["previousblockhash"] else: # this is the genesis block # it does not have a previous block hash prevblockhash = "00"*32 h1 = struct.pack("<i32s32sIII", header["version"], binascii.unhexlify(prevblockhash)[::-1], binascii.unhexlify(header["merkleroot"])[::-1], header["time"], int(header["bits"], 16), header["nonce"]) result.extend(h1) if "nextblockhash" not in header: break the_hash = header["nextblockhash"] send_response(sock, query, binascii.hexlify(result).decode("utf-8")) elif method == "blockchain.transaction.broadcast": try: result = rpc.call("sendrawtransaction", [query["params"][0]]) except JsonRpcError as e: result = e.message debug("tx broadcast result = " + str(result)) send_response(sock, query, result) elif method == "mempool.get_fee_histogram": result = [] #TODO not handling, sending empty send_response(sock, query, result) elif method == "blockchain.estimatefee": estimate = rpc.call("estimatesmartfee", [query["params"][0]]) feerate = 0.0001 if "feerate" in estimate: feerate = estimate["feerate"] send_response(sock, query, feerate) elif method == "blockchain.relayfee": networkinfo = rpc.call("getnetworkinfo", []) send_response(sock, query, networkinfo["relayfee"]) elif method == "server.banner": networkinfo = rpc.call("getnetworkinfo", []) blockchaininfo = rpc.call("getblockchaininfo", []) uptime = rpc.call("uptime", []) send_response(sock, query, BANNER.format( detwallets=len(txmonitor.deterministic_wallets), addr=len(txmonitor.address_history), useragent=networkinfo["subversion"], peers=networkinfo["connections"], uptime=str(datetime.timedelta(seconds=uptime)), blocksonly=not networkinfo["localrelay"], pruning=blockchaininfo["pruned"], donationaddr=DONATION_ADDR)) elif method == "server.donation_address": send_response(sock, query, DONATION_ADDR) elif method == "server.version": send_response(sock, query, ["ElectrumPersonalServer " + VERSION_NUMBER, VERSION_NUMBER]) elif method == "server.peers.subscribe": send_response(sock, query, []) #no peers to report else: log("*** BUG! Not handling method: " + method + " query=" + str(query))
def get_electrum_history_hash(self, scrhash): return hashes.get_status_electrum([ (h["tx_hash"], h["height"]) for h in self.address_history[scrhash]["history"] ])