ethbump.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  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. test.cmdtest_d.ethbump: Ethereum transaction bumping tests for the cmdtest.py test suite
  12. """
  13. import sys, time, asyncio, json
  14. from mmgen.cfg import Config
  15. from mmgen.protocol import init_proto
  16. from mmgen.util import ymsg, suf
  17. from ..include.common import imsg, omsg_r
  18. from .include.common import cleanup_env, dfl_words_file, dfl_sid
  19. from .include.runner import CmdTestRunner
  20. from .httpd.thornode import ThornodeServer
  21. from .ethdev import CmdTestEthdev, CmdTestEthdevMethods
  22. from .regtest import CmdTestRegtest
  23. from .swap import CmdTestSwapMethods
  24. from .ethswap import CmdTestEthSwapMethods
  25. thornode_server = ThornodeServer()
  26. burn_addr = 'beefcafe22' * 4
  27. method_template = """
  28. def {name}(self):
  29. self.spawn(log_only=True)
  30. return ethbump_ltc.run_test("{ltc_name}", sub=True)
  31. """
  32. class CmdTestEthBumpMethods:
  33. @property
  34. def devnet_block_period(self):
  35. return self.cfg.devnet_block_period or self.dfl_devnet_block_period[self.daemon.id]
  36. def _txcreate(self, args, acct):
  37. self.get_file_with_ext('rawtx', delete_all=True)
  38. return self.txcreate(args, acct=acct, interactive_fee='0.9G', fee_info_data=('0.0000189', '0.9'))
  39. def _txsign(self, has_label=True):
  40. self.get_file_with_ext('sigtx', delete_all=True)
  41. return self.txsign(has_label=has_label)
  42. def _txsend(self, has_label=True):
  43. return self.txsend(has_label=has_label)
  44. def _txbump_feebump(self, *args, **kwargs):
  45. self.get_file_with_ext('rawtx', delete_all=True)
  46. return self._txbump(*args, **kwargs)
  47. def _txbump_new_outputs(self, *, args, fee, add_opts=[]):
  48. self.get_file_with_ext('rawtx', delete_all=True)
  49. ext = '{}.regtest.sigtx'.format('-α' if self.cfg.debug_utf8 else '')
  50. txfile = self.get_file_with_ext(ext, no_dot=True)
  51. return self.txbump_ui_common(
  52. self.spawn('mmgen-txbump', self.eth_opts + add_opts + args + [txfile]),
  53. fee = fee,
  54. fee_desc = 'or gas price',
  55. bad_fee = '0.9G')
  56. def _token_txcreate(self, *, args, cmd='txcreate'):
  57. self.get_file_with_ext('sigtx', delete_all=True)
  58. t = self._create_token_tx(cmd=cmd, fee='1.3G', args=args, add_opts=self.eth_opts)
  59. t.expect('to confirm: ', 'YES\n')
  60. t.written_to_file('Sent transaction')
  61. return t
  62. async def _wait_for_block(self, require_seen=True):
  63. self.spawn(msg_only=True)
  64. if self.devnet_block_period:
  65. empty_pools_seen = 0
  66. tx_seen = False
  67. while True:
  68. await asyncio.sleep(1)
  69. t = self.spawn(
  70. 'mmgen-cli',
  71. ['--regtest=1', 'txpool_content'],
  72. env = cleanup_env(self.cfg),
  73. no_msg = True,
  74. silent = True)
  75. res = json.loads(t.read().strip())
  76. if p := res['pending']:
  77. imsg(f'Pool has {len(p)} transaction{suf(p)}')
  78. if self.tr.quiet:
  79. omsg_r('+')
  80. tx_seen = True
  81. else:
  82. imsg('Pool is empty')
  83. if self.tr.quiet:
  84. omsg_r('+')
  85. if tx_seen or not require_seen:
  86. break
  87. empty_pools_seen += 1
  88. if empty_pools_seen > 5:
  89. m = ('\nTransaction pool empty! Try increasing the block period with the'
  90. ' --devnet-block-period option (current value is {})')
  91. ymsg(m.format(self.devnet_block_period))
  92. sys.exit(1)
  93. return 'ok'
  94. class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev):
  95. 'Ethereum transaction bumping operations'
  96. networks = ('eth',)
  97. tmpdir_nums = [42]
  98. dfl_devnet_block_period = {'geth': 7, 'reth': 9}
  99. fund_amt = 100000
  100. token_fund_amt = 1000
  101. cmd_group_in = (
  102. ('subgroup.ltc_init', []),
  103. ('subgroup.eth_init', []),
  104. ('subgroup.feebump', ['eth_init']),
  105. ('subgroup.new_outputs', ['eth_init']),
  106. ('subgroup.swap_feebump', ['ltc_init', 'eth_init']),
  107. ('subgroup.swap_new_outputs', ['ltc_init', 'eth_init']),
  108. ('subgroup.token_init', ['eth_init']),
  109. ('subgroup.token_feebump', ['token_init']),
  110. ('subgroup.token_new_outputs', ['token_init']),
  111. ('ltc_stop', ''),
  112. ('stop', 'stopping daemon'),
  113. )
  114. cmd_subgroups = {
  115. 'eth_init': (
  116. 'initializing ETH tracking wallet',
  117. ('setup', 'dev mode transaction bumping tests for Ethereum (start daemon)'),
  118. ('addrgen', 'generating addresses'),
  119. ('addrimport', 'importing addresses'),
  120. ('addrimport_devaddr', 'importing the dev address'),
  121. ('addrimport_reth_devaddr','importing the reth dev address'),
  122. ('fund_devaddr', 'funding the dev address'),
  123. ('wait_reth1', 'waiting for block'),
  124. ('del_reth_devaddr', 'deleting the reth dev address'),
  125. ('fund_mmgen_addr1', 'funding user address :1)'),
  126. ('fund_mmgen_addr2', 'funding user address :11)'),
  127. ('fund_mmgen_addr3', 'funding user address :21)'),
  128. ('wait1', 'waiting for block'),
  129. ),
  130. 'ltc_init': (
  131. 'initializing LTC tracking wallet',
  132. ('ltc_setup', ''),
  133. ('ltc_walletconv_bob', ''),
  134. ('ltc_addrgen_bob', ''),
  135. ('ltc_addrimport_bob', ''),
  136. ),
  137. 'feebump': (
  138. 'creating, signing, sending, bumping and resending a transaction (fee-bump only)',
  139. ('txcreate1', 'creating a transaction (send to burn address)'),
  140. ('txsign1', 'signing the transaction'),
  141. ('txsend1', 'sending the transaction'),
  142. ('txbump1', 'creating a replacement transaction (fee-bump)'),
  143. ('txbump1sign', 'signing the replacement transaction'),
  144. ('txbump1send', 'sending the replacement transaction'),
  145. ('wait2', 'waiting for block'),
  146. ('bal1', 'checking the balance'),
  147. ),
  148. 'new_outputs': (
  149. 'creating, signing, sending, bumping and resending a transaction (new outputs)',
  150. ('txcreate2', 'creating a transaction (send to burn address)'),
  151. ('txsign2', 'signing the transaction'),
  152. ('txsend2', 'sending the transaction'),
  153. ('txbump2', 'creating a replacement transaction (new outputs)'),
  154. ('txbump2sign', 'signing the replacement transaction'),
  155. ('txbump2send', 'sending the replacement transaction'),
  156. ('wait3', 'waiting for block'),
  157. ('bal2', 'checking the balance'),
  158. ),
  159. 'swap_feebump': (
  160. 'creating, signing, sending, bumping and resending a swap transaction (fee-bump only)',
  161. ('swaptxcreate1', 'creating a swap transaction (from address :11)'),
  162. ('swaptxsign1', 'signing the transaction'),
  163. ('swaptxsend1', 'sending the transaction'),
  164. ('swaptxbump1', 'creating a replacement swap transaction (fee-bump)'),
  165. ('swaptxbump1sign', 'signing the replacement transaction'),
  166. ('swaptxbump1send', 'sending the replacement transaction'),
  167. ('wait4', 'waiting for block'),
  168. ('bal3', 'checking the balance'),
  169. ),
  170. 'swap_new_outputs': (
  171. 'creating, signing, sending, bumping and resending a swap transaction (new output)',
  172. ('swaptxcreate2', 'creating a swap transaction (from address :21)'),
  173. ('swaptxsign2', 'signing the transaction'),
  174. ('swaptxsend2', 'sending the transaction'),
  175. ('swaptxbump2', 'creating a replacement swap transaction (new output)'),
  176. ('swaptxbump2sign', 'signing the replacement transaction'),
  177. ('swaptxbump2send', 'sending the replacement transaction'),
  178. ('wait5', 'waiting for block'),
  179. ('bal4', 'checking the balance'),
  180. ),
  181. 'token_init': (
  182. 'initializing token wallets',
  183. ('token_compile1', 'compiling ERC20 token #1'),
  184. ('token_deploy_a', 'deploying ERC20 token MM1 (SafeMath)'),
  185. ('token_deploy_b', 'deploying ERC20 token MM1 (Owned)'),
  186. ('token_deploy_c', 'deploying ERC20 token MM1 (Token)'),
  187. ('token_fund_user', 'transferring token funds from dev to user'),
  188. ('token_addrgen', 'generating token addresses'),
  189. ('token_addrimport', 'importing token addresses using token address (MM1)'),
  190. ('token_bal1', 'the token balance'),
  191. ),
  192. 'token_feebump': (
  193. 'creating, signing, sending, bumping and resending a token transaction (fee-bump only)',
  194. ('token_txdo1', 'creating, signing and sending a token transaction'),
  195. ('token_txbump1', 'bumping the token transaction (fee-bump)'),
  196. ('wait6', 'waiting for block'),
  197. ('token_bal2', 'the token balance'),
  198. ),
  199. 'token_new_outputs': (
  200. 'creating, signing, sending, bumping and resending a token transaction (new outputs)',
  201. ('token_txdo2', 'creating, signing and sending a token transaction'),
  202. ('token_txbump2', 'creating a replacement token transaction (new outputs)'),
  203. ('token_txbump2sign', 'signing the replacement transaction'),
  204. ('token_txbump2send', 'sending the replacement transaction'),
  205. ('wait7', 'waiting for block'),
  206. ('token_bal3', 'the token balance'),
  207. )
  208. }
  209. ltc_tests = [c[0] for v in tuple(cmd_subgroups.values()) + (cmd_group_in,)
  210. for c in v if isinstance(c, tuple) and c[0].startswith('ltc_')]
  211. exec(''.join(method_template.format(name=k, ltc_name=k.removeprefix('ltc_')) for k in ltc_tests))
  212. def __init__(self, cfg, trunner, cfgs, spawn):
  213. CmdTestEthdev.__init__(self, cfg, trunner, cfgs, spawn)
  214. if not trunner:
  215. return
  216. self.daemon.usr_coind_args = {
  217. 'reth': [f'--dev.block-time={self.devnet_block_period}s'],
  218. 'geth': [f'--dev.period={self.devnet_block_period}']
  219. }[self.daemon.id]
  220. global ethbump_ltc
  221. cfg = Config({
  222. '_clone': trunner.cfg,
  223. 'coin': 'ltc',
  224. 'resume': None,
  225. 'resuming': None,
  226. 'resume_after': None,
  227. 'exit_after': None,
  228. 'log': None})
  229. t = trunner
  230. ethbump_ltc = CmdTestRunner(cfg, t.repo_root, t.data_dir, t.trash_dir, t.trash_dir2)
  231. ethbump_ltc.init_group('ethbump_ltc')
  232. thornode_server.start()
  233. def txcreate1(self):
  234. return self._txcreate(args=[f'{burn_addr},987'], acct='1')
  235. def txbump1(self):
  236. return self._txbump_feebump(fee='1.3G', ext='{}.regtest.sigtx')
  237. def txcreate2(self):
  238. return self._txcreate(args=[f'{burn_addr},789'], acct='1')
  239. def txbump2(self):
  240. return self._txbump_new_outputs(args=[f'{dfl_sid}:E:2,777'], fee='1.3G')
  241. def swaptxcreate1(self):
  242. return self._swaptxcreate_ui_common(
  243. self._swaptxcreate(['ETH', '12.34567', 'LTC', f'{dfl_sid}:B:3']),
  244. inputs = 4)
  245. def swaptxsign1(self):
  246. return self._swaptxsign()
  247. def swaptxsend1(self):
  248. return self._swaptxsend()
  249. def swaptxbump1(self):
  250. return self._swaptxbump('41.1G')
  251. def swaptxbump2(self):
  252. return self._swaptxbump('1.9G', output_args=[f'{dfl_sid}:E:12,4444.3333'])
  253. def bal1(self):
  254. return self._bal_check(pat=rf'{dfl_sid}:E:1\s+99012\.9999727\s')
  255. def bal2(self):
  256. return self._bal_check(pat=rf'{dfl_sid}:E:2\s+777\s')
  257. def bal3(self):
  258. return self._bal_check(pat=rf'{dfl_sid}:E:11\s+99987\.653431389777251448\s')
  259. def bal4(self):
  260. return self._bal_check(pat=rf'{dfl_sid}:E:12\s+4444\.3333\s')
  261. def token_fund_user(self):
  262. return self._token_transfer_ops(
  263. op = 'fund_user',
  264. mm_idxs = [1],
  265. token_addr = 'token_addr1',
  266. amt = self.token_fund_amt)
  267. def token_txdo1(self):
  268. return self._token_txcreate(cmd='txdo', args=[f'{dfl_sid}:E:2,1.23456', dfl_words_file])
  269. def token_txbump1(self):
  270. t = self._txbump_feebump(
  271. fee = '60G',
  272. ext = '{}.regtest.sigtx',
  273. add_opts = ['--token=MM1'],
  274. add_args = [dfl_words_file])
  275. t.expect('to confirm: ', 'YES\n')
  276. t.written_to_file('Sent transaction')
  277. return t
  278. def token_bal2(self):
  279. return self._token_bal_check(pat=rf'{dfl_sid}:E:2\s+1\.23456')
  280. def token_txdo2(self):
  281. return self._token_txcreate(cmd='txdo', args=[f'{dfl_sid}:E:3,5.4321', dfl_words_file])
  282. def token_txbump2(self):
  283. return self._txbump_new_outputs(
  284. args = [f'{dfl_sid}:E:4,6.54321'],
  285. fee = '1.6G',
  286. add_opts = ['--token=mm1'])
  287. def token_txbump2sign(self):
  288. return self._txsign(has_label=False)
  289. def token_txbump2send(self):
  290. return self._txsend(has_label=False)
  291. def token_bal3(self):
  292. return self._token_bal_check(pat=rf'{dfl_sid}:E:4\s+6\.54321')
  293. def wait_reth1(self):
  294. return self._wait_for_block() if self.daemon.id == 'reth' else 'silent'
  295. wait1 = wait2 = wait3 = wait4 = wait5 = wait6 = wait7 = CmdTestEthBumpMethods._wait_for_block
  296. txsign1 = txsign2 = txbump1sign = txbump2sign = CmdTestEthBumpMethods._txsign
  297. txsend1 = txsend2 = txbump1send = txbump2send = CmdTestEthBumpMethods._txsend
  298. swaptxcreate2 = swaptxcreate1
  299. swaptxsign2 = swaptxsign1
  300. swaptxsend2 = swaptxsend1
  301. swaptxbump1sign = swaptxbump2sign = token_txbump2sign
  302. swaptxbump1send = swaptxbump2send = token_txbump2send
  303. class CmdTestEthBumpLTC(CmdTestSwapMethods, CmdTestRegtest):
  304. network = ('ltc',)
  305. tmpdir_nums = [43]
  306. cmd_group_in = CmdTestRegtest.cmd_group_in + (
  307. ('setup', 'LTC regtest setup'),
  308. ('walletconv_bob', 'LTC wallet generation'),
  309. ('addrgen_bob', 'LTC address generation'),
  310. ('addrimport_bob', 'importing LTC addresses'),
  311. ('stop', 'stopping the Litecoin daemon'),
  312. )