ethbump.py 13 KB

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