ut_rpc.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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, in_nix_environment, test_exec
  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. msg(f' BIND_PORT: {rpc.daemon.bind_port}')
  58. if rpc.proto.base_proto == 'Bitcoin':
  59. def fmt_dict(d):
  60. return '\n ' + '\n '.join(pp_fmt(d).split('\n')) + '\n'
  61. msg(f"""
  62. NETWORKINFO: {fmt_dict(rpc.cached["networkinfo"])}
  63. BLOCKCHAININFO: {fmt_dict(rpc.cached["blockchaininfo"])}
  64. DEPLOYMENTINFO: {fmt_dict(rpc.cached["deploymentinfo"])}
  65. WALLETINFO: {fmt_dict(await rpc.walletinfo)}
  66. """.rstrip())
  67. if rpc.proto.base_proto == 'Ethereum':
  68. msg(f' CHAIN_NAMES: {" ".join(rpc.daemon.proto.chain_names)}')
  69. msg('')
  70. def do_msg(rpc, backend):
  71. bname = type(rpc.backend).__name__
  72. qmsg(' Testing backend {!r}{}'.format(bname, '' if backend == bname else f' [{backend}]'))
  73. class init_test:
  74. @staticmethod
  75. async def btc(cfg, daemon, backend, cfg_override):
  76. rpc = await rpc_init(cfg, daemon.proto, backend, daemon)
  77. do_msg(rpc, backend)
  78. wi = await rpc.walletinfo
  79. assert wi['walletname'] == cfg_override['btc_tw_name']
  80. assert wi['walletname'] == rpc.cfg._proto.tw_name, f'{wi["walletname"]!r} != {rpc.cfg._proto.tw_name!r}'
  81. assert daemon.bind_port == cfg_override['btc_rpc_port']
  82. bh = (await rpc.call('getblockchaininfo', timeout=300))['bestblockhash']
  83. await rpc.gathered_call('getblock', ((bh,), (bh, 1)), timeout=300)
  84. await rpc.gathered_call(None, (('getblock', (bh,)), ('getblock', (bh, 1))), timeout=300)
  85. return rpc
  86. @staticmethod
  87. async def bch(cfg, daemon, backend, cfg_override):
  88. rpc = await rpc_init(cfg, daemon.proto, backend, daemon)
  89. do_msg(rpc, backend)
  90. return rpc
  91. ltc = bch
  92. @staticmethod
  93. async def eth(cfg, daemon, backend, cfg_override):
  94. rpc = await rpc_init(cfg, daemon.proto, backend, daemon)
  95. do_msg(rpc, backend)
  96. await rpc.call('eth_blockNumber', timeout=300)
  97. if rpc.proto.network == 'testnet':
  98. assert daemon.proto.chain_names == cfg_override['eth_testnet_chain_names']
  99. assert daemon.bind_port == cfg_override['eth_rpc_port']
  100. return rpc
  101. etc = eth
  102. async def run_test(network_ids, test_cf_auth=False, daemon_ids=None, cfg_override=None):
  103. async def do_test(d, cfg):
  104. d.wait = True
  105. if not cfg.no_daemon_stop:
  106. d.stop()
  107. d.remove_datadir()
  108. if not cfg.no_daemon_autostart:
  109. d.remove_datadir()
  110. d.start()
  111. for n, backend in enumerate(cfg._autoset_opts['rpc_backend'].choices):
  112. test = getattr(init_test, d.proto.coin.lower())
  113. rpc = await test(cfg, d, backend, cfg_override)
  114. if not n and cfg.verbose:
  115. await print_daemon_info(rpc)
  116. if not cfg.no_daemon_stop:
  117. d.stop()
  118. d.remove_datadir()
  119. if test_cf_auth and sys.platform != 'win32':
  120. await cfg_file_auth_test(cfg, d)
  121. await cfg_file_auth_test(cfg, d, bad_auth=True)
  122. qmsg('')
  123. my_cfg = Config(cfg_override) if cfg_override else cfg
  124. for network_id in network_ids:
  125. proto = init_proto(my_cfg, network_id=network_id)
  126. all_ids = CoinDaemon.get_daemon_ids(my_cfg, proto.coin)
  127. ids = set(daemon_ids) & set(all_ids) if daemon_ids else all_ids
  128. for daemon_id in ids:
  129. await do_test(CoinDaemon(my_cfg, proto=proto, test_suite=True, daemon_id=daemon_id), my_cfg)
  130. return True
  131. class unit_tests:
  132. altcoin_deps = ('ltc', 'bch', 'geth', 'erigon', 'parity', 'xmrwallet')
  133. arm_skip = ('parity',) # no prebuilt binaries for ARM
  134. async def btc(self, name, ut):
  135. return await run_test(
  136. ['btc', 'btc_tn'],
  137. test_cf_auth = True,
  138. cfg_override = {
  139. '_clone': cfg,
  140. 'btc_rpc_port': 19777,
  141. 'rpc_port': 32323, # ignored
  142. 'btc_tw_name': 'alternate-tracking-wallet',
  143. 'tw_name': 'this-is-overridden',
  144. 'ltc_tw_name': 'this-is-ignored',
  145. 'eth_mainnet_chain_names': ['also', 'ignored'],
  146. })
  147. async def ltc(self, name, ut):
  148. return await run_test(['ltc', 'ltc_tn'], test_cf_auth=True)
  149. async def bch(self, name, ut):
  150. return await run_test(['bch', 'bch_tn'], test_cf_auth=True)
  151. async def geth(self, name, ut):
  152. # mainnet returns EIP-155 error on empty blockchain:
  153. return await run_test(
  154. ['eth_tn', 'eth_rt'],
  155. daemon_ids = ['geth'],
  156. cfg_override = {
  157. '_clone': cfg,
  158. 'eth_rpc_port': 19777,
  159. 'rpc_port': 32323, # ignored
  160. 'btc_tw_name': 'ignored',
  161. 'tw_name': 'also-ignored',
  162. 'eth_testnet_chain_names': ['goerli', 'foo', 'bar', 'baz'],
  163. })
  164. async def erigon(self, name, ut):
  165. return await run_test(['eth', 'eth_tn', 'eth_rt'], daemon_ids=['erigon'])
  166. async def parity(self, name, ut):
  167. if in_nix_environment() and not test_exec('parity --help'):
  168. ut.skip_msg('Nix environment')
  169. return True
  170. return await run_test(['etc'])
  171. async def xmrwallet(self, name, ut):
  172. async def test_monerod_rpc(md):
  173. rpc = MoneroRPCClient(
  174. cfg = cfg,
  175. proto = md.proto,
  176. host = 'localhost',
  177. port = md.rpc_port,
  178. user = None,
  179. passwd = None,
  180. daemon = md,
  181. )
  182. if cfg.verbose:
  183. await print_daemon_info(rpc)
  184. rpc.call_raw('get_height')
  185. rpc.call('get_last_block_header')
  186. from mmgen.xmrseed import xmrseed
  187. async def run():
  188. networks = init_proto(cfg, 'xmr').networks
  189. daemons = [(
  190. CoinDaemon(cfg, proto=proto, test_suite=True),
  191. MoneroWalletDaemon(
  192. cfg = cfg,
  193. proto = proto,
  194. test_suite = True,
  195. wallet_dir = os.path.join('test', 'trash2'),
  196. datadir = os.path.join('test', 'trash2', 'wallet_rpc'),
  197. passwd = 'ut_rpc_passw0rd')
  198. ) for proto in (init_proto(cfg, 'xmr', network=network) for network in networks)]
  199. for md, wd in daemons:
  200. if not cfg.no_daemon_autostart:
  201. md.start()
  202. wd.start()
  203. await test_monerod_rpc(md)
  204. c = MoneroWalletRPCClient(cfg=cfg, daemon=wd)
  205. fn = f'monero-{wd.network}-junk-wallet'
  206. qmsg(f'Creating {wd.network} wallet')
  207. c.call(
  208. 'restore_deterministic_wallet',
  209. filename = fn,
  210. password = 'foo',
  211. seed = xmrseed().fromhex('beadface'*8, tostr=True))
  212. if sys.platform == 'win32':
  213. wd.stop()
  214. wd.start()
  215. qmsg(f'Opening {wd.network} wallet')
  216. c.call('open_wallet', filename=fn, password='foo')
  217. await c.stop_daemon()
  218. if not cfg.no_daemon_stop:
  219. md.stop()
  220. gmsg('OK')
  221. import shutil
  222. shutil.rmtree('test/trash2', ignore_errors=True)
  223. os.makedirs('test/trash2/wallet_rpc')
  224. await run()
  225. return True