daemon.py,rpc.py: cleanups and fixes

This commit is contained in:
The MMGen Project 2021-07-30 10:44:02 +00:00
commit 305f986698
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 53 additions and 112 deletions

View file

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

View file

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

View file

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

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,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: