CoinDaemon: add subclasses, support multiple chains for ETH

This commit is contained in:
The MMGen Project 2021-07-29 14:30:23 +00:00
commit 8bb6c40ec6
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
5 changed files with 126 additions and 149 deletions

View file

@ -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()

View file

@ -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)

View file

@ -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]

View file

@ -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'

View file

@ -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()