rpc.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
  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: Bitcoin RPC library for the MMGen suite
  20. """
  21. import httplib,base64,json
  22. from mmgen.common import *
  23. from decimal import Decimal
  24. from mmgen.obj import BTCAmt
  25. class BitcoinRPCConnection(object):
  26. client_version = 0
  27. def __init__(
  28. self,
  29. host='localhost',port=(8332,18332)[g.testnet],
  30. user=None,passwd=None,auth_cookie=None,
  31. ):
  32. if auth_cookie:
  33. self.auth_str = auth_cookie
  34. elif user and passwd:
  35. self.auth_str = '{}:{}'.format(user,passwd)
  36. else:
  37. msg('Error: no Bitcoin RPC authentication method found')
  38. if passwd: die(1,"'rpcuser' entry missing in bitcoin.conf")
  39. elif user: die(1,"'rpcpassword' entry missing in bitcoin.conf")
  40. else:
  41. m1 = 'Either provide rpcuser/rpcpassword in bitcoin.conf'
  42. m2 = '(or, alternatively, copy the authentication cookie to Bitcoin data dir'
  43. m3 = 'if {} and Bitcoin are running as different users)'.format(g.proj_name)
  44. die(1,'\n'.join((m1,m2,m3)))
  45. self.host = host
  46. self.port = port
  47. # Normal mode: call with arg list unrolled, exactly as with 'bitcoin-cli'
  48. # Batch mode: call with list of arg lists as first argument
  49. # kwargs are for local use and are not passed to server
  50. # By default, dies with an error msg on all errors and exceptions
  51. # With on_fail='return', returns 'rpcfail',(resp_object,(die_args))
  52. def request(self,cmd,*args,**kwargs):
  53. cf = { 'timeout':g.http_timeout, 'batch':False, 'on_fail':'die' }
  54. for k in cf:
  55. if k in kwargs and kwargs[k]: cf[k] = kwargs[k]
  56. hc = httplib.HTTPConnection(self.host, self.port, False, cf['timeout'])
  57. if cf['batch']:
  58. p = [{'method':cmd,'params':r,'id':n} for n,r in enumerate(args[0],1)]
  59. else:
  60. p = {'method':cmd,'params':args,'id':1}
  61. def die_maybe(*args):
  62. if cf['on_fail'] == 'return':
  63. return 'rpcfail',args
  64. else:
  65. die(*args[1:])
  66. dmsg('=== rpc.py debug ===')
  67. dmsg(' RPC POST data ==> %s\n' % p)
  68. caller = self
  69. class MyJSONEncoder(json.JSONEncoder):
  70. def default(self, obj):
  71. if isinstance(obj, BTCAmt):
  72. return (float,str)[caller.client_version>=120000](obj)
  73. return json.JSONEncoder.default(self, obj)
  74. # Can't do UTF-8 labels yet: httplib only ascii?
  75. # if type(p) != list and p['method'] == 'importaddress':
  76. # dump = json.dumps(p,cls=MyJSONEncoder,ensure_ascii=False)
  77. # print(dump)
  78. try:
  79. hc.request('POST', '/', json.dumps(p,cls=MyJSONEncoder), {
  80. 'Host': self.host,
  81. 'Authorization': 'Basic ' + base64.b64encode(self.auth_str)
  82. })
  83. except Exception as e:
  84. return die_maybe(None,2,'%s\nUnable to connect to bitcoind' % e)
  85. r = hc.getresponse() # returns HTTPResponse instance
  86. if r.status != 200:
  87. msgred('RPC Error: {} {}'.format(r.status,r.reason))
  88. e1 = r.read()
  89. try:
  90. e2 = json.loads(e1)['error']['message']
  91. except:
  92. e2 = str(e1)
  93. return die_maybe(r,1,e2)
  94. r2 = r.read()
  95. dmsg(' RPC REPLY data ==> %s\n' % r2)
  96. if not r2:
  97. return die_maybe(r,2,'Error: empty reply')
  98. # from decimal import Decimal
  99. r3 = json.loads(r2.decode('utf8'), parse_float=Decimal)
  100. ret = []
  101. for resp in r3 if cf['batch'] else [r3]:
  102. if 'error' in resp and resp['error'] != None:
  103. return die_maybe(r,1,'Bitcoind returned an error: %s' % resp['error'])
  104. elif 'result' not in resp:
  105. return die_maybe(r,1, 'Missing JSON-RPC result\n' + repr(resps))
  106. else:
  107. ret.append(resp['result'])
  108. return ret if cf['batch'] else ret[0]
  109. rpcmethods = (
  110. 'createrawtransaction',
  111. 'backupwallet',
  112. 'decoderawtransaction',
  113. 'estimatefee',
  114. 'getaddressesbyaccount',
  115. 'getbalance',
  116. 'getblock',
  117. 'getblockcount',
  118. 'getblockhash',
  119. 'getinfo',
  120. 'importaddress',
  121. 'listaccounts',
  122. 'listunspent',
  123. 'sendrawtransaction',
  124. 'signrawtransaction',
  125. 'getrawmempool',
  126. )
  127. for name in rpcmethods:
  128. exec "def {n}(self,*a,**k):return self.request('{n}',*a,**k)\n".format(n=name)
  129. def rpc_error(ret):
  130. return type(ret) is tuple and ret and ret[0] == 'rpcfail'
  131. def rpc_errmsg(ret): return ret[1][2]