commit e8ea79bf3393e0a427ece4add23a41728206191a
parent 720e15a7f78eecba1720909c66bcf28a86975672
Author: parazyd <parazyd@dyne.org>
Date: Thu, 15 Apr 2021 11:59:30 +0200
Reimplement JSON-RPC errors as a class.
Diffstat:
5 files changed, 112 insertions(+), 106 deletions(-)
diff --git a/obelisk/errors.py b/obelisk/errors.py
@@ -1,58 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (C) 2020-2021 Ivan J. <parazyd@dyne.org>
-#
-# This file is part of obelisk
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License version 3
-# as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# 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/>.
-"""JSON-RPC errors
-https://www.jsonrpc.org/specification#error_object
-"""
-
-ERRORS = {
- "invalidparams": {
- "error": {
- "code": -32602,
- "message": "invalid parameters"
- }
- },
- "internalerror": {
- "error": {
- "code": -32603,
- "message": "internal error"
- }
- },
- "parseerror": {
- "error": {
- "code": -32700,
- "message": "parse error"
- }
- },
- "invalidrequest": {
- "error": {
- "code": -32600,
- "message": "invalid request"
- }
- },
- "nomethod": {
- "error": {
- "code": -32601,
- "message": "method not found"
- }
- },
- "protonotsupported": {
- "error": {
- "code": -32100,
- "message": "client protocol version is not supported",
- }
- },
-}
diff --git a/obelisk/errors_jsonrpc.py b/obelisk/errors_jsonrpc.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+# Copyright (C) 2021 Ivan J. <parazyd@dyne.org>
+#
+# This file is part of obelisk
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# 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/>.
+"""JSON-RPC errors: https://www.jsonrpc.org/specification#error_object"""
+
+
+class JsonRPCError:
+
+ def __init__(self):
+ return
+
+ @staticmethod
+ def invalidrequest():
+ return {"error": {"code": -32600, "message": "invalid request"}}
+
+ @staticmethod
+ def methodnotfound():
+ return {"error": {"code": -32601, "message": "method not found"}}
+
+ @staticmethod
+ def invalidparams():
+ return {"error": {"code": -32602, "message": "invalid parameters"}}
+
+ @staticmethod
+ def internalerror():
+ return {"error": {"code": -32603, "message": "internal error"}}
+
+ @staticmethod
+ def parseerror():
+ return {"error": {"code": -37200, "message": "parse error"}}
+
+ @staticmethod
+ def protonotsupported():
+ return {
+ "error": {
+ "code": -32100,
+ "message": "protocol version unsupported"
+ }
+ }
diff --git a/obelisk/libbitcoin_errors.py b/obelisk/errors_libbitcoin.py
diff --git a/obelisk/protocol.py b/obelisk/protocol.py
@@ -21,7 +21,7 @@ import asyncio
import json
from binascii import unhexlify
-from obelisk.errors import ERRORS
+from obelisk.errors_jsonrpc import JsonRPCError
from obelisk.merkle import merkle_branch, merkle_branch_and_root
from obelisk.util import (
bh2u,
@@ -213,7 +213,8 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
func = self.methodmap.get(method)
if not func:
self.log.error("Unhandled method %s, query=%s", method, query)
- return await self._send_reply(writer, ERRORS["nomethod"], query)
+ return await self._send_reply(writer, JsonRPCError.methodnotfound(),
+ query)
resp = await func(writer, query)
return await self._send_reply(writer, resp, query)
@@ -222,22 +223,22 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
Return the block header at the given height.
"""
if "params" not in query or len(query["params"]) < 1:
- return ERRORS["invalidparams"]
+ 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 ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
if not is_non_negative_integer(cp_height):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
if cp_height != 0 and not index <= cp_height:
- return ERRORS["invalidparams"]
+ 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 ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
return {"result": safe_hexlify(header)}
cp_headers = []
@@ -246,7 +247,7 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
_ec, data = await self.bx.fetch_block_header(i)
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
cp_headers.append(data)
# TODO: Review
@@ -264,7 +265,7 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
Return a concatenated chunk of block headers from the main chain.
"""
if "params" not in query or len(query["params"]) < 2:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
# Electrum doesn't allow max_chunk_size to be less than 2016
# gopher://bitreich.org/9/memecache/convenience-store.mkv
# TODO: cp_height
@@ -273,9 +274,9 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
count = query["params"][1]
if not is_non_negative_integer(start_height):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
if not is_non_negative_integer(count):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
count = min(count, max_chunk_size)
headers = bytearray()
@@ -283,7 +284,7 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
_ec, data = await self.bx.fetch_block_header(i)
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
headers.extend(data)
resp = {
@@ -325,11 +326,11 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
_ec, height = await self.bx.fetch_last_height()
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ 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 ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
self.tasks.append(asyncio.create_task(self.header_notifier(writer)))
ret = {"height": height, "hex": safe_hexlify(tip_header)}
@@ -348,15 +349,15 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
Return the confirmed and unconfirmed balances of a script hash.
"""
if "params" not in query or len(query["params"]) != 1:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
if not is_hash256_str(query["params"][0]):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
_ec, data = await self.bx.fetch_balance(query["params"][0])
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
# TODO: confirmed/unconfirmed, see what's happening in libbitcoin
ret = {"confirmed": data, "unconfirmed": 0}
@@ -367,15 +368,15 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
Return the confirmed and unconfirmed history of a script hash.
"""
if "params" not in query or len(query["params"]) != 1:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
if not is_hash256_str(query["params"][0]):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
_ec, data = await self.bx.fetch_history4(query["params"][0])
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
self.log.debug("hist: %s", data)
ret = []
@@ -398,6 +399,7 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
"""Method: blockchain.scripthash.get_mempool
Return the unconfirmed transactions of a script hash.
"""
+ # TODO: Implement
return
async def blockchain_scripthash_listunspent(self, writer, query): # pylint: disable=W0613
@@ -405,16 +407,16 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
Return an ordered list of UTXOs sent to a script hash.
"""
if "params" not in query or len(query["params"]) != 1:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
scripthash = query["params"][0]
if not is_hash256_str(scripthash):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
_ec, utxo = await self.bx.fetch_utxo(scripthash)
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
# TODO: Check mempool
ret = []
@@ -445,15 +447,15 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
Subscribe to a script hash.
"""
if "params" not in query or len(query["params"]) != 1:
- return ERRORS["invalidparamas"]
+ return JsonRPCError.invalidparams()
scripthash = query["params"][0]
if not is_hash256_str(scripthash):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
_ec, history = await self.bx.fetch_history4(scripthash)
if _ec and _ec != 0:
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
task = asyncio.create_task(self.scripthash_notifier(writer, scripthash))
self.sh_subscriptions[scripthash] = {"task": task}
@@ -491,11 +493,11 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
if its status changes.
"""
if "params" not in query or len(query["params"]) != 1:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
scripthash = query["params"][0]
if not is_hash256_str(scripthash):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
if scripthash in self.sh_subscriptions:
self.sh_subscriptions[scripthash]["task"].cancel()
@@ -511,16 +513,16 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
"""
# Note: Not yet implemented in bs v4
if "params" not in query or len(query["params"]) != 1:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
hextx = query["params"][0]
if not is_hex_str(hextx):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
_ec, _ = await self.bx.broadcast_transaction(unhexlify(hextx)[::-1])
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
rawtx = unhexlify(hextx)
txid = double_sha256(rawtx)
@@ -531,7 +533,8 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
Return a raw transaction.
"""
if "params" not in query or len(query["params"]) < 1:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
+
tx_hash = query["params"][0]
verbose = query["params"][1] if len(query["params"]) > 1 else False
@@ -539,7 +542,7 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
_ec, rawtx = await self.bx.fetch_mempool_transaction(tx_hash)
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
# Behaviour is undefined in spec
if not rawtx:
@@ -547,7 +550,7 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
if verbose:
# TODO: Help needed
- return ERRORS["invalidrequest"]
+ return JsonRPCError.invalidrequest()
return {"result": bh2u(rawtx)}
@@ -557,19 +560,20 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
hash and height.
"""
if "params" not in query or len(query["params"]) != 2:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
+
tx_hash = query["params"][0]
height = query["params"][1]
if not is_hash256_str(tx_hash):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
if not is_non_negative_integer(height):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
_ec, hashes = await self.bx.fetch_block_transaction_hashes(height)
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
# Decouple from tuples
hashes = [i[0] for i in hashes]
@@ -589,25 +593,26 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
block height and a position in the block.
"""
if "params" not in query or len(query["params"]) < 2:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
+
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 ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
if not is_non_negative_integer(tx_pos):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
if not is_boolean(merkle):
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
_ec, hashes = await self.bx.fetch_block_transaction_hashes(height)
if _ec and _ec != 0:
self.log.debug("Got error: %s", repr(_ec))
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
if len(hashes) - 1 < tx_pos:
- return ERRORS["internalerror"]
+ return JsonRPCError.internalerror()
# Decouple from tuples
hashes = [i[0] for i in hashes]
@@ -615,6 +620,7 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
if not merkle:
return {"result": txid}
+
branch = merkle_branch(hashes, tx_pos)
return {"result": {"tx_hash": txid, "merkle": branch}}
@@ -689,13 +695,18 @@ class ElectrumProtocol(asyncio.Protocol): # pylint: disable=R0904,R0902
Identify the client to the server and negotiate the protocol version.
"""
if "params" not in query or len(query["params"]) != 2:
- return ERRORS["invalidparams"]
+ return JsonRPCError.invalidparams()
+
client_ver = query["params"][1]
+
if isinstance(client_ver, list):
client_min, client_max = client_ver[0], client_ver[1]
else:
client_min = client_max = client_ver
+
version = min(client_max, SERVER_PROTO_MAX)
+
if version < max(client_min, SERVER_PROTO_MIN):
- return ERRORS["protonotsupported"]
+ return JsonRPCError.protonotsupported()
+
return {"result": [f"obelisk {VERSION}", version]}
diff --git a/obelisk/zeromq.py b/obelisk/zeromq.py
@@ -24,7 +24,8 @@ from random import randint
import zmq
import zmq.asyncio
-from obelisk.libbitcoin_errors import make_error_code, ErrorCode
+from obelisk.errors_libbitcoin import make_error_code, ErrorCode
+from obelisk.util import hash_to_hex_str
def create_random_id():
@@ -372,7 +373,7 @@ class Client:
},
height,
value,
- checksum(tx_hash[::-1].hex(), index),
+ checksum(hash_to_hex_str(tx_hash), index),
)
rows = unpack_table("<BI32sIQ", raw_points)