daemon.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2025 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-wallet
  9. # https://gitlab.com/mmgen/mmgen-wallet
  10. """
  11. proto.eth.daemon: Ethereum base protocol daemon classes
  12. """
  13. import os
  14. from ...cfg import gc
  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, test_suite=False, **kwargs):
  25. if not hasattr(self, 'all_daemons'):
  26. ethereum_daemon.all_daemons = get_subclasses(ethereum_daemon, names=True)
  27. daemon_idx_offset = (
  28. self.all_daemons.index(self.id+'_daemon') * self.daemon_port_offset
  29. if test_suite else 0)
  30. self.port_offset = daemon_idx_offset + getattr(self.network_port_offsets, self.network)
  31. super().__init__(*args, test_suite=test_suite, **kwargs)
  32. def get_rpc_port(self):
  33. return self.base_rpc_port + self.port_offset
  34. @property
  35. def authrpc_port(self):
  36. return self.base_authrpc_port + self.port_offset
  37. def get_p2p_port(self):
  38. return self.base_p2p_port + self.port_offset
  39. def init_datadir(self):
  40. self.logdir = super().init_datadir()
  41. return os.path.join(
  42. self.logdir,
  43. self.id,
  44. getattr(self.chain_subdirs, self.network))
  45. class openethereum_daemon(ethereum_daemon):
  46. daemon_data = _dd('OpenEthereum', 3003005, '3.3.5')
  47. version_pat = r'OpenEthereum//v(\d+)\.(\d+)\.(\d+)'
  48. exec_fn = 'openethereum'
  49. cfg_file = 'parity.conf'
  50. datadirs = {
  51. 'linux': [gc.home_dir, '.local', 'share', 'io.parity.ethereum'],
  52. 'darwin': [gc.home_dir, 'Library', 'Application Support', 'io.parity.ethereum'],
  53. 'win32': [os.getenv('LOCALAPPDATA'), 'Parity', 'Ethereum']
  54. }
  55. def init_subclass(self):
  56. self.use_pidfile = self.platform == 'linux' and not self.opt.no_daemonize
  57. self.use_threads = self.platform in ('win32', 'darwin')
  58. self.coind_args = list_gen(
  59. ['--no-ws'],
  60. ['--no-ipc'],
  61. ['--no-secretstore'],
  62. [f'--jsonrpc-port={self.rpc_port}'],
  63. [f'--port={self.p2p_port}', self.p2p_port],
  64. [f'--base-path={self.datadir}', self.non_dfl_datadir],
  65. [f'--chain={self.proto.chain_name}', self.network!='regtest'],
  66. ['--config=dev', self.network=='regtest'], # no presets for mainnet or testnet
  67. ['--mode=offline', self.test_suite or self.network=='regtest'],
  68. [f'--log-file={self.logfile}', self.non_dfl_datadir],
  69. ['daemon', self.use_pidfile],
  70. [self.pidfile, self.use_pidfile],
  71. )
  72. class parity_daemon(openethereum_daemon):
  73. daemon_data = _dd('Parity', 2007002, '2.7.2')
  74. version_pat = r'Parity-Ethereum//v(\d+)\.(\d+)\.(\d+)'
  75. exec_fn = 'parity'
  76. class geth_daemon(ethereum_daemon):
  77. # v1.14.0 -> ? (v1.15.11 and later OK)
  78. # mempool deadlock in dev mode: "transaction indexing is in progress"
  79. # https://github.com/ethereum/go-ethereum/issues/29475
  80. # offending commit (via git bisect): 0a2f33946b95989e8ce36e72a88138adceab6a23
  81. daemon_data = _dd('Geth', 1016003, '1.16.3')
  82. version_pat = r'Geth/v(\d+)\.(\d+)\.(\d+)'
  83. exec_fn = 'geth'
  84. use_pidfile = False
  85. use_threads = True
  86. avail_opts = ('no_daemonize', 'online')
  87. version_info_arg = 'version'
  88. datadirs = {
  89. 'linux': [gc.home_dir, '.ethereum', 'geth'],
  90. 'darwin': [gc.home_dir, 'Library', 'Ethereum', 'geth'],
  91. 'win32': [os.getenv('LOCALAPPDATA'), 'Geth'] # FIXME
  92. }
  93. def init_subclass(self):
  94. self.coind_args = list_gen(
  95. ['node', self.id == 'reth'],
  96. ['--quiet', self.id == 'reth'],
  97. ['--disable-dns-discovery', self.id == 'reth' and self.test_suite],
  98. ['--verbosity=0', self.id == 'geth'],
  99. ['--ipcdisable'], # IPC-RPC: if path to socket is longer than 108 chars, geth fails to start
  100. ['--http'],
  101. ['--http.api=eth,web3,txpool'],
  102. [f'--http.port={self.rpc_port}'],
  103. [f'--authrpc.port={self.authrpc_port}'],
  104. [f'--port={self.p2p_port}', self.p2p_port], # geth binds p2p port even with --maxpeers=0
  105. [f'--discovery.port={self.p2p_port}', self.id == 'reth' and self.p2p_port],
  106. ['--maxpeers=0', self.id == 'geth' and not self.opt.online],
  107. [f'--datadir={self.datadir}', self.non_dfl_datadir],
  108. ['--holesky', self.network=='testnet' and self.id == 'geth'],
  109. ['--chain=holesky', self.network=='testnet' and self.id == 'reth'],
  110. ['--dev', self.network=='regtest'],
  111. )
  112. class reth_daemon(geth_daemon):
  113. daemon_data = _dd('Reth', 1007000, '1.7.0')
  114. version_pat = r'reth/v(\d+)\.(\d+)\.(\d+)'
  115. exec_fn = 'reth'
  116. version_info_arg = '--version'
  117. datadirs = {
  118. 'linux': [gc.home_dir, '.local', 'share', 'reth'],
  119. }
  120. # https://github.com/ledgerwatch/erigon
  121. class erigon_daemon(geth_daemon):
  122. daemon_data = _dd('Erigon', 2022099099, '2022.99.99')
  123. version_pat = r'erigon/(\d+)\.(\d+)\.(\d+)'
  124. exec_fn = 'erigon'
  125. private_ports = _nw(9090, 9091, 9092) # testnet and regtest are non-standard
  126. torrent_ports = _nw(42069, 42070, None) # testnet is non-standard
  127. version_info_arg = '--version'
  128. datadirs = {
  129. 'linux': [gc.home_dir, '.local', 'share', 'erigon'],
  130. 'win32': [os.getenv('LOCALAPPDATA'), 'Erigon'] # FIXME
  131. }
  132. def init_subclass(self):
  133. if self.network == 'regtest':
  134. self.force_kill = True
  135. self.coind_args = list_gen(
  136. ['--verbosity=0'],
  137. [f'--port={self.p2p_port}', self.p2p_port],
  138. ['--maxpeers=0', not self.opt.online],
  139. [f'--private.api.addr=127.0.0.1:{self.private_port}'],
  140. [f'--datadir={self.datadir}', self.non_dfl_datadir],
  141. ['--chain=goerli', self.network=='testnet'],
  142. [f'--torrent.port={self.torrent_ports.testnet}', self.network=='testnet'],
  143. ['--chain=dev', self.network=='regtest'],
  144. ['--mine', self.network=='regtest'],
  145. )
  146. self.rpc_d = erigon_rpcdaemon(
  147. cfg = self.cfg,
  148. proto = self.proto,
  149. rpc_port = self.rpc_port,
  150. private_port = self.private_port,
  151. test_suite = self.test_suite,
  152. datadir = self.datadir)
  153. def start(self, *, quiet=False, silent=False):
  154. super().start(quiet=quiet, silent=silent)
  155. self.rpc_d.debug = self.debug
  156. return self.rpc_d.start(quiet=quiet, silent=silent)
  157. def stop(self, *, quiet=False, silent=False):
  158. self.rpc_d.debug = self.debug
  159. self.rpc_d.stop(quiet=quiet, silent=silent)
  160. return super().stop(quiet=quiet, silent=silent)
  161. @property
  162. def start_cmds(self):
  163. return [self.start_cmd, self.rpc_d.start_cmd]
  164. class erigon_rpcdaemon(RPCDaemon):
  165. master_daemon = 'erigon_daemon'
  166. rpc_desc = 'Erigon'
  167. exec_fn = 'rpcdaemon'
  168. use_pidfile = False
  169. use_threads = True
  170. def __init__(self, cfg, proto, *, rpc_port, private_port, test_suite, datadir):
  171. self.proto = proto
  172. self.test_suite = test_suite
  173. super().__init__(cfg)
  174. self.network = proto.network
  175. self.rpc_port = rpc_port
  176. self.datadir = datadir
  177. self.daemon_args = list_gen(
  178. ['--verbosity=0'],
  179. [f'--private.api.addr=127.0.0.1:{private_port}'],
  180. [f'--http.port={self.rpc_port}'],
  181. [f'--datadir={self.datadir}'],
  182. ['--http.api=eth,erigon,web3,net,debug,trace,txpool,parity'],
  183. )