rpc.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2018 The MMGen Project <mmgen@tuta.io>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. rpc.py: Cryptocoin RPC library for the MMGen suite
  20. """
  21. import httplib,base64,json
  22. from mmgen.common import *
  23. from decimal import Decimal
  24. class CoinDaemonRPCConnection(object):
  25. def __init__(self,host=None,port=None,user=None,passwd=None,auth_cookie=None):
  26. dmsg('=== CoinDaemonRPCConnection.__init__() debug ===')
  27. dmsg(' host [{}] port [{}] user [{}] passwd [{}] auth_cookie [{}]\n'.format(
  28. host,port,user,passwd,auth_cookie))
  29. import socket
  30. try:
  31. socket.create_connection((host,port),timeout=3).close()
  32. except:
  33. die(1,'Unable to connect to {}:{}'.format(host,port))
  34. if user and passwd:
  35. self.auth_str = '{}:{}'.format(user,passwd)
  36. elif auth_cookie:
  37. self.auth_str = auth_cookie
  38. else:
  39. msg('Error: no {} RPC authentication method found'.format(g.proto.name.capitalize()))
  40. if passwd: die(1,"'rpcuser' entry not found in {}.conf or mmgen.cfg".format(g.proto.name))
  41. elif user: die(1,"'rpcpassword' entry not found in {}.conf or mmgen.cfg".format(g.proto.name))
  42. else:
  43. m1 = 'Either provide rpcuser/rpcpassword in {pn}.conf or mmgen.cfg\n'
  44. m2 = '(or, alternatively, copy the authentication cookie to the {pnu}\n'
  45. m3 = 'data dir if {pnm} and {dn} are running as different users)'
  46. die(1,(m1+m2+m3).format(
  47. pn=g.proto.name,
  48. pnu=g.proto.name.capitalize(),
  49. dn=g.proto.daemon_name,
  50. pnm=g.proj_name))
  51. self.host = host
  52. self.port = port
  53. # Normal mode: call with arg list unrolled, exactly as with cli
  54. # Batch mode: call with list of arg lists as first argument
  55. # kwargs are for local use and are not passed to server
  56. # By default, dies with an error msg on all errors and exceptions
  57. # With on_fail='return', returns 'rpcfail',(resp_object,(die_args))
  58. def request(self,cmd,*args,**kwargs):
  59. cf = { 'timeout':g.http_timeout, 'batch':False, 'on_fail':'die' }
  60. for k in cf:
  61. if k in kwargs and kwargs[k]: cf[k] = kwargs[k]
  62. hc = httplib.HTTPConnection(self.host, self.port, False, cf['timeout'])
  63. if cf['batch']:
  64. p = [{'method':cmd,'params':r,'id':n} for n,r in enumerate(args[0],1)]
  65. else:
  66. p = {'method':cmd,'params':args,'id':1}
  67. def die_maybe(*args):
  68. if cf['on_fail'] in ('return','silent'):
  69. return 'rpcfail',args
  70. else:
  71. try: s = u'{}'.format(args[2])
  72. except: s = repr(args[2])
  73. die(args[1],yellow(s))
  74. dmsg('=== request() debug ===')
  75. dmsg(' RPC POST data ==> %s\n' % p)
  76. caller = self
  77. class MyJSONEncoder(json.JSONEncoder):
  78. def default(self, obj):
  79. if isinstance(obj,g.proto.coin_amt):
  80. return g.proto.get_rpc_coin_amt_type()(obj)
  81. return json.JSONEncoder.default(self, obj)
  82. # TODO: UTF-8 labels
  83. # if type(p) != list and p['method'] == 'importaddress':
  84. # dump = json.dumps(p,cls=MyJSONEncoder,ensure_ascii=False)
  85. # print(dump)
  86. dmsg(' RPC AUTHORIZATION data ==> raw: [{}]\n{}enc: [Basic {}]\n'.format(
  87. self.auth_str,' '*31,base64.b64encode(self.auth_str)))
  88. try:
  89. hc.request('POST', '/', json.dumps(p,cls=MyJSONEncoder), {
  90. 'Host': self.host,
  91. 'Authorization': 'Basic {}'.format(base64.b64encode(self.auth_str))
  92. })
  93. except Exception as e:
  94. m = '{}\nUnable to connect to {} at {}:{}'
  95. return die_maybe(None,2,m.format(e,g.proto.daemon_name,self.host,self.port))
  96. try:
  97. r = hc.getresponse() # returns HTTPResponse instance
  98. except Exception:
  99. m = 'Unable to connect to {} at {}:{} (but port is bound?)'
  100. return die_maybe(None,2,m.format(g.proto.daemon_name,self.host,self.port))
  101. dmsg(' RPC GETRESPONSE data ==> %s\n' % r.__dict__)
  102. if r.status != 200:
  103. if cf['on_fail'] != 'silent':
  104. msg_r(yellow('{} RPC Error: '.format(g.proto.daemon_name.capitalize())))
  105. msg(red('{} {}'.format(r.status,r.reason)))
  106. e1 = r.read()
  107. try:
  108. e3 = json.loads(e1)['error']
  109. e2 = '{} (code {})'.format(e3['message'],e3['code'])
  110. except:
  111. e2 = str(e1)
  112. return die_maybe(r,1,e2)
  113. r2 = r.read()
  114. dmsg(' RPC REPLY data ==> %s\n' % r2)
  115. if not r2:
  116. return die_maybe(r,2,'Error: empty reply')
  117. # from decimal import Decimal
  118. r3 = json.loads(r2.decode('utf8'), parse_float=Decimal)
  119. ret = []
  120. for resp in r3 if cf['batch'] else [r3]:
  121. if 'error' in resp and resp['error'] != None:
  122. return die_maybe(r,1,'{} returned an error: {}'.format(
  123. g.proto.daemon_name.capitalize(),resp['error']))
  124. elif 'result' not in resp:
  125. return die_maybe(r,1, 'Missing JSON-RPC result\n' + repr(resps))
  126. else:
  127. ret.append(resp['result'])
  128. return ret if cf['batch'] else ret[0]
  129. rpcmethods = (
  130. 'backupwallet',
  131. 'createrawtransaction',
  132. 'decoderawtransaction',
  133. 'disconnectnode',
  134. 'estimatefee',
  135. 'getaddressesbyaccount',
  136. 'getbalance',
  137. 'getblock',
  138. 'getblockchaininfo',
  139. 'getblockcount',
  140. 'getblockhash',
  141. 'getmempoolinfo',
  142. 'getmempoolentry',
  143. 'getnettotals',
  144. 'getnetworkinfo',
  145. 'getpeerinfo',
  146. 'getrawmempool',
  147. 'getmempoolentry',
  148. 'getrawtransaction',
  149. 'gettransaction',
  150. 'importaddress',
  151. 'listaccounts',
  152. 'listunspent',
  153. 'sendrawtransaction',
  154. 'signrawtransaction',
  155. 'validateaddress',
  156. 'walletpassphrase',
  157. )
  158. for name in rpcmethods:
  159. exec "def {n}(self,*a,**k):return self.request('{n}',*a,**k)\n".format(n=name)
  160. def rpc_error(ret):
  161. return type(ret) is tuple and ret and ret[0] == 'rpcfail'
  162. def rpc_errmsg(ret): return ret[1][2]