obelisk

Electrum server using libbitcoin as its backend
git clone https://git.parazyd.org/obelisk
Log | Files | Refs | README | LICENSE

commit c1161ee71243b8109c3dea742ec7c2081f1db264
parent b5e4ed32fabaa3d57057f12114be7aaa22dc0124
Author: parazyd <parazyd@dyne.org>
Date:   Fri, 16 Apr 2021 00:34:43 +0200

Work out an inefficient implementation for header merkle proofs.

The proper solution would be to have something like this in libbitcoin.

Diffstat:
Mobelisk/protocol.py | 71+++++++++++++++++++++++++++++++++--------------------------------------
Mtests/test_electrum_protocol.py | 17+++++++----------
2 files changed, 40 insertions(+), 48 deletions(-)

diff --git a/obelisk/protocol.py b/obelisk/protocol.py @@ -218,6 +218,29 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902 resp = await func(writer, query) return await self._send_reply(writer, resp, query) + 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 != 0: + self.log.debug("Got error: %s", repr(_ec)) + 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), + } + async def blockchain_block_header(self, writer, query): # pylint: disable=W0613,R0911 """Method: blockchain.block.header Return the block header at the given height. @@ -241,26 +264,8 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902 return JsonRPCError.internalerror() return {"result": safe_hexlify(header)} - # 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, 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) - - hashed = [double_sha256(i) for i in cp_headers] - branch, root = merkle_branch_and_root(hashed, index) - return { - "result": { - "branch": [hash_to_hex_str(i) for i in branch], - "header": safe_hexlify(cp_headers[index]), - "root": hash_to_hex_str(root), - } - } + res = await self._merkle_proof_for_headers(cp_height, index) + return {"result": res} async def blockchain_block_headers(self, writer, query): # pylint: disable=W0613,R0911 """Method: blockchain.block.headers @@ -279,7 +284,8 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902 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: + # 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) @@ -296,25 +302,14 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902 "count": len(headers) // 80, "max": max_chunk_size, } - return {"result": resp} - - # The assumption is to fetch more headers if necessary. - # TODO: Review everything below, help needed - return JsonRPCError.invalidrequest() - 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: - 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) + 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} async def blockchain_estimatefee(self, writer, query): # pylint: disable=W0613 """Method: blockchain.estimatefee diff --git a/tests/test_electrum_protocol.py b/tests/test_electrum_protocol.py @@ -76,7 +76,7 @@ async def test_blockchain_block_header(protocol, writer): data = await protocol.blockchain_block_header(writer, {"params": params}) assert data["result"] == expect["result"] - params = [13, 25] + params = [1, 5] expect = get_expect(method, params) data = await protocol.blockchain_block_header(writer, {"params": params}) assert data["result"] == expect["result"] @@ -89,13 +89,11 @@ async def test_blockchain_block_headers(protocol, writer): data = await protocol.blockchain_block_headers(writer, {"params": params}) assert data["result"] == expect["result"] - # params = [123, 3, 127] - # expect = get_expect(method, params) - # data = await protocol.blockchain_block_headers(writer, {"params": params}) - # pprint(expect) - # print() - # pprint(data) - # assert data["result"] == expect["result"] + # params = [1, 4, 11] + params = [11, 3, 14] + expect = get_expect(method, params) + data = await protocol.blockchain_block_headers(writer, {"params": params}) + assert data["result"] == expect["result"] async def test_blockchain_scripthash_get_balance(protocol, writer): @@ -162,13 +160,12 @@ async def test_blockchain_scripthash_subscribe(protocol, writer): async def test_blockchain_scripthash_unsubscribe(protocol, writer): # Here blockstream doesn't even care - method = "blockchain.scripthash.unsubscribe" params = [ "92dd1eb7c042956d3dd9185a58a2578f61fee91347196604540838ccd0f8c08c" ] data = await protocol.blockchain_scripthash_unsubscribe( writer, {"params": params}) - assert data["result"] == True + assert data["result"] is True async def test_blockchain_transaction_get(protocol, writer):