From b17b6f6275bc0829894db137cb12a47e00165eb5 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sat, 5 Feb 2022 13:32:57 +0000 Subject: [PATCH] modularize daemon classes - protocol-independent base classes remain in `daemon.py` - protocol-dependent subclasses are moved to `base_proto/{name}/daemon.py` --- mmgen/base_proto/bitcoin/daemon.py | 159 ++++++++++++ mmgen/base_proto/ethereum/daemon.py | 178 +++++++++++++ mmgen/base_proto/monero/daemon.py | 61 ++++- mmgen/daemon.py | 382 ++-------------------------- mmgen/data/release_date | 2 +- mmgen/data/version | 2 +- mmgen/proto/xmr.py | 1 + test/unit_tests_d/ut_daemon.py | 2 +- 8 files changed, 413 insertions(+), 374 deletions(-) create mode 100755 mmgen/base_proto/bitcoin/daemon.py create mode 100755 mmgen/base_proto/ethereum/daemon.py diff --git a/mmgen/base_proto/bitcoin/daemon.py b/mmgen/base_proto/bitcoin/daemon.py new file mode 100755 index 00000000..3f846832 --- /dev/null +++ b/mmgen/base_proto/bitcoin/daemon.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet +# Copyright (C)2013-2022 The MMGen Project +# Licensed under the GNU General Public License, Version 3: +# https://www.gnu.org/licenses +# Public project repositories: +# https://github.com/mmgen/mmgen +# https://gitlab.com/mmgen/mmgen + +""" +base_proto.bitcoin.daemon: Bitcoin base protocol daemon classes +""" + +import os + +from ...globalvars import g +from ...util import list_gen +from ...daemon import CoinDaemon,_nw,_dd + +class bitcoin_core_daemon(CoinDaemon): + daemon_data = _dd('Bitcoin Core', 220000, '22.0.0') + exec_fn = 'bitcoind' + cli_fn = 'bitcoin-cli' + testnet_dir = 'testnet3' + cfg_file_hdr = '# Bitcoin Core config file\n' + tracking_wallet_name = 'mmgen-tracking-wallet' + rpc_ports = _nw(8332, 18332, 18443) + cfg_file = 'bitcoin.conf' + datadirs = { + 'linux': [g.home_dir,'.bitcoin'], + 'win': [os.getenv('APPDATA'),'Bitcoin'] + } + nonstd_datadir = False + + def init_datadir(self): + if self.network == 'regtest' and not self.test_suite: + return os.path.join( g.data_dir_root, 'regtest', g.coin.lower() ) + else: + return super().init_datadir() + + @property + def network_datadir(self): + "location of the network's blockchain data and authentication cookie" + return os.path.join ( + self.datadir, { + 'mainnet': '', + 'testnet': self.testnet_dir, + 'regtest': 'regtest', + }[self.network] ) + + def init_subclass(self): + + if self.network == 'regtest': + """ + fall back on hard-coded credentials + """ + from ...regtest import MMGenRegtest + self.rpc_user = MMGenRegtest.rpc_user + self.rpc_password = MMGenRegtest.rpc_password + + self.shared_args = list_gen( + [f'--datadir={self.datadir}', self.nonstd_datadir or self.non_dfl_datadir], + [f'--rpcport={self.rpc_port}'], + [f'--rpcuser={self.rpc_user}', self.network == 'regtest'], + [f'--rpcpassword={self.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 self.opt.no_daemonize], + ['--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' or self.network == 'regtest'], + ) + + self.lockfile = os.path.join(self.network_datadir,'.cookie') + + @property + def state(self): + cp = self.cli('getblockcount',silent=True) + err = cp.stderr.decode() + if ("error: couldn't connect" in err + or "error: Could not connect" in err + or "does not exist" in err ): + # regtest has no cookie file, so test will always fail + ret = 'busy' if (self.lockfile and os.path.exists(self.lockfile)) else 'stopped' + elif cp.returncode == 0: + ret = 'ready' + else: + ret = 'busy' + if self.debug: + print(f'State: {ret!r}') + return ret + + @property + def stop_cmd(self): + return self.cli_cmd('stop') + + def set_label_args(self,rpc,coinaddr,lbl): + if 'label_api' in rpc.caps: + return ('setlabel',coinaddr,lbl) + else: + # NOTE: this works because importaddress() removes the old account before + # associating the new account with the address. + # RPC args: addr,label,rescan[=true],p2sh[=none] + return ('importaddress',coinaddr,lbl,False) + + def estimatefee_args(self,rpc): + return (opt.tx_confs,) + + def sigfail_errmsg(self,e): + return e.args[0] + +class bitcoin_cash_node_daemon(bitcoin_core_daemon): + daemon_data = _dd('Bitcoin Cash Node', 24000000, '24.0.0') + exec_fn = 'bitcoind-bchn' + cli_fn = 'bitcoin-cli-bchn' + rpc_ports = _nw(8432, 18432, 18543) # use non-standard ports (core+100) + datadirs = { + 'linux': [g.home_dir,'.bitcoin-bchn'], + 'win': [os.getenv('APPDATA'),'Bitcoin_ABC'] + } + cfg_file_hdr = '# Bitcoin Cash Node config file\n' + nonstd_datadir = True + + def set_label_args(self,rpc,coinaddr,lbl): + # bitcoin-{abc,bchn} 'setlabel' RPC is broken, so use old 'importaddress' method to set label + # Broken behavior: new label is set OK, but old label gets attached to another address + return ('importaddress',coinaddr,lbl,False) + + def estimatefee_args(self,rpc): + return () if rpc.daemon_version >= 190100 else (opt.tx_confs,) + + def sigfail_errmsg(self,e): + return ( + 'This is not the BCH chain.\nRe-run the script without the --coin=bch option.' + if 'Invalid sighash param' in e.args[0] else + e.args[0] ) + +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 = _nw(9332, 19332, 19443) + cfg_file = 'litecoin.conf' + datadirs = { + 'linux': [g.home_dir,'.litecoin'], + 'win': [os.getenv('APPDATA'),'Litecoin'] + } + cfg_file_hdr = '# Litecoin Core config file\n' diff --git a/mmgen/base_proto/ethereum/daemon.py b/mmgen/base_proto/ethereum/daemon.py new file mode 100755 index 00000000..bfde3a06 --- /dev/null +++ b/mmgen/base_proto/ethereum/daemon.py @@ -0,0 +1,178 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet +# Copyright (C)2013-2022 The MMGen Project +# Licensed under the GNU General Public License, Version 3: +# https://www.gnu.org/licenses +# Public project repositories: +# https://github.com/mmgen/mmgen +# https://gitlab.com/mmgen/mmgen + +""" +base_proto.ethereum.daemon: Ethereum base protocol daemon classes +""" + +import os + +from ...globalvars import g +from ...util import list_gen,get_subclasses +from ...daemon import CoinDaemon,RPCDaemon,_nw,_dd + +class ethereum_daemon(CoinDaemon): + chain_subdirs = _nw('ethereum','goerli','DevelopmentChain') + base_rpc_port = 8545 # same for all networks! + base_p2p_port = 30303 # same for all networks! + daemon_port_offset = 100 + network_port_offsets = _nw(0,10,20) + + def __init__(self,*args,**kwargs): + + if not hasattr(self,'all_daemons'): + ethereum_daemon.all_daemons = get_subclasses(ethereum_daemon,names=True) + + self.port_offset = ( + self.all_daemons.index(self.id+'_daemon') * self.daemon_port_offset + + getattr(self.network_port_offsets,self.network) ) + + return super().__init__(*args,**kwargs) + + def get_rpc_port(self): + return self.base_rpc_port + self.port_offset + + def get_p2p_port(self): + return self.base_p2p_port + self.port_offset + + def init_datadir(self): + self.logdir = super().init_datadir() + return os.path.join( + self.logdir, + self.id, + getattr(self.chain_subdirs,self.network) ) + +class openethereum_daemon(ethereum_daemon): + daemon_data = _dd('OpenEthereum', 3003000, '3.3.0') + version_pat = r'OpenEthereum//v(\d+)\.(\d+)\.(\d+)' + exec_fn = 'openethereum' + cfg_file = 'parity.conf' + datadirs = { + 'linux': [g.home_dir,'.local','share','io.parity.ethereum'], + 'win': [os.getenv('LOCALAPPDATA'),'Parity','Ethereum'] + } + + def init_subclass(self): + + ld = self.platform == 'linux' and not self.opt.no_daemonize + + self.coind_args = list_gen( + ['--no-ws'], + ['--no-ipc'], + ['--no-secretstore'], + [f'--jsonrpc-port={self.rpc_port}'], + [f'--port={self.p2p_port}', self.p2p_port], + [f'--base-path={self.datadir}', self.non_dfl_datadir], + [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'], + [f'--log-file={self.logfile}', self.non_dfl_datadir], + ['daemon', ld], + [self.pidfile, ld], + ) + +class parity_daemon(openethereum_daemon): + daemon_data = _dd('Parity', 2007002, '2.7.2') + version_pat = r'Parity-Ethereum//v(\d+)\.(\d+)\.(\d+)' + exec_fn = 'parity' + +class geth_daemon(ethereum_daemon): + daemon_data = _dd('Geth', 1010014, '1.10.14') + version_pat = r'Geth/v(\d+)\.(\d+)\.(\d+)' + exec_fn = 'geth' + use_pidfile = False + use_threads = True + datadirs = { + 'linux': [g.home_dir,'.ethereum','geth'], + 'win': [os.getenv('LOCALAPPDATA'),'Geth'] # FIXME + } + + def init_subclass(self): + self.coind_args = list_gen( + ['--verbosity=0'], + ['--http'], + ['--http.api=eth,web3,txpool'], + [f'--http.port={self.rpc_port}'], + [f'--port={self.p2p_port}', self.p2p_port], # geth binds p2p port even with --maxpeers=0 + ['--maxpeers=0', not self.opt.online], + [f'--datadir={self.datadir}', self.non_dfl_datadir], + ['--goerli', self.network=='testnet'], + ['--dev', self.network=='regtest'], + ) + +# https://github.com/ledgerwatch/erigon +class erigon_daemon(geth_daemon): + daemon_data = _dd('Erigon', 2021009005, '2021.09.5') + version_pat = r'erigon/(\d+)\.(\d+)\.(\d+)' + exec_fn = 'erigon' + private_ports = _nw(9090,9091,9092) # testnet and regtest are non-standard + datadirs = { + 'linux': [g.home_dir,'.local','share','erigon'], + 'win': [os.getenv('LOCALAPPDATA'),'Erigon'] # FIXME + } + + def init_subclass(self): + self.coind_args = list_gen( + ['--verbosity=0'], + [f'--port={self.p2p_port}', self.p2p_port], + ['--maxpeers=0', not self.opt.online], + [f'--private.api.addr=127.0.0.1:{self.private_port}'], + [f'--datadir={self.datadir}', self.non_dfl_datadir and not self.network=='regtest'], + ['--chain=goerli', self.network=='testnet'], + ['--chain=dev', self.network=='regtest'], + ['--mine', self.network=='regtest'], + ) + self.rpc_d = erigon_rpcdaemon( + proto = self.proto, + rpc_port = self.rpc_port, + private_port = self.private_port, + test_suite = self.test_suite, + datadir = self.datadir ) + + def start(self,quiet=False,silent=False): + super().start(quiet=quiet,silent=silent) + self.rpc_d.debug = self.debug + return self.rpc_d.start(quiet=quiet,silent=silent) + + def stop(self,quiet=False,silent=False): + self.rpc_d.debug = self.debug + self.rpc_d.stop(quiet=quiet,silent=silent) + return super().stop(quiet=quiet,silent=silent) + + @property + def start_cmds(self): + return [self.start_cmd,self.rpc_d.start_cmd] + +class erigon_rpcdaemon(RPCDaemon): + + master_daemon = 'erigon_daemon' + rpc_type = 'Erigon' + exec_fn = 'rpcdaemon' + use_pidfile = False + use_threads = True + + def __init__(self,proto,rpc_port,private_port,test_suite,datadir): + + self.proto = proto + self.test_suite = test_suite + + super().__init__() + + self.network = proto.network + self.rpc_port = rpc_port + self.datadir = datadir + + self.daemon_args = list_gen( + ['--verbosity=0'], + [f'--private.api.addr=127.0.0.1:{private_port}'], + [f'--http.port={self.rpc_port}'], + [f'--datadir={self.datadir}', self.network != 'regtest'], + ['--http.api=eth,web3,txpool'], + ) diff --git a/mmgen/base_proto/monero/daemon.py b/mmgen/base_proto/monero/daemon.py index 0c1c29a1..a4dbe0bb 100755 --- a/mmgen/base_proto/monero/daemon.py +++ b/mmgen/base_proto/monero/daemon.py @@ -9,15 +9,69 @@ # https://gitlab.com/mmgen/mmgen """ -base_proto.monero.daemon: Monero daemon classes +base_proto.monero.daemon: Monero base protocol daemon classes """ import os from ...globalvars import g from ...opts import opt -from ...util import list_gen -from ...daemon import CoinDaemon,RPCDaemon,_nw +from ...util import list_gen,die +from ...daemon import CoinDaemon,RPCDaemon,_nw,_dd + +class monero_daemon(CoinDaemon): + daemon_data = _dd('Monero', 'N/A', 'N/A') + networks = ('mainnet','testnet') + exec_fn = 'monerod' + testnet_dir = 'stagenet' + new_console_mswin = True + host = 'localhost' # FIXME + rpc_ports = _nw(18081, 38081, None) # testnet is stagenet + cfg_file = 'bitmonero.conf' + datadirs = { + 'linux': [g.home_dir,'.bitmonero'], + 'win': ['/','c','ProgramData','bitmonero'] + } + + def init_datadir(self): + self.logdir = super().init_datadir() + return os.path.join( + self.logdir, + self.testnet_dir if self.network == 'testnet' else '' ) + + def get_p2p_port(self): + return self.rpc_port - 1 + + 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], + [f'--rpc-bind-port={self.rpc_port}'], + ['--stagenet', self.network == 'testnet'], + ) + + self.coind_args = list_gen( + ['--hide-my-port'], + ['--no-igd'], + [f'--data-dir={self.datadir}', self.non_dfl_datadir], + [f'--pidfile={self.pidfile}', self.platform == 'linux'], + ['--detach', not (self.opt.no_daemonize or self.platform=='win')], + ['--offline', not self.opt.online], + ) + + @property + def stop_cmd(self): + return ['kill','-Wf',self.pid] if self.platform == 'win' else [self.exec_fn] + self.shared_args + ['exit'] class MoneroWalletDaemon(RPCDaemon): @@ -65,7 +119,6 @@ class MoneroWalletDaemon(RPCDaemon): assert self.host assert self.user if not self.passwd: - from ...util import die die(1, 'You must set your Monero wallet RPC password.\n' + 'This can be done on the command line with the --wallet-rpc-password option\n' + diff --git a/mmgen/daemon.py b/mmgen/daemon.py index 4da24ca6..84bdf025 100755 --- a/mmgen/daemon.py +++ b/mmgen/daemon.py @@ -20,17 +20,15 @@ daemon.py: Daemon control interface for the MMGen suite """ -import os,shutil,time +import os,time,importlib from subprocess import run,PIPE,CompletedProcess from collections import namedtuple from .globalvars import g -from .opts import opt -from .util import msg,die,list_gen,get_subclasses +from .util import msg,die from .flags import * _dd = namedtuple('daemon_data',['coind_name','coind_version','coind_version_str']) # latest tested version -_cd = namedtuple('coins_data',['coin_name','daemon_ids']) _nw = namedtuple('coin_networks',['mainnet','testnet','regtest']) class Daemon(Lockable): @@ -256,22 +254,23 @@ class CoinDaemon(Daemon): rpc_user = None rpc_password = None + _cd = namedtuple('coins_data',['coin_name','networks','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','geth'] + (['erigon'] if g.enable_erigon else []) ), - 'ETC': _cd('Ethereum Classic', ['parity']), + 'BTC': _cd('Bitcoin', networks, ['bitcoin_core']), + 'BCH': _cd('Bitcoin Cash Node', networks, ['bitcoin_cash_node']), + 'LTC': _cd('Litecoin', networks, ['litecoin_core']), + 'XMR': _cd('Monero', ('mainnet','testnet'), ['monero']), + 'ETH': _cd('Ethereum', networks, ['openethereum','geth'] + + (['erigon'] if g.enable_erigon else []) ), + 'ETC': _cd('Ethereum Classic', networks, ['parity']), } @classmethod def get_network_ids(cls): # FIXME: gets IDs for _default_ daemon only from .protocol import CoinProtocol - import mmgen.daemon as daemon_mod def gen(): for coin,data in cls.coins.items(): - for network in getattr( daemon_mod, data.daemon_ids[0]+'_daemon' ).networks: + for network in data.networks: yield CoinProtocol.Base.create_network_id(coin,network) return list(gen()) @@ -306,8 +305,10 @@ class CoinDaemon(Daemon): if daemon_id not in daemon_ids: die(1,f'{daemon_id!r}: invalid daemon_id - valid choices: {fmt_list(daemon_ids)}') - import mmgen.daemon - me = Daemon.__new__( getattr(mmgen.daemon, daemon_id+'_daemon') ) + me = Daemon.__new__( + getattr( + importlib.import_module(f'mmgen.base_proto.{proto.base_proto.lower()}.daemon'), + daemon_id + '_daemon' )) assert network in me.networks, f'{network!r}: unsupported network for daemon {daemon_id}' me.network = network @@ -429,356 +430,3 @@ class CoinDaemon(Daemon): pass else: msg(f'Cannot remove {self.network_datadir!r} - daemon is not stopped') - -class bitcoin_core_daemon(CoinDaemon): - daemon_data = _dd('Bitcoin Core', 220000, '22.0.0') - exec_fn = 'bitcoind' - cli_fn = 'bitcoin-cli' - testnet_dir = 'testnet3' - cfg_file_hdr = '# Bitcoin Core config file\n' - tracking_wallet_name = 'mmgen-tracking-wallet' - rpc_ports = _nw(8332, 18332, 18443) - cfg_file = 'bitcoin.conf' - datadirs = { - 'linux': [g.home_dir,'.bitcoin'], - 'win': [os.getenv('APPDATA'),'Bitcoin'] - } - nonstd_datadir = False - - def init_datadir(self): - if self.network == 'regtest' and not self.test_suite: - return os.path.join( g.data_dir_root, 'regtest', g.coin.lower() ) - else: - return super().init_datadir() - - @property - def network_datadir(self): - "location of the network's blockchain data and authentication cookie" - return os.path.join ( - self.datadir, { - 'mainnet': '', - 'testnet': self.testnet_dir, - 'regtest': 'regtest', - }[self.network] ) - - def init_subclass(self): - - if self.network == 'regtest': - """ - fall back on hard-coded credentials - """ - from .regtest import MMGenRegtest - self.rpc_user = MMGenRegtest.rpc_user - self.rpc_password = MMGenRegtest.rpc_password - - self.shared_args = list_gen( - [f'--datadir={self.datadir}', self.nonstd_datadir or self.non_dfl_datadir], - [f'--rpcport={self.rpc_port}'], - [f'--rpcuser={self.rpc_user}', self.network == 'regtest'], - [f'--rpcpassword={self.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 self.opt.no_daemonize], - ['--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' or self.network == 'regtest'], - ) - - self.lockfile = os.path.join(self.network_datadir,'.cookie') - - @property - def state(self): - cp = self.cli('getblockcount',silent=True) - err = cp.stderr.decode() - if ("error: couldn't connect" in err - or "error: Could not connect" in err - or "does not exist" in err ): - # regtest has no cookie file, so test will always fail - ret = 'busy' if (self.lockfile and os.path.exists(self.lockfile)) else 'stopped' - elif cp.returncode == 0: - ret = 'ready' - else: - ret = 'busy' - if self.debug: - print(f'State: {ret!r}') - return ret - - @property - def stop_cmd(self): - return self.cli_cmd('stop') - - def set_label_args(self,rpc,coinaddr,lbl): - if 'label_api' in rpc.caps: - return ('setlabel',coinaddr,lbl) - else: - # NOTE: this works because importaddress() removes the old account before - # associating the new account with the address. - # RPC args: addr,label,rescan[=true],p2sh[=none] - return ('importaddress',coinaddr,lbl,False) - - def estimatefee_args(self,rpc): - return (opt.tx_confs,) - - def sigfail_errmsg(self,e): - return e.args[0] - -class bitcoin_cash_node_daemon(bitcoin_core_daemon): - daemon_data = _dd('Bitcoin Cash Node', 24000000, '24.0.0') - exec_fn = 'bitcoind-bchn' - cli_fn = 'bitcoin-cli-bchn' - rpc_ports = _nw(8432, 18432, 18543) # use non-standard ports (core+100) - datadirs = { - 'linux': [g.home_dir,'.bitcoin-bchn'], - 'win': [os.getenv('APPDATA'),'Bitcoin_ABC'] - } - cfg_file_hdr = '# Bitcoin Cash Node config file\n' - nonstd_datadir = True - - def set_label_args(self,rpc,coinaddr,lbl): - # bitcoin-{abc,bchn} 'setlabel' RPC is broken, so use old 'importaddress' method to set label - # Broken behavior: new label is set OK, but old label gets attached to another address - return ('importaddress',coinaddr,lbl,False) - - def estimatefee_args(self,rpc): - return () if rpc.daemon_version >= 190100 else (opt.tx_confs,) - - def sigfail_errmsg(self,e): - return ( - 'This is not the BCH chain.\nRe-run the script without the --coin=bch option.' - if 'Invalid sighash param' in e.args[0] else - e.args[0] ) - -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 = _nw(9332, 19332, 19443) - cfg_file = 'litecoin.conf' - datadirs = { - 'linux': [g.home_dir,'.litecoin'], - 'win': [os.getenv('APPDATA'),'Litecoin'] - } - cfg_file_hdr = '# Litecoin Core config file\n' - -class monero_daemon(CoinDaemon): - daemon_data = _dd('Monero', 'N/A', 'N/A') - networks = ('mainnet','testnet') - exec_fn = 'monerod' - testnet_dir = 'stagenet' - new_console_mswin = True - host = 'localhost' # FIXME - rpc_ports = _nw(18081, 38081, None) # testnet is stagenet - cfg_file = 'bitmonero.conf' - datadirs = { - 'linux': [g.home_dir,'.bitmonero'], - 'win': ['/','c','ProgramData','bitmonero'] - } - - def init_datadir(self): - self.logdir = super().init_datadir() - return os.path.join( - self.logdir, - self.testnet_dir if self.network == 'testnet' else '' ) - - def get_p2p_port(self): - return self.rpc_port - 1 - - 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], - [f'--rpc-bind-port={self.rpc_port}'], - ['--stagenet', self.network == 'testnet'], - ) - - self.coind_args = list_gen( - ['--hide-my-port'], - ['--no-igd'], - [f'--data-dir={self.datadir}', self.non_dfl_datadir], - [f'--pidfile={self.pidfile}', self.platform == 'linux'], - ['--detach', not (self.opt.no_daemonize or self.platform=='win')], - ['--offline', not self.opt.online], - ) - - @property - def stop_cmd(self): - return ['kill','-Wf',self.pid] if self.platform == 'win' else [self.exec_fn] + self.shared_args + ['exit'] - -class ethereum_daemon(CoinDaemon): - chain_subdirs = _nw('ethereum','goerli','DevelopmentChain') - base_rpc_port = 8545 # same for all networks! - base_p2p_port = 30303 # same for all networks! - daemon_port_offset = 100 - network_port_offsets = _nw(0,10,20) - - def __init__(self,*args,**kwargs): - - if not hasattr(self,'all_daemons'): - ethereum_daemon.all_daemons = get_subclasses(ethereum_daemon,names=True) - - self.port_offset = ( - self.all_daemons.index(self.id+'_daemon') * self.daemon_port_offset - + getattr(self.network_port_offsets,self.network) ) - - return super().__init__(*args,**kwargs) - - def get_rpc_port(self): - return self.base_rpc_port + self.port_offset - - def get_p2p_port(self): - return self.base_p2p_port + self.port_offset - - def init_datadir(self): - self.logdir = super().init_datadir() - return os.path.join( - self.logdir, - self.id, - getattr(self.chain_subdirs,self.network) ) - -class openethereum_daemon(ethereum_daemon): - daemon_data = _dd('OpenEthereum', 3003000, '3.3.0') - version_pat = r'OpenEthereum//v(\d+)\.(\d+)\.(\d+)' - exec_fn = 'openethereum' - cfg_file = 'parity.conf' - datadirs = { - 'linux': [g.home_dir,'.local','share','io.parity.ethereum'], - 'win': [os.getenv('LOCALAPPDATA'),'Parity','Ethereum'] - } - - def init_subclass(self): - - ld = self.platform == 'linux' and not self.opt.no_daemonize - - self.coind_args = list_gen( - ['--no-ws'], - ['--no-ipc'], - ['--no-secretstore'], - [f'--jsonrpc-port={self.rpc_port}'], - [f'--port={self.p2p_port}', self.p2p_port], - [f'--base-path={self.datadir}', self.non_dfl_datadir], - [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'], - [f'--log-file={self.logfile}', self.non_dfl_datadir], - ['daemon', ld], - [self.pidfile, ld], - ) - -class parity_daemon(openethereum_daemon): - daemon_data = _dd('Parity', 2007002, '2.7.2') - version_pat = r'Parity-Ethereum//v(\d+)\.(\d+)\.(\d+)' - exec_fn = 'parity' - -class geth_daemon(ethereum_daemon): - daemon_data = _dd('Geth', 1010014, '1.10.14') - version_pat = r'Geth/v(\d+)\.(\d+)\.(\d+)' - exec_fn = 'geth' - use_pidfile = False - use_threads = True - datadirs = { - 'linux': [g.home_dir,'.ethereum','geth'], - 'win': [os.getenv('LOCALAPPDATA'),'Geth'] # FIXME - } - - def init_subclass(self): - self.coind_args = list_gen( - ['--verbosity=0'], - ['--http'], - ['--http.api=eth,web3,txpool'], - [f'--http.port={self.rpc_port}'], - [f'--port={self.p2p_port}', self.p2p_port], # geth binds p2p port even with --maxpeers=0 - ['--maxpeers=0', not self.opt.online], - [f'--datadir={self.datadir}', self.non_dfl_datadir], - ['--goerli', self.network=='testnet'], - ['--dev', self.network=='regtest'], - ) - -# https://github.com/ledgerwatch/erigon -class erigon_daemon(geth_daemon): - daemon_data = _dd('Erigon', 2021009005, '2021.09.5') - version_pat = r'erigon/(\d+)\.(\d+)\.(\d+)' - exec_fn = 'erigon' - private_ports = _nw(9090,9091,9092) # testnet and regtest are non-standard - datadirs = { - 'linux': [g.home_dir,'.local','share','erigon'], - 'win': [os.getenv('LOCALAPPDATA'),'Erigon'] # FIXME - } - - def init_subclass(self): - self.coind_args = list_gen( - ['--verbosity=0'], - [f'--port={self.p2p_port}', self.p2p_port], - ['--maxpeers=0', not self.opt.online], - [f'--private.api.addr=127.0.0.1:{self.private_port}'], - [f'--datadir={self.datadir}', self.non_dfl_datadir and not self.network=='regtest'], - ['--chain=goerli', self.network=='testnet'], - ['--chain=dev', self.network=='regtest'], - ['--mine', self.network=='regtest'], - ) - self.rpc_d = erigon_rpcdaemon( - proto = self.proto, - rpc_port = self.rpc_port, - private_port = self.private_port, - test_suite = self.test_suite, - datadir = self.datadir ) - - def start(self,quiet=False,silent=False): - super().start(quiet=quiet,silent=silent) - self.rpc_d.debug = self.debug - return self.rpc_d.start(quiet=quiet,silent=silent) - - def stop(self,quiet=False,silent=False): - self.rpc_d.debug = self.debug - self.rpc_d.stop(quiet=quiet,silent=silent) - return super().stop(quiet=quiet,silent=silent) - - @property - def start_cmds(self): - return [self.start_cmd,self.rpc_d.start_cmd] - -class erigon_rpcdaemon(RPCDaemon): - - master_daemon = 'erigon_daemon' - rpc_type = 'Erigon' - exec_fn = 'rpcdaemon' - use_pidfile = False - use_threads = True - - def __init__(self,proto,rpc_port,private_port,test_suite,datadir): - - self.proto = proto - self.test_suite = test_suite - - super().__init__() - - self.network = proto.network - self.rpc_port = rpc_port - self.datadir = datadir - - self.daemon_args = list_gen( - ['--verbosity=0'], - [f'--private.api.addr=127.0.0.1:{private_port}'], - [f'--http.port={self.rpc_port}'], - [f'--datadir={self.datadir}', self.network != 'regtest'], - ['--http.api=eth,web3,txpool'], - ) diff --git a/mmgen/data/release_date b/mmgen/data/release_date index d8e33285..a23b2fd9 100644 --- a/mmgen/data/release_date +++ b/mmgen/data/release_date @@ -1 +1 @@ -January 2022 +February 2022 diff --git a/mmgen/data/version b/mmgen/data/version index 211d6184..49e789a4 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.1.dev013 +13.1.dev014 diff --git a/mmgen/proto/xmr.py b/mmgen/proto/xmr.py index d5dd30ee..115e2538 100755 --- a/mmgen/proto/xmr.py +++ b/mmgen/proto/xmr.py @@ -19,6 +19,7 @@ class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Base): network_names = _nw('mainnet','stagenet',None) base_coin = 'XMR' + base_proto = 'Monero' addr_ver_bytes = { '12': 'monero', '2a': 'monero_sub' } addr_len = 68 wif_ver_num = {} diff --git a/test/unit_tests_d/ut_daemon.py b/test/unit_tests_d/ut_daemon.py index e277b990..f3063452 100755 --- a/test/unit_tests_d/ut_daemon.py +++ b/test/unit_tests_d/ut_daemon.py @@ -62,7 +62,7 @@ def test_cmds(op): for daemon_id in data.daemon_ids: if daemon_id in arm_skip_daemons: continue - for network in getattr( daemon_mod, daemon_id+'_daemon' ).networks: + for network in data.networks: if opt.no_altcoin_deps and coin != 'BTC': continue d = CoinDaemon(