commit f707ca4997dfa23685946434974ddb3c9db3552d
parent 24a277654df91c955e2edc9ec8dc9a20bcf112b4
Author: parazyd <parazyd@dyne.org>
Date: Wed, 7 Apr 2021 17:37:03 +0200
Implement blockchain.block.header
Diffstat:
4 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/electrumobelisk/merkle.py b/electrumobelisk/merkle.py
@@ -17,7 +17,7 @@
"""Module for calculating merkle branches"""
from math import ceil, log
-from hashes import double_sha256
+from electrumobelisk.hashes import double_sha256
def branch_length(hash_count):
diff --git a/electrumobelisk/protocol.py b/electrumobelisk/protocol.py
@@ -17,7 +17,8 @@
import asyncio
import json
-from zeromq import Client
+from electrumobelisk.util import is_non_negative_integer, safe_hexlify
+from electrumobelisk.zeromq import Client
VERSION = 0.0
DONATION_ADDR = "bc1q7an9p5pz6pjwjk4r48zke2yfaevafzpglg26mz"
@@ -89,7 +90,19 @@ class ElectrumProtocol(asyncio.Protocol):
async def blockchain_block_header(self, query):
self.log.debug("query: %s", query)
- return {"result": "foo"}
+ # TODO: cp_height
+ index = query["params"][0]
+ cp_height = query["params"][1] if len(query["params"]) == 2 else 0
+
+ if not is_non_negative_integer(index):
+ return {"error": "Invalid block height"}
+ if not is_non_negative_integer(cp_height):
+ return {"error": "Invalid cp_height"}
+
+ _ec, data = await self.bx.block_header(index)
+ if _ec and _ec != 0:
+ return {"error": "Request corrupted"}
+ return {"result": safe_hexlify(data)}
async def handle_query(self, writer, query): # pylint: disable=R0915,R0912,R0911
"""Electrum protocol method handlers"""
@@ -105,12 +118,15 @@ class ElectrumProtocol(asyncio.Protocol):
if method == "blockchain.block.header":
self.log.debug("blockchain.block.header")
+ if "params" not in query:
+ return await self._send_error(writer, "Malformed query",
+ query["id"])
resp = await self.blockchain_block_header(query)
if "error" in resp:
- await self._send_error(writer, resp["error"], query["id"])
- else:
- await self._send_response(writer, resp["result"], query["id"])
- return
+ return await self._send_error(writer, resp["error"],
+ query["id"])
+ return await self._send_response(writer, resp["result"],
+ query["id"])
if method == "blockchain.block.headers":
self.log.debug("blockchain.block.headers")
diff --git a/electrumobelisk/util.py b/electrumobelisk/util.py
@@ -15,13 +15,21 @@
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Utility functions"""
+from binascii import hexlify
def is_integer(val):
+ """Check if val is of type int"""
return isinstance(val, int)
def is_non_negative_integer(val):
+ """Check if val is of type int and non-negative"""
if is_integer(val):
return val >= 0
return False
+
+
+def safe_hexlify(val):
+ """hexlify and return a string"""
+ return str(hexlify(val), "utf-8")
diff --git a/electrumobelisk/zeromq.py b/electrumobelisk/zeromq.py
@@ -17,12 +17,13 @@
"""ZeroMQ implementation for libbitcoin"""
import asyncio
import struct
+from binascii import unhexlify
from random import randint
import zmq
import zmq.asyncio
-from libbitcoin_errors import make_error_code, ErrorCode
+from electrumobelisk.libbitcoin_errors import make_error_code, ErrorCode
def create_random_id():
@@ -31,6 +32,19 @@ def create_random_id():
return randint(0, max_uint32)
+def pack_block_index(index):
+ if isinstance(index, str):
+ index = unhexlify(index)
+ assert len(index) == 32
+ return index
+ if isinstance(index, int):
+ return struct.pack("<I", index)
+
+ raise ValueError(
+ f"Unknown index type {type(index)} v:{index}, should be int or bytearray"
+ )
+
+
class ClientSettings:
"""Class implementing ZMQ client settings"""
def __init__(self, timeout=10, context=None, loop=None):
@@ -41,7 +55,7 @@ class ClientSettings:
@property
def context(self):
"""ZMQ context property"""
- if not self.context:
+ if not self._context:
ctx = zmq.asyncio.Context()
ctx.linger = 500 # in milliseconds
self._context = ctx
@@ -234,3 +248,9 @@ class Client:
assert response.command == request.command
assert response.request_id == request.id_
return response.error_code, response.data
+
+ async def block_header(self, index):
+ """Fetch a block header by its height or integer index"""
+ command = b"blockchain.fetch_block_header"
+ data = pack_block_index(index)
+ return await self._simple_request(command, data)