commit 9b6accd058e54b69c16596c303fa67682134f34c
parent f1a7d430089c56ba5f6446a2eae245ce50c86967
Author: parazyd <parazyd@dyne.org>
Date: Wed, 7 Apr 2021 19:14:06 +0200
Implement blockchain.transaction.id_from_pos
Diffstat:
3 files changed, 87 insertions(+), 3 deletions(-)
diff --git a/electrumobelisk/protocol.py b/electrumobelisk/protocol.py
@@ -17,7 +17,13 @@
import asyncio
import json
-from electrumobelisk.util import is_non_negative_integer, safe_hexlify
+from electrumobelisk.merkle import merkle_branch
+from electrumobelisk.util import (
+ is_boolean,
+ is_hash256_str,
+ is_non_negative_integer,
+ safe_hexlify,
+)
from electrumobelisk.zeromq import Client
VERSION = 0.0
@@ -235,8 +241,36 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
async def blockchain_transaction_get_merkle(self, query):
return
- async def blockchain_transaction_from_pos(self, query):
- return
+ async def blockchain_transaction_from_pos(self, query): # pylint: disable=R0911
+ if "params" not in query or len(query["params"]) < 2:
+ return {"error": "malformed request"}
+ height = query["params"][0]
+ tx_pos = query["params"][1]
+ merkle = query["params"][2] if len(query["params"]) > 2 else False
+
+ if not is_non_negative_integer(height):
+ return {"error": "height is not a non-negative integer"}
+ if not is_non_negative_integer(tx_pos):
+ return {"error": "tx_pos is not a non-negative integer"}
+ if not is_boolean(merkle):
+ return {"error": "merkle is not a boolean value"}
+
+ _ec, hashes = await self.bx.fetch_block_transaction_hashes(height)
+ if _ec and _ec != 0:
+ self.log.debug("Got error: %s", repr(_ec))
+ return {"error": "request corrupted"}
+
+ if len(hashes) - 1 < tx_pos:
+ return {"error": "index not in block"}
+
+ # Decouple from tuples
+ hashes = [i[0] for i in hashes]
+ txid = safe_hexlify(hashes[tx_pos][::-1])
+
+ if not merkle:
+ return {"result": txid}
+ branch = merkle_branch(hashes, tx_pos)
+ return {"result": {"tx_hash": txid, "merkle": branch}}
async def mempool_get_fee_histogram(self, query): # pylint: disable=W0613
# Help wanted
diff --git a/electrumobelisk/util.py b/electrumobelisk/util.py
@@ -30,6 +30,34 @@ def is_non_negative_integer(val):
return False
+def is_boolean(val):
+ """Check if val is of type bool"""
+ return isinstance(val, bool)
+
+
+def is_hex_str(text):
+ """Check if text is a hex string"""
+ if not isinstance(text, str):
+ return False
+ try:
+ b = bytes.fromhex(text)
+ except:
+ return False
+ # Forbid whitespaces in text:
+ if len(text) != 2 * len(b):
+ return False
+ return True
+
+
+def is_hash256_str(text):
+ """Check if text is a sha256 hash"""
+ if not isinstance(text, str):
+ return False
+ if len(text) != 64:
+ return False
+ return is_hex_str(text)
+
+
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
@@ -45,6 +45,19 @@ def pack_block_index(index):
)
+def unpack_table(row_fmt, data):
+ # Get the number of rows
+ row_size = struct.calcsize(row_fmt)
+ nrows = len(data) // row_size
+ # Unpack
+ rows = []
+ for idx in range(nrows):
+ offset = idx * row_size
+ row = struct.unpack_from(row_fmt, data, offset)
+ rows.append(row)
+ return rows
+
+
class ClientSettings:
"""Class implementing ZMQ client settings"""
def __init__(self, timeout=10, context=None, loop=None):
@@ -254,3 +267,12 @@ class Client:
command = b"blockchain.fetch_block_header"
data = pack_block_index(index)
return await self._simple_request(command, data)
+
+ async def fetch_block_transaction_hashes(self, index):
+ """Fetch transaction hashes in a block at height index"""
+ command = b"blockchain.fetch_block_transaction_hashes"
+ data = pack_block_index(index)
+ error_code, data = await self._simple_request(command, data)
+ if error_code:
+ return error_code, None
+ return error_code, unpack_table("32s", data)