Skip to content

Instantly share code, notes, and snippets.

Created September 19, 2013 18:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/6627745 to your computer and use it in GitHub Desktop.
Save anonymous/6627745 to your computer and use it in GitHub Desktop.
Sketch of a RPC client for *coind using async calls.
import json
import base64
import decimal
import futures
import urllib2
# The following are only for the repl.
import pprint
import readline
import traceback
import rlcompleter
readline.parse_and_bind('tab: complete')
MAX_WORKERS = 4
METHOD = {'walletlock': {'minparams': 0},
'walletpassphrase': {'minparams': 2, 'int': [1]},
'walletpassphrasechange': {'minparams': 2},
'verifymessage': {'minparams': 3},
'validateaddress': {'minparams': 1},
'stop': {'minparams': 0},
'signmessage': {'minparams': 2},
'settxfee': {'minparams': 1, 'float': [0]},
'setmininput': {'minparams': 1},
'setaccount': {'minparams': 2},
'sendtoaddress': {'minparams': 2, 'optparams': 2, 'float': [1]},
'sendrawtransaction': {'minparams': 1},
'sendfrom': {'minparams': 3, 'optparams': 3, 'float': [2], 'int': [3]},
'move': {'minparams': 3, 'optparams': 2, 'float': [2], 'int': [3]},
'listunspent': {'minparams': 0, 'optparams': 2, 'int': [0, 1]},
'listtransactions': {'minparams': 1, 'optparams': 2, 'int': [1, 2]},
'listsinceblock': {'minparams': 2},
'listreceivedbyaddress': {'minparams': 0, 'optparams': 2, 'int': [0]},
'listreceivedbyaccount': {'minparams': 0, 'optparams': 2, 'int': [0]},
'listaccounts': {'minparams': 0, 'optparams': 1, 'int': [0]},
'keypoolrefill': {'minparams': 0},
'importprivkey': {'minparams': 1, 'optparams': 1},
'help': {'minparams': 0, 'optparams': 1},
'getworkex': {'minparams': 0, 'optparams': 2},
'getwork': {'minparams': 0, 'optparams': 1},
'gettransaction': {'minparams': 1},
'getreceivedbyaddress': {'minparams': 1, 'optparams': 0, 'int': [1]},
'getreceivedbyaccount': {'minparams': 1, 'optparams': 0, 'int': [1]},
'getrawtransaction': {'minparams': 1, 'optparams': 1, 'int': [1]},
'getrawmempool': {'minparams': 0},
'getpeerinfo': {'minparams': 0},
'getnewaddress': {'minparams': 0, 'optparams': 1},
'getnetworkhashps': {'minparams': 0, 'optparams': 1},
'getmininginfo': {'minparams': 0},
'getinfo': {'minparams': 0},
'getdifficulty': {'minparams': 0},
'getconnectioncount': {'minparams': 0},
'getblocktemplate': {'minparams': 0, 'optparams': 1},
'getblockhash': {'minparams': 1, 'int': [0]},
'getblockcount': {'minparams': 0},
'getblock': {'minparams': 1},
'getbalance': {'minparams': 0, 'optparams': 2, 'int': [1]},
'getaddressesbyaccount': {'minparams': 1},
'getaccountaddress': {'minparams': 1},
'getaccount': {'minparams': 1},
'encryptwallet': {'minparams': 1},
'dumpprivkey': {'minparams': 1},
'decoderawtransaction': {'minparams': 1},
'backupwallet': {'minparams': 1},
'addnode': {'minparams': 2},
}
class AuthClientRPC:
def __init__(self, host, port, auth, protocol='https', timeout=6):
self.uri = '%s://%s:%s' % (protocol, host, port)
self.auth = auth
self.timeout = timeout
self.nonce = 1
self._executor = futures.ThreadPoolExecutor(max_workers=MAX_WORKERS)
def post(self, **data):
data.update({'id': self.nonce})
self.nonce += 1
return self._executor.submit(self._post, data)
def shutdown(self):
self._executor.shutdown()
def _post(self, data):
request = urllib2.Request(self.uri)
request.add_header('Authorization', self.auth)
result = urllib2.urlopen(request, json.dumps(data),
timeout=self.timeout)
return result.read()
def make_basic_auth(a, b):
return "Basic %s" % base64.b64encode('%s:%s' % (a, b))
def generic_cb(future, cb=None):
try:
result = future.result()
except urllib2.HTTPError, e:
result = e.read()
except urllib2.URLError, e:
data = e.reason
errcode, message = data.errno, data.strerror
if errcode is None:
errcode = 1000 # Timeout
message = 'daemon is certainly down'
result = {'error': {'code': -errcode, 'message': message}}
result = json.dumps(result)
jsdata = json.loads(result, parse_float=decimal.Decimal)
if jsdata['error'] is not None:
code, res = jsdata['error']['code'], jsdata['error']['message']
else:
code, res = 0, jsdata['result']
if cb:
callback, params = cb
return callback(code, res, params=params)
else:
return code, res
def generic_method(name, descr):
def func(self, *args, **kwargs):
if len(args) < descr['minparams']:
print "invalid call to %s, expected at least %d args" % (name,
descr['minparams'])
return None
args = list(args)
for i in xrange(len(args)):
if args[i] in ('""', "''"): # This is useful for the repl.
args[i] = ''
if 'float' in descr:
for i in descr['float']:
if i >= len(args):
break
args[i] = float(decimal.Decimal(args[i]))
if 'int' in descr:
for i in descr['int']:
if i >= len(args):
break
args[i] = int(args[i])
future = self.post(method=name, params=args)
if 'callback' in kwargs:
func = lambda future: generic_cb(future,
cb=(kwargs['callback'], args))
future.add_done_callback(func)
return future
return func
for name, params in METHOD.iteritems():
setattr(AuthClientRPC, name, generic_method(name, params))
def main(host, port, auth):
rpc = AuthClientRPC(host, port, auth)
print "Connected"
try:
repl(rpc)
finally:
rpc.shutdown()
def repl(rpc):
def show_result(code, res, params=None):
if isinstance(res, (dict, list)):
pprint.pprint(res)
else:
print res
while True:
cmd = raw_input("> ")
try:
todo = cmd.split()
if todo[0] in METHOD:
future = getattr(rpc, todo[0])(*todo[1:])
generic_cb(future, (show_result, None)) # Wait for completion.
else:
raise Exception("Unknown command %s" % todo[0])
except Exception, e:
print "Error: %s" % e
traceback.print_exc()
if __name__ == "__main__":
import socket
import getpass
rpchost = socket.gethostbyname(raw_input('Host: '))
rpcport = raw_input('Port: ')
username = getpass.getpass('Username: ')
pwd = getpass.getpass('Password: ')
rpcauth = make_basic_auth(username, pwd)
main(rpchost, rpcport, rpcauth)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment