jsonrpc.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"]