ut_rpc.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. #!/usr/bin/env python3
  2. """
  3. test.unit_tests_d.ut_rpc: RPC unit test for the MMGen suite
  4. """
  5. import sys,os,time
  6. from mmgen.color import yellow,cyan
  7. from mmgen.util import msg,gmsg,async_run,make_timestr,pp_fmt,die
  8. from mmgen.protocol import init_proto
  9. from mmgen.rpc import rpc_init
  10. from mmgen.daemon import CoinDaemon
  11. from mmgen.proto.xmr.rpc import MoneroRPCClient,MoneroWalletRPCClient
  12. from mmgen.proto.xmr.daemon import MoneroWalletDaemon
  13. from ..include.common import cfg,qmsg,vmsg
  14. def cfg_file_auth_test(proto,d,bad_auth=False):
  15. m = 'missing credentials' if bad_auth else f'credentials from {d.cfg_file}'
  16. qmsg(cyan(f'\n Testing authentication with {m}:'))
  17. time.sleep(0.1) # race condition
  18. d.remove_datadir() # removes cookie file to force authentication from cfg file
  19. os.makedirs(d.network_datadir)
  20. if not bad_auth:
  21. cf = os.path.join(d.datadir,d.cfg_file)
  22. with open(cf,'a') as fp:
  23. fp.write('\nrpcuser = ut_rpc\nrpcpassword = ut_rpc_passw0rd\n')
  24. d.flag.keep_cfg_file = True
  25. d.start()
  26. if bad_auth:
  27. os.rename(d.auth_cookie_fn,d.auth_cookie_fn+'.bak')
  28. try:
  29. async_run(rpc_init(cfg,proto))
  30. except Exception as e:
  31. vmsg(yellow(str(e)))
  32. else:
  33. die(3,'No error on missing credentials!')
  34. os.rename(d.auth_cookie_fn+'.bak',d.auth_cookie_fn)
  35. else:
  36. rpc = async_run(rpc_init(cfg,proto))
  37. assert rpc.auth.user == 'ut_rpc', f'{rpc.auth.user}: user is not ut_rpc!'
  38. d.stop()
  39. def print_daemon_info(rpc):
  40. if rpc.proto.base_proto == 'Monero':
  41. msg(f"""
  42. DAEMON VERSION: {rpc.daemon_version} [{rpc.daemon_version_str}]
  43. NETWORK: {rpc.proto.coin} {rpc.proto.network.upper()}
  44. """.rstrip())
  45. else:
  46. msg(f"""
  47. DAEMON VERSION: {rpc.daemon_version} [{rpc.daemon_version_str}]
  48. CAPS: {rpc.caps}
  49. NETWORK: {rpc.proto.coin} {rpc.proto.network.upper()}
  50. CHAIN: {rpc.chain}
  51. BLOCKCOUNT: {rpc.blockcount}
  52. CUR_DATE: {rpc.cur_date} [{make_timestr(rpc.cur_date)}]
  53. """.rstrip())
  54. if rpc.proto.base_proto == 'Bitcoin':
  55. def fmt_dict(d):
  56. return '\n ' + '\n '.join( pp_fmt(d).split('\n') ) + '\n'
  57. msg(f"""
  58. NETWORKINFO: {fmt_dict(rpc.cached["networkinfo"])}
  59. BLOCKCHAININFO: {fmt_dict(rpc.cached["blockchaininfo"])}
  60. DEPLOYMENTINFO: {fmt_dict(rpc.cached["deploymentinfo"])}
  61. """.rstrip())
  62. msg('')
  63. def do_msg(rpc,backend):
  64. bname = type(rpc.backend).__name__
  65. qmsg(' Testing backend {!r}{}'.format( bname, '' if backend == bname else f' [{backend}]' ))
  66. class init_test:
  67. @staticmethod
  68. async def btc(proto,backend,daemon):
  69. rpc = await rpc_init(cfg,proto,backend,daemon)
  70. do_msg(rpc,backend)
  71. bh = (await rpc.call('getblockchaininfo',timeout=300))['bestblockhash']
  72. await rpc.gathered_call('getblock',((bh,),(bh,1)),timeout=300)
  73. await rpc.gathered_call(None,(('getblock',(bh,)),('getblock',(bh,1))),timeout=300)
  74. return rpc
  75. @staticmethod
  76. async def bch(proto,backend,daemon):
  77. rpc = await rpc_init(cfg,proto,backend,daemon)
  78. do_msg(rpc,backend)
  79. return rpc
  80. ltc = bch
  81. @staticmethod
  82. async def eth(proto,backend,daemon):
  83. rpc = await rpc_init(cfg,proto,backend,daemon)
  84. do_msg(rpc,backend)
  85. await rpc.call('eth_blockNumber',timeout=300)
  86. return rpc
  87. etc = eth
  88. def run_test(network_ids,test_cf_auth=False,daemon_ids=None):
  89. def do_test(d):
  90. if not cfg.no_daemon_stop:
  91. d.stop()
  92. if not cfg.no_daemon_autostart:
  93. d.remove_datadir()
  94. d.start()
  95. for n,backend in enumerate(cfg._autoset_opts['rpc_backend'].choices):
  96. test = getattr(init_test,d.proto.coin.lower())
  97. rpc = async_run(test(d.proto,backend,d))
  98. if not n and cfg.verbose:
  99. print_daemon_info(rpc)
  100. if not cfg.no_daemon_stop:
  101. d.stop()
  102. if test_cf_auth and sys.platform != 'win32':
  103. cfg_file_auth_test(d.proto,d)
  104. cfg_file_auth_test(d.proto,d,bad_auth=True)
  105. qmsg('')
  106. for network_id in network_ids:
  107. proto = init_proto( cfg, network_id=network_id )
  108. all_ids = CoinDaemon.get_daemon_ids(cfg,proto.coin)
  109. ids = set(daemon_ids) & set(all_ids) if daemon_ids else all_ids
  110. for daemon_id in ids:
  111. do_test( CoinDaemon(cfg, proto=proto,test_suite=True,daemon_id=daemon_id) )
  112. return True
  113. class unit_tests:
  114. altcoin_deps = ('ltc','bch','geth','erigon','parity','xmrwallet')
  115. arm_skip = ('parity',) # no prebuilt binaries for ARM
  116. def btc(self,name,ut):
  117. return run_test(['btc','btc_tn'],test_cf_auth=True)
  118. def ltc(self,name,ut):
  119. return run_test(['ltc','ltc_tn'],test_cf_auth=True)
  120. def bch(self,name,ut):
  121. return run_test(['bch','bch_tn'],test_cf_auth=True)
  122. def geth(self,name,ut):
  123. return run_test(['eth_tn','eth_rt'],daemon_ids=['geth']) # mainnet returns EIP-155 error on empty blockchain
  124. def erigon(self,name,ut):
  125. return run_test(['eth','eth_tn','eth_rt'],daemon_ids=['erigon'])
  126. def parity(self,name,ut):
  127. return run_test(['etc'])
  128. def xmrwallet(self,name,ut):
  129. def test_monerod_rpc(md):
  130. rpc = MoneroRPCClient(
  131. cfg = cfg,
  132. proto = md.proto,
  133. host = 'localhost',
  134. port = md.rpc_port,
  135. user = None,
  136. passwd = None,
  137. daemon = md,
  138. )
  139. if cfg.verbose:
  140. print_daemon_info(rpc)
  141. rpc.call_raw('get_height')
  142. rpc.call('get_last_block_header')
  143. from mmgen.xmrseed import xmrseed
  144. async def run():
  145. networks = init_proto( cfg, 'xmr' ).networks
  146. daemons = [(
  147. CoinDaemon( cfg, proto=proto, test_suite=True ),
  148. MoneroWalletDaemon(
  149. cfg = cfg,
  150. proto = proto,
  151. test_suite = True,
  152. wallet_dir = os.path.join('test','trash2'),
  153. datadir = os.path.join('test','trash2','wallet_rpc'),
  154. passwd = 'ut_rpc_passw0rd' )
  155. ) for proto in (init_proto( cfg, 'xmr', network=network ) for network in networks) ]
  156. for md,wd in daemons:
  157. if not cfg.no_daemon_autostart:
  158. md.start()
  159. wd.start()
  160. test_monerod_rpc(md)
  161. c = MoneroWalletRPCClient( cfg=cfg, daemon=wd )
  162. fn = f'monero-{wd.network}-junk-wallet'
  163. qmsg(f'Creating {wd.network} wallet')
  164. c.call(
  165. 'restore_deterministic_wallet',
  166. filename = fn,
  167. password = 'foo',
  168. seed = xmrseed().fromhex('beadface'*8,tostr=True) )
  169. if sys.platform == 'win32':
  170. wd.stop()
  171. wd.start()
  172. qmsg(f'Opening {wd.network} wallet')
  173. c.call( 'open_wallet', filename=fn, password='foo' )
  174. await c.stop_daemon()
  175. if not cfg.no_daemon_stop:
  176. await md.rpc.stop_daemon()
  177. gmsg('OK')
  178. import shutil
  179. shutil.rmtree('test/trash2',ignore_errors=True)
  180. os.makedirs('test/trash2/wallet_rpc')
  181. async_run(run())
  182. return True