ethbump.py 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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.util import ymsg, suf
  15. from ..include.common import imsg, omsg_r
  16. from .include.common import cleanup_env, dfl_words_file
  17. from .ethdev import CmdTestEthdev, CmdTestEthdevMethods, dfl_sid
  18. from .regtest import CmdTestRegtest
  19. from .swap import CmdTestSwapMethods
  20. burn_addr = 'beefcafe22' * 4
  21. method_template = """
  22. def {name}(self):
  23. self.spawn(log_only=True)
  24. return ethbump_ltc.run_test("{ltc_name}", sub=True)
  25. """
  26. class CmdTestEthBumpMethods:
  27. def init_block_period(self):
  28. d = self.daemon
  29. bp = self.cfg.devnet_block_period or self.dfl_devnet_block_period[d.id]
  30. d.usr_coind_args = {
  31. 'reth': [f'--dev.block-time={bp}s'],
  32. 'geth': [f'--dev.period={bp}']
  33. }[d.id]
  34. self.devnet_block_period = bp
  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_txcreate(self, *, args, cmd='txcreate'):
  56. self.get_file_with_ext('sigtx', delete_all=True)
  57. t = self._create_token_tx(cmd=cmd, fee='1.3G', args=args, add_opts=self.eth_opts)
  58. t.expect('to confirm: ', 'YES\n')
  59. t.written_to_file('Sent transaction')
  60. return t
  61. async def _wait_for_block(self, require_seen=True):
  62. self.spawn(msg_only=True)
  63. if self.devnet_block_period:
  64. empty_pools_seen = 0
  65. tx_seen = False
  66. while True:
  67. await asyncio.sleep(1)
  68. t = self.spawn(
  69. 'mmgen-cli',
  70. ['--regtest=1', 'txpool_content'],
  71. env = cleanup_env(self.cfg),
  72. no_msg = True,
  73. silent = True)
  74. res = json.loads(t.read().strip())
  75. if p := res['pending']:
  76. imsg(f'Pool has {len(p)} transaction{suf(p)}')
  77. if self.tr.quiet:
  78. omsg_r('+')
  79. tx_seen = True
  80. else:
  81. imsg('Pool is empty')
  82. if self.tr.quiet:
  83. omsg_r('+')
  84. if tx_seen or not require_seen:
  85. break
  86. empty_pools_seen += 1
  87. if empty_pools_seen > 5:
  88. m = ('\nTransaction pool empty! Try increasing the block period with the'
  89. ' --devnet-block-period option (current value is {})')
  90. ymsg(m.format(self.devnet_block_period))
  91. sys.exit(1)
  92. return 'ok'
  93. class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthdev, CmdTestSwapMethods):
  94. 'Ethereum transaction bumping operations'
  95. networks = ('eth',)
  96. tmpdir_nums = [42]
  97. dfl_devnet_block_period = {'geth': 7, 'reth': 9}
  98. cmd_group_in = (
  99. ('subgroup.eth_init', []),
  100. ('subgroup.feebump', ['eth_init']),
  101. ('subgroup.new_outputs', ['eth_init']),
  102. ('subgroup.token_init', ['eth_init']),
  103. ('subgroup.token_feebump', ['token_init']),
  104. ('subgroup.token_new_outputs', ['token_init']),
  105. ('stop', 'stopping daemon'),
  106. )
  107. cmd_subgroups = {
  108. 'eth_init': (
  109. 'initializing ETH tracking wallet',
  110. ('setup', 'dev mode transaction bumping tests for Ethereum (start daemon)'),
  111. ('addrgen', 'generating addresses'),
  112. ('addrimport', 'importing addresses'),
  113. ('addrimport_dev_addr', 'importing dev faucet address ‘Ox00a329c..’'),
  114. ('fund_dev_address', 'funding the default (Parity dev) address'),
  115. ('fund_mmgen_address1', 'spend from dev address to address :1)'),
  116. ('fund_mmgen_address2', 'spend from dev address to address :11)'),
  117. ('fund_mmgen_address3', 'spend from dev address to address :21)'),
  118. ('wait1', 'waiting for block'),
  119. ),
  120. 'feebump': (
  121. 'creating, signing, sending, bumping and resending a transaction (fee-bump only)',
  122. ('txcreate1', 'creating a transaction (send to burn address)'),
  123. ('txsign1', 'signing the transaction'),
  124. ('txsend1', 'sending the transaction'),
  125. ('txbump1', 'creating a replacement transaction (fee-bump)'),
  126. ('txbump1sign', 'signing the replacement transaction'),
  127. ('txbump1send', 'sending the replacement transaction'),
  128. ('wait2', 'waiting for block'),
  129. ('bal1', 'checking the balance'),
  130. ),
  131. 'new_outputs': (
  132. 'creating, signing, sending, bumping and resending a transaction (new outputs)',
  133. ('txcreate2', 'creating a transaction (send to burn address)'),
  134. ('txsign2', 'signing the transaction'),
  135. ('txsend2', 'sending the transaction'),
  136. ('txbump2', 'creating a replacement transaction (new outputs)'),
  137. ('txbump2sign', 'signing the replacement transaction'),
  138. ('txbump2send', 'sending the replacement transaction'),
  139. ('wait3', 'waiting for block'),
  140. ('bal2', 'checking the balance'),
  141. ),
  142. 'token_init': (
  143. 'initializing token wallets',
  144. ('token_compile1', 'compiling ERC20 token #1'),
  145. ('token_deploy_a', 'deploying ERC20 token MM1 (SafeMath)'),
  146. ('token_deploy_b', 'deploying ERC20 token MM1 (Owned)'),
  147. ('token_deploy_c', 'deploying ERC20 token MM1 (Token)'),
  148. ('wait_reth1', 'waiting for block'),
  149. ('token_fund_user', 'transferring token funds from dev to user'),
  150. ('wait6', 'waiting for block'),
  151. ('token_addrgen', 'generating token addresses'),
  152. ('token_addrimport', 'importing token addresses using token address (MM1)'),
  153. ('token_bal1', 'the token balance'),
  154. ),
  155. 'token_feebump': (
  156. 'creating, signing, sending, bumping and resending a token transaction (fee-bump only)',
  157. ('token_txdo1', 'creating, signing and sending a token transaction'),
  158. ('token_txbump1', 'bumping the token transaction (fee-bump)'),
  159. ('wait7', 'waiting for block'),
  160. ('token_bal2', 'the token balance'),
  161. ),
  162. 'token_new_outputs': (
  163. 'creating, signing, sending, bumping and resending a token transaction (new outputs)',
  164. ('token_txdo2', 'creating, signing and sending a token transaction'),
  165. ('token_txbump2', 'creating a replacement token transaction (new outputs)'),
  166. ('token_txbump2sign', 'signing the replacement transaction'),
  167. ('token_txbump2send', 'sending the replacement transaction'),
  168. ('wait8', 'waiting for block'),
  169. ('token_bal3', 'the token balance'),
  170. )
  171. }
  172. def __init__(self, cfg, trunner, cfgs, spawn):
  173. CmdTestEthdev.__init__(self, cfg, trunner, cfgs, spawn)
  174. def fund_mmgen_address1(self):
  175. return self._fund_mmgen_address(arg=f'{dfl_sid}:E:1,100000')
  176. def fund_mmgen_address2(self):
  177. return self._fund_mmgen_address(arg=f'{dfl_sid}:E:11,100000')
  178. def fund_mmgen_address3(self):
  179. return self._fund_mmgen_address(arg=f'{dfl_sid}:E:21,100000')
  180. def txcreate1(self):
  181. return self._txcreate(args=[f'{burn_addr},987'], acct='1')
  182. def txbump1(self):
  183. return self._txbump_feebump(fee='1.3G', ext='{}.regtest.sigtx')
  184. def txcreate2(self):
  185. return self._txcreate(args=[f'{burn_addr},789'], acct='1')
  186. def txbump2(self):
  187. return self._txbump_new_outputs(args=[f'{dfl_sid}:E:2,777'], fee='1.3G')
  188. def bal1(self):
  189. return self._bal_check(pat=rf'{dfl_sid}:E:1\s+99012\.9999727\s')
  190. def bal2(self):
  191. return self._bal_check(pat=rf'{dfl_sid}:E:2\s+777\s')
  192. async def token_deploy_a(self):
  193. return await self._token_deploy_math(num=1, get_receipt=False)
  194. async def token_deploy_b(self):
  195. return await self._token_deploy_owned(num=1, get_receipt=False)
  196. async def token_deploy_c(self):
  197. return await self._token_deploy_token(num=1, get_receipt=False)
  198. def token_fund_user(self):
  199. return self._token_transfer_ops(op='fund_user', mm_idxs=[1], get_receipt=False)
  200. def token_addrgen(self):
  201. return self._token_addrgen(mm_idxs=[1], naddrs=5)
  202. def token_addrimport(self):
  203. return self._token_addrimport('token_addr1', '1-5', expect='5/5')
  204. def token_bal1(self):
  205. return self._token_bal_check(pat=rf'{dfl_sid}:E:1\s+1000\s')
  206. def token_txdo1(self):
  207. return self._token_txcreate(cmd='txdo', args=[f'{dfl_sid}:E:2,1.23456', dfl_words_file])
  208. def token_txbump1(self):
  209. t = self._txbump_feebump(
  210. fee = '60G',
  211. ext = '{}.regtest.sigtx',
  212. add_opts = ['--token=MM1'],
  213. add_args = [dfl_words_file])
  214. t.expect('to confirm: ', 'YES\n')
  215. t.written_to_file('Signed transaction')
  216. return t
  217. def token_bal2(self):
  218. return self._token_bal_check(pat=rf'{dfl_sid}:E:2\s+1\.23456')
  219. def token_txdo2(self):
  220. return self._token_txcreate(cmd='txdo', args=[f'{dfl_sid}:E:3,5.4321', dfl_words_file])
  221. def token_txbump2(self):
  222. return self._txbump_new_outputs(
  223. args = [f'{dfl_sid}:E:4,6.54321'],
  224. fee = '1.6G',
  225. add_opts = ['--token=mm1'])
  226. def token_txbump2sign(self):
  227. return self._txsign(has_label=False)
  228. def token_txbump2send(self):
  229. return self._txsend(has_label=False)
  230. def token_bal3(self):
  231. return self._token_bal_check(pat=rf'{dfl_sid}:E:4\s+6\.54321')
  232. def wait_reth1(self):
  233. return self._wait_for_block() if self.daemon.id == 'reth' else 'silent'
  234. wait1 = wait2 = wait3 = wait4 = wait5 = wait6 = wait7 = wait8 = CmdTestEthBumpMethods._wait_for_block
  235. txsign1 = txsign2 = txbump1sign = txbump2sign = CmdTestEthBumpMethods._txsign
  236. txsend1 = txsend2 = txbump1send = txbump2send = CmdTestEthBumpMethods._txsend