Browse Source

CoinDaemon: add subclasses, support multiple chains for ETH

The MMGen Project 3 years ago
parent
commit
8bb6c40ec6
5 changed files with 126 additions and 149 deletions
  1. 117 142
      mmgen/daemon.py
  2. 1 1
      mmgen/regtest.py
  3. 1 1
      test/start-coin-daemons.py
  4. 4 2
      test/test_py_d/ts_ethdev.py
  5. 3 3
      test/unit_tests_d/ut_rpc.py

+ 117 - 142
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)
-
-		me = Daemon.__new__(globals()[cls.daemon_ids[daemon_id].cls_pfx+'Daemon'])
-		me.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()
+
+		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()

+ 1 - 1
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)
 

+ 1 - 1
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]
 

+ 4 - 2
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'

+ 3 - 3
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()