From 46d8e0c6086f87787fbc35eb329d10ba74e7cc58 Mon Sep 17 00:00:00 2001 From: Neil Booth Date: Mon, 16 Jul 2018 15:27:00 +0800 Subject: [PATCH] Implement merkle proofs for blockchain.block.header --- docs/protocol-methods.rst | 22 +++++++++++++++---- electrumx/server/controller.py | 16 +++++++++++--- electrumx/server/session.py | 40 ++++++++++++++++++++++++++-------- tests/lib/test_merkle.py | 6 ++--- 4 files changed, 65 insertions(+), 19 deletions(-) diff --git a/docs/protocol-methods.rst b/docs/protocol-methods.rst index d3a0a22..9e3a214 100644 --- a/docs/protocol-methods.rst +++ b/docs/protocol-methods.rst @@ -34,6 +34,10 @@ Return the block header at the given height. proof that the given header is present in the blockchain; presumably the client has the merkle root hard-coded as a checkpoint. + * *branch* + + The merkle branch of *header* up to *root*, deepest pairing first. + * *header* The raw block header as a hexadecimal string. @@ -43,17 +47,27 @@ Return the block header at the given height. The merkle root of all blockchain headers up to and including *cp_height*. - * *branch* - - The merkle branch of *header* up to *root*, deepest pairing first. - **Example Result** +With *cp_height* zero: + :: "0100000085144a84488ea88d221c8bd6c059da090e88f8a2c99690ee55dbba4e00000000e11c48fecdd9e72510ca84f023370c9a38bf91ac5cae88019bee94d24528526344c36649ffff001d1d03e477" +With *cp_height* 8 on the Bitcoin Cash chain:: + + { + "branch": [ + "000000004ebadb55ee9096c9a2f8880e09da59c0d68b1c228da88e48844a1485", + "96cbbc84783888e4cc971ae8acf86dd3c1a419370336bb3c634c97695a8c5ac9", + "965ac94082cebbcffe458075651e9cc33ce703ab0115c72d9e8b1a9906b2b636", + "89e5daa6950b895190716dd26054432b564ccdc2868188ba1da76de8e1dc7591" + ], + "header": "0100000085144a84488ea88d221c8bd6c059da090e88f8a2c99690ee55dbba4e00000000e11c48fecdd9e72510ca84f023370c9a38bf91ac5cae88019bee94d24528526344c36649ffff001d1d03e477", + "root": "e347b1c43fd9b5415bf0d92708db8284b78daf4d0e24f9c3405f45feb85e25db" + } blockchain.block.headers ======================== diff --git a/electrumx/server/controller.py b/electrumx/server/controller.py index cdd1981..821d9d0 100644 --- a/electrumx/server/controller.py +++ b/electrumx/server/controller.py @@ -20,9 +20,9 @@ from functools import partial import pylru from aiorpcx import RPCError, TaskSet, _version as aiorpcx_version -from electrumx.lib.hash import double_sha256, hash_to_hex_str, hex_str_to_hash +from electrumx.lib.hash import hash_to_hex_str, hex_str_to_hash from electrumx.lib.hash import HASHX_LEN -from electrumx.lib.merkle import Merkle +from electrumx.lib.merkle import Merkle, MerkleCache from electrumx.lib.peer import Peer from electrumx.lib.server_base import ServerBase import electrumx.lib.util as util @@ -37,6 +37,12 @@ version_string = util.version_string merkle = Merkle() +class HeaderSource(object): + + def __init__(self, db): + self.hashes = db.fs_block_hashes + + class SessionGroup(object): def __init__(self, gid): @@ -54,7 +60,7 @@ class Controller(ServerBase): CATCHING_UP, LISTENING, PAUSED, SHUTTING_DOWN = range(4) PROTOCOL_MIN = '1.1' - PROTOCOL_MAX = '1.3' + PROTOCOL_MAX = '1.4' AIORPCX_MIN = (0, 5, 6) VERSION = VERSION @@ -236,6 +242,10 @@ class Controller(ServerBase): synchronize, then kick off server background processes.''' await self.bp.caught_up_event.wait() self.logger.info('block processor has caught up') + length = max(1, self.bp.db_height - self.env.reorg_limit) + source = HeaderSource(self.bp) + self.header_mc = MerkleCache(merkle, source, length) + self.logger.info('populated header merkle cache') self.create_task(self.mempool.main_loop()) await self.mempool.synchronized_event.wait() self.create_task(self.peer_mgr.main_loop()) diff --git a/electrumx/server/session.py b/electrumx/server/session.py index 7ce52ab..9a3b936 100644 --- a/electrumx/server/session.py +++ b/electrumx/server/session.py @@ -297,13 +297,35 @@ class ElectrumX(SessionBase): hashX = self.controller.scripthash_to_hashX(scripthash) return await self.hashX_subscribe(hashX, scripthash) - def block_header(self, height): + def block_header(self, height, cp_height=0): '''Return a raw block header as a hexadecimal string. height: the header's height''' height = self.controller.non_negative_integer(height) - raw_header = self.controller.raw_header(height) - return raw_header.hex() + cp_height = self.controller.non_negative_integer(cp_height) + raw_header_hex = self.controller.raw_header(height).hex() + if cp_height == 0: + return raw_header_hex + if height > cp_height: + raise RPCError(BAD_REQUEST, + f'height {height:,d} > cp_height {cp_height:,d}') + max_height = self.height() + if cp_height > max_height: + raise RPCError(BAD_REQUEST, f'cp_height {cp_height:,d} > ' + f'max height {max_height:,d}') + header_mc = self.controller.header_mc + branch, root = header_mc.branch_and_root(cp_height + 1, height) + return { + 'branch': [hash_to_hex_str(elt) for elt in branch], + 'header': raw_header_hex, + 'root': hash_to_hex_str(root), + } + + def block_header_13(self, height): + '''Return a raw block header as a hexadecimal string. + + height: the header's height''' + return self.block_header(height) def block_headers(self, start_height, count): '''Return count concatenated block headers as hex for the main chain; @@ -477,9 +499,14 @@ class ElectrumX(SessionBase): 'server.ping': self.ping, }) - if ptuple >= (1, 3): + if ptuple >= (1, 4): handlers.update({ 'blockchain.block.header': self.block_header, + 'blockchain.headers.subscribe': self.headers_subscribe, + }) + elif ptuple >= (1, 3): + handlers.update({ + 'blockchain.block.header': self.block_header_13, 'blockchain.headers.subscribe': self.headers_subscribe_True, }) else: @@ -496,11 +523,6 @@ class ElectrumX(SessionBase): 'blockchain.address.subscribe': self.address_subscribe, }) - if ptuple >= (1, 4): - handlers.update({ - `'blockchain.headers.subscribe': self.headers_subscribe, - }) - self.electrumx_handlers = handlers def request_handler(self, method): diff --git a/tests/lib/test_merkle.py b/tests/lib/test_merkle.py index af095d4..dd9d9ff 100644 --- a/tests/lib/test_merkle.py +++ b/tests/lib/test_merkle.py @@ -149,10 +149,10 @@ class Source(object): def __init__(self, length): self._hashes = [os.urandom(32) for _ in range(length)] - def hashes(self, start, length): + def hashes(self, start, count): assert start >= 0 - assert start + length <= len(self._hashes) - return self._hashes[start: start + length] + assert start + count <= len(self._hashes) + return self._hashes[start: start + count] def test_merkle_cache():