rpc.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2021 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 base64,json,asyncio
  22. from decimal import Decimal
  23. from .common import *
  24. from .obj import aInitMeta
  25. rpc_credentials_msg = '\n'+fmt("""
  26. Error: no {proto_name} RPC authentication method found
  27. RPC credentials must be supplied using one of the following methods:
  28. A) If daemon is local and running as same user as you:
  29. - no credentials required, or matching rpcuser/rpcpassword and
  30. rpc_user/rpc_password values in {cf_name}.conf and mmgen.cfg
  31. B) If daemon is running remotely or as different user:
  32. - matching credentials in {cf_name}.conf and mmgen.cfg as described above
  33. The --rpc-user/--rpc-password options may be supplied on the MMGen command line.
  34. They override the corresponding values in mmgen.cfg. Set them to an empty string
  35. to use cookie authentication with a local server when the options are set
  36. in mmgen.cfg.
  37. For better security, rpcauth should be used in {cf_name}.conf instead of
  38. rpcuser/rpcpassword.
  39. """,strip_char='\t')
  40. def dmsg_rpc(fs,data=None,is_json=False):
  41. if g.debug_rpc:
  42. msg(fs if data == None else fs.format(pp_fmt(json.loads(data) if is_json else data)))
  43. class json_encoder(json.JSONEncoder):
  44. def default(self,obj):
  45. if isinstance(obj,Decimal):
  46. return str(obj)
  47. else:
  48. return json.JSONEncoder.default(self,obj)
  49. class RPCBackends:
  50. class base:
  51. def __init__(self,caller):
  52. self.host = caller.host
  53. self.port = caller.port
  54. self.url = caller.url
  55. self.timeout = caller.timeout
  56. self.http_hdrs = caller.http_hdrs
  57. class aiohttp(base):
  58. def __init__(self,caller):
  59. super().__init__(caller)
  60. self.session = g.session
  61. if caller.auth_type == 'basic':
  62. import aiohttp
  63. self.auth = aiohttp.BasicAuth(*caller.auth,encoding='UTF-8')
  64. else:
  65. self.auth = None
  66. async def run(self,payload,timeout):
  67. dmsg_rpc('\n RPC PAYLOAD data (aiohttp) ==>\n{}\n',payload)
  68. async with self.session.post(
  69. url = self.url,
  70. auth = self.auth,
  71. data = json.dumps(payload,cls=json_encoder),
  72. timeout = timeout or self.timeout,
  73. ) as res:
  74. return (await res.text(),res.status)
  75. class requests(base):
  76. def __init__(self,caller):
  77. super().__init__(caller)
  78. import requests,urllib3
  79. urllib3.disable_warnings()
  80. self.session = requests.Session()
  81. self.session.headers = caller.http_hdrs
  82. if caller.auth_type:
  83. auth = 'HTTP' + caller.auth_type.capitalize() + 'Auth'
  84. self.session.auth = getattr(requests.auth,auth)(*caller.auth)
  85. async def run(self,payload,timeout):
  86. dmsg_rpc('\n RPC PAYLOAD data (requests) ==>\n{}\n',payload)
  87. res = self.session.post(
  88. url = self.url,
  89. data = json.dumps(payload,cls=json_encoder),
  90. timeout = timeout or self.timeout,
  91. verify = False )
  92. return (res.content,res.status_code)
  93. class httplib(base):
  94. def __init__(self,caller):
  95. super().__init__(caller)
  96. import http.client
  97. self.session = http.client.HTTPConnection(caller.host,caller.port,caller.timeout)
  98. if caller.auth_type == 'basic':
  99. auth_str = f'{caller.auth.user}:{caller.auth.passwd}'
  100. auth_str_b64 = 'Basic ' + base64.b64encode(auth_str.encode()).decode()
  101. self.http_hdrs.update({ 'Host': self.host, 'Authorization': auth_str_b64 })
  102. fs = ' RPC AUTHORIZATION data ==> raw: [{}]\n{:>31}enc: [{}]\n'
  103. dmsg_rpc(fs.format(auth_str,'',auth_str_b64))
  104. async def run(self,payload,timeout):
  105. dmsg_rpc('\n RPC PAYLOAD data (httplib) ==>\n{}\n',payload)
  106. if timeout:
  107. import http.client
  108. s = http.client.HTTPConnection(self.host,self.port,timeout)
  109. else:
  110. s = self.session
  111. try:
  112. s.request(
  113. method = 'POST',
  114. url = '/',
  115. body = json.dumps(payload,cls=json_encoder),
  116. headers = self.http_hdrs )
  117. r = s.getresponse() # => http.client.HTTPResponse instance
  118. except Exception as e:
  119. raise RPCFailure(str(e))
  120. return (r.read(),r.status)
  121. class curl(base):
  122. def __init__(self,caller):
  123. def gen_opts():
  124. for k,v in caller.http_hdrs.items():
  125. for s in ('--header',f'{k}: {v}'):
  126. yield s
  127. if caller.auth_type:
  128. """
  129. Authentication with curl is insecure, as it exposes the user's credentials
  130. via the command line. Use for testing only.
  131. """
  132. for s in ('--user',f'{caller.auth.user}:{caller.auth.passwd}'):
  133. yield s
  134. if caller.auth_type == 'digest':
  135. yield '--digest'
  136. if caller.network_proto == 'https' and caller.verify_server == False:
  137. yield '--insecure'
  138. super().__init__(caller)
  139. self.exec_opts = list(gen_opts()) + ['--silent']
  140. self.arg_max = 8192 # set way below system ARG_MAX, just to be safe
  141. async def run(self,payload,timeout):
  142. data = json.dumps(payload,cls=json_encoder)
  143. if len(data) > self.arg_max:
  144. return self.httplib(payload,timeout=timeout)
  145. dmsg_rpc('\n RPC PAYLOAD data (curl) ==>\n{}\n',payload)
  146. exec_cmd = [
  147. 'curl',
  148. '--proxy', '',
  149. '--connect-timeout', str(timeout or self.timeout),
  150. '--request', 'POST',
  151. '--write-out', '%{http_code}',
  152. '--data-binary', data
  153. ] + self.exec_opts + [self.url]
  154. dmsg_rpc(' RPC curl exec data ==>\n{}\n',exec_cmd)
  155. from subprocess import run,PIPE
  156. res = run(exec_cmd,stdout=PIPE,check=True).stdout.decode()
  157. # res = run(exec_cmd,stdout=PIPE,check=True,text='UTF-8').stdout # Python 3.7+
  158. return (res[:-3],int(res[-3:]))
  159. from collections import namedtuple
  160. auth_data = namedtuple('rpc_auth_data',['user','passwd'])
  161. class RPCClient(MMGenObject):
  162. auth_type = None
  163. has_auth_cookie = False
  164. network_proto = 'http'
  165. host_path = ''
  166. def __init__(self,host,port):
  167. dmsg_rpc('=== {}.__init__() debug ==='.format(type(self).__name__))
  168. dmsg_rpc(f' cls [{type(self).__name__}] host [{host}] port [{port}]\n')
  169. import socket
  170. try:
  171. socket.create_connection((host,port),timeout=1).close()
  172. except:
  173. raise SocketError(f'Unable to connect to {host}:{port}')
  174. self.http_hdrs = { 'Content-Type': 'application/json' }
  175. self.url = f'{self.network_proto}://{host}:{port}{self.host_path}'
  176. self.host = host
  177. self.port = port
  178. self.timeout = g.http_timeout
  179. self.auth = None
  180. def set_backend(self,backend=None):
  181. bn = backend or opt.rpc_backend
  182. if bn == 'auto':
  183. self.backend = {'linux':RPCBackends.httplib,'win':RPCBackends.curl}[g.platform](self)
  184. else:
  185. self.backend = getattr(RPCBackends,bn)(self)
  186. def set_auth(self):
  187. """
  188. MMGen's credentials override coin daemon's
  189. """
  190. if g.rpc_user:
  191. user,passwd = (g.rpc_user,g.rpc_password)
  192. else:
  193. user,passwd = self.get_daemon_cfg_options(('rpcuser','rpcpassword')).values()
  194. if user and passwd:
  195. self.auth = auth_data(user,passwd)
  196. return
  197. if self.has_auth_cookie:
  198. cookie = self.get_daemon_auth_cookie()
  199. if cookie:
  200. self.auth = auth_data(*cookie.split(':'))
  201. return
  202. die(1,rpc_credentials_msg.format(
  203. proto_name = self.proto.name,
  204. cf_name = (self.proto.is_fork_of or self.proto.name).lower(),
  205. ))
  206. # Call family of methods - direct-to-daemon RPC call:
  207. # positional params are passed to the daemon, 'timeout' kwarg to the backend
  208. async def call(self,method,*params,timeout=None):
  209. """
  210. default call: call with param list unrolled, exactly as with cli
  211. """
  212. if method == g.rpc_fail_on_command:
  213. method = 'badcommand_' + method
  214. return await self.process_http_resp(self.backend.run(
  215. payload = {'id': 1, 'jsonrpc': '2.0', 'method': method, 'params': params },
  216. timeout = timeout,
  217. ))
  218. async def batch_call(self,method,param_list,timeout=None):
  219. """
  220. Make a single call with a list of tuples as first argument
  221. For RPC calls that return a list of results
  222. """
  223. return await self.process_http_resp(self.backend.run(
  224. payload = [{
  225. 'id': n,
  226. 'jsonrpc': '2.0',
  227. 'method': method,
  228. 'params': params } for n,params in enumerate(param_list,1) ],
  229. timeout = timeout,
  230. ),batch=True)
  231. async def gathered_call(self,method,args_list,timeout=None):
  232. """
  233. Perform multiple RPC calls, returning results in a list
  234. Can be called two ways:
  235. 1) method = methodname, args_list = [args_tuple1, args_tuple2,...]
  236. 2) method = None, args_list = [(methodname1,args_tuple1), (methodname2,args_tuple2), ...]
  237. """
  238. cmd_list = args_list if method == None else tuple(zip([method] * len(args_list), args_list))
  239. cur_pos = 0
  240. chunk_size = 1024
  241. ret = []
  242. while cur_pos < len(cmd_list):
  243. tasks = [self.process_http_resp(self.backend.run(
  244. payload = {'id': n, 'jsonrpc': '2.0', 'method': method, 'params': params },
  245. timeout = timeout,
  246. )) for n,(method,params) in enumerate(cmd_list[cur_pos:chunk_size+cur_pos],1)]
  247. ret.extend(await asyncio.gather(*tasks))
  248. cur_pos += chunk_size
  249. return ret
  250. async def process_http_resp(self,coro,batch=False):
  251. text,status = await coro
  252. if status == 200:
  253. dmsg_rpc(' RPC RESPONSE data ==>\n{}\n',text,is_json=True)
  254. if batch:
  255. return [r['result'] for r in json.loads(text,parse_float=Decimal,encoding='UTF-8')]
  256. else:
  257. try:
  258. return json.loads(text,parse_float=Decimal,encoding='UTF-8')['result']
  259. except:
  260. raise RPCFailure(json.loads(text)['error']['message'])
  261. else:
  262. import http
  263. s = http.HTTPStatus(status)
  264. m = ''
  265. if text:
  266. try: m = ': ' + json.loads(text)['error']['message']
  267. except:
  268. try: m = f': {text.decode()}'
  269. except: m = f': {text}'
  270. raise RPCFailure(f'{s.value} {s.name}{m}')
  271. class BitcoinRPCClient(RPCClient,metaclass=aInitMeta):
  272. auth_type = 'basic'
  273. has_auth_cookie = True
  274. def __init__(self,*args,**kwargs):
  275. pass
  276. async def __ainit__(self,proto,daemon,backend,caller):
  277. self.proto = proto
  278. self.daemon = daemon
  279. super().__init__(
  280. host = 'localhost' if g.test_suite else (g.rpc_host or 'localhost'),
  281. port = daemon.rpc_port )
  282. self.set_auth() # set_auth() requires cookie, so must be called after __init__() tests daemon is listening
  283. self.set_backend(backend) # backend requires self.auth
  284. if caller != 'regtest' and (g.bob or g.alice):
  285. from .regtest import MMGenRegtest
  286. await MMGenRegtest(self.proto.coin).switch_user(('alice','bob')[g.bob],quiet=True)
  287. self.cached = {}
  288. (
  289. self.cached['networkinfo'],
  290. self.blockcount,
  291. self.cached['blockchaininfo'],
  292. block0
  293. ) = await self.gathered_call(None, (
  294. ('getnetworkinfo',()),
  295. ('getblockcount',()),
  296. ('getblockchaininfo',()),
  297. ('getblockhash',(0,)),
  298. ))
  299. self.daemon_version = self.cached['networkinfo']['version']
  300. self.daemon_version_str = self.cached['networkinfo']['subversion']
  301. self.chain = self.cached['blockchaininfo']['chain']
  302. tip = await self.call('getblockhash',self.blockcount)
  303. self.cur_date = (await self.call('getblockheader',tip))['time']
  304. if self.chain != 'regtest':
  305. self.chain += 'net'
  306. assert self.chain in self.proto.networks
  307. async def check_chainfork_mismatch(block0):
  308. try:
  309. if block0 != self.proto.block0:
  310. raise ValueError(f'Invalid Genesis block for {self.proto.cls_name} protocol')
  311. for fork in self.proto.forks:
  312. if fork.height == None or self.blockcount < fork.height:
  313. break
  314. if fork.hash != await self.call('getblockhash',fork.height):
  315. die(3,f'Bad block hash at fork block {fork.height}. Is this the {fork.name} chain?')
  316. except Exception as e:
  317. die(2,'{!s}\n{c!r} requested, but this is not the {c} chain!'.format(e,c=self.proto.coin))
  318. if self.chain == 'mainnet': # skip this for testnet, as Genesis block may change
  319. await check_chainfork_mismatch(block0)
  320. self.caps = ('full_node',)
  321. for func,cap in (
  322. ('setlabel','label_api'),
  323. ('signrawtransactionwithkey','sign_with_key') ):
  324. if len((await self.call('help',func)).split('\n')) > 3:
  325. self.caps += (cap,)
  326. if caller != 'regtest':
  327. try:
  328. await self.call('getbalance')
  329. except:
  330. await self.create_tracking_wallet()
  331. async def create_tracking_wallet(self):
  332. """
  333. Quirk: when --datadir is specified (even if standard), wallet is created directly in
  334. datadir, otherwise in datadir/wallets
  335. """
  336. wname = self.daemon.tracking_wallet_name
  337. await self.call('createwallet',
  338. wname, # wallet_name
  339. True, # disable_private_keys
  340. True, # blank (no keys or seed)
  341. '', # passphrase (empty string for non-encrypted)
  342. False, # avoid_reuse (track address reuse)
  343. False, # descriptors (native descriptor wallet)
  344. True # load_on_startup
  345. )
  346. ymsg(f'Created {self.daemon.coind_name} wallet {wname!r}')
  347. def get_daemon_cfg_fn(self):
  348. # Use dirname() to remove 'bob' or 'alice' component
  349. return os.path.join(
  350. (os.path.dirname(g.data_dir) if self.proto.regtest else self.daemon.datadir),
  351. self.daemon.cfg_file )
  352. def get_daemon_auth_cookie_fn(self):
  353. return os.path.join( self.daemon.datadir, self.daemon.data_subdir, '.cookie' )
  354. def get_daemon_cfg_options(self,req_keys):
  355. fn = self.get_daemon_cfg_fn()
  356. try:
  357. lines = get_lines_from_file(fn,'',silent=not opt.verbose)
  358. except:
  359. vmsg(f'Warning: {fn!r} does not exist or is unreadable')
  360. return dict((k,None) for k in req_keys)
  361. def gen():
  362. for key in req_keys:
  363. val = None
  364. for l in lines:
  365. if l.startswith(key):
  366. res = l.split('=',1)
  367. if len(res) == 2 and not ' ' in res[1].strip():
  368. val = res[1].strip()
  369. yield (key,val)
  370. return dict(gen())
  371. def get_daemon_auth_cookie(self):
  372. fn = self.get_daemon_auth_cookie_fn()
  373. return get_lines_from_file(fn,'')[0] if file_is_readable(fn) else ''
  374. def info(self,info_id):
  375. def segwit_is_active():
  376. d = self.cached['blockchaininfo']
  377. if d['chain'] == 'regtest':
  378. return True
  379. try:
  380. if d['softforks']['segwit']['active'] == True:
  381. return True
  382. except:
  383. pass
  384. try:
  385. if d['bip9_softforks']['segwit']['status'] == 'active':
  386. return True
  387. except:
  388. pass
  389. if g.test_suite:
  390. return True
  391. return False
  392. return locals()[info_id]()
  393. rpcmethods = (
  394. 'backupwallet',
  395. 'createrawtransaction',
  396. 'decoderawtransaction',
  397. 'disconnectnode',
  398. 'estimatefee',
  399. 'estimatesmartfee',
  400. 'getaddressesbyaccount',
  401. 'getaddressesbylabel',
  402. 'getblock',
  403. 'getblockchaininfo',
  404. 'getblockcount',
  405. 'getblockhash',
  406. 'getblockheader',
  407. 'getblockstats', # mmgen-node-tools
  408. 'getmempoolinfo',
  409. 'getmempoolentry',
  410. 'getnettotals',
  411. 'getnetworkinfo',
  412. 'getpeerinfo',
  413. 'getrawmempool',
  414. 'getmempoolentry',
  415. 'getrawtransaction',
  416. 'gettransaction',
  417. 'importaddress',
  418. 'listaccounts',
  419. 'listlabels',
  420. 'listunspent',
  421. 'setlabel',
  422. 'sendrawtransaction',
  423. 'signrawtransaction',
  424. 'signrawtransactionwithkey', # method new to Core v0.17.0
  425. 'validateaddress',
  426. 'walletpassphrase',
  427. )
  428. class EthereumRPCClient(RPCClient,metaclass=aInitMeta):
  429. def __init__(self,*args,**kwargs):
  430. pass
  431. async def __ainit__(self,proto,daemon,backend,caller):
  432. self.proto = proto
  433. self.daemon = daemon
  434. super().__init__(
  435. host = 'localhost' if g.test_suite else (g.rpc_host or 'localhost'),
  436. port = daemon.rpc_port )
  437. self.set_backend(backend)
  438. self.blockcount = int(await self.call('eth_blockNumber'),16)
  439. vi,bh,ch,nk = await self.gathered_call(None, (
  440. ('parity_versionInfo',()),
  441. ('parity_getBlockHeaderByNumber',()),
  442. ('parity_chain',()),
  443. ('parity_nodeKind',()),
  444. ))
  445. self.daemon_version = int((
  446. lambda v: '{:d}{:03d}{:03d}'.format(v['major'],v['minor'],v['patch'])
  447. )(vi['version']))
  448. self.daemon_version_str = (
  449. lambda v: '{}.{}.{}'.format(v['major'],v['minor'],v['patch'])
  450. )(vi['version'])
  451. self.cur_date = int(bh['timestamp'],16)
  452. self.chain = ch.replace(' ','_')
  453. self.caps = ('full_node',) if nk['capability'] == 'full' else ()
  454. try:
  455. await self.call('eth_chainId')
  456. self.caps += ('eth_chainId',)
  457. except RPCFailure:
  458. pass
  459. rpcmethods = (
  460. 'eth_accounts',
  461. 'eth_blockNumber',
  462. 'eth_call',
  463. # Returns the EIP155 chain ID used for transaction signing at the current best block.
  464. # Null is returned if not available.
  465. 'eth_chainId',
  466. 'eth_gasPrice',
  467. 'eth_getBalance',
  468. 'eth_getBlockByHash',
  469. 'eth_getCode',
  470. 'eth_getTransactionByHash',
  471. 'eth_getTransactionReceipt',
  472. 'eth_protocolVersion',
  473. 'eth_sendRawTransaction',
  474. 'eth_signTransaction',
  475. 'eth_syncing',
  476. 'net_listening',
  477. 'net_peerCount',
  478. 'net_version',
  479. 'parity_chain',
  480. 'parity_chainId', # superseded by eth_chainId
  481. 'parity_chainStatus',
  482. 'parity_composeTransaction',
  483. 'parity_gasCeilTarget',
  484. 'parity_gasFloorTarget',
  485. 'parity_getBlockHeaderByNumber',
  486. 'parity_localTransactions',
  487. 'parity_minGasPrice',
  488. 'parity_mode',
  489. 'parity_netPeers',
  490. 'parity_nextNonce',
  491. 'parity_nodeKind',
  492. 'parity_nodeName',
  493. 'parity_pendingTransactions',
  494. 'parity_pendingTransactionsStats',
  495. 'parity_versionInfo',
  496. )
  497. class MoneroWalletRPCClient(RPCClient):
  498. auth_type = 'digest'
  499. network_proto = 'https'
  500. host_path = '/json_rpc'
  501. verify_server = False
  502. def __init__(self,host,port,user,passwd):
  503. super().__init__(host,port)
  504. self.auth = auth_data(user,passwd)
  505. if True:
  506. self.set_backend('requests')
  507. else: # insecure, for debugging only
  508. self.set_backend('curl')
  509. self.backend.exec_opts.remove('--silent')
  510. self.backend.exec_opts.append('--verbose')
  511. async def call(self,method,*params,**kwargs):
  512. assert params == (), f'{type(self).__name__}.call() accepts keyword arguments only'
  513. return await self.process_http_resp(self.backend.run(
  514. payload = {'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': kwargs },
  515. timeout = 3600, # allow enough time to sync ≈1,000,000 blocks
  516. ))
  517. rpcmethods = (
  518. 'get_version',
  519. 'get_height', # sync height of the open wallet
  520. 'get_balance', # account_index=0, address_indices=[]
  521. 'create_wallet', # filename, password, language="English"
  522. 'open_wallet', # filename, password
  523. 'close_wallet',
  524. 'restore_deterministic_wallet', # name,password,seed (restore_height,language,seed_offset,autosave_current)
  525. 'refresh', # start_height
  526. )
  527. def handle_unsupported_daemon_version(rpc,proto,ignore_daemon_version,warning_shown=[]):
  528. if ignore_daemon_version or proto.ignore_daemon_version or g.ignore_daemon_version:
  529. if not type(proto) in warning_shown:
  530. ymsg(f'WARNING: ignoring unsupported {rpc.daemon.coind_name} daemon version at user request')
  531. warning_shown.append(type(proto))
  532. else:
  533. rdie(1,fmt(
  534. """
  535. The running {} daemon has version {}.
  536. This version of MMGen is tested only on {} v{} and below.
  537. To avoid this error, downgrade your daemon to a supported version.
  538. Alternatively, you may invoke the command with the --ignore-daemon-version
  539. option, in which case you proceed at your own risk.
  540. """.format(
  541. rpc.daemon.coind_name,
  542. rpc.daemon_version_str,
  543. rpc.daemon.coind_name,
  544. rpc.daemon.coind_version_str,
  545. ),indent=' ').rstrip())
  546. async def rpc_init(proto,backend=None,daemon=None,ignore_daemon_version=False,caller=None):
  547. if not 'rpc' in proto.mmcaps:
  548. die(1,f'Coin daemon operations not supported for {proto.name} protocol!')
  549. from .daemon import CoinDaemon
  550. rpc = await {
  551. 'Bitcoin': BitcoinRPCClient,
  552. 'Ethereum': EthereumRPCClient,
  553. }[proto.base_proto](
  554. proto = proto,
  555. daemon = daemon or CoinDaemon(proto=proto,test_suite=g.test_suite),
  556. backend = backend or opt.rpc_backend,
  557. caller = caller )
  558. if rpc.daemon_version > rpc.daemon.coind_version:
  559. handle_unsupported_daemon_version(rpc,proto,ignore_daemon_version)
  560. if proto.chain_name != rpc.chain:
  561. raise RPCChainMismatch(
  562. '{} protocol chain is {}, but coin daemon chain is {}'.format(
  563. proto.cls_name,
  564. proto.chain_name.upper(),
  565. rpc.chain.upper() ))
  566. if g.bogus_wallet_data:
  567. rpc.blockcount = 1000000
  568. return rpc