def test():
    for proof in merkle_proof_test_vectors:
        try:
            electrum_proof = convert_core_to_electrum_merkle_proof(proof)
            #print(electrum_proof)
            implied_merkle_root = hash_merkle_root(electrum_proof["merkle"],
                                                   electrum_proof["txid"],
                                                   electrum_proof["pos"])
            assert implied_merkle_root == electrum_proof["merkleroot"]
        except ValueError:
            import traceback
            traceback.print_exc()
            assert 0
    print("All tests passed")
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))
Exemplo n.º 3
0
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))