From 305f986698ec8ec31dd32d29a2621f5e0aa18ef3 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Fri, 30 Jul 2021 10:44:02 +0000 Subject: [PATCH] daemon.py,rpc.py: cleanups and fixes --- mmgen/daemon.py | 108 ++++++++---------------------------- mmgen/protocol.py | 9 ++- mmgen/rpc.py | 47 ++++++++-------- test/unit_tests_d/ut_rpc.py | 3 +- 4 files changed, 54 insertions(+), 113 deletions(-) diff --git a/mmgen/daemon.py b/mmgen/daemon.py index 4e765df8..a02b9a45 100755 --- a/mmgen/daemon.py +++ b/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() diff --git a/mmgen/protocol.py b/mmgen/protocol.py index b548e22c..f2aa8737 100755 --- a/mmgen/protocol.py +++ b/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}') diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 48aa4682..f77e225c 100755 --- a/mmgen/rpc.py +++ b/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.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.blockcount = int(bh['number'],16) + self.cur_date = int(bh['timestamp'],16) + + 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 diff --git a/test/unit_tests_d/ut_rpc.py b/test/unit_tests_d/ut_rpc.py index 0dde9797..0f66a424 100755 --- a/test/unit_tests_d/ut_rpc.py +++ b/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: