From 8bb6c40ec6f4b32586505c90455f6de8b831df08 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 29 Jul 2021 14:30:23 +0000 Subject: [PATCH] CoinDaemon: add subclasses, support multiple chains for ETH --- mmgen/daemon.py | 257 ++++++++++++++++-------------------- mmgen/regtest.py | 2 +- test/start-coin-daemons.py | 2 +- test/test_py_d/ts_ethdev.py | 6 +- test/unit_tests_d/ut_rpc.py | 6 +- 5 files changed, 125 insertions(+), 148 deletions(-) diff --git a/mmgen/daemon.py b/mmgen/daemon.py index 3db5647e..06c4de54 100755 --- a/mmgen/daemon.py +++ b/mmgen/daemon.py @@ -26,6 +26,9 @@ from collections import namedtuple 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']) + class Daemon(MMGenObject): desc = 'daemon' @@ -181,7 +184,7 @@ class Daemon(MMGenObject): time.sleep(0.2) else: m = 'Wait for state {!r} timeout exceeded for daemon {} {} (port {})' - die(2,m.format(req_state,self.daemon_id.upper(),self.network,self.rpc_port)) + die(2,m.format(req_state,self.coin,self.network,self.rpc_port)) @classmethod def check_implement(cls): @@ -223,7 +226,7 @@ class MoneroWalletDaemon(Daemon): desc = 'RPC daemon' net_desc = 'Monero wallet' - daemon_id = 'xmr' + coin = 'XMR' network = 'wallet RPC' new_console_mswin = True ps_pid_mswin = True @@ -314,6 +317,7 @@ class MoneroWalletDaemon(Daemon): 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',) @@ -321,109 +325,24 @@ class CoinDaemon(Daemon): datadir_is_subdir = False data_subdir = '' - network_ids = ( - 'btc','btc_tn','btc_rt', - 'bch','bch_tn','bch_rt', - 'ltc','ltc_tn','ltc_rt', - 'xmr','xmr_tn', - 'eth','etc' - ) - - cd = namedtuple('daemon_data', [ - 'id', - 'coin', - 'cls_pfx', - 'coind_name', - 'coind_version', 'coind_version_str', # latest tested version - 'exec_fn', - 'cli_fn', - 'cfg_file', - 'testnet_dir', - 'dfl_rpc', - 'dfl_rpc_tn', - 'dfl_rpc_rt' ]) - - daemon_ids = { - 'btc': cd( - 'bitcoin_core', - 'Bitcoin', - 'Bitcoin', - 'Bitcoin Core', 210100, '0.21.1', - 'bitcoind', - 'bitcoin-cli', - 'bitcoin.conf', - 'testnet3', - 8332, 18332, 18444), - 'bch': cd( - 'bitcoin_cash_node', - 'BitcoinCashNode', - 'Bitcoin', - 'Bitcoin Cash Node', 23000000, '23.0.0', - 'bitcoind-bchn', - 'bitcoin-cli-bchn', - 'bitcoin.conf', - 'testnet3', - 8442, 18442, 18553), # for BCH we use non-standard RPC ports - 'ltc': cd( - 'litecoin_core', - 'Litecoin', - 'Bitcoin', - 'Litecoin Core', 180100, '0.18.1', - 'litecoind', - 'litecoin-cli', - 'litecoin.conf', - 'testnet4', - 9332, 19332, 19444), - 'xmr': cd( - 'monerod', - 'Monero', - 'Monero', - 'Monero', 'N/A', 'N/A', - 'monerod', - 'monerod', - 'bitmonero.conf', - 'stagenet', - 18081, 38081, None), - 'eth': cd( - 'openethereum', - 'Ethereum', - 'Ethereum', - 'OpenEthereum', 3003000, '3.3.0', - 'openethereum', - 'openethereum', - 'parity.conf', - None, - 8545, 8545, 8545), - 'etc': cd( - 'openethereum', - 'Ethereum Classic', - 'Ethereum', - 'OpenEthereum', 3003000, '3.3.0', - 'openethereum', - 'openethereum', - 'parity.conf', - None, - 8545, 8545, 8545) + _cd = namedtuple('coins_data',['coin_name','daemon_ids']) + coins = { + 'BTC': _cd('Bitcoin', ['bitcoin_core']), + 'BCH': _cd('Bitcoin Cash Node', ['bitcoin_cash_node']), + 'LTC': _cd('Litecoin', ['litecoin_core']), + 'XMR': _cd('Monero', ['monero']), + 'ETH': _cd('Ethereum', ['openethereum']), + 'ETC': _cd('Ethereum Classic', ['openethereum_etc']), } - 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'], - }, - } + @classmethod + def get_network_ids(cls): # FIXME: gets IDs for _default_ daemon only + from .protocol import CoinProtocol + def gen(): + for coin,data in cls.coins.items(): + for network in globals()[data.daemon_ids[0]+'_daemon'].networks: + yield CoinProtocol.Base.create_network_id(coin,network) + return list(gen()) def __new__(cls, network_id = None, @@ -440,17 +359,23 @@ class CoinDaemon(Daemon): if proto: network_id = proto.network_id network = proto.network - daemon_id = proto.coin.lower() + coin = proto.coin else: network_id = network_id.lower() - assert network_id in cls.network_ids, f'{network_id!r}: invalid network ID' - from .protocol import CoinProtocol - daemon_id,network = CoinProtocol.Base.parse_network_id(network_id) + from .protocol import CoinProtocol,init_proto + proto = init_proto(network_id=network_id) + coin,network = CoinProtocol.Base.parse_network_id(network_id) + coin = coin.upper() - me = Daemon.__new__(globals()[cls.daemon_ids[daemon_id].cls_pfx+'Daemon']) - me.network_id = network_id + daemon_id = cls.coins[coin].daemon_ids[0] + me = Daemon.__new__(globals()[daemon_id + '_daemon']) + assert network in me.networks, f'{network!r}: unsupported network for daemon {daemon_id}' me.network = network - me.daemon_id = daemon_id + me.network_id = network_id + me.coin = coin + me.coin_name = cls.coins[coin].coin_name + me.id = daemon_id + me.proto = proto return me @@ -485,25 +410,23 @@ class CoinDaemon(Daemon): for flag in flags: self.add_flag(flag) - for k in self.daemon_ids[self.daemon_id]._fields: - setattr(self,k,getattr(self.daemon_ids[self.daemon_id],k)) + for k,v in self.daemon_data._asdict().items(): + setattr(self,k,v) - if self.network == 'regtest': - self.desc = 'regtest daemon' + if self.network == 'regtest' and isinstance(self,bitcoin_core_daemon): if test_suite: rel_datadir = os.path.join( 'test', 'data_dir{}'.format('-α' if g.debug_utf8 else ''), 'regtest', - self.daemon_id ) + self.coin.lower() ) else: - dfl_datadir = os.path.join(g.data_dir_root,'regtest',self.daemon_id) + dfl_datadir = os.path.join(g.data_dir_root,'regtest',self.coin.lower()) elif test_suite: self.desc = 'test suite daemon' - rel_datadir = os.path.join('test','daemons',self.daemon_id) + rel_datadir = os.path.join('test','daemons',self.coin.lower()) else: - dfl_datadir = os.path.join( *self.dfl_datadirs[g.platform][self.daemon_id] ) - + dfl_datadir = os.path.join(*self.datadirs[g.platform]) if test_suite: dfl_datadir = os.path.join(os.getcwd(),rel_datadir) @@ -519,17 +442,13 @@ class CoinDaemon(Daemon): self.datadir = os.path.join(self.datadir,self.testnet_dir) self.port_shift = (1237 if test_suite else 0) + (port_shift or 0) - self.rpc_port = { - 'mainnet': self.dfl_rpc, - 'testnet': self.dfl_rpc_tn, - 'regtest': self.dfl_rpc_rt, - }[self.network] + self.port_shift + self.rpc_port = getattr(self.rpc_ports,self.network) + self.port_shift 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.net_desc = '{} {}'.format(self.coin_name,self.network) self.subclass_init() @property @@ -544,13 +463,23 @@ class CoinDaemon(Daemon): + self.shared_args + list(cmds) ) -class BitcoinDaemon(CoinDaemon): - cfg_file_hdr = '# BitcoinDaemon config file\n' +class bitcoin_core_daemon(CoinDaemon): + daemon_data = _dd('Bitcoin Core', 210100, '0.21.1') + exec_fn = 'bitcoind' + cli_fn = 'bitcoin-cli' + testnet_dir = 'testnet3' + cfg_file_hdr = '# BitcoinCoreDaemon config file\n' tracking_wallet_name = 'mmgen-tracking-wallet' + rpc_ports = _pd(8332, 18332, 18444) + cfg_file = 'bitcoin.conf' + datadirs = { + 'linux': [g.home_dir,'.bitcoin'], + 'win': [os.getenv('APPDATA'),'Bitcoin'] + } def subclass_init(self): - if self.platform == 'win' and self.daemon_id == 'bch': + if self.platform == 'win' and self.coin == 'BCH': self.use_pidfile = False from .regtest import MMGenRegtest @@ -570,10 +499,10 @@ class BitcoinDaemon(CoinDaemon): [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'], + ['--fallbackfee=0.0002', self.coin == 'BTC' and self.network == 'regtest'], + ['--usecashaddr=0', self.coin == 'BCH'], + ['--mempoolreplacement=1', self.coin == 'LTC'], + ['--txindex=1', self.coin == 'LTC'], ) if self.network == 'testnet': @@ -602,16 +531,47 @@ class BitcoinDaemon(CoinDaemon): def stop_cmd(self): return self.cli_cmd('stop') -class MoneroDaemon(CoinDaemon): +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 + datadirs = { + 'linux': [g.home_dir,'.bitcoin-bchn'], + 'win': [os.getenv('APPDATA'),'Bitcoin_ABC'] + } +class litecoin_core_daemon(bitcoin_core_daemon): + daemon_data = _dd('Litecoin Core', 180100, '0.18.1') + exec_fn = 'litecoind' + cli_fn = 'litecoin-cli' + testnet_dir = 'testnet4' + rpc_ports = _pd(9332, 19332, 19444) + cfg_file = 'litecoin.conf' + datadirs = { + 'linux': [g.home_dir,'.litecoin'], + 'win': [os.getenv('APPDATA'),'Litecoin'] + } + +class monero_daemon(CoinDaemon): + daemon_data = _dd('Monero', 'N/A', 'N/A') + networks = ('mainnet','testnet') + exec_fn = 'monerod' + testnet_dir = 'stagenet' ps_pid_mswin = True new_console_mswin = True host = 'localhost' # FIXME + rpc_ports = _pd(18081, 38081, None) + cfg_file = 'bitmonero.conf' datadir_is_subdir = True + datadirs = { + 'linux': [g.home_dir,'.bitmonero'], + 'win': ['/','c','ProgramData','bitmonero'] + } def subclass_init(self): if self.network == 'testnet': - self.net_desc = f'{self.coin} stagenet' + self.net_desc = f'{self.coin_name} stagenet' self.p2p_port = self.rpc_port - 1 self.zmq_port = self.rpc_port + 1 @@ -656,28 +616,40 @@ class MoneroDaemon(CoinDaemon): else: return [self.exec_fn] + self.shared_args + ['exit'] -class EthereumDaemon(CoinDaemon): - +class openethereum_daemon(CoinDaemon): + daemon_data = _dd('OpenEthereum', 3003000, '3.3.0') + 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 + cfg_file = 'parity.conf' + datadirs = { + 'linux': [g.home_dir,'.local','share','io.parity.ethereum'], + 'win': [g.home_dir,'.local','share','io.parity.ethereum'] # FIXME + } + testnet_dir = 'testnet' # FIXME 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,'devchain') + base_path = os.path.join(self.datadir,self.proto.chain_name) shutil.rmtree(base_path,ignore_errors=True) + ps = self.port_shift + self.ports_shift[self.network] ld = self.platform == 'linux' and not 'no_daemonize' in self.opts + self.coind_args = list_gen( ['--no-ws'], ['--no-ipc'], ['--no-secretstore'], - [f'--ports-shift={self.port_shift}'], + [f'--ports-shift={ps}'], [f'--base-path={base_path}'], - ['--config=dev'], - ['--mode=offline',self.test_suite], - ['--log-file='+os.path.join(self.datadir,'openethereum.log')], + [f'--chain={self.proto.chain_name}', self.network!='regtest'], + [f'--config=dev', self.network=='regtest'], # no presets for mainnet or testnet + ['--mode=offline', self.test_suite or self.network=='regtest'], + ['--log-file='+os.path.join(self.datadir, f'openethereum-{self.network}.log')], ['daemon', ld], [self.pidfile, ld], ) @@ -700,4 +672,7 @@ class EthereumDaemon(CoinDaemon): 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/regtest.py b/mmgen/regtest.py index cd433cfc..26a0d5c2 100755 --- a/mmgen/regtest.py +++ b/mmgen/regtest.py @@ -165,7 +165,7 @@ class MMGenRegtest(MMGenObject): msg(fs.format('Total balance:',sum(v for k,v in bal.items()))) async def send(self,addr,amt): - gmsg('Sending {} miner {} to address {}'.format(amt,self.d.daemon_id.upper(),addr)) + gmsg('Sending {} miner {} to address {}'.format(amt,self.d.coin,addr)) cp = await self.rpc_call('sendtoaddress',addr,str(amt),wallet='miner') await self.generate(1) diff --git a/test/start-coin-daemons.py b/test/start-coin-daemons.py index 9fa7cae8..7511e5a7 100755 --- a/test/start-coin-daemons.py +++ b/test/start-coin-daemons.py @@ -5,7 +5,7 @@ from include.tests_header import repo_root from mmgen.common import * from mmgen.daemon import CoinDaemon -network_ids = CoinDaemon.network_ids +network_ids = CoinDaemon.get_network_ids() action = g.prog_name.split('-')[0] diff --git a/test/test_py_d/ts_ethdev.py b/test/test_py_d/ts_ethdev.py index 433abc4d..ca8d5bf0 100755 --- a/test/test_py_d/ts_ethdev.py +++ b/test/test_py_d/ts_ethdev.py @@ -332,7 +332,8 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): for d in ('mm1','mm2'): copytree(os.path.join(srcdir,d),os.path.join(self.tmpdir,d)) if not opt.no_daemon_autostart: - start_test_daemons(self.proto.coin,remove_datadir=True) + if not start_test_daemons(self.proto.coin+'_rt',remove_datadir=True): + return False return 'ok' def wallet_upgrade(self,src_file): @@ -995,5 +996,6 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): def stop(self): self.spawn('',msg_only=True) if not opt.no_daemon_stop: - stop_test_daemons(self.proto.coin) + if not stop_test_daemons(self.proto.coin+'_rt'): + return False return 'ok' diff --git a/test/unit_tests_d/ut_rpc.py b/test/unit_tests_d/ut_rpc.py index 1854835c..0dde9797 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 +from mmgen.daemon import CoinDaemon,MoneroWalletDaemon,bitcoin_core_daemon def auth_test(proto,d): if g.platform != 'win': @@ -62,9 +62,9 @@ class init_test: def run_test(coin,auth): - proto = init_proto(coin,network=('mainnet','regtest')[coin=='eth']) # FIXME CoinDaemon's network handling broken + proto = init_proto(coin) - d = CoinDaemon(network_id=coin,test_suite=True) + d = CoinDaemon(proto=proto,test_suite=True) if not opt.no_daemon_stop: d.stop()