commit 96b81af376581e3ce32317c8feb8d9e41357a185
parent 0d68b0827e7aac7c0202a1dc95d21a4353c6983e
Author: chris-belcher <chris-belcher@users.noreply.github.com>
Date: Tue, 20 Mar 2018 11:40:49 +0000
Made the jsonrpc code use a persistent connection
Diffstat:
M | jsonrpc.py | | | 125 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------ |
1 file changed, 97 insertions(+), 28 deletions(-)
diff --git a/jsonrpc.py b/jsonrpc.py
@@ -1,59 +1,128 @@
-#jsonrpc.py from https://github.com/JoinMarket-Org/joinmarket/blob/master/joinmarket/jsonrpc.py
-#copyright # Copyright (C) 2013,2015 by Daniel Kraft <d@domob.eu> and phelix / blockchained.com
+# from https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/py3/jmclient/jmclient/jsonrpc.py
+
+# Copyright (C) 2013,2015 by Daniel Kraft <d@domob.eu>
+# Copyright (C) 2014 by phelix / blockchained.com
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
import base64
-import http.client
+try:
+ import http.client as httplib
+except ImportError:
+ import httplib
import json
+
class JsonRpcError(Exception):
+ """
+ The called method returned an error in the JSON-RPC response.
+ """
def __init__(self, obj):
self.code = obj["code"]
self.message = obj["message"]
-class JsonRpcConnectionError(JsonRpcError): pass
+
+class JsonRpcConnectionError(Exception):
+ """
+ Error thrown when the RPC connection itself failed. This means
+ that the server is either down or the connection settings
+ are wrong.
+ """
+ pass
+
class JsonRpc(object):
+ """
+ Simple implementation of a JSON-RPC client that is used
+ to connect to Bitcoin.
+ """
def __init__(self, host, port, user, password):
self.host = host
self.port = port
- self.authstr = "%s:%s" % (user, password)
+ self.conn = httplib.HTTPConnection(self.host, self.port)
+ self.authstr = bytes("%s:%s" % (user, password), "utf-8")
self.queryId = 1
def queryHTTP(self, obj):
- headers = {"User-Agent": "electrum-personal-server",
+ """
+ Send an appropriate HTTP query to the server. The JSON-RPC
+ request should be (as object) in 'obj'. If the call succeeds,
+ the resulting JSON object is returned. In case of an error
+ with the connection (not JSON-RPC itself), an exception is raised.
+ """
+ headers = {"User-Agent": "joinmarket",
"Content-Type": "application/json",
"Accept": "application/json"}
headers["Authorization"] = "Basic %s" % base64.b64encode(
- self.authstr.encode()).decode()
+ self.authstr).decode("utf-8")
body = json.dumps(obj)
- try:
- conn = http.client.HTTPConnection(self.host, self.port)
- conn.request("POST", "", body, headers)
- response = conn.getresponse()
- if response.status == 401:
- conn.close()
- raise JsonRpcConnectionError(
- "authentication for JSON-RPC failed")
- # All of the codes below are 'fine' from a JSON-RPC point of view.
- if response.status not in [200, 404, 500]:
- conn.close()
- raise JsonRpcConnectionError("unknown error in JSON-RPC")
- data = response.read()
- conn.close()
- return json.loads(data.decode())
- except JsonRpcConnectionError as exc:
- raise exc
- except Exception as exc:
- raise JsonRpcConnectionError("JSON-RPC connection failed. Err:" +
- repr(exc))
+ while True:
+ try:
+ self.conn.request("POST", "", body, headers)
+ response = self.conn.getresponse()
+ if response.status == 401:
+ self.conn.close()
+ raise JsonRpcConnectionError(
+ "authentication for JSON-RPC failed")
+ # All of below codes are 'fine' from a JSON-RPC point of view.
+ if response.status not in [200, 404, 500]:
+ self.conn.close()
+ raise JsonRpcConnectionError("unknown error in JSON-RPC")
+ data = response.read()
+ return json.loads(data.decode("utf-8"))
+ except JsonRpcConnectionError as exc:
+ raise exc
+ except httplib.BadStatusLine:
+ return "CONNFAILURE"
+ except Exception as exc:
+ if str(exc) == "Connection reset by peer":
+ self.conn.connect()
+ continue
+ else:
+ raise JsonRpcConnectionError("JSON-RPC connection failed" +
+ ". Err:" + repr(exc))
+ break
def call(self, method, params):
+ """
+ Call a method over JSON-RPC.
+ """
currentId = self.queryId
self.queryId += 1
request = {"method": method, "params": params, "id": currentId}
- response = self.queryHTTP(request)
+ #query can fail from keepalive timeout; keep retrying if it does, up
+ #to a reasonable limit, then raise (failure to access blockchain
+ #is a critical failure). Note that a real failure to connect (e.g.
+ #wrong port) is raised in queryHTTP directly.
+ response_received = False
+ for i in range(100):
+ response = self.queryHTTP(request)
+ if response != "CONNFAILURE":
+ response_received = True
+ break
+ #Failure means keepalive timed out, just make a new one
+ self.conn = httplib.HTTPConnection(self.host, self.port)
+ if not response_received:
+ raise JsonRpcConnectionError("Unable to connect over RPC")
if response["id"] != currentId:
raise JsonRpcConnectionError("invalid id returned by query")
if response["error"] is not None:
raise JsonRpcError(response["error"])
return response["result"]
+