electrum-personal-server

Maximally lightweight electrum server for a single user
git clone https://git.parazyd.org/electrum-personal-server
Log | Files | Refs | README

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