Browse Source

stop Monero coin and wallet daemons via RPC

The MMGen Project 3 years ago
parent
commit
35f9201174

+ 12 - 0
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],

+ 1 - 1
mmgen/main_xmrwallet.py

@@ -234,4 +234,4 @@ try:
 except KeyboardInterrupt:
 	ymsg('\nUser interrupt')
 finally:
-	m.stop_daemons()
+	run_session(m.stop_wallet_daemon())

+ 30 - 2
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:

+ 22 - 18
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

+ 4 - 1
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:

+ 4 - 6
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())

+ 4 - 1
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:
 

+ 12 - 3
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')