automount.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  1. #!/usr/bin/env python3
  2. #
  3. # MMGen Wallet, a terminal-based cryptocurrency wallet
  4. # Copyright (C)2013-2026 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.automount: autosigning with automount tests for the cmdtest.py test suite
  12. """
  13. import time
  14. from .autosign import CmdTestAutosignThreaded
  15. from .regtest import CmdTestRegtest, rt_pw
  16. from ..include.common import gr_uc, create_addrpairs
  17. class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
  18. 'automounted transacting operations via regtest mode'
  19. networks = ('btc', 'bch', 'ltc')
  20. tmpdir_nums = [49]
  21. bdb_wallet = True
  22. keylist_passwd = 'abc'
  23. rt_data = {
  24. 'rtFundAmt': {'btc':'500', 'bch':'500', 'ltc':'5500'},
  25. }
  26. bal1_chk = {
  27. 'btc': '502.46',
  28. 'bch': '502.46',
  29. 'ltc': '5502.46'}
  30. bal2_chk = {
  31. 'btc': '493.56992828',
  32. 'bch': '501.22524576',
  33. 'ltc': '5493.56992828'}
  34. cmd_group = (
  35. ('setup', 'regtest mode setup'),
  36. ('walletgen_alice', 'wallet generation (Alice)'),
  37. ('addrgen_alice', 'address generation (Alice)'),
  38. ('addrimport_alice', 'importing Alice’s addresses'),
  39. ('addrimport_alice_non_mmgen', 'importing Alice’s non-MMGen addresses'),
  40. ('fund_alice', 'funding Alice’s wallet'),
  41. ('fund_alice_non_mmgen1', 'funding Alice’s wallet (non-MMGen addr #1)'),
  42. ('fund_alice_non_mmgen2', 'funding Alice’s wallet (non-MMGen addr #2)'),
  43. ('generate', 'mining a block'),
  44. ('alice_bal1', 'checking Alice’s balance'),
  45. ('alice_txcreate1', 'creating a transaction'),
  46. ('alice_add_xmr_compat_tx', 'adding XMR compat TX to txauto dir'),
  47. ('alice_txcreate_bad_have_unsigned', 'creating the transaction again (error)'),
  48. ('alice_run_autosign_setup', 'running ‘autosign setup’ (with default wallet)'),
  49. ('wait_loop_start', 'starting autosign wait loop'),
  50. ('alice_txstatus1', 'getting transaction status (unsigned)'),
  51. ('alice_txstatus2', 'getting transaction status (unsent)'),
  52. ('alice_txcreate_bad_have_unsent', 'creating the transaction again (error)'),
  53. ('alice_txsend1', 'sending a transaction, editing comment'),
  54. ('alice_txstatus3', 'getting transaction status (in mempool)'),
  55. ('alice_txsend_bad_no_unsent', 'sending the transaction again (error)'),
  56. ('generate', 'mining a block'),
  57. ('alice_txstatus4', 'getting transaction status (one confirmation)'),
  58. ('alice_txcreate2', 'creating a transaction'),
  59. ('alice_txsend_abort1', 'aborting the transaction (raw only)'),
  60. ('alice_txsend_abort2', 'aborting the transaction again (error)'),
  61. ('alice_txcreate3', 'creating a transaction'),
  62. ('alice_txsend_abort3', 'aborting the transaction (user exit)'),
  63. ('alice_txsend_abort4', 'aborting the transaction (raw + signed)'),
  64. ('alice_txsend_abort5', 'aborting the transaction again (error)'),
  65. ('generate', 'mining a block'),
  66. ('alice_txcreate4', 'creating a transaction'),
  67. ('alice_txbump1', 'bumping the unsigned transaction (error, idx=0)'),
  68. ('alice_txbump2', 'bumping the unsent transaction (error)'),
  69. ('alice_txsend2_dump_hex', 'dumping the transaction to hex'),
  70. ('alice_txsend2_cli', 'sending the transaction via cli'),
  71. ('alice_txsend2_mark_sent', 'marking the transaction sent'),
  72. ('alice_txbump3', 'bumping the transaction'),
  73. ('alice_txsend3', 'sending the bumped transaction'),
  74. ('alice_txbump4', 'bumping the transaction (new outputs, fee too low, idx=0)'),
  75. ('alice_txbump_abort1', 'aborting the transaction'),
  76. ('alice_txbump5', 'bumping the transaction (new outputs)'),
  77. ('alice_txsend5', 'sending the bumped transaction'),
  78. ('alice_txstatus5', 'getting transaction status (in mempool)'),
  79. ('alice_txstatus6', 'getting transaction status (tx_range=0, in mempool)'),
  80. ('alice_txstatus7', 'getting transaction status (tx_range=1, replaced)'),
  81. ('alice_txstatus8', 'getting transaction status (tx_range=3, 2 confirmations)'),
  82. ('alice_txstatus9', 'getting transaction status (tx_range=0-3)'),
  83. ('alice_txbump6', 'bumping the next-to-last sent transaction (idx=1)'),
  84. ('generate', 'mining a block'),
  85. ('alice_bal2', 'checking Alice’s balance'),
  86. ('wait_loop_kill', 'stopping autosign wait loop'),
  87. ('stop', 'stopping regtest daemon'),
  88. ('txview', 'viewing transactions'),
  89. )
  90. def __init__(self, cfg, trunner, cfgs, spawn):
  91. self.coins = [cfg.coin.lower()]
  92. CmdTestAutosignThreaded.__init__(self, cfg, trunner, cfgs, spawn)
  93. CmdTestRegtest.__init__(self, cfg, trunner, cfgs, spawn)
  94. if trunner is None:
  95. return
  96. self.opts.append('--alice')
  97. self.non_mmgen_addrs = create_addrpairs(self.proto, 'C', 2)
  98. def addrimport_alice_non_mmgen(self):
  99. self.write_to_tmpfile(
  100. 'non_mmgen_addrs',
  101. '\n'.join(e.addr for e in self.non_mmgen_addrs))
  102. return self.spawn(
  103. 'mmgen-addrimport',
  104. ['--alice', '--quiet', '--addrlist', f'{self.tmpdir}/non_mmgen_addrs'])
  105. def fund_alice_non_mmgen1(self):
  106. return self.fund_wallet('alice', '1.23', addr=self.non_mmgen_addrs[0].addr)
  107. def fund_alice_non_mmgen2(self):
  108. return self.fund_wallet('alice', '1.23', addr=self.non_mmgen_addrs[1].addr)
  109. def alice_bal1(self):
  110. return self._user_bal_cli('alice', chk=self.bal1_chk[self.coin])
  111. def alice_add_xmr_compat_tx(self):
  112. self.spawn(msg_only=True)
  113. self.insert_device()
  114. self.do_mount()
  115. from shutil import copyfile
  116. tx_fn = '16E53D-E41192-XMR[0.012345].asubtx'
  117. copyfile(f'test/ref/monero/{tx_fn}', f'{self.asi.txauto_dir}/{tx_fn}')
  118. self.do_umount()
  119. self.remove_device()
  120. return 'ok'
  121. def alice_txcreate1(self):
  122. return self._user_txcreate(
  123. 'alice',
  124. inputs = '1-3',
  125. tweaks = ['confirm_non_mmgen'],
  126. chg_addr = 'C:5',
  127. data_arg = 'data:'+gr_uc[:24])
  128. def alice_txcreate2(self):
  129. return self._user_txcreate('alice', chg_addr='L:5')
  130. alice_txcreate3 = alice_txcreate2
  131. def alice_txcreate4(self):
  132. return self._user_txcreate('alice', chg_addr='L:4', need_rbf=True)
  133. def _alice_txsend_abort(self, err=False, send_resp='y', expect=None, shred_expect=[]):
  134. self.insert_device_online()
  135. t = self.spawn(
  136. 'mmgen-txsend',
  137. ['--quiet', '--abort'],
  138. no_passthru_opts = ['coin'],
  139. exit_val = 2 if err else 1 if send_resp == 'n' else None)
  140. if err:
  141. t.expect(expect)
  142. else:
  143. t.expect('(y/N): ', send_resp)
  144. if expect:
  145. t.expect(expect)
  146. for pat in shred_expect:
  147. t.expect(pat, regex=True)
  148. t.read()
  149. self.remove_device_online()
  150. return t
  151. def alice_txsend_abort1(self):
  152. return self._alice_txsend_abort(shred_expect=['Shredding .*arawtx'])
  153. def alice_txsend_abort2(self):
  154. return self._alice_txsend_abort(err=True, expect='No unsent transactions')
  155. def alice_txsend_abort3(self):
  156. return self._alice_txsend_abort(send_resp='n', expect='Exiting at user request')
  157. def alice_txsend_abort4(self):
  158. self._wait_signed('transaction')
  159. return self._alice_txsend_abort(shred_expect=[r'Shredding .*arawtx', r'Shredding .*asigtx'])
  160. alice_txsend_abort5 = alice_txsend_abort2
  161. def alice_txcreate_bad_have_unsigned(self):
  162. return self._user_txcreate('alice', chg_addr='C:5', exit_val=2, expect_str='already present')
  163. def alice_txcreate_bad_have_unsent(self):
  164. return self._user_txcreate('alice', chg_addr='C:5', exit_val=2, expect_str='unsent transaction')
  165. def alice_run_autosign_setup(self):
  166. from mmgen.crypto import Crypto
  167. from mmgen.cfg import Config
  168. new_cfg = Config({'_clone': self.cfg, 'usr_randchars': 0, 'hash_preset': '1'})
  169. enc_data = Crypto(new_cfg).mmgen_encrypt(
  170. '\n'.join(e.wif for e in self.non_mmgen_addrs).encode(), passwd=self.keylist_passwd)
  171. self.write_to_tmpfile('non_mmgen_keys.mmenc', enc_data, binary=True)
  172. return self.run_setup(
  173. mn_type = 'default',
  174. use_dfl_wallet = True,
  175. wallet_passwd = rt_pw,
  176. add_opts = [f'--keys-from-file={self.tmpdir}/non_mmgen_keys.mmenc'],
  177. keylist_passwd = self.keylist_passwd)
  178. def alice_txsend1(self):
  179. return self._user_txsend('alice', comment='This one’s worth a comment', no_wait=True)
  180. def alice_txsend2_dump_hex(self):
  181. return self._user_txsend('alice', need_rbf=True, dump_hex=True)
  182. def alice_txsend2_cli(self):
  183. if not self.proto.cap('rbf'):
  184. return 'skip'
  185. return self._user_dump_hex_send_cli('alice')
  186. def alice_txsend2_mark_sent(self):
  187. return self._user_txsend('alice', need_rbf=True, mark_sent=True)
  188. def alice_txsend3(self):
  189. return self._user_txsend('alice', need_rbf=True)
  190. def alice_txsend5(self):
  191. return self._user_txsend('alice', need_rbf=True)
  192. def _alice_txstatus(
  193. self,
  194. expect,
  195. exit_val = None,
  196. need_rbf = False,
  197. tx_range = None,
  198. verbose = True,
  199. batch = False):
  200. if need_rbf and not self.proto.cap('rbf'):
  201. return 'skip'
  202. self.insert_device_online()
  203. t = self.spawn(
  204. 'mmgen-txsend',
  205. ['--alice', '--autosign', '--status']
  206. + (['--verbose'] if verbose else [])
  207. + ([] if tx_range is None else [tx_range]),
  208. no_passthru_opts = ['coin'],
  209. exit_val = exit_val)
  210. t.expect(expect, regex=True)
  211. if not (exit_val or batch):
  212. t.expect('view: ', 'n')
  213. t.read()
  214. self.remove_device_online()
  215. return t
  216. def alice_txstatus1(self):
  217. return self._alice_txstatus('unsigned', 1)
  218. def alice_txstatus2(self):
  219. self._wait_signed('transaction')
  220. return self._alice_txstatus('unsent', 1)
  221. def alice_txstatus3(self):
  222. return self._alice_txstatus('in mempool', 0)
  223. def alice_txstatus4(self):
  224. return self._alice_txstatus('1 confirmation', 0)
  225. def alice_txstatus5(self):
  226. return self._alice_txstatus('in mempool', need_rbf=True)
  227. def alice_txstatus6(self):
  228. return self._alice_txstatus('in mempool', need_rbf=True, tx_range='0')
  229. def alice_txstatus7(self):
  230. return self._alice_txstatus('replaced', need_rbf=True, tx_range='1')
  231. def alice_txstatus8(self):
  232. return self._alice_txstatus('2 confirmations', need_rbf=True, tx_range='3')
  233. def alice_txstatus9(self):
  234. return self._alice_txstatus(
  235. 'in mempool.*replaced.*replaced.*2 confirmations',
  236. need_rbf = True,
  237. tx_range = '0-3',
  238. verbose = False,
  239. batch = True)
  240. def alice_txsend_bad_no_unsent(self):
  241. self.insert_device_online()
  242. t = self.spawn('mmgen-txsend', ['--quiet', '--autosign'], exit_val=2, no_passthru_opts=['coin'])
  243. t.expect('No unsent transactions')
  244. t.read()
  245. self.remove_device_online()
  246. return t
  247. def _alice_txbump(
  248. self,
  249. fee_opt = None,
  250. output_args = [],
  251. bad_tx_expect = None,
  252. low_fee_fix = None,
  253. orig_tx_expect = None,
  254. idx = None):
  255. if not self.proto.cap('rbf'):
  256. return 'skip'
  257. self.insert_device_online()
  258. t = self.spawn(
  259. 'mmgen-txbump',
  260. ['--alice', '--autosign']
  261. + ([fee_opt] if fee_opt else [])
  262. + ([] if idx is None else [str(idx)])
  263. + output_args,
  264. exit_val = 1 if bad_tx_expect else None)
  265. if bad_tx_expect:
  266. time.sleep(0.5)
  267. t.expect('Only sent transactions')
  268. t.expect(bad_tx_expect)
  269. else:
  270. if orig_tx_expect:
  271. t.expect(orig_tx_expect)
  272. if not output_args:
  273. t.expect(r'to deduct the fee from .* change output\): ', '\n', regex=True)
  274. t.expect(r'(Y/n): ', 'y') # output OK?
  275. if low_fee_fix or not fee_opt:
  276. if low_fee_fix:
  277. t.expect('Please choose a higher fee')
  278. t.expect('transaction fee: ', (low_fee_fix or '200s') + '\n')
  279. if output_args:
  280. t.expect(r'(Y/n): ', 'y')
  281. t.expect(r'(Y/n): ', 'y') # fee OK?
  282. t.expect(r'(y/N): ', '\n') # add comment?
  283. t.expect(r'(y/N): ', 'y') # save?
  284. t.read()
  285. self.remove_device_online()
  286. return t
  287. def alice_txbump1(self):
  288. return self._alice_txbump(bad_tx_expect='unsigned transaction', idx=0)
  289. def alice_txbump2(self):
  290. self._wait_signed('transaction')
  291. return self._alice_txbump(bad_tx_expect='unsent transaction')
  292. def alice_txbump3(self):
  293. return self._alice_txbump()
  294. def alice_txbump4(self):
  295. sid = self._user_sid('alice')
  296. return self._alice_txbump(
  297. fee_opt = '--fee=3s',
  298. output_args = [f'{self.burn_addr},7.654321', f'{sid}:C:1'],
  299. low_fee_fix = '300s',
  300. idx = 0)
  301. def alice_txbump_abort1(self):
  302. if not self.proto.cap('rbf'):
  303. return 'skip'
  304. return self._alice_txsend_abort(shred_expect=['Shredding .*arawtx'])
  305. def alice_txbump5(self):
  306. sid = self._user_sid('alice')
  307. return self._alice_txbump(
  308. fee_opt = '--fee=400s',
  309. output_args = ['data:message for posterity', f'{self.burn_addr},7.654321', f'{sid}:C:1'])
  310. def alice_txbump6(self):
  311. return self._alice_txbump(idx=1, fee_opt='--fee=250s', orig_tx_expect='1.23456')
  312. def alice_bal2(self):
  313. return self.user_bal('alice', self.bal2_chk[self.coin])