ut_rpc.py 7.0 KB

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