ethbump.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  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.swap import ThornodeSwapServer
  21. from .ethdev import CmdTestEthdev, CmdTestEthdevMethods
  22. from .regtest import CmdTestRegtest
  23. from .swap import CmdTestSwapMethods, create_cross_methods
  24. from .ethswap import CmdTestEthSwapMethods
  25. burn_addr = 'beefcafe22' * 4
  26. method_template = """
  27. def {name}(self):
  28. self.spawn(log_only=True)
  29. return ethbump_ltc.run_test("{ltc_name}", sub=True)
  30. """
  31. class CmdTestEthBumpMethods:
  32. @property
  33. def devnet_block_period(self):
  34. return self.cfg.devnet_block_period or self.dfl_devnet_block_period[self.daemon.id]
  35. def _txcreate(self, args, acct):
  36. self.get_file_with_ext('rawtx', delete_all=True)
  37. return self.txcreate(args, acct=acct, interactive_fee='0.9G', fee_info_data=('0.0000189', '0.9'))
  38. def _txsign(self, has_label=True):
  39. self.get_file_with_ext('sigtx', delete_all=True)
  40. return self.txsign(has_label=has_label)
  41. def _txsend(self, has_label=True):
  42. return self.txsend(has_label=has_label)
  43. def _txbump_feebump(self, *args, **kwargs):
  44. self.get_file_with_ext('rawtx', delete_all=True)
  45. return self._txbump(*args, **kwargs)
  46. def _txbump_new_outputs(self, *, args, fee, add_opts=[]):
  47. self.get_file_with_ext('rawtx', delete_all=True)
  48. ext = '{}.regtest.sigtx'.format('-α' if self.cfg.debug_utf8 else '')
  49. txfile = self.get_file_with_ext(ext, no_dot=True)
  50. return self.txbump_ui_common(
  51. self.spawn('mmgen-txbump', self.eth_opts + add_opts + args + [txfile]),
  52. fee = fee,
  53. fee_desc = 'or gas price',
  54. bad_fee = '0.9G')
  55. def _token_fund_user(self, *, mm_idxs):
  56. return self._token_transfer_ops(
  57. op = 'fund_user',
  58. mm_idxs = mm_idxs,
  59. token_addr = 'token_addr1',
  60. amt = self.token_fund_amt)
  61. def _token_txcreate(self, *, args, cmd='txcreate'):
  62. self.get_file_with_ext('sigtx', delete_all=True)
  63. t = self._create_token_tx(cmd=cmd, fee='1.3G', args=args, add_opts=self.eth_opts)
  64. t.expect('to confirm: ', 'YES\n')
  65. t.written_to_file('Sent transaction')
  66. return t
  67. async def _wait_for_block(self, require_seen=True):
  68. self.spawn(msg_only=True)
  69. if self.devnet_block_period:
  70. empty_pools_seen = 0
  71. tx_seen = False
  72. while True:
  73. await asyncio.sleep(1)
  74. t = self.spawn(
  75. 'mmgen-cli',
  76. ['--regtest=1', 'txpool_content'],
  77. env = cleanup_env(self.cfg),
  78. no_msg = True,
  79. silent = True)
  80. res = json.loads(t.read().strip())
  81. if p := res['pending']:
  82. imsg(f'Pool has {len(p)} transaction{suf(p)}')
  83. if self.tr.quiet:
  84. omsg_r('+')
  85. tx_seen = True
  86. else:
  87. imsg('Pool is empty')
  88. if self.tr.quiet:
  89. omsg_r('+')
  90. if tx_seen or not require_seen:
  91. break
  92. empty_pools_seen += 1
  93. if empty_pools_seen > 5:
  94. m = ('\nTransaction pool empty! Try increasing the block period with the'
  95. ' --devnet-block-period option (current value is {})')
  96. ymsg(m.format(self.devnet_block_period))
  97. sys.exit(1)
  98. return 'ok'
  99. class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev):
  100. 'Ethereum transaction bumping operations'
  101. networks = ('eth',)
  102. tmpdir_nums = [42]
  103. dfl_devnet_block_period = {'geth': 7, 'reth': 9}
  104. fund_amt = 100000
  105. token_fund_amt = 1000
  106. cross_group = 'ethbump_ltc'
  107. cross_coin = 'ltc'
  108. cmd_group_in = (
  109. ('subgroup.ltc_init', []),
  110. ('subgroup.eth_init', []),
  111. ('subgroup.feebump', ['eth_init']),
  112. ('subgroup.new_outputs', ['eth_init']),
  113. ('subgroup.swap_feebump', ['ltc_init', 'eth_init']),
  114. ('subgroup.swap_new_outputs', ['ltc_init', 'eth_init']),
  115. ('subgroup.token_init', ['eth_init']),
  116. ('subgroup.token_feebump', ['token_init']),
  117. ('subgroup.token_new_outputs', ['token_init']),
  118. ('subgroup.token_init_swap', ['token_init']),
  119. # ('subgroup.token_feebump_swap', ['token_init_swap']), # TBD
  120. ('subgroup.token_new_outputs_swap', ['token_init_swap']),
  121. ('ltc_stop', ''),
  122. ('swap_server_stop', 'stopping the Thornode server'),
  123. ('stop', 'stopping daemon'),
  124. )
  125. cmd_subgroups = {
  126. 'eth_init': (
  127. 'initializing ETH tracking wallet',
  128. ('setup', 'dev mode transaction bumping tests for Ethereum (start daemon)'),
  129. ('addrgen', 'generating addresses'),
  130. ('addrimport', 'importing addresses'),
  131. ('addrimport_devaddr', 'importing the dev address'),
  132. ('addrimport_reth_devaddr','importing the reth dev address'),
  133. ('fund_devaddr', 'funding the dev address'),
  134. ('wait_reth1', 'waiting for block'),
  135. ('del_reth_devaddr', 'deleting the reth dev address'),
  136. ('fund_mmgen_addr1', 'funding user address :1)'),
  137. ('fund_mmgen_addr2', 'funding user address :11)'),
  138. ('fund_mmgen_addr3', 'funding user address :21)'),
  139. ('wait1', 'waiting for block'),
  140. ),
  141. 'ltc_init': (
  142. 'initializing LTC tracking wallet',
  143. ('ltc_setup', ''),
  144. ('ltc_walletconv_bob', ''),
  145. ('ltc_addrgen_bob', ''),
  146. ('ltc_addrimport_bob', ''),
  147. ),
  148. 'feebump': (
  149. 'creating, signing, sending, bumping and resending a transaction (fee-bump only)',
  150. ('txcreate1', 'creating a transaction (send to burn address)'),
  151. ('txsign1', 'signing the transaction'),
  152. ('txsend1', 'sending the transaction'),
  153. ('txbump1', 'creating a replacement transaction (fee-bump)'),
  154. ('txbump1sign', 'signing the replacement transaction'),
  155. ('txbump1send', 'sending the replacement transaction'),
  156. ('wait2', 'waiting for block'),
  157. ('bal1', 'checking the balance'),
  158. ),
  159. 'new_outputs': (
  160. 'creating, signing, sending, bumping and resending a transaction (new outputs)',
  161. ('txcreate2', 'creating a transaction (send to burn address)'),
  162. ('txsign2', 'signing the transaction'),
  163. ('txsend2', 'sending the transaction'),
  164. ('txbump2', 'creating a replacement transaction (new outputs)'),
  165. ('txbump2sign', 'signing the replacement transaction'),
  166. ('txbump2send', 'sending the replacement transaction'),
  167. ('wait3', 'waiting for block'),
  168. ('bal2', 'checking the balance'),
  169. ),
  170. 'swap_feebump': (
  171. 'creating, signing, sending, bumping and resending a swap transaction (fee-bump only)',
  172. ('swaptxcreate1', 'creating a swap transaction (from address :11)'),
  173. ('swaptxsign1', 'signing the transaction'),
  174. ('swaptxsend1', 'sending the transaction'),
  175. ('swaptxbump1', 'creating a replacement swap transaction (fee-bump)'),
  176. ('swaptxbump1sign', 'signing the replacement transaction'),
  177. ('swaptxbump1send', 'sending the replacement transaction'),
  178. ('wait4', 'waiting for block'),
  179. ('bal3', 'checking the balance'),
  180. ),
  181. 'swap_new_outputs': (
  182. 'creating, signing, sending, bumping and resending a swap transaction (new output)',
  183. ('swaptxcreate2', 'creating a swap transaction (from address :21)'),
  184. ('swaptxsign2', 'signing the transaction'),
  185. ('swaptxsend2', 'sending the transaction'),
  186. ('swaptxbump2', 'creating a replacement swap transaction (new output)'),
  187. ('swaptxbump2sign', 'signing the replacement transaction'),
  188. ('swaptxbump2send', 'sending the replacement transaction'),
  189. ('wait5', 'waiting for block'),
  190. ('bal4', 'checking the balance'),
  191. ),
  192. 'token_init': (
  193. 'initializing token wallets',
  194. ('token_compile1', 'compiling ERC20 token #1'),
  195. ('token_deploy_a', 'deploying ERC20 token MM1 (SafeMath)'),
  196. ('token_deploy_b', 'deploying ERC20 token MM1 (Owned)'),
  197. ('token_deploy_c', 'deploying ERC20 token MM1 (Token)'),
  198. ('token_fund_user1', 'transferring token funds from dev to user (addr #1)'),
  199. ('token_addrgen', 'generating token addresses'),
  200. ('token_addrimport', 'importing token addresses using token address (MM1)'),
  201. ('token_bal1', 'the token balance'),
  202. ),
  203. 'token_feebump': (
  204. 'creating, signing, sending, bumping and resending a token transaction (fee-bump only)',
  205. ('token_txdo1', 'creating, signing and sending a token transaction'),
  206. ('token_txbump1', 'bumping the token transaction (fee-bump)'),
  207. ('wait6', 'waiting for block'),
  208. ('token_bal2', 'the token balance'),
  209. ),
  210. 'token_new_outputs': (
  211. 'creating, signing, sending, bumping and resending a token transaction (new outputs)',
  212. ('token_txdo2', 'creating, signing and sending a token transaction'),
  213. ('token_txbump2', 'creating a replacement token transaction (new outputs)'),
  214. ('token_txbump2sign', 'signing the replacement transaction'),
  215. ('token_txbump2send', 'sending the replacement transaction'),
  216. ('wait7', 'waiting for block'),
  217. ('token_bal3', 'the token balance'),
  218. ),
  219. 'token_init_swap': (
  220. 'initializing token swap configuration',
  221. ('token_compile_router', 'compiling THORChain router contract'),
  222. ('token_deploy_router', 'deploying THORChain router contract'),
  223. ),
  224. 'token_feebump_swap': (
  225. 'creating, signing, sending, bumping and resending a token swap transaction (feebump)',
  226. ('token_fund_user11', 'transferring token funds from dev to user (addr #11)'),
  227. ('token_addrimport_inbound', 'importing THORNode inbound token address'),
  228. ('token_swaptxdo1', 'creating, signing and sending a token transaction (feebump)'),
  229. ('token_swaptxbump1', 'bumping the token transaction (fee-bump)'),
  230. ('token_swaptxbump1sign', 'signing the replacement transaction'),
  231. ('token_swaptxbump1send', 'sending the replacement transaction'),
  232. ('wait8', 'waiting for block'),
  233. ('token_bal5', 'the token balance'),
  234. ),
  235. 'token_new_outputs_swap': (
  236. 'creating, signing, sending, bumping and resending a token swap transaction (new outputs)',
  237. ('token_swaptxdo2', 'creating, signing and sending a token swap transaction (new outputs)'),
  238. ('token_swaptxbump2', 'creating a replacement token transaction'),
  239. ('token_swaptxbump2sign', 'signing the replacement transaction'),
  240. ('token_swaptxbump2send', 'sending the replacement transaction'),
  241. ('wait9', 'waiting for block'),
  242. ('token_bal6', 'the token balance'),
  243. ),
  244. }
  245. exec(create_cross_methods(cross_coin, cross_group, cmd_group_in, cmd_subgroups))
  246. def __init__(self, cfg, trunner, cfgs, spawn):
  247. CmdTestEthdev.__init__(self, cfg, trunner, cfgs, spawn)
  248. if not trunner:
  249. return
  250. self.daemon.usr_coind_args = {
  251. 'reth': [f'--dev.block-time={self.devnet_block_period}s'],
  252. 'geth': [f'--dev.period={self.devnet_block_period}']
  253. }[self.daemon.id]
  254. globals()[self.cross_group] = self.create_cross_runner(trunner)
  255. self.swap_server = ThornodeSwapServer()
  256. self.swap_server.start()
  257. def txcreate1(self):
  258. return self._txcreate(args=[f'{burn_addr},987'], acct='1')
  259. def txbump1(self):
  260. return self._txbump_feebump(fee='1.3G', ext='{}.regtest.sigtx')
  261. def txcreate2(self):
  262. return self._txcreate(args=[f'{burn_addr},789'], acct='1')
  263. def txbump2(self):
  264. return self._txbump_new_outputs(args=[f'{dfl_sid}:E:2,777'], fee='1.3G')
  265. def swaptxcreate1(self):
  266. return self._swaptxcreate_ui_common(
  267. self._swaptxcreate(['ETH', '12.34567', 'LTC', f'{dfl_sid}:B:3']),
  268. inputs = 4)
  269. def swaptxsign1(self):
  270. return self._swaptxsign()
  271. def swaptxsend1(self):
  272. return self._swaptxsend()
  273. def swaptxbump1(self):
  274. return self._swaptxbump('41.1G')
  275. def swaptxbump2(self):
  276. return self._swaptxbump('1.9G', output_args=[f'{dfl_sid}:E:12,4444.3333'])
  277. def bal1(self):
  278. return self._bal_check(pat=rf'{dfl_sid}:E:1\s+99012\.9999727\s')
  279. def bal2(self):
  280. return self._bal_check(pat=rf'{dfl_sid}:E:2\s+777\s')
  281. def bal3(self):
  282. return self._bal_check(pat=rf'{dfl_sid}:E:11\s+99987\.653431389777251448\s')
  283. def bal4(self):
  284. return self._bal_check(pat=rf'{dfl_sid}:E:12\s+4444\.3333\s')
  285. def token_fund_user1(self):
  286. return self._token_fund_user(mm_idxs=[1])
  287. def token_fund_user11(self):
  288. return self._token_fund_user(mm_idxs=[11])
  289. def token_txdo1(self):
  290. return self._token_txcreate(cmd='txdo', args=[f'{dfl_sid}:E:2,1.23456', dfl_words_file])
  291. def token_txbump1(self):
  292. t = self._txbump_feebump(
  293. fee = '60G',
  294. ext = '{}.regtest.sigtx',
  295. add_opts = ['--token=MM1'],
  296. add_args = [dfl_words_file])
  297. t.expect('to confirm: ', 'YES\n')
  298. t.written_to_file('Sent transaction')
  299. return t
  300. def token_bal2(self):
  301. return self._token_bal_check(pat=rf'{dfl_sid}:E:2\s+1\.23456')
  302. def token_txdo2(self):
  303. return self._token_txcreate(cmd='txdo', args=[f'{dfl_sid}:E:3,5.4321', dfl_words_file])
  304. def token_txbump2(self):
  305. return self._txbump_new_outputs(
  306. args = [f'{dfl_sid}:E:4,6.54321'],
  307. fee = '1.6G',
  308. add_opts = ['--token=mm1', '--gas=75000'])
  309. def token_txbump2sign(self):
  310. return self._txsign(has_label=False)
  311. def token_txbump2send(self):
  312. return self._txsend(has_label=False)
  313. def token_bal3(self):
  314. return self._token_bal_check(pat=rf'{dfl_sid}:E:4\s+6\.54321')
  315. def wait_reth1(self):
  316. return self._wait_for_block() if self.daemon.id == 'reth' else 'silent'
  317. def token_swaptxdo1(self):
  318. self.get_file_with_ext('sigtx', delete_all=True)
  319. t = self._swaptxcreate(
  320. ['ETH.MM1', '0.321', 'ETH', dfl_words_file],
  321. action = 'txdo')
  322. t.expect('(Y/n): ', '\n')
  323. return self._swaptxcreate_ui_common(
  324. t,
  325. sign_and_send = True,
  326. need_passphrase = False,
  327. file_desc = 'Sent transaction',
  328. inputs = 11)
  329. def token_swaptxbump1(self):
  330. time.sleep(0.2)
  331. self.get_file_with_ext('rawtx', delete_all=True)
  332. txfile = self.get_file_with_ext('sigtx', no_dot=True)
  333. t = self.spawn(
  334. 'mmgen-txbump',
  335. self.eth_opts
  336. + ['--gas=50000'] # , '--router-gas=600000']
  337. + ['--yes', txfile])
  338. t.expect('to continue: ', '\n') # exit swap quote view
  339. t.expect('or gas price: ', '8G\n') # enter fee
  340. t.expect(r'Gas limit:.*\D650000\D', regex=True)
  341. t.written_to_file('Fee-bumped transaction')
  342. return t
  343. def token_swaptxdo2(self):
  344. self.get_file_with_ext('sigtx', delete_all=True)
  345. return self._swaptxcreate_ui_common(
  346. self._swaptxcreate(
  347. ['ETH.MM1', '0.321', 'ETH', f'{dfl_sid}:E:21', dfl_words_file],
  348. action = 'txdo'),
  349. sign_and_send = True,
  350. need_passphrase = False,
  351. file_desc = 'Sent transaction',
  352. inputs = 1)
  353. def token_swaptxbump2(self):
  354. return self._txbump_new_outputs(
  355. args = [f'{dfl_sid}:E:8,0.54321'],
  356. fee = '1.4G',
  357. add_opts = ['--gas=67888', '--fee=3G'])
  358. def token_bal5(self):
  359. return self._token_bal_check(pat=r'feedbeefcafe\s+non-MMGen\s+0\.321\s')
  360. def token_bal6(self):
  361. return self._token_bal_check(pat=rf'{dfl_sid}:E:8\s+0\.54321')
  362. wait1 = wait2 = wait3 = wait4 = wait5 = wait6 = wait7 = wait8 = wait9 = CmdTestEthBumpMethods._wait_for_block
  363. txsign1 = txsign2 = txbump1sign = txbump2sign = CmdTestEthBumpMethods._txsign
  364. txsend1 = txsend2 = txbump1send = txbump2send = CmdTestEthBumpMethods._txsend
  365. swaptxcreate2 = swaptxcreate1
  366. swaptxsign2 = swaptxsign1
  367. swaptxsend2 = swaptxsend1
  368. token_swaptxbump1sign = token_swaptxbump2sign = swaptxbump1sign = swaptxbump2sign = token_txbump2sign
  369. token_swaptxbump1send = token_swaptxbump2send = swaptxbump1send = swaptxbump2send = token_txbump2send
  370. def swap_server_stop(self):
  371. return self._thornode_server_stop()
  372. class CmdTestEthBumpLTC(CmdTestSwapMethods, CmdTestRegtest):
  373. 'Ethereum transaction bumping operations - LTC wallet'
  374. network = ('ltc',)
  375. tmpdir_nums = [43]
  376. is_helper = True
  377. cmd_group_in = CmdTestRegtest.cmd_group_in + (
  378. ('setup', 'LTC regtest setup'),
  379. ('walletconv_bob', 'LTC wallet generation'),
  380. ('addrgen_bob', 'LTC address generation'),
  381. ('addrimport_bob', 'importing LTC addresses'),
  382. ('stop', 'stopping the Litecoin daemon'),
  383. )