Browse Source

daemon.py: cleanups

The MMGen Project 3 years ago
parent
commit
2fc96b8b8d
4 changed files with 185 additions and 189 deletions
  1. 163 183
      mmgen/daemon.py
  2. 15 2
      mmgen/util.py
  3. 1 1
      mmgen/xmrwallet.py
  4. 6 3
      test/start-coin-daemons.py

+ 163 - 183
mmgen/daemon.py

@@ -28,6 +28,7 @@ from .common import *
 
 class Daemon(MMGenObject):
 
+	desc = 'daemon'
 	debug = False
 	wait = True
 	use_pidfile = True
@@ -35,9 +36,11 @@ class Daemon(MMGenObject):
 	new_console_mswin = False
 	ps_pid_mswin = False
 	lockfile = None
-	avail_flags = ()
+	avail_opts = ()
+	avail_flags = () # like opts, but can be added or removed after instantiation
 
 	def __init__(self):
+		self.opts = []
 		self._flags = []
 
 	def subclass_init(self): pass
@@ -226,20 +229,20 @@ class MoneroWalletDaemon(Daemon):
 	ps_pid_mswin = True
 
 	def __init__(self, wallet_dir,
-			test_suite     = False,
-			host           = None,
-			user           = None,
-			passwd         = None,
-			daemon_addr    = None,
-			proxy          = None,
-			rpc_port_shift = None ):
+			test_suite  = False,
+			host        = None,
+			user        = None,
+			passwd      = None,
+			daemon_addr = None,
+			proxy       = None,
+			port_shift  = None ):
 
 		super().__init__()
 		self.platform = g.platform
 		self.wallet_dir = wallet_dir
 		self.rpc_port = 13142 if test_suite else 13131
-		if rpc_port_shift:
-			self.rpc_port += rpc_port_shift
+		if port_shift:
+			self.rpc_port += port_shift
 
 		bn = 'monero-wallet-rpc'
 		id_str = f'{bn}-{self.rpc_port}'
@@ -268,32 +271,24 @@ class MoneroWalletDaemon(Daemon):
 				"option (insecure, not recommended), or by setting 'monero_wallet_rpc_password'\n" +
 				"in the MMGen config file." )
 
-	@property
-	def start_cmd(self):
-		cmd = [
-			'monero-wallet-rpc',
-			'--untrusted-daemon',
-			'--rpc-bind-port={}'.format(self.rpc_port),
-			'--wallet-dir='+self.wallet_dir,
-			'--log-file='+self.logfile,
-			'--rpc-login={}:{}'.format(self.user,self.passwd) ]
-
-		if self.daemon_addr:
-			cmd.append(f'--daemon-address={self.daemon_addr}')
-		else:
-			cmd.append(f'--daemon-port={self.daemon_port}')
-
-		if self.proxy:
-			cmd.append(f'--proxy={self.proxy}')
-
-		if self.platform == 'linux':
-			cmd += ['--pidfile={}'.format(self.pidfile)]
-			cmd += [] if 'no_daemonize' in self.flags else ['--detach']
-		return cmd
+		self.start_cmd = list_gen(
+			['monero-wallet-rpc'],
+			['--untrusted-daemon'],
+			[f'--rpc-bind-port={self.rpc_port}'],
+			['--wallet-dir='+self.wallet_dir],
+			['--log-file='+self.logfile],
+			[f'--rpc-login={self.user}:{self.passwd}'],
+			[f'--daemon-address={self.daemon_addr}', self.daemon_addr],
+			[f'--daemon-port={self.daemon_port}',    not self.daemon_addr],
+			[f'--proxy={self.proxy}',                self.proxy],
+			[f'--pidfile={self.pidfile}',            self.platform == 'linux'],
+			['--detach',                             not 'no_daemonize' in self.opts],
+		)
 
 	@property
 	def state(self):
 		return 'ready' if self.test_socket('localhost',self.rpc_port) else 'stopped'
+		# TBD:
 		if not self.test_socket(self.host,self.rpc_port):
 			return 'stopped'
 		from .rpc import MoneroWalletRPCClient
@@ -314,9 +309,16 @@ class MoneroWalletDaemon(Daemon):
 class CoinDaemon(Daemon):
 	cfg_file_hdr = ''
 	subclasses_must_implement = ('state','stop_cmd')
-	avail_flags = ('no_daemonize','keep_cfg_file')
+	avail_flags = ('keep_cfg_file',)
+	avail_opts = ('no_daemonize','online')
+	data_subdir = ''
 
-	network_ids = ('btc','btc_tn','btc_rt','bch','bch_tn','bch_rt','ltc','ltc_tn','ltc_rt','xmr','eth','etc')
+	network_ids = (
+		'btc','btc_tn','btc_rt',
+		'bch','bch_tn','bch_rt',
+		'ltc','ltc_tn','ltc_rt',
+		'xmr','eth','etc'
+	)
 
 	cd = namedtuple('daemon_data', [
 		'id',
@@ -371,7 +373,7 @@ class CoinDaemon(Daemon):
 			'monerod',
 			'monerod',
 			'bitmonero.conf',
-			None,
+			'testnet',
 			18081, None, None),
 		'eth': cd(
 			'openethereum',
@@ -395,9 +397,33 @@ class CoinDaemon(Daemon):
 			8545, 8545, 8545)
 	}
 
-	def __new__(cls,network_id=None,test_suite=False,flags=None,proto=None):
+	dfl_datadirs = {
+		'linux': {
+			'btc': [g.home_dir,'.bitcoin'],
+			'bch': [g.home_dir,'.bitcoin-bchn'],
+			'ltc': [g.home_dir,'.litecoin'],
+			'xmr': [g.home_dir,'.bitmonero'],
+			'eth': [g.home_dir,'.local','share','io.parity.ethereum'],
+			'etc': [g.home_dir,'.local','share','io.parity.ethereum'],
+		},
+		'win': {
+			'btc': [os.getenv('APPDATA'),'Bitcoin'],
+			'bch': [os.getenv('APPDATA'),'Bitcoin_ABC'],
+			'ltc': [os.getenv('APPDATA'),'Litecoin'],
+			'xmr': ['/','c','ProgramData','bitmonero'],
+			'eth': [g.home_dir,'.local','share','io.parity.ethereum'],
+			'etc': [g.home_dir,'.local','share','io.parity.ethereum'],
+		},
+	}
+
+	def __new__(cls,
+			network_id = None,
+			test_suite = False,
+			flags      = None,
+			proto      = None,
+			opts       = None ):
 
-		assert network_id or proto, 'CoinDaemon_chk1'
+		assert network_id or proto,        'CoinDaemon_chk1'
 		assert not (network_id and proto), 'CoinDaemon_chk2'
 
 		if proto:
@@ -406,7 +432,7 @@ class CoinDaemon(Daemon):
 			daemon_id  = proto.coin.lower()
 		else:
 			network_id = network_id.lower()
-			assert network_id in cls.network_ids, '{!r}: invalid network ID'.format(network_id)
+			assert network_id in cls.network_ids, f'{network_id!r}: invalid network ID'
 			from mmgen.protocol import CoinProtocol
 			daemon_id,network = CoinProtocol.Base.parse_network_id(network_id)
 
@@ -415,125 +441,91 @@ class CoinDaemon(Daemon):
 		me.network = network
 		me.daemon_id = daemon_id
 
-		me.desc = 'daemon'
-		if network == 'regtest':
-			me.desc = 'regtest daemon'
-			if test_suite:
-				rel_datadir = os.path.join(
-					'test',
-					'data_dir{}'.format('-α' if g.debug_utf8 else ''),
-					'regtest',
-					daemon_id )
-			else:
-				datadir = os.path.join(g.data_dir_root,'regtest',daemon_id)
-		elif test_suite:
-			me.desc = 'test suite daemon'
-			rel_datadir = os.path.join('test','daemons',daemon_id)
-		else:
-			datadir = me.dfl_datadir
-
-		if test_suite:
-			datadir = os.path.join(os.getcwd(),rel_datadir)
-
-		if g.daemon_data_dir: # user-set value overrides everything else
-			datadir = g.daemon_data_dir
-
-		me.datadir = os.path.abspath(datadir)
-		me.data_subdir = (lambda x: x if network == 'testnet' and x else '')(me.daemon_ids[daemon_id].testnet_dir)
-
-		me.port_shift = 1237 if test_suite else 0
-		me.platform = g.platform
 		return me
 
-	def __init__(self,network_id=None,test_suite=False,flags=None,proto=None):
-		super().__init__()
-
-		self.testnet_arg = []
-		self.daemonize_args = []
-		self.cli_args = []
-		self.coind_cmd = []
+	def __init__(self,
+			network_id = None,
+			test_suite = False,
+			flags      = None,
+			proto      = None,
+			opts       = None ):
 
-		self.coin_specific_coind_args = []
-		self.coin_specific_cli_args = []
-		self.coin_specific_shared_args = []
+		super().__init__()
 
+		self.shared_args = []
 		self.usr_coind_args = []
-		self.usr_cli_args = []
-		self.usr_shared_args = []
+		self.platform = g.platform
 
+		if opts:
+			if type(opts) not in (list,tuple):
+				die(1,f'{opts!r}: illegal value for opts (must be list or tuple)')
+			for o in opts:
+				if o not in self.avail_opts:
+					die(1,f'{o!r}: unrecognized opt')
+			self.opts = list(opts)
 
 		if flags:
 			if type(flags) not in (list,tuple):
-				m = '{!r}: illegal value for flags (must be list or tuple)'
-				die(1,m.format(flags))
+				die(1,f'{flags!r}: illegal value for flags (must be list or tuple)')
 			for flag in flags:
 				self.add_flag(flag)
 
-		self.pidfile = '{}/{}-daemon.pid'.format(self.datadir,self.network)
-
 		for k in self.daemon_ids[self.daemon_id]._fields:
 			setattr(self,k,getattr(self.daemon_ids[self.daemon_id],k))
 
+		if self.network == 'regtest':
+			self.desc = 'regtest daemon'
+			if test_suite:
+				rel_datadir = os.path.join(
+					'test',
+					'data_dir{}'.format('-α' if g.debug_utf8 else ''),
+					'regtest',
+					self.daemon_id )
+			else:
+				dfl_datadir = os.path.join(g.data_dir_root,'regtest',self.daemon_id)
+		elif test_suite:
+			self.desc = 'test suite daemon'
+			rel_datadir = os.path.join('test','daemons',self.daemon_id)
+		else:
+			dfl_datadir = os.path.join( *self.dfl_datadirs[g.platform][self.daemon_id] )
+
+
+		if test_suite:
+			dfl_datadir = os.path.join(os.getcwd(),rel_datadir)
+
+		# user-set value takes precedence
+		datadir = g.daemon_data_dir or dfl_datadir
+
+		self.datadir = os.path.abspath(datadir)
+
+		if self.network == 'testnet' and self.testnet_dir:
+			self.data_subdir = self.testnet_dir
+
+		self.port_shift = 1237 if test_suite else 0
 		self.rpc_port = {
 				'mainnet': self.dfl_rpc,
 				'testnet': self.dfl_rpc_tn,
 				'regtest': self.dfl_rpc_rt,
 			}[self.network] + self.port_shift
 
-		if g.rpc_port: # user-set value overrides everything else
+		if g.rpc_port: # user-set global overrides everything else
 			self.rpc_port = g.rpc_port
 
+		self.pidfile = '{}/{}-daemon-{}.pid'.format(self.datadir,self.network,self.rpc_port)
 		self.net_desc = '{} {}'.format(self.coin,self.network)
 		self.subclass_init()
 
-	@property
-	def dfl_datadir(self):
-		if g.platform == 'linux':
-			path_data = {
-				'btc': ['.bitcoin'],
-				'bch': ['.bitcoin-bchn'],
-				'ltc': ['.litecoin'],
-				'xmr': ['.bitmonero'],
-				'eth': ['.local','share','io.parity.ethereum'],
-				'etc': ['.local','share','io.parity.ethereum'],
-			}
-			return os.path.join( *([g.home_dir] + path_data[self.daemon_id]) )
-		elif g.platform == 'win':
-			path_data = {
-				'btc': [os.getenv('APPDATA'),'Bitcoin'],
-				'bch': [os.getenv('APPDATA'),'Bitcoin_ABC'],
-				'ltc': [os.getenv('APPDATA'),'Litecoin'],
-				'xmr': ['/','c','ProgramData','bitmonero'],
-				'eth': [g.home_dir,'.local','share','io.parity.ethereum'],
-				'etc': [g.home_dir,'.local','share','io.parity.ethereum'],
-			}
-			return os.path.join(*path_data[self.daemon_id])
-		else:
-			raise ValueError(f'{g.platform}: unrecognized platform')
-
 	@property
 	def start_cmd(self):
 		return ([self.coind_exec]
-				+ self.testnet_arg
 				+ self.coind_args
 				+ self.shared_args
-				+ self.coin_specific_coind_args
-				+ self.coin_specific_shared_args
-				+ self.usr_coind_args
-				+ self.usr_shared_args
-				+ self.daemonize_args
-				+ self.coind_cmd )
+				+ self.usr_coind_args )
 
 	def cli_cmd(self,*cmds):
 		return ([self.cli_exec]
-				+ self.testnet_arg
-				+ self.cli_args
 				+ self.shared_args
-				+ self.coin_specific_cli_args
-				+ self.coin_specific_shared_args
-				+ self.usr_cli_args
-				+ self.usr_shared_args
-				+ list(cmds))
+				+ list(cmds) )
 
 class BitcoinDaemon(CoinDaemon):
 	cfg_file_hdr = '# BitcoinDaemon config file\n'
@@ -544,39 +536,28 @@ class BitcoinDaemon(CoinDaemon):
 		if self.platform == 'win' and self.daemon_id == 'bch':
 			self.use_pidfile = False
 
-		if self.network == 'testnet':
-			self.testnet_arg = ['--testnet']
-
-		self.shared_args = [
-			'--datadir={}'.format(self.datadir),
-			'--rpcport={}'.format(self.rpc_port) ]
-
-		self.coind_args = [
-			'--listen=0',
-			'--keypool=1',
-			'--rpcallowip=127.0.0.1',
-			'--rpcbind=127.0.0.1:{}'.format(self.rpc_port) ]
-
-		if self.use_pidfile:
-			self.coind_args += ['--pid='+self.pidfile]
-
-		if self.platform == 'linux' and not 'no_daemonize' in self.flags:
-			self.daemonize_args = ['--daemon']
-
-		if self.network == 'regtest':
-			from mmgen.regtest import MMGenRegtest
-			self.shared_args += [
-				f'--rpcuser={MMGenRegtest.rpc_user}',
-				f'--rpcpassword={MMGenRegtest.rpc_password}',
-				'--regtest' ]
-
-		if self.daemon_id == 'btc':
-			if self.network == 'regtest':
-				self.coin_specific_coind_args = ['--fallbackfee=0.0002']
-		elif self.daemon_id == 'bch':
-			self.coin_specific_coind_args = ['--usecashaddr=0']
-		elif self.daemon_id == 'ltc':
-			self.coin_specific_coind_args = ['--mempoolreplacement=1','--txindex=1']
+		from mmgen.regtest import MMGenRegtest
+		self.shared_args = list_gen(
+			[f'--datadir={self.datadir}'],
+			[f'--rpcport={self.rpc_port}'],
+			[f'--rpcuser={MMGenRegtest.rpc_user}',         self.network == 'regtest'],
+			[f'--rpcpassword={MMGenRegtest.rpc_password}', self.network == 'regtest'],
+			['--testnet',                                  self.network == 'testnet'],
+			['--regtest',                                  self.network == 'regtest'],
+		)
+
+		self.coind_args = list_gen(
+			['--listen=0'],
+			['--keypool=1'],
+			['--rpcallowip=127.0.0.1'],
+			[f'--rpcbind=127.0.0.1:{self.rpc_port}'],
+			['--pid='+self.pidfile,    self.use_pidfile],
+			['--daemon',               self.platform == 'linux' and not 'no_daemonize' in self.opts],
+			['--fallbackfee=0.0002',   self.daemon_id == 'btc' and self.network == 'regtest'],
+			['--usecashaddr=0',        self.daemon_id == 'bch'],
+			['--mempoolreplacement=1', self.daemon_id == 'ltc'],
+			['--txindex=1',            self.daemon_id == 'ltc'],
+		)
 
 		if self.network == 'testnet':
 			self.lockfile = os.path.join(self.datadir,self.testnet_dir,'.cookie')
@@ -591,10 +572,7 @@ class BitcoinDaemon(CoinDaemon):
 			or "error: Could not connect" in err
 			or "does not exist" in err ):
 			# regtest has no cookie file, so test will always fail
-			if self.lockfile and os.path.exists(self.lockfile):
-				ret = 'busy'
-			else:
-				ret = 'stopped'
+			ret = 'busy' if (self.lockfile and os.path.exists(self.lockfile)) else 'stopped'
 		elif cp.returncode == 0:
 			ret = 'ready'
 		else:
@@ -618,23 +596,24 @@ class MoneroDaemon(CoinDaemon):
 		if self.platform == 'win':
 			self.use_pidfile = False
 
-	@property
-	def shared_args(self):
-		return ['--zmq-rpc-bind-port={}'.format(self.rpc_port+1),'--rpc-bind-port={}'.format(self.rpc_port)]
+		self.shared_args = list_gen(
+			[f'--zmq-rpc-bind-port={self.rpc_port+1}'],
+			[f'--rpc-bind-port={self.rpc_port}'],
+		)
 
-	@property
-	def coind_args(self):
-		cmd = [
-			'--bg-mining-enable',
-			'--data-dir={}'.format(self.datadir),
-			'--offline' ]
-		if self.platform == 'linux':
-			cmd += ['--pidfile={}'.format(self.pidfile)]
-			cmd += [] if 'no_daemonize' in self.flags else ['--detach']
-		return cmd
+		self.coind_args = list_gen(
+			['--hide-my-port'],
+			['--no-igd'],
+			[f'--data-dir={self.datadir}'],
+			[f'--pidfile={self.pidfile}', self.platform == 'linux'],
+			['--detach',                  not 'no_daemonize' in self.opts],
+			['--offline',                 not 'online' in self.opts],
+		)
 
 	@property
 	def state(self):
+		return 'ready' if self.test_socket(self.host,self.rpc_port) else 'stopped'
+		# TODO:
 		if not self.test_socket(self.host,self.rpc_port):
 			return 'stopped'
 		cp = self.run_cmd(
@@ -658,21 +637,22 @@ class EthereumDaemon(CoinDaemon):
 	ps_pid_mswin = True
 
 	def subclass_init(self):
-		self.shared_args = []
 		# defaults:
 		# linux: $HOME/.local/share/io.parity.ethereum/chains/DevelopmentChain
 		# win:   $LOCALAPPDATA/Parity/Ethereum/chains/DevelopmentChain
-		self.chaindir = os.path.join(self.datadir,'devchain')
-		shutil.rmtree(self.chaindir,ignore_errors=True)
-		if self.platform == 'linux' and not 'no_daemonize' in self.flags:
-			self.daemonize_args = ['daemon',self.pidfile]
 
-	@property
-	def coind_args(self):
-		return ['--ports-shift={}'.format(self.port_shift),
-				'--base-path={}'.format(self.chaindir),
-				'--config=dev',
-				'--log-file={}'.format(os.path.join(self.datadir,'openethereum.log')) ]
+		chaindir = os.path.join(self.datadir,'devchain')
+		shutil.rmtree(chaindir,ignore_errors=True)
+
+		ld = self.platform == 'linux' and not 'no_daemonize' in self.opts
+		self.coind_args = list_gen(
+			[f'--ports-shift={self.port_shift}'],
+			[f'--base-path={chaindir}'],
+			['--config=dev'],
+			['--log-file='+os.path.join(self.datadir,'openethereum.log')],
+			['daemon',     ld],
+			[self.pidfile, ld],
+		)
 
 	@property
 	def state(self):

+ 15 - 2
mmgen/util.py

@@ -27,6 +27,9 @@ from .color import *
 from .exception import *
 from .globalvars import *
 
+CUR_HIDE = '\033[?25l'
+CUR_SHOW = '\033[?25h'
+
 if g.platform == 'win':
 	def msg_r(s):
 		try:
@@ -113,8 +116,18 @@ def fmt_list(l,fmt='dfl',indent=''):
 	}[fmt]
 	return lq + sep.join(l) + rq
 
-CUR_HIDE = '\033[?25l'
-CUR_SHOW = '\033[?25h'
+def list_gen(*data):
+	"""
+	add element to list if condition is true or absent
+	"""
+	assert type(data) in (list,tuple), f'{type(data).__name__} not in (list,tuple)'
+	def gen():
+		for i in data:
+			assert type(i) == list, f'{type(i).__name__} != list'
+			assert len(i) in (1,2), f'{len(i)} not in (1,2)'
+			if len(i) == 1 or i[1]:
+				yield i[0]
+	return list(gen())
 
 def exit_if_mswin(feature):
 	if g.platform == 'win':

+ 1 - 1
mmgen/xmrwallet.py

@@ -439,7 +439,7 @@ class MoneroWalletOps:
 					test_suite = g.test_suite,
 					daemon_addr = m[1],
 					proxy = m[2],
-					rpc_port_shift = 16,
+					port_shift = 16,
 				)
 
 				if uarg.start_wallet_daemon:

+ 6 - 3
test/start-coin-daemons.py

@@ -15,8 +15,8 @@ opts_data = {
 		'options': """
 -h, --help           Print this help message
 --, --longhelp       Print help message for long options (common options)
--d, --debug          Produce debugging output (implies --verbose)
--D, --no-daemonize   Don't fork daemon to background
+-D, --debug          Produce debugging output (implies --verbose)
+-n, --no-daemonize   Don't fork daemon to background
 -s, --get-state      Get the state of the daemon(s) and exit
 -t, --testing        Testing mode.  Print commands but don't execute them
 -v, --verbose        Produce more verbose output
@@ -55,7 +55,10 @@ if 'eth' in ids and 'etc' in ids:
 
 for network_id in ids:
 	network_id = network_id.lower()
-	d = CoinDaemon(network_id,test_suite=True,flags=['no_daemonize'] if opt.no_daemonize else None)
+	d = CoinDaemon(
+		network_id,
+		test_suite = True,
+		opts       = ['no_daemonize'] if opt.no_daemonize else None )
 	d.debug = opt.debug
 	d.wait = not opt.no_wait
 	if opt.get_state: