Example #1
0
    async def blockchain_block_headers(self, writer, query):  # pylint: disable=W0613,R0911
        """Method: blockchain.block.headers
        Return a concatenated chunk of block headers from the main chain.
        """
        if "params" not in query or len(query["params"]) < 2:
            return JsonRPCError.invalidparams()
        # Electrum doesn't allow max_chunk_size to be less than 2016
        # gopher://bitreich.org/9/memecache/convenience-store.mkv
        max_chunk_size = 2016
        start_height = query["params"][0]
        count = query["params"][1]
        cp_height = query["params"][2] if len(query["params"]) == 3 else 0

        if not is_non_negative_integer(start_height):
            return JsonRPCError.invalidparams()
        if not is_non_negative_integer(count):
            return JsonRPCError.invalidparams()
        if cp_height != 0 and not start_height + (count - 1) <= cp_height:
            return JsonRPCError.invalidparams()

        count = min(count, max_chunk_size)
        headers = bytearray()
        for i in range(count):
            _ec, data = await self.bx.fetch_block_header(start_height + i)
            if _ec and _ec != 0:
                self.log.debug("Got error: %s", repr(_ec))
                return JsonRPCError.internalerror()
            headers.extend(data)

        resp = {
            "hex": safe_hexlify(headers),
            "count": len(headers) // 80,
            "max": max_chunk_size,
        }

        # The assumption is to fetch more headers if necessary.
        # TODO: Review
        if cp_height > 0 and cp_height - start_height > count:
            for i in range(cp_height - start_height):
                _ec, data = await self.bx.fetch_block_header(start_height +
                                                             count + i)
                if _ec and _ec != 0:
                    self.log.debug("Got error: %s", repr(_ec))
                    return JsonRPCError.internalerror()
                headers.extend(data)

        if cp_height > 0:
            # TODO: Review
            # TODO: Is index is 0 or last elem?
            hdr_lst = [headers[i:i + 80] for i in range(0, len(headers), 80)]
            branch, root = merkle_branch_and_root(hdr_lst, 0)
            resp["branch"] = [safe_hexlify(i) for i in branch]
            resp["root"] = safe_hexlify(root)

        return {"result": resp}
Example #2
0
    async def block_header(self, writer, query):  # pylint: disable=W0613,R0911
        """Method: blockchain.block.header
        Return the block header at the given height.
        """
        if "params" not in query or len(query["params"]) < 1:
            return JsonRPCError.invalidparams()
        index = query["params"][0]
        cp_height = query["params"][1] if len(query["params"]) == 2 else 0

        if not is_non_negative_integer(index):
            return JsonRPCError.invalidparams()
        if not is_non_negative_integer(cp_height):
            return JsonRPCError.invalidparams()
        if cp_height != 0 and not index <= cp_height:
            return JsonRPCError.invalidparams()

        if cp_height == 0:
            _ec, header = await self.bx.fetch_block_header(index)
            if _ec and _ec != ZMQError.success:
                self.log.error("bx.fetch_block_header: %s", _ec.name)
                return JsonRPCError.internalerror()
            return {"result": safe_hexlify(header)}

        res = await self._merkle_proof_for_headers(cp_height, index)
        return {"result": res}
Example #3
0
    async def blockchain_block_header(self, writer, query):  # pylint: disable=W0613,R0911
        """Method: blockchain.block.header
        Return the block header at the given height.
        """
        if "params" not in query or len(query["params"]) < 1:
            return JsonRPCError.invalidparams()
        index = query["params"][0]
        cp_height = query["params"][1] if len(query["params"]) == 2 else 0

        if not is_non_negative_integer(index):
            return JsonRPCError.invalidparams()
        if not is_non_negative_integer(cp_height):
            return JsonRPCError.invalidparams()
        if cp_height != 0 and not index <= cp_height:
            return JsonRPCError.invalidparams()

        if cp_height == 0:
            _ec, header = await self.bx.fetch_block_header(index)
            if _ec and _ec != 0:
                self.log.debug("Got error: %s", repr(_ec))
                return JsonRPCError.internalerror()
            return {"result": safe_hexlify(header)}

        cp_headers = []
        # headers up to and including cp_height
        for i in range(index, cp_height + 1):
            _ec, data = await self.bx.fetch_block_header(i)
            if _ec and _ec != 0:
                self.log.debug("Got error: %s", repr(_ec))
                return JsonRPCError.internalerror()
            cp_headers.append(data)

        # TODO: Review
        # TODO: Is index is 0 or last elem?
        branch, root = merkle_branch_and_root(cp_headers, 0)
        return {
            "result": {
                "branch": [safe_hexlify(i) for i in branch],
                "header": safe_hexlify(cp_headers[0]),
                "root": safe_hexlify(root),
            }
        }
Example #4
0
    async def header_notifier(self, writer):
        self.block_queue = asyncio.Queue()
        await self.bx.subscribe_to_blocks(self.block_queue)
        while True:
            item = await self.block_queue.get()
            if len(item) != 3:
                self.log.debug("error: item from block queue len != 3")
                continue

            header = block_to_header(item[2])
            params = [{"height": item[1], "hex": safe_hexlify(header)}]
            await self._send_notification(writer,
                                          "blockchain.headers.subscribe",
                                          params)
Example #5
0
    async def blockchain_headers_subscribe(self, writer, query):  # pylint: disable=W0613
        """Method: blockchain.headers.subscribe
        Subscribe to receive block headers when a new block is found.
        """
        # Tip height and header are returned upon request
        _ec, height = await self.bx.fetch_last_height()
        if _ec and _ec != 0:
            self.log.debug("Got error: %s", repr(_ec))
            return JsonRPCError.internalerror()
        _ec, tip_header = await self.bx.fetch_block_header(height)
        if _ec and _ec != 0:
            self.log.debug("Got error: %s", repr(_ec))
            return JsonRPCError.internalerror()

        self.tasks.append(asyncio.create_task(self.header_notifier(writer)))
        ret = {"height": height, "hex": safe_hexlify(tip_header)}
        return {"result": ret}
Example #6
0
    async def headers_subscribe(self, writer, query):  # pylint: disable=W0613
        """Method: blockchain.headers.subscribe
        Subscribe to receive block headers when a new block is found.
        """
        # Tip height and header are returned upon request
        _ec, height = await self.bx.fetch_last_height()
        if _ec and _ec != ZMQError.success:
            self.log.error("bx.fetch_last_height: %s", _ec.name)
            return JsonRPCError.internalerror()
        _ec, tip_header = await self.bx.fetch_block_header(height)
        if _ec and _ec != ZMQError.success:
            self.log.error("bx.fetch_block_header: %s", _ec.name)
            return JsonRPCError.internalerror()

        self.peers[self._get_peer(writer)]["tasks"].append(
            asyncio.create_task(self.header_notifier(writer)))
        ret = {"height": height, "hex": safe_hexlify(tip_header)}
        return {"result": ret}
Example #7
0
    async def block_headers(self, writer, query):  # pylint: disable=W0613,R0911
        """Method: blockchain.block.headers
        Return a concatenated chunk of block headers from the main chain.
        """
        if "params" not in query or len(query["params"]) < 2:
            return JsonRPCError.invalidparams()
        # Electrum doesn't allow max_chunk_size to be less than 2016
        # gopher://bitreich.org/9/memecache/convenience-store.mkv
        max_chunk_size = 2016
        start_height = query["params"][0]
        count = query["params"][1]
        cp_height = query["params"][2] if len(query["params"]) == 3 else 0

        if not is_non_negative_integer(start_height):
            return JsonRPCError.invalidparams()
        if not is_non_negative_integer(count):
            return JsonRPCError.invalidparams()
        # BUG: spec says <= cp_height
        if cp_height != 0 and not start_height + (count - 1) < cp_height:
            return JsonRPCError.invalidparams()

        count = min(count, max_chunk_size)
        headers = bytearray()
        for i in range(count):
            _ec, data = await self.bx.fetch_block_header(start_height + i)
            if _ec and _ec != ZMQError.success:
                self.log.error("bx.fetch_block_header: %s", _ec.name)
                return JsonRPCError.internalerror()
            headers.extend(data)

        resp = {
            "hex": safe_hexlify(headers),
            "count": len(headers) // 80,
            "max": max_chunk_size,
        }

        if cp_height > 0:
            data = await self._merkle_proof_for_headers(
                cp_height, start_height + (len(headers) // 80) - 1)
            resp["branch"] = data["branch"]
            resp["root"] = data["root"]

        return {"result": resp}
Example #8
0
    async def _merkle_proof_for_headers(self, height, idx):
        """Extremely inefficient merkle proof for headers"""
        # The following works, but is extremely inefficient.
        # The best solution would be to figure something out in
        # libbitcoin-server
        cp_headers = []

        for i in range(0, height + 1):
            _ec, data = await self.bx.fetch_block_header(i)
            if _ec and _ec != ZMQError.success:
                self.log.error("bx.fetch_block_header: %s", _ec.name)
                return JsonRPCError.internalerror()
            cp_headers.append(data)

        branch, root = merkle_branch_and_root(
            [double_sha256(i) for i in cp_headers], idx)

        return {
            "branch": [hash_to_hex_str(i) for i in branch],
            "header": safe_hexlify(cp_headers[idx]),
            "root": hash_to_hex_str(root),
        }