rpc.py 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. #!/usr/bin/env python3
  2. """
  3. test.daemontest_d.rpc: RPC unit test for the MMGen suite
  4. """
  5. import sys, os, asyncio
  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, async_run
  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=backend, daemon=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=backend, daemon=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=backend, daemon=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. def run_test(network_ids, test_cf_auth=False, daemon_ids=None, cfg_override=None):
  103. 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. cfg_b = Config({'_clone': cfg, 'rpc_backend': backend})
  114. rpc = async_run(cfg_b, test, args=(cfg_b, d, backend, cfg_override))
  115. if not n and cfg.verbose:
  116. asyncio.run(print_daemon_info(rpc))
  117. if not cfg.no_daemon_stop:
  118. d.stop()
  119. d.remove_datadir()
  120. if test_cf_auth and sys.platform != 'win32':
  121. asyncio.run(cfg_file_auth_test(cfg, d))
  122. asyncio.run(cfg_file_auth_test(cfg, d, bad_auth=True))
  123. qmsg('')
  124. my_cfg = Config(cfg_override) if cfg_override else cfg
  125. for network_id in network_ids:
  126. proto = init_proto(my_cfg, network_id=network_id)
  127. all_ids = CoinDaemon.get_daemon_ids(my_cfg, proto.coin)
  128. ids = set(daemon_ids) & set(all_ids) if daemon_ids else all_ids
  129. for daemon_id in ids:
  130. do_test(CoinDaemon(my_cfg, proto=proto, test_suite=True, daemon_id=daemon_id), my_cfg)
  131. return True
  132. class unit_tests:
  133. altcoin_deps = ('ltc', 'bch', 'geth', 'reth', 'erigon', 'parity', 'xmrwallet')
  134. arm_skip = ('parity',) # no prebuilt binaries for ARM
  135. riscv_skip = ('parity',) # no prebuilt binaries for RISC-V
  136. fast_skip = ('reth', 'erigon')
  137. def btc(self, name, ut):
  138. return run_test(
  139. ['btc', 'btc_tn'],
  140. test_cf_auth = True,
  141. cfg_override = {
  142. '_clone': cfg,
  143. 'btc_rpc_port': 19777,
  144. 'rpc_port': 32323, # ignored
  145. 'btc_tw_name': 'alternate-tracking-wallet',
  146. 'tw_name': 'this-is-overridden',
  147. 'ltc_tw_name': 'this-is-ignored',
  148. 'eth_mainnet_chain_names': ['also', 'ignored'],
  149. })
  150. def ltc(self, name, ut):
  151. return run_test(['ltc', 'ltc_tn'], test_cf_auth=True)
  152. def bch(self, name, ut):
  153. return run_test(['bch', 'bch_tn'], test_cf_auth=True)
  154. def geth(self, name, ut):
  155. # mainnet returns EIP-155 error on empty blockchain:
  156. return run_test(
  157. ['eth_tn', 'eth_rt'],
  158. daemon_ids = ['geth'],
  159. cfg_override = {
  160. '_clone': cfg,
  161. 'eth_rpc_port': 19777,
  162. 'rpc_port': 32323, # ignored
  163. 'btc_tw_name': 'ignored',
  164. 'tw_name': 'also-ignored',
  165. 'eth_testnet_chain_names': ['goerli', 'holesky', 'foo', 'bar', 'baz'],
  166. })
  167. def reth(self, name, ut):
  168. return run_test(['eth', 'eth_rt'], daemon_ids=['reth']) # TODO: eth_tn
  169. def erigon(self, name, ut):
  170. return run_test(['eth', 'eth_tn', 'eth_rt'], daemon_ids=['erigon'])
  171. def parity(self, name, ut):
  172. if in_nix_environment() and not test_exec('parity --help'):
  173. ut.skip_msg('Nix environment')
  174. return True
  175. return run_test(['etc'])
  176. async def xmrwallet(self, name, ut):
  177. async def test_monerod_rpc(md):
  178. rpc = MoneroRPCClient(
  179. cfg = cfg,
  180. proto = md.proto,
  181. host = 'localhost',
  182. port = md.rpc_port,
  183. user = None,
  184. passwd = None,
  185. daemon = md,
  186. )
  187. if cfg.verbose:
  188. await print_daemon_info(rpc)
  189. rpc.call_raw('get_height')
  190. rpc.call('get_last_block_header')
  191. from mmgen.xmrseed import xmrseed
  192. async def run():
  193. networks = init_proto(cfg, 'xmr').networks
  194. daemons = [(
  195. CoinDaemon(cfg, proto=proto, test_suite=True),
  196. MoneroWalletDaemon(
  197. cfg = cfg,
  198. proto = proto,
  199. test_suite = True,
  200. wallet_dir = os.path.join('test', 'trash2'),
  201. datadir = os.path.join('test', 'trash2', 'wallet_rpc'),
  202. passwd = 'ut_rpc_passw0rd')
  203. ) for proto in (init_proto(cfg, 'xmr', network=network) for network in networks)]
  204. for md, wd in daemons:
  205. if not cfg.no_daemon_autostart:
  206. md.start()
  207. wd.start()
  208. await test_monerod_rpc(md)
  209. c = MoneroWalletRPCClient(cfg=cfg, daemon=wd)
  210. fn = f'monero-{wd.network}-junk-wallet'
  211. qmsg(f'Creating {wd.network} wallet')
  212. c.call(
  213. 'restore_deterministic_wallet',
  214. filename = fn,
  215. password = 'foo',
  216. seed = xmrseed().fromhex('beadface'*8, tostr=True))
  217. if sys.platform == 'win32':
  218. wd.stop()
  219. wd.start()
  220. qmsg(f'Opening {wd.network} wallet')
  221. c.call('open_wallet', filename=fn, password='foo')
  222. await c.stop_daemon()
  223. if not cfg.no_daemon_stop:
  224. md.stop()
  225. gmsg('OK')
  226. import shutil
  227. shutil.rmtree('test/trash2', ignore_errors=True)
  228. os.makedirs('test/trash2/wallet_rpc')
  229. await run()
  230. return True