daemon.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. #!/usr/bin/env python3
  2. #
  3. # mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet
  4. # Copyright (C)2013-2022 The MMGen Project <mmgen@tuta.io>
  5. # Licensed under the GNU General Public License, Version 3:
  6. # https://www.gnu.org/licenses
  7. # Public project repositories:
  8. # https://github.com/mmgen/mmgen
  9. # https://gitlab.com/mmgen/mmgen
  10. """
  11. base_proto.ethereum.daemon: Ethereum base protocol daemon classes
  12. """
  13. import os
  14. from ...globalvars import g
  15. from ...util import list_gen,get_subclasses
  16. from ...daemon import CoinDaemon,RPCDaemon,_nw,_dd
  17. class ethereum_daemon(CoinDaemon):
  18. chain_subdirs = _nw('ethereum','goerli','DevelopmentChain')
  19. base_rpc_port = 8545 # same for all networks!
  20. base_authrpc_port = 8551 # same for all networks!
  21. base_p2p_port = 30303 # same for all networks!
  22. daemon_port_offset = 100
  23. network_port_offsets = _nw(0,10,20)
  24. def __init__(self,*args,**kwargs):
  25. if not hasattr(self,'all_daemons'):
  26. ethereum_daemon.all_daemons = get_subclasses(ethereum_daemon,names=True)
  27. self.port_offset = (
  28. self.all_daemons.index(self.id+'_daemon') * self.daemon_port_offset
  29. + getattr(self.network_port_offsets,self.network) )
  30. return super().__init__(*args,**kwargs)
  31. def get_rpc_port(self):
  32. return self.base_rpc_port + self.port_offset
  33. @property
  34. def authrpc_port(self):
  35. return self.base_authrpc_port + self.port_offset
  36. def get_p2p_port(self):
  37. return self.base_p2p_port + self.port_offset
  38. def init_datadir(self):
  39. self.logdir = super().init_datadir()
  40. return os.path.join(
  41. self.logdir,
  42. self.id,
  43. getattr(self.chain_subdirs,self.network) )
  44. class openethereum_daemon(ethereum_daemon):
  45. daemon_data = _dd('OpenEthereum', 3003005, '3.3.5')
  46. version_pat = r'OpenEthereum//v(\d+)\.(\d+)\.(\d+)'
  47. exec_fn = 'openethereum'
  48. cfg_file = 'parity.conf'
  49. datadirs = {
  50. 'linux': [g.home_dir,'.local','share','io.parity.ethereum'],
  51. 'win': [os.getenv('LOCALAPPDATA'),'Parity','Ethereum']
  52. }
  53. def init_subclass(self):
  54. ld = self.platform == 'linux' and not self.opt.no_daemonize
  55. self.coind_args = list_gen(
  56. ['--no-ws'],
  57. ['--no-ipc'],
  58. ['--no-secretstore'],
  59. [f'--jsonrpc-port={self.rpc_port}'],
  60. [f'--port={self.p2p_port}', self.p2p_port],
  61. [f'--base-path={self.datadir}', self.non_dfl_datadir],
  62. [f'--chain={self.proto.chain_name}', self.network!='regtest'],
  63. [f'--config=dev', self.network=='regtest'], # no presets for mainnet or testnet
  64. ['--mode=offline', self.test_suite or self.network=='regtest'],
  65. [f'--log-file={self.logfile}', self.non_dfl_datadir],
  66. ['daemon', ld],
  67. [self.pidfile, ld],
  68. )
  69. class parity_daemon(openethereum_daemon):
  70. daemon_data = _dd('Parity', 2007002, '2.7.2')
  71. version_pat = r'Parity-Ethereum//v(\d+)\.(\d+)\.(\d+)'
  72. exec_fn = 'parity'
  73. class geth_daemon(ethereum_daemon):
  74. daemon_data = _dd('Geth', 1010021, '1.10.21')
  75. version_pat = r'Geth/v(\d+)\.(\d+)\.(\d+)'
  76. exec_fn = 'geth'
  77. use_pidfile = False
  78. use_threads = True
  79. datadirs = {
  80. 'linux': [g.home_dir,'.ethereum','geth'],
  81. 'win': [os.getenv('LOCALAPPDATA'),'Geth'] # FIXME
  82. }
  83. avail_opts = ('no_daemonize','online')
  84. version_info_arg = 'version'
  85. def init_subclass(self):
  86. def have_authrpc():
  87. from subprocess import run,PIPE
  88. try:
  89. return b'authrpc' in run(['geth','help'],check=True,stdout=PIPE).stdout
  90. except:
  91. return False
  92. self.coind_args = list_gen(
  93. ['--verbosity=0'],
  94. ['--ipcdisable'], # IPC-RPC: if path to socket is longer than 108 chars, geth fails to start
  95. ['--http'],
  96. ['--http.api=eth,web3,txpool'],
  97. [f'--http.port={self.rpc_port}'],
  98. [f'--authrpc.port={self.authrpc_port}', have_authrpc()],
  99. [f'--port={self.p2p_port}', self.p2p_port], # geth binds p2p port even with --maxpeers=0
  100. ['--maxpeers=0', not self.opt.online],
  101. [f'--datadir={self.datadir}', self.non_dfl_datadir],
  102. ['--goerli', self.network=='testnet'],
  103. ['--dev', self.network=='regtest'],
  104. )
  105. # https://github.com/ledgerwatch/erigon
  106. class erigon_daemon(geth_daemon):
  107. daemon_data = _dd('Erigon', 2022099099, '2022.99.99')
  108. version_pat = r'erigon/(\d+)\.(\d+)\.(\d+)'
  109. exec_fn = 'erigon'
  110. private_ports = _nw(9090,9091,9092) # testnet and regtest are non-standard
  111. torrent_ports = _nw(42069,42070,None) # testnet is non-standard
  112. datadirs = {
  113. 'linux': [g.home_dir,'.local','share','erigon'],
  114. 'win': [os.getenv('LOCALAPPDATA'),'Erigon'] # FIXME
  115. }
  116. version_info_arg = '--version'
  117. def init_subclass(self):
  118. if self.network == 'regtest':
  119. self.force_kill = True
  120. self.coind_args = list_gen(
  121. ['--verbosity=0'],
  122. [f'--port={self.p2p_port}', self.p2p_port],
  123. ['--maxpeers=0', not self.opt.online],
  124. [f'--private.api.addr=127.0.0.1:{self.private_port}'],
  125. [f'--datadir={self.datadir}', self.non_dfl_datadir],
  126. ['--chain=goerli', self.network=='testnet'],
  127. [f'--torrent.port={self.torrent_ports.testnet}', self.network=='testnet'],
  128. ['--chain=dev', self.network=='regtest'],
  129. ['--mine', self.network=='regtest'],
  130. )
  131. self.rpc_d = erigon_rpcdaemon(
  132. proto = self.proto,
  133. rpc_port = self.rpc_port,
  134. private_port = self.private_port,
  135. test_suite = self.test_suite,
  136. datadir = self.datadir )
  137. def start(self,quiet=False,silent=False):
  138. super().start(quiet=quiet,silent=silent)
  139. self.rpc_d.debug = self.debug
  140. return self.rpc_d.start(quiet=quiet,silent=silent)
  141. def stop(self,quiet=False,silent=False):
  142. self.rpc_d.debug = self.debug
  143. self.rpc_d.stop(quiet=quiet,silent=silent)
  144. return super().stop(quiet=quiet,silent=silent)
  145. @property
  146. def start_cmds(self):
  147. return [self.start_cmd,self.rpc_d.start_cmd]
  148. class erigon_rpcdaemon(RPCDaemon):
  149. master_daemon = 'erigon_daemon'
  150. rpc_type = 'Erigon'
  151. exec_fn = 'rpcdaemon'
  152. use_pidfile = False
  153. use_threads = True
  154. def __init__(self,proto,rpc_port,private_port,test_suite,datadir):
  155. self.proto = proto
  156. self.test_suite = test_suite
  157. super().__init__()
  158. self.network = proto.network
  159. self.rpc_port = rpc_port
  160. self.datadir = datadir
  161. self.daemon_args = list_gen(
  162. ['--verbosity=0'],
  163. [f'--private.api.addr=127.0.0.1:{private_port}'],
  164. [f'--http.port={self.rpc_port}'],
  165. [f'--datadir={self.datadir}'],
  166. ['--http.api=eth,erigon,web3,net,debug,trace,txpool,parity'],
  167. )