tjsonrpc.py - electrum-personal-server - Maximally lightweight electrum server for a single user
HTML git clone https://git.parazyd.org/electrum-personal-server
DIR Log
DIR Files
DIR Refs
DIR README
---
tjsonrpc.py (5094B)
---
1 # Copyright (C) 2013,2015 by Daniel Kraft <d@domob.eu>
2 # Copyright (C) 2014 by phelix / blockchained.com
3
4 #jsonrpc.py from https://github.com/JoinMarket-Org/joinmarket-clientserver/blob/master/jmclient/jmclient/jsonrpc.py
5
6 import base64
7 import http.client
8 import json
9
10 class JsonRpcError(Exception): pass
11 class JsonRpcConnectionError(JsonRpcError): pass
12
13 class JsonRpc(object):
14 """
15 Simple implementation of a JSON-RPC client that is used
16 to connect to Bitcoin.
17 """
18 def __init__(self, host, port, user, password, cookie_path=None,
19 wallet_filename="", logger=None):
20 self.host = host
21 self.port = port
22
23 self.cookie_path = cookie_path
24 if cookie_path:
25 self.load_from_cookie()
26 else:
27 self.create_authstr(user, password)
28
29 self.conn = http.client.HTTPConnection(self.host, self.port)
30 if len(wallet_filename) > 0:
31 self.url = "/wallet/" + wallet_filename
32 else:
33 self.url = ""
34 self.logger = logger
35 self.queryId = 1
36
37 def create_authstr(self, username, password):
38 self.authstr = "%s:%s" % (username, password)
39
40 def load_from_cookie(self):
41 fd = open(self.cookie_path)
42 username, password = fd.read().strip().split(":")
43 fd.close()
44 self.create_authstr(username, password)
45
46 def queryHTTP(self, obj):
47 """
48 Send an appropriate HTTP query to the server. The JSON-RPC
49 request should be (as object) in 'obj'. If the call succeeds,
50 the resulting JSON object is returned. In case of an error
51 with the connection (not JSON-RPC itself), an exception is raised.
52 """
53 headers = {"User-Agent": "electrum-personal-server",
54 "Content-Type": "application/json",
55 "Accept": "application/json"}
56 headers["Authorization"] = (b"Basic " +
57 base64.b64encode(self.authstr.encode('utf-8')))
58 body = json.dumps(obj)
59 auth_failed_once = False
60 for i in range(20):
61 try:
62 self.conn.request("POST", self.url, body, headers)
63 response = self.conn.getresponse()
64 if response.status == 401:
65 if self.cookie_path == None or auth_failed_once:
66 self.conn.close()
67 raise JsonRpcConnectionError(
68 "authentication for JSON-RPC failed")
69 else:
70 auth_failed_once = True
71 #try reloading u/p from the cookie file once
72 self.load_from_cookie()
73 raise OSError() #jump to error handler below
74 auth_failed_once = False
75 #All the codes below are 'fine' from a JSON-RPC point of view.
76 if response.status not in [200, 404, 500]:
77 self.conn.close()
78 raise JsonRpcConnectionError("unknown error in JSON-RPC")
79 data = response.read()
80 return json.loads(data.decode('utf-8'))
81 except JsonRpcConnectionError as exc:
82 raise exc
83 except http.client.BadStatusLine:
84 return "CONNFAILURE"
85 except OSError:
86 # connection dropped, reconnect
87 try:
88 self.conn.close()
89 self.conn.connect()
90 except ConnectionError as e:
91 #node probably offline, notify with jsonrpc error
92 raise JsonRpcConnectionError(repr(e))
93 continue
94 except Exception as exc:
95 raise JsonRpcConnectionError("JSON-RPC connection failed. Err:"
96 + repr(exc))
97 break
98 return None
99
100 def call(self, method, params):
101 currentId = self.queryId
102 self.queryId += 1
103
104 request = {"method": method, "params": params, "id": currentId}
105 #query can fail from keepalive timeout; keep retrying if it does, up
106 #to a reasonable limit, then raise (failure to access blockchain
107 #is a critical failure). Note that a real failure to connect (e.g.
108 #wrong port) is raised in queryHTTP directly.
109 response_received = False
110 for i in range(100):
111 response = self.queryHTTP(request)
112 if response != "CONNFAILURE":
113 response_received = True
114 break
115 #Failure means keepalive timed out, just make a new one
116 self.conn = http.client.HTTPConnection(self.host, self.port)
117 self.logger.debug("Creating new jsonrpc HTTPConnection")
118 if not response_received:
119 raise JsonRpcConnectionError("Unable to connect over RPC")
120 if response["id"] != currentId:
121 raise JsonRpcConnectionError("invalid id returned by query")
122 if response["error"] is not None:
123 raise JsonRpcError(response["error"])
124 return response["result"]