rpc.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2023 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen
  9. # https://gitlab.com/mmgen/mmgen
  10. """
  11. proto.xmr.rpc: Monero base protocol RPC client class
  12. """
  13. import re
  14. from ...rpc import RPCClient,IPPort,auth_data
  15. class MoneroRPCClient(RPCClient):
  16. auth_type = None
  17. network_proto = 'https'
  18. verify_server = False
  19. def __init__(
  20. self,
  21. cfg,
  22. proto,
  23. host,
  24. port,
  25. user,
  26. passwd,
  27. test_connection = True,
  28. proxy = None,
  29. daemon = None,
  30. ignore_daemon_version = False ):
  31. self.proto = proto
  32. if proxy is not None:
  33. self.proxy = IPPort(proxy)
  34. test_connection = False
  35. if host.endswith('.onion'):
  36. self.network_proto = 'http'
  37. super().__init__(cfg,host,port,test_connection)
  38. if self.auth_type:
  39. self.auth = auth_data(user,passwd)
  40. if True:
  41. self.set_backend('requests')
  42. else: # insecure, for debugging only
  43. self.set_backend('curl')
  44. self.backend.exec_opts.remove('--silent')
  45. self.backend.exec_opts.append('--verbose')
  46. self.daemon = daemon
  47. if test_connection:
  48. # https://github.com/monero-project/monero src/rpc/rpc_version_str.cpp
  49. ver_str = self.call_raw('getinfo')['version']
  50. if ver_str:
  51. self.daemon_version_str = ver_str
  52. self.daemon_version = sum(
  53. int(m) * (1000 ** n) for n,m in
  54. enumerate(reversed(re.match(r'(\d+)\.(\d+)\.(\d+)\.(\d+)',ver_str).groups()))
  55. )
  56. if self.daemon and self.daemon_version > self.daemon.coind_version:
  57. self.handle_unsupported_daemon_version(
  58. proto.name,
  59. ignore_daemon_version or proto.ignore_daemon_version or self.cfg.ignore_daemon_version )
  60. else: # restricted (public) node:
  61. self.daemon_version_str = None
  62. self.daemon_version = None
  63. def call(self,method,*params,**kwargs):
  64. assert not params, f'{self.name}.call() accepts keyword arguments only'
  65. return self.process_http_resp(self.backend.run_noasync(
  66. payload = {'id': 0, 'jsonrpc': '2.0', 'method': method, 'params': kwargs },
  67. timeout = 3600, # allow enough time to sync ≈1,000,000 blocks
  68. host_path = '/json_rpc'
  69. ))
  70. def call_raw(self,method,*params,**kwargs):
  71. assert not params, f'{self.name}.call() accepts keyword arguments only'
  72. return self.process_http_resp(self.backend.run_noasync(
  73. payload = kwargs,
  74. timeout = self.timeout,
  75. host_path = f'/{method}'
  76. ),json_rpc=False)
  77. async def do_stop_daemon(self,silent=False):
  78. return self.call_raw('stop_daemon')
  79. rpcmethods = ( 'get_info', )
  80. rpcmethods_raw = ( 'get_height', 'send_raw_transaction', 'stop_daemon' )
  81. class MoneroWalletRPCClient(MoneroRPCClient):
  82. auth_type = 'digest'
  83. def __init__(self,cfg,daemon,test_connection=True):
  84. RPCClient.__init__(
  85. self = self,
  86. cfg = cfg,
  87. host = 'localhost',
  88. port = daemon.rpc_port,
  89. test_connection = test_connection )
  90. self.daemon = daemon
  91. self.auth = auth_data(daemon.user,daemon.passwd)
  92. self.set_backend('requests')
  93. rpcmethods = (
  94. 'get_version',
  95. 'get_height', # sync height of the open wallet
  96. 'get_balance', # account_index=0, address_indices=[]
  97. 'create_wallet', # filename, password, language="English"
  98. 'open_wallet', # filename, password
  99. 'close_wallet',
  100. # filename,password,seed (restore_height,language,seed_offset,autosave_current)
  101. 'restore_deterministic_wallet',
  102. 'refresh', # start_height
  103. )
  104. def call_raw(self,*args,**kwargs):
  105. raise NotImplementedError('call_raw() not implemented for class MoneroWalletRPCClient')
  106. async def do_stop_daemon(self,silent=False):
  107. """
  108. NB: the 'stop_wallet' RPC call closes the open wallet before shutting down the daemon,
  109. returning an error if no wallet is open
  110. """
  111. try:
  112. return self.call('stop_wallet')
  113. except Exception as e:
  114. from ...util import msg,msg_r,ymsg
  115. from ...color import yellow
  116. msg(f'{type(e).__name__}: {e}')
  117. msg_r(yellow('Unable to shut down wallet daemon gracefully, so killing process instead...'))
  118. ret = self.daemon.stop(silent=True)
  119. ymsg('done')
  120. return ret