Browse Source

daemon.py,rpc.py: cleanups and fixes

The MMGen Project 3 years ago
parent
commit
305f986698
4 changed files with 53 additions and 112 deletions
  1. 22 86
      mmgen/daemon.py
  2. 6 3
      mmgen/protocol.py
  3. 24 21
      mmgen/rpc.py
  4. 1 2
      test/unit_tests_d/ut_rpc.py

+ 22 - 86
mmgen/daemon.py

@@ -27,7 +27,8 @@ from .exception import *
 from .common import *
 
 _dd = namedtuple('daemon_data',['coind_name','coind_version','coind_version_str']) # latest tested version
-_pd = namedtuple('rpc_ports_data',['mainnet','testnet','regtest'])
+_cd = namedtuple('coins_data',['coin_name','daemon_ids'])
+_nw = namedtuple('coin_networks',['mainnet','testnet','regtest'])
 
 class Daemon(MMGenObject):
 
@@ -106,6 +107,13 @@ class Daemon(MMGenObject):
 		else:
 			return '(unknown)'
 
+	@property
+	def state(self):
+		return 'ready' if self.test_socket('localhost',self.rpc_port) else 'stopped'
+
+	@property
+	def stop_cmd(self):
+		return ['kill','-Wf',self.pid] if self.platform == 'win' else ['kill',self.pid]
 
 	def cmd(self,action,*args,**kwargs):
 		return getattr(self,action)(*args,**kwargs)
@@ -186,13 +194,6 @@ class Daemon(MMGenObject):
 			m = 'Wait for state {!r} timeout exceeded for daemon {} {} (port {})'
 			die(2,m.format(req_state,self.coin,self.network,self.rpc_port))
 
-	@classmethod
-	def check_implement(cls):
-		m = 'required method {}() missing in class {}'
-		for subcls in cls.__subclasses__():
-			for k in cls.subclasses_must_implement:
-				assert k in subcls.__dict__, m.format(k,subcls.__name__)
-
 	@property
 	def flags(self):
 		return self._flags
@@ -295,37 +296,14 @@ class MoneroWalletDaemon(Daemon):
 	def start_cmd(self):
 		return (['monero-wallet-rpc'] + self.daemon_args + self.usr_daemon_args )
 
-	@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
-		try:
-			MoneroWalletRPCClient(
-				self.host,
-				self.rpc_port,
-				self.user,
-				self.passwd).call('get_version')
-			return 'ready'
-		except:
-			return 'stopped'
-
-	@property
-	def stop_cmd(self):
-		return ['kill','-Wf',self.pid] if self.platform == 'win' else ['kill',self.pid]
-
 class CoinDaemon(Daemon):
 	networks = ('mainnet','testnet','regtest')
 	cfg_file_hdr = ''
-	subclasses_must_implement = ('state','stop_cmd')
 	avail_flags = ('keep_cfg_file',)
 	avail_opts = ('no_daemonize','online')
 	datadir_is_subdir = False
 	data_subdir = ''
 
-	_cd = namedtuple('coins_data',['coin_name','daemon_ids'])
 	coins = {
 		'BTC': _cd('Bitcoin',           ['bitcoin_core']),
 		'BCH': _cd('Bitcoin Cash Node', ['bitcoin_cash_node']),
@@ -470,7 +448,7 @@ class bitcoin_core_daemon(CoinDaemon):
 	testnet_dir = 'testnet3'
 	cfg_file_hdr = '# BitcoinCoreDaemon config file\n'
 	tracking_wallet_name = 'mmgen-tracking-wallet'
-	rpc_ports = _pd(8332, 18332, 18444)
+	rpc_ports = _nw(8332, 18332, 18444)
 	cfg_file = 'bitcoin.conf'
 	datadirs = {
 		'linux': [g.home_dir,'.bitcoin'],
@@ -535,7 +513,7 @@ class bitcoin_cash_node_daemon(bitcoin_core_daemon):
 	daemon_data = _dd('Bitcoin Cash Node', 23000000, '23.0.0')
 	exec_fn = 'bitcoind-bchn'
 	cli_fn = 'bitcoin-cli-bchn'
-	rpc_ports = _pd(8442, 18442, 18553) # use non-standard ports
+	rpc_ports = _nw(8442, 18442, 18553) # use non-standard ports
 	datadirs = {
 		'linux': [g.home_dir,'.bitcoin-bchn'],
 		'win':   [os.getenv('APPDATA'),'Bitcoin_ABC']
@@ -546,7 +524,7 @@ class litecoin_core_daemon(bitcoin_core_daemon):
 	exec_fn = 'litecoind'
 	cli_fn = 'litecoin-cli'
 	testnet_dir = 'testnet4'
-	rpc_ports = _pd(9332, 19332, 19444)
+	rpc_ports = _nw(9332, 19332, 19444)
 	cfg_file = 'litecoin.conf'
 	datadirs = {
 		'linux': [g.home_dir,'.litecoin'],
@@ -561,7 +539,7 @@ class monero_daemon(CoinDaemon):
 	ps_pid_mswin = True
 	new_console_mswin = True
 	host = 'localhost' # FIXME
-	rpc_ports = _pd(18081, 38081, None)
+	rpc_ports = _nw(18081, 38081, None)
 	cfg_file = 'bitmonero.conf'
 	datadir_is_subdir = True
 	datadirs = {
@@ -595,49 +573,30 @@ class monero_daemon(CoinDaemon):
 			['--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(
-			[self.exec_fn]
-			+ self.shared_args
-			+ ['status'],
-			silent=True,
-			check=False )
-		return 'stopped' if 'Error:' in cp.stdout.decode() else 'ready'
-
 	@property
 	def stop_cmd(self):
-		if self.platform == 'win':
-			return ['kill','-Wf',self.pid]
-		else:
-			return [self.exec_fn] + self.shared_args + ['exit']
+		return ['kill','-Wf',self.pid] if self.platform == 'win' else [self.exec_fn] + self.shared_args + ['exit']
 
 class openethereum_daemon(CoinDaemon):
 	daemon_data = _dd('OpenEthereum', 3003000, '3.3.0')
+	chain_subdirs = _nw('ethereum','goerli','DevelopmentChain')
+	version_pat = r'OpenEthereum//v(\d+)\.(\d+)\.(\d+)'
 	exec_fn = 'openethereum'
 	ps_pid_mswin = True
-	ports_shift = { 'mainnet': 0, 'testnet': 20, 'regtest': 40 }
-	rpc_ports = _pd(*[8545 + n for n in ports_shift.values()]) # testnet and regtest are non-standard
+	ports_shift = _nw(0,20,40)
+	rpc_ports = _nw(*[8545 + n for n in ports_shift]) # testnet and regtest are non-standard
 	cfg_file = 'parity.conf'
 	datadirs = {
 		'linux': [g.home_dir,'.local','share','io.parity.ethereum'],
-		'win':   [g.home_dir,'.local','share','io.parity.ethereum'] # FIXME
+		'win':   [os.getenv('LOCALAPPDATA'),'Parity','Ethereum']
 	}
-	testnet_dir = 'testnet' # FIXME
+	testnet_dir = None
 
 	def subclass_init(self):
-		# defaults:
-		# linux: $HOME/.local/share/io.parity.ethereum/chains/DevelopmentChain
-		# win:   $LOCALAPPDATA/Parity/Ethereum/chains/DevelopmentChain
-
-		base_path = os.path.join(self.datadir,self.proto.chain_name)
+		base_path = os.path.join(self.datadir,'chains',getattr(self.chain_subdirs,self.network))
 		shutil.rmtree(base_path,ignore_errors=True)
 
-		ps = self.port_shift + self.ports_shift[self.network]
+		ps = self.port_shift + getattr(self.ports_shift,self.network)
 		ld = self.platform == 'linux' and not 'no_daemonize' in self.opts
 
 		self.coind_args = list_gen(
@@ -653,26 +612,3 @@ class openethereum_daemon(CoinDaemon):
 			['daemon', ld],
 			[self.pidfile, ld],
 		)
-
-	@property
-	def state(self):
-		return 'ready' if self.test_socket('localhost',self.rpc_port) else 'stopped'
-
-		# the following code does not work
-		async def do():
-			ret = await self.rpc.call('eth_chainId')
-			return ('stopped','ready')[ret == '0x11']
-
-		try:
-			return run_session(do()) # socket exception is not propagated
-		except:# SocketError:
-			return 'stopped'
-
-	@property
-	def stop_cmd(self):
-		return ['kill','-Wf',self.pid] if self.platform == 'win' else ['kill',self.pid]
-
-# class openethereum_etc_daemon(openethereum_daemon):
-#	rpc_ports = _pd(*[8645 + n for n in openethereum_daemon.ports_shift.values()])
-
-CoinDaemon.check_implement()

+ 6 - 3
mmgen/protocol.py

@@ -102,8 +102,11 @@ class CoinProtocol(MMGenObject):
 				'regtest': '_rt',
 			}[network]
 
-			# first chain name is default
-			self.chain_name = self.chain_names[0] if hasattr(self,'chain_names') else self.network
+			if hasattr(self,'chain_names'):
+				self.chain_name = self.chain_names[0] # first chain name is default
+			else:
+				self.chain_name = self.network
+				self.chain_names = [self.network]
 
 			if self.tokensym:
 				assert isinstance(self,CoinProtocol.Ethereum), 'CoinProtocol.Base_chk1'
@@ -124,7 +127,7 @@ class CoinProtocol(MMGenObject):
 			"""
 			for network in ('mainnet','testnet','regtest'):
 				proto = init_proto(coin,network=network)
-				for proto_chain_name in ( getattr(proto,'chain_names',None) or [network] ):
+				for proto_chain_name in proto.chain_names:
 					if chain_name == proto_chain_name:
 						return network
 			raise ValueError(f'{chain_name}: unrecognized chain name for coin {coin}')

+ 24 - 21
mmgen/rpc.py

@@ -598,28 +598,32 @@ class EthereumRPCClient(RPCClient,metaclass=aInitMeta):
 
 		self.set_backend(backend)
 
-		self.blockcount = int(await self.call('eth_blockNumber'),16)
-
-		vi,bh,ch,nk = await self.gathered_call(None, (
+		vi,bh,ci = await self.gathered_call(None, (
 				('web3_clientVersion',()),
-				('parity_getBlockHeaderByNumber',()),
-				('parity_chain',()),
-				('parity_nodeKind',()),
+				('eth_getBlockByNumber',('latest',False)),
+				('eth_chainId',()),
 			))
 
 		import re
-		vip = re.match(r'OpenEthereum//v(\d+)\.(\d+)\.(\d+)',vi,re.ASCII)
+		vip = re.match(self.daemon.version_pat,vi,re.ASCII)
+		if not vip:
+			ydie(1,fmt(f"""
+			Aborting on daemon mismatch:
+			  Requested daemon: {self.daemon.id}
+			  Running daemon:   {vi}
+			""",strip_char='\t').rstrip())
 		self.daemon_version = int('{:d}{:03d}{:03d}'.format(*[int(e) for e in vip.groups()]))
 		self.daemon_version_str = '{}.{}.{}'.format(*vip.groups())
+
+		self.blockcount = int(bh['number'],16)
 		self.cur_date = int(bh['timestamp'],16)
-		self.chain = ch.replace(' ','_')
-		self.caps = ('full_node',) if nk['capability'] == 'full' else ()
 
-		try:
-			await self.call('eth_chainId')
-			self.caps += ('eth_chainId',)
-		except RPCFailure:
-			pass
+		self.caps = ()
+		if self.daemon.id == 'openethereum':
+			if (await self.call('parity_nodeKind'))['capability'] == 'full':
+				self.caps += ('full_node',)
+			self.chainID = None
+			self.chain = (await self.call('parity_chain')).replace(' ','_')
 
 	rpcmethods = (
 		'eth_accounts',
@@ -642,7 +646,6 @@ class EthereumRPCClient(RPCClient,metaclass=aInitMeta):
 		'net_peerCount',
 		'net_version',
 		'parity_chain',
-		'parity_chainId', # superseded by eth_chainId
 		'parity_getBlockHeaderByNumber',
 		'parity_nextNonce',
 		'parity_nodeKind',
@@ -755,12 +758,12 @@ async def rpc_init(proto,backend=None,daemon=None,ignore_daemon_version=False):
 	if rpc.daemon_version > rpc.daemon.coind_version:
 		handle_unsupported_daemon_version(rpc,proto,ignore_daemon_version)
 
-	if proto.chain_name != rpc.chain:
-		raise RPCChainMismatch(
-			'{} protocol chain is {}, but coin daemon chain is {}'.format(
-				proto.cls_name,
-				proto.chain_name.upper(),
-				rpc.chain.upper() ))
+	if rpc.chain not in proto.chain_names:
+		raise RPCChainMismatch('\n'+fmt(f"""
+			Protocol:           {proto.cls_name}
+			Valid chain names:  {fmt_list(proto.chain_names,fmt='bare')}
+			RPC client chain:   {rpc.chain}
+			""",indent='  ').rstrip())
 
 	if g.bogus_wallet_data:
 		rpc.blockcount = 1000000

+ 1 - 2
test/unit_tests_d/ut_rpc.py

@@ -8,7 +8,7 @@ from mmgen.exception import *
 
 from mmgen.protocol import init_proto
 from mmgen.rpc import rpc_init,MoneroWalletRPCClient
-from mmgen.daemon import CoinDaemon,MoneroWalletDaemon,bitcoin_core_daemon
+from mmgen.daemon import CoinDaemon,MoneroWalletDaemon
 
 def auth_test(proto,d):
 	if g.platform != 'win':
@@ -82,7 +82,6 @@ def run_test(coin,auth):
 	if auth:
 		auth_test(proto,d)
 
-	qmsg('  OK')
 	return True
 
 class unit_tests: