From 35f92011744b77c27451a357ad01de0dd8dd873c Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 10 Oct 2021 20:18:16 +0000 Subject: [PATCH] stop Monero coin and wallet daemons via RPC --- mmgen/daemon.py | 12 +++++++++ mmgen/main_xmrwallet.py | 2 +- mmgen/rpc.py | 32 +++++++++++++++++++++-- mmgen/xmrwallet.py | 40 ++++++++++++++++------------- test/include/coin_daemon_control.py | 5 +++- test/test_py_d/ts_xmrwallet.py | 10 +++----- test/unit_tests_d/ut_daemon.py | 5 +++- test/unit_tests_d/ut_rpc.py | 15 ++++++++--- 8 files changed, 89 insertions(+), 32 deletions(-) diff --git a/mmgen/daemon.py b/mmgen/daemon.py index 636a55f2..ae90928d 100755 --- a/mmgen/daemon.py +++ b/mmgen/daemon.py @@ -306,6 +306,9 @@ class MoneroWalletDaemon(RPCDaemon): ['--stagenet', self.network == 'testnet'], ) + from .rpc import MoneroWalletRPCClient + self.rpc = MoneroWalletRPCClient( daemon=self, test_connection=False ) + class CoinDaemon(Daemon): networks = ('mainnet','testnet','regtest') cfg_file_hdr = '' @@ -622,6 +625,15 @@ class monero_daemon(CoinDaemon): def init_subclass(self): + from .rpc import MoneroRPCClientRaw + self.rpc = MoneroRPCClientRaw( + host = self.host, + port = self.rpc_port, + user = None, + passwd = None, + test_connection = False, + daemon = self ) + self.shared_args = list_gen( [f'--no-zmq'], [f'--p2p-bind-port={self.p2p_port}', self.p2p_port], diff --git a/mmgen/main_xmrwallet.py b/mmgen/main_xmrwallet.py index 0ad1b6ca..dcc78f20 100755 --- a/mmgen/main_xmrwallet.py +++ b/mmgen/main_xmrwallet.py @@ -234,4 +234,4 @@ try: except KeyboardInterrupt: ymsg('\nUser interrupt') finally: - m.stop_daemons() + run_session(m.stop_wallet_daemon()) diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 3c675b7f..fd013077 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -403,6 +403,23 @@ class RPCClient(MMGenObject): except: m = text raise RPCFailure(f'{s.value} {s.name}: {m}') + async def stop_daemon(self,quiet=False,silent=False): + if self.daemon.state == 'ready': + if not (quiet or silent): + msg(f'Stopping {self.daemon.desc} on port {self.daemon.bind_port}') + ret = await self.do_stop_daemon(silent=silent) + if self.daemon.wait: + self.daemon.wait_for_state('stopped') + return ret + else: + if not (quiet or silent): + msg(f'{self.daemon.desc} on port {self.daemon.bind_port} not running') + return True + + async def restart_daemon(self,quiet=False,silent=False): + await self.stop_daemon(quiet=quiet,silent=silent) + return self.daemon.start(silent=silent) + class BitcoinRPCClient(RPCClient,metaclass=AsyncInit): auth_type = 'basic' @@ -657,7 +674,7 @@ class MoneroRPCClient(RPCClient): host_path = '/json_rpc' verify_server = False - def __init__(self,host,port,user,passwd,test_connection=True,proxy=None): + def __init__(self,host,port,user,passwd,test_connection=True,proxy=None,daemon=None): if proxy is not None: from .obj import IPPort self.proxy = IPPort(proxy) @@ -673,6 +690,7 @@ class MoneroRPCClient(RPCClient): self.set_backend('curl') self.backend.exec_opts.remove('--silent') self.backend.exec_opts.append('--verbose') + self.daemon = daemon async def call(self,method,*params,**kwargs): assert params == (), f'{type(self).__name__}.call() accepts keyword arguments only' @@ -701,7 +719,10 @@ class MoneroRPCClientRaw(MoneroRPCClient): def make_host_path(arg): return arg - rpcmethods = ( 'get_height', 'send_raw_transaction' ) + async def do_stop_daemon(self,silent=False): + return await self.call('stop_daemon') + + rpcmethods = ( 'get_height', 'send_raw_transaction', 'stop_daemon' ) class MoneroWalletRPCClient(MoneroRPCClient): @@ -731,6 +752,13 @@ class MoneroWalletRPCClient(MoneroRPCClient): 'refresh', # start_height ) + async def do_stop_daemon(self,silent=False): + """ + NB: the 'stop_wallet' RPC call closes the open wallet before shutting down the daemon, + returning an error if no wallet is open + """ + return await self.call('stop_wallet') + class daemon_warning(oneshot_warning_group): class geth: diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index a1c323e0..058a8b73 100755 --- a/mmgen/xmrwallet.py +++ b/mmgen/xmrwallet.py @@ -295,7 +295,8 @@ class MoneroWalletOps: def post_main(self): pass - def stop_daemons(self): pass + async def stop_wallet_daemon(self): + pass class wallet(base): @@ -338,10 +339,10 @@ class MoneroWalletOps: daemon_addr = uopt.daemon or None, ) - if not uopt.no_start_wallet_daemon: - self.wd.restart() + self.c = MoneroWalletRPCClient(daemon=self.wd,test_connection=False) - self.c = MoneroWalletRPCClient(daemon=self.wd) + if not uopt.no_start_wallet_daemon: + run_session(self.c.restart_daemon()) def create_addr_data(self): if uarg.wallets: @@ -352,11 +353,9 @@ class MoneroWalletOps: else: self.addr_data = self.kal.data - def stop_daemons(self): + async def stop_wallet_daemon(self): if not uopt.no_stop_wallet_daemon: - self.wd.stop() - if uopt.tx_relay_daemon and hasattr(self,'wd2'): - self.wd2.stop() + await self.c.stop_daemon() def get_wallet_fn(self,d): return os.path.join( @@ -415,6 +414,12 @@ class MoneroWalletOps: await self.c.call('close_wallet') gmsg_r('done') + async def stop_wallet(self,desc): + msg(f'Stopping {self.c.daemon.desc} on port {self.c.daemon.bind_port}') + gmsg_r(f'\n Stopping {desc} wallet...') + await self.c.stop_daemon(quiet=True) # closes wallet + gmsg_r('done') + def print_accts(self,data,addrs_data,indent=' '): d = data['subaddress_accounts'] msg('\n' + indent + f'Accounts of wallet {os.path.basename(self.fn)}:') @@ -658,7 +663,9 @@ class MoneroWalletOps: t_elapsed // 60, t_elapsed % 60 )) - await self.c.call('close_wallet') + if not last: + await self.c.call('close_wallet') + return wallet_height >= chain_height def post_main(self): @@ -724,20 +731,19 @@ class MoneroWalletOps: m = re.fullmatch(uarg_info['tx_relay_daemon'].pat,uopt.tx_relay_daemon,re.ASCII) - self.wd2 = MoneroWalletDaemon( + wd2 = MoneroWalletDaemon( proto = self.proto, wallet_dir = uopt.wallet_dir or '.', test_suite = g.test_suite, daemon_addr = m[1], - proxy = m[2], - port_shift = 16 ) + proxy = m[2] ) if g.test_suite: - self.wd2.usr_daemon_args = ['--daemon-ssl-allow-any-cert'] + wd2.usr_daemon_args = ['--daemon-ssl-allow-any-cert'] - self.wd2.start() + wd2.start() - self.c = MoneroWalletRPCClient(daemon=self.wd2) + self.c = MoneroWalletRPCClient(daemon=wd2) async def main(self): gmsg(f'\n{self.desc}ing account #{self.account} of wallet {self.source.idx}' + ( @@ -808,7 +814,7 @@ class MoneroWalletOps: elif keypress_confirm(f'Relay {self.name} transaction?'): w_desc = 'source' if uopt.tx_relay_daemon: - await h.close_wallet('source') + await h.stop_wallet('source') msg('') self.init_tx_relay_daemon() h = self.rpc(self,self.source) @@ -816,11 +822,9 @@ class MoneroWalletOps: await h.open_wallet(w_desc,refresh=False) msg_r(f'\n Relaying {self.name} transaction...') await h.relay_tx(new_tx.data.metadata) - await h.close_wallet(w_desc) gmsg('\n\nAll done') else: - await h.close_wallet('source') die(1,'\nExiting at user request') return True diff --git a/test/include/coin_daemon_control.py b/test/include/coin_daemon_control.py index 842b491f..1e40267a 100755 --- a/test/include/coin_daemon_control.py +++ b/test/include/coin_daemon_control.py @@ -55,7 +55,10 @@ def run(network_id=None,proto=None,daemon_id=None): for cmd in d.start_cmds if action == 'start' else [d.stop_cmd]: print(' '.join(cmd)) else: - d.cmd(action,quiet=opt.quiet) + if action == 'stop' and hasattr(d,'rpc'): + run_session(d.rpc.stop_daemon(quiet=opt.quiet)) + else: + d.cmd(action,quiet=opt.quiet) if 'all' in cmd_args or 'no_xmr' in cmd_args: if len(cmd_args) != 1: diff --git a/test/test_py_d/ts_xmrwallet.py b/test/test_py_d/ts_xmrwallet.py index b8505cb5..af296353 100755 --- a/test/test_py_d/ts_xmrwallet.py +++ b/test/test_py_d/ts_xmrwallet.py @@ -221,6 +221,7 @@ class TestSuiteXMRWallet(TestSuiteBase): user = None, passwd = None, test_connection = False, + daemon = md, ) md_json_rpc = MoneroRPCClient( host = md.host, @@ -228,6 +229,7 @@ class TestSuiteXMRWallet(TestSuiteBase): user = None, passwd = None, test_connection = False, + daemon = md, ) wd = MoneroWalletDaemon( proto = self.proto, @@ -659,11 +661,7 @@ class TestSuiteXMRWallet(TestSuiteBase): def stop_daemons(self): for v in self.users.values(): - if v.md.state != 'stopped': - v.md.stop() - - def start_wallet_daemons(self): - self.users['miner'].wd.start() + run_session(v.md_rpc.stop_daemon()) def stop_miner_wallet_daemon(self): - self.users['miner'].wd.stop() + run_session(self.users['miner'].wd_rpc.stop_daemon()) diff --git a/test/unit_tests_d/ut_daemon.py b/test/unit_tests_d/ut_daemon.py index 10d63eaa..2df1a1ff 100755 --- a/test/unit_tests_d/ut_daemon.py +++ b/test/unit_tests_d/ut_daemon.py @@ -82,7 +82,10 @@ def test_cmds(op): else: if opt.quiet: msg_r('.') - getattr(d,op)(silent=opt.quiet) + if op == 'stop' and hasattr(d,'rpc'): + run_session(d.rpc.stop_daemon(quiet=opt.quiet)) + else: + getattr(d,op)(silent=opt.quiet) class unit_tests: diff --git a/test/unit_tests_d/ut_rpc.py b/test/unit_tests_d/ut_rpc.py index 058e53da..20098282 100755 --- a/test/unit_tests_d/ut_rpc.py +++ b/test/unit_tests_d/ut_rpc.py @@ -125,17 +125,26 @@ class unit_tests: md.start() wd.start() c = MoneroWalletRPCClient(daemon=wd) - await c.call('get_version') + fn = f'monero-{wd.network}-junk-wallet' + qmsg(f'Creating {wd.network} wallet') + await c.call( + 'restore_deterministic_wallet', + filename = fn, + password = 'foo', + seed = baseconv.fromhex('beadface'*8,'xmrseed',tostr=True) ) + qmsg(f'Opening {wd.network} wallet') + await c.call( 'open_wallet', filename=fn, password='foo' ) for md,wd in daemons: wd.wait = False - wd.stop() + await wd.rpc.stop_daemon() if not opt.no_daemon_stop: md.wait = False - md.stop() + await md.rpc.stop_daemon() gmsg('OK') + from mmgen.baseconv import baseconv import shutil shutil.rmtree('test/trash2',ignore_errors=True) os.makedirs('test/trash2')