daemon.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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. def init_subclass(self):
  55. self.use_pidfile = self.platform == 'linux' and not self.opt.no_daemonize
  56. self.use_threads = self.platform in ('win32', 'darwin')
  57. self.coind_args = list_gen(
  58. ['--no-ws'],
  59. ['--no-ipc'],
  60. ['--no-secretstore'],
  61. [f'--jsonrpc-port={self.rpc_port}'],
  62. [f'--port={self.p2p_port}', self.p2p_port],
  63. [f'--base-path={self.datadir}', self.non_dfl_datadir],
  64. [f'--chain={self.proto.chain_name}', self.network!='regtest'],
  65. ['--config=dev', self.network=='regtest'], # no presets for mainnet or testnet
  66. ['--mode=offline', self.test_suite or self.network=='regtest'],
  67. [f'--log-file={self.logfile}', self.non_dfl_datadir],
  68. ['daemon', self.use_pidfile],
  69. [self.pidfile, self.use_pidfile],
  70. )
  71. class parity_daemon(openethereum_daemon):
  72. daemon_data = _dd('Parity', 2007002, '2.7.2')
  73. version_pat = r'Parity-Ethereum//v(\d+)\.(\d+)\.(\d+)'
  74. exec_fn = 'parity'
  75. class geth_daemon(ethereum_daemon):
  76. # v1.14.0 -> ? (v1.15.11 and later OK)
  77. # mempool deadlock in dev mode: "transaction indexing is in progress"
  78. # https://github.com/ethereum/go-ethereum/issues/29475
  79. # offending commit (via git bisect): 0a2f33946b95989e8ce36e72a88138adceab6a23
  80. daemon_data = _dd('Geth', 1016007, '1.16.7')
  81. version_pat = r'Geth/v(\d+)\.(\d+)\.(\d+)'
  82. exec_fn = 'geth'
  83. use_pidfile = False
  84. use_threads = True
  85. avail_opts = ('no_daemonize', 'online')
  86. version_info_arg = 'version'
  87. datadirs = {
  88. 'linux': [gc.home_dir, '.ethereum', 'geth'],
  89. 'darwin': [gc.home_dir, 'Library', 'Ethereum', 'geth'],
  90. 'win32': [os.getenv('LOCALAPPDATA'), 'Geth']} # FIXME
  91. def init_subclass(self):
  92. self.coind_args = list_gen(
  93. ['node', self.id == 'reth'],
  94. ['--quiet', self.id == 'reth'],
  95. ['--disable-dns-discovery', self.id == 'reth' and self.test_suite],
  96. ['--verbosity=0', self.id == 'geth'],
  97. ['--ipcdisable'], # IPC-RPC: if path to socket is longer than 108 chars, geth fails to start
  98. ['--http'],
  99. ['--http.api=eth,web3,txpool'],
  100. [f'--http.port={self.rpc_port}'],
  101. [f'--authrpc.port={self.authrpc_port}'],
  102. [f'--port={self.p2p_port}', self.p2p_port], # geth binds p2p port even with --maxpeers=0
  103. [f'--discovery.port={self.p2p_port}', self.id == 'reth' and self.p2p_port],
  104. ['--maxpeers=0', self.id == 'geth' and not self.opt.online],
  105. [f'--datadir={self.datadir}', self.non_dfl_datadir],
  106. ['--holesky', self.network=='testnet' and self.id == 'geth'],
  107. ['--chain=holesky', self.network=='testnet' and self.id == 'reth'],
  108. ['--dev', self.network=='regtest'],
  109. )
  110. class reth_daemon(geth_daemon):
  111. daemon_data = _dd('Reth', 1008004, '1.8.4')
  112. version_pat = r'reth/v(\d+)\.(\d+)\.(\d+)'
  113. exec_fn = 'reth'
  114. version_info_arg = '--version'
  115. datadirs = {
  116. 'linux': [gc.home_dir, '.local', 'share', 'reth']}
  117. # https://github.com/ledgerwatch/erigon
  118. class erigon_daemon(geth_daemon):
  119. daemon_data = _dd('Erigon', 2022099099, '2022.99.99')
  120. version_pat = r'erigon/(\d+)\.(\d+)\.(\d+)'
  121. exec_fn = 'erigon'
  122. private_ports = _nw(9090, 9091, 9092) # testnet and regtest are non-standard
  123. torrent_ports = _nw(42069, 42070, None) # testnet is non-standard
  124. version_info_arg = '--version'
  125. datadirs = {
  126. 'linux': [gc.home_dir, '.local', 'share', 'erigon'],
  127. 'win32': [os.getenv('LOCALAPPDATA'), 'Erigon']} # FIXME
  128. def init_subclass(self):
  129. if self.network == 'regtest':
  130. self.force_kill = True
  131. self.coind_args = list_gen(
  132. ['--verbosity=0'],
  133. [f'--port={self.p2p_port}', self.p2p_port],
  134. ['--maxpeers=0', not self.opt.online],
  135. [f'--private.api.addr=127.0.0.1:{self.private_port}'],
  136. [f'--datadir={self.datadir}', self.non_dfl_datadir],
  137. ['--chain=goerli', self.network=='testnet'],
  138. [f'--torrent.port={self.torrent_ports.testnet}', self.network=='testnet'],
  139. ['--chain=dev', self.network=='regtest'],
  140. ['--mine', self.network=='regtest'],
  141. )
  142. self.rpc_d = erigon_rpcdaemon(
  143. cfg = self.cfg,
  144. proto = self.proto,
  145. rpc_port = self.rpc_port,
  146. private_port = self.private_port,
  147. test_suite = self.test_suite,
  148. datadir = self.datadir)
  149. def start(self, *, quiet=False, silent=False):
  150. super().start(quiet=quiet, silent=silent)
  151. self.rpc_d.debug = self.debug
  152. return self.rpc_d.start(quiet=quiet, silent=silent)
  153. def stop(self, *, quiet=False, silent=False):
  154. self.rpc_d.debug = self.debug
  155. self.rpc_d.stop(quiet=quiet, silent=silent)
  156. return super().stop(quiet=quiet, silent=silent)
  157. @property
  158. def start_cmds(self):
  159. return [self.start_cmd, self.rpc_d.start_cmd]
  160. class erigon_rpcdaemon(RPCDaemon):
  161. master_daemon = 'erigon_daemon'
  162. rpc_desc = 'Erigon'
  163. exec_fn = 'rpcdaemon'
  164. use_pidfile = False
  165. use_threads = True
  166. def __init__(self, cfg, proto, *, rpc_port, private_port, test_suite, datadir):
  167. self.proto = proto
  168. self.test_suite = test_suite
  169. super().__init__(cfg)
  170. self.network = proto.network
  171. self.rpc_port = rpc_port
  172. self.datadir = datadir
  173. self.daemon_args = list_gen(
  174. ['--verbosity=0'],
  175. [f'--private.api.addr=127.0.0.1:{private_port}'],
  176. [f'--http.port={self.rpc_port}'],
  177. [f'--datadir={self.datadir}'],
  178. ['--http.api=eth,erigon,web3,net,debug,trace,txpool,parity'],
  179. )