ct_swap.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734
  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.ct_swap: asset swap tests for the cmdtest.py test suite
  12. """
  13. from pathlib import Path
  14. from mmgen.protocol import init_proto
  15. from ..include.common import make_burn_addr, gr_uc
  16. from .common import dfl_bip39_file
  17. from .thornode import run_thornode_server
  18. from .ct_autosign import CmdTestAutosign, CmdTestAutosignThreaded
  19. from .ct_regtest import CmdTestRegtest, rt_data, dfl_wcls, rt_pw, cfg, strip_ansi_escapes
  20. sample1 = gr_uc[:24]
  21. sample2 = '00010203040506'
  22. def thornode_server_start():
  23. import threading
  24. t = threading.Thread(target=run_thornode_server, name='Thornode server thread')
  25. t.daemon = True
  26. t.start()
  27. class CmdTestSwap(CmdTestRegtest, CmdTestAutosignThreaded):
  28. bdb_wallet = True
  29. networks = ('btc',)
  30. tmpdir_nums = [37]
  31. passthru_opts = ('rpc_backend',)
  32. coins = ('btc',)
  33. need_daemon = True
  34. cmd_group_in = (
  35. ('subgroup.init_data', []),
  36. ('subgroup.data', ['init_data']),
  37. ('subgroup.init_swap', []),
  38. ('subgroup.create', ['init_swap']),
  39. ('subgroup.create_bad', ['init_swap']),
  40. ('subgroup.signsend', ['init_swap']),
  41. ('subgroup.signsend_bad', ['init_swap']),
  42. ('subgroup.autosign', ['init_data', 'signsend']),
  43. ('stop', 'stopping regtest daemons'),
  44. )
  45. cmd_subgroups = {
  46. 'init_data': (
  47. 'Initialize regtest setup for OP_RETURN data operations',
  48. ('setup', 'regtest (Bob and Alice) mode setup'),
  49. ('walletcreate_bob', 'wallet creation (Bob)'),
  50. ('addrgen_bob', 'address generation (Bob)'),
  51. ('addrimport_bob', 'importing Bob’s addresses'),
  52. ('fund_bob1', 'funding Bob’s wallet (bech32)'),
  53. ('fund_bob2', 'funding Bob’s wallet (native Segwit)'),
  54. ('bob_bal', 'displaying Bob’s balance'),
  55. ),
  56. 'data': (
  57. 'OP_RETURN data operations',
  58. ('data_tx1_create', 'Creating a transaction with OP_RETURN data (hex-encoded UTF-8)'),
  59. ('data_tx1_sign', 'Signing the transaction'),
  60. ('data_tx1_send', 'Sending the transaction'),
  61. ('data_tx1_chk', 'Checking the sent transaction'),
  62. ('generate3', 'Generate 3 blocks'),
  63. ('data_tx2_do', 'Creating and sending a transaction with OP_RETURN data (binary)'),
  64. ('data_tx2_chk', 'Checking the sent transaction'),
  65. ('generate3', 'Generate 3 blocks'),
  66. ('bob_listaddrs', 'Display Bob’s addresses'),
  67. ),
  68. 'init_swap': (
  69. 'Initialize regtest setup for swap operations',
  70. ('setup_send_coin', 'setting up the sending coin regtest blockchain'),
  71. ('walletcreate_bob', 'wallet creation (Bob)'),
  72. ('addrgen_bob_send', 'address generation (Bob, sending coin)'),
  73. ('addrimport_bob_send', 'importing Bob’s addresses (sending coin)'),
  74. ('fund_bob_send', 'funding Bob’s wallet (bech32)'),
  75. ('bob_bal_send', 'displaying Bob’s send balance'),
  76. ('setup_recv_coin', 'setting up the receiving coin regtest blockchain'),
  77. ('addrgen_bob_recv', 'address generation (Bob, receiving coin)'),
  78. ('addrimport_bob_recv', 'importing Bob’s addresses (receiving coin)'),
  79. ('fund_bob_recv1', 'funding Bob’s wallet (bech32)'),
  80. ('fund_bob_recv2', 'funding Bob’s wallet (native Segwit)'),
  81. ('addrgen_bob_recv_subwallet', 'address generation (Bob, receiving coin)'),
  82. ('addrimport_bob_recv_subwallet', 'importing Bob’s addresses (receiving coin)'),
  83. ('fund_bob_recv_subwallet', 'funding Bob’s subwwallet (native Segwit)'),
  84. ('bob_bal_recv', 'displaying Bob’s receive balance'),
  85. ),
  86. 'create': (
  87. 'Swap TX create operations (BCH => LTC)',
  88. ('swaptxcreate1', 'creating a swap transaction (full args)'),
  89. ('swaptxcreate2', 'creating a swap transaction (coin args only)'),
  90. ('swaptxcreate3', 'creating a swap transaction (no chg arg)'),
  91. ('swaptxcreate4', 'creating a swap transaction (chg and dest by addrtype)'),
  92. ('swaptxcreate5', 'creating a swap transaction (chg and dest by addrlist ID)'),
  93. ('swaptxcreate6', 'creating a swap transaction (dest is non-wallet addr)'),
  94. ('swaptxcreate7', 'creating a swap transaction (coin-amt-coin)'),
  95. ),
  96. 'create_bad': (
  97. 'Swap TX create operations: error handling',
  98. ('swaptxcreate_bad1', 'creating a swap transaction (bad, used destination address)'),
  99. ('swaptxcreate_bad2', 'creating a swap transaction (bad, used change address)'),
  100. ('swaptxcreate_bad3', 'creating a swap transaction (bad, unsupported send coin)'),
  101. ('swaptxcreate_bad4', 'creating a swap transaction (bad, unsupported recv coin)'),
  102. ('swaptxcreate_bad5', 'creating a swap transaction (bad, malformed cmdline)'),
  103. ('swaptxcreate_bad6', 'creating a swap transaction (bad, malformed cmdline)'),
  104. ('swaptxcreate_bad7', 'creating a swap transaction (bad, bad user input, user exit)'),
  105. ('swaptxcreate_bad8', 'creating a swap transaction (bad, non-MMGen change address)'),
  106. ('swaptxcreate_bad9', 'creating a swap transaction (bad, invalid addrtype)'),
  107. ),
  108. 'signsend': (
  109. 'Swap TX create, sign and send operations (LTC => BCH)',
  110. ('swaptxsign1_create', 'creating a swap transaction (full args)'),
  111. ('swaptxsign1', 'signing the transaction'),
  112. ('swaptxsend1', 'sending the transaction'),
  113. ('swaptxsend1_status', 'getting status of sent transaction'),
  114. ('generate1', 'generating a block'),
  115. ('swaptxsign2_create', 'creating a swap transaction (non-wallet swap address)'),
  116. ('swaptxsign2', 'signing the transaction'),
  117. ('swaptxsend2', 'sending the transaction'),
  118. ('mempool1', 'viewing the mempool'),
  119. ('swaptxbump1', 'bumping the transaction'),
  120. ('swaptxsign3', 'signing the transaction'),
  121. ('swaptxsend3', 'sending the transaction'),
  122. ('mempool1', 'viewing the mempool'),
  123. ('swaptxbump2', 'bumping the transaction again'),
  124. ('swaptxsign4', 'signing the transaction'),
  125. ('swaptxsend4', 'sending the transaction'),
  126. ('mempool1', 'viewing the mempool'),
  127. ('generate1', 'generating a block'),
  128. ('swap_bal1', 'checking the balance'),
  129. ('swaptxsign1_do', 'creating, signing and sending a swap transaction'),
  130. ('generate1', 'generating a block'),
  131. ('swap_bal2', 'checking the balance'),
  132. ),
  133. 'signsend_bad': (
  134. 'Swap TX create, sign and send operations: error handling',
  135. ('swaptxsign_bad1_create', 'creating a swap transaction (non-wallet swap address)'),
  136. ('swaptxsign_bad1', 'signing the transaction (non-wallet swap address)'),
  137. ('swaptxsign_bad2_create', 'creating a swap transaction'),
  138. ('swaptxsign_bad2', 'signing the transaction'),
  139. ('swaptxsend_bad2', 'sending the transaction (swap quote expired)'),
  140. ),
  141. 'autosign': (
  142. 'Swap TX operations with autosigning (BTC => LTC)',
  143. ('run_setup_bip39', 'setting up offline autosigning'),
  144. ('swap_wait_loop_start', 'starting autosign wait loop'),
  145. ('autosign_swaptxcreate1', 'creating a swap transaction'),
  146. ('autosign_swaptxsend1', 'sending the transaction'),
  147. ('autosign_swaptxbump1', 'bumping the transaction'),
  148. ('autosign_swaptxsend2', 'sending the transaction'),
  149. ('generate0', 'generating a block'),
  150. ('swap_bal3', 'checking the balance'),
  151. ('wait_loop_kill', 'stopping autosign wait loop'),
  152. ),
  153. }
  154. def __init__(self, trunner, cfgs, spawn):
  155. CmdTestAutosignThreaded.__init__(self, trunner, cfgs, spawn)
  156. CmdTestRegtest.__init__(self, trunner, cfgs, spawn)
  157. if trunner is None:
  158. return
  159. globals_dict = globals()
  160. for k in rt_data:
  161. globals_dict[k] = rt_data[k]['btc']
  162. self.protos = [init_proto(cfg, k, network='regtest', need_amt=True) for k in ('btc', 'ltc', 'bch')]
  163. thornode_server_start() # TODO: stop server when test group finishes executing
  164. self.opts.append('--bob')
  165. @property
  166. def sid(self):
  167. return self._user_sid('bob')
  168. def walletcreate_bob(self):
  169. dest = Path(self.tr.data_dir, 'regtest', 'bob')
  170. dest.mkdir(exist_ok=True)
  171. t = self.spawn('mmgen-walletconv', [
  172. '--quiet',
  173. '--usr-randchars=0',
  174. '--hash-preset=1',
  175. '--label=SwapWalletLabel',
  176. f'--outdir={str(dest)}',
  177. dfl_bip39_file])
  178. t.expect('wallet: ', rt_pw + '\n')
  179. t.expect('phrase: ', rt_pw + '\n')
  180. t.written_to_file('wallet')
  181. return t
  182. def _addrgen_bob(self, proto_idx, mmtypes, subseed_idx=None):
  183. return self.addrgen('bob', subseed_idx=subseed_idx, mmtypes=mmtypes, proto=self.protos[proto_idx])
  184. def _addrimport_bob(self, proto_idx):
  185. return self.addrimport('bob', mmtypes=['S', 'B'], proto=self.protos[proto_idx])
  186. def _fund_bob(self, proto_idx, addrtype_code, amt):
  187. return self.fund_wallet('bob', addrtype_code, amt, proto=self.protos[proto_idx])
  188. def _bob_bal(self, proto_idx, bal, skip_check=False):
  189. return self.user_bal('bob', bal, proto=self.protos[proto_idx], skip_check=skip_check)
  190. def addrgen_bob(self):
  191. return self._addrgen_bob(0, ['S', 'B'])
  192. def addrimport_bob(self):
  193. return self._addrimport_bob(0)
  194. def fund_bob1(self):
  195. return self._fund_bob(0, 'B', '500')
  196. def fund_bob2(self):
  197. return self._fund_bob(0, 'S', '500')
  198. def bob_bal(self):
  199. return self._bob_bal(0, '1000')
  200. def data_tx1_create(self):
  201. return self._data_tx_create('1', 'B:2', 'B:3', 'data', sample1)
  202. def _data_tx_create(self, src, dest, chg, pfx, sample):
  203. t = self.spawn(
  204. 'mmgen-txcreate',
  205. ['-d', self.tmpdir, '-B', '--bob', f'{self.sid}:{dest},1', f'{self.sid}:{chg}', f'{pfx}:{sample}'])
  206. return self.txcreate_ui_common(t, menu=[], inputs='1', interactive_fee='3s')
  207. def data_tx1_sign(self):
  208. return self._data_tx_sign()
  209. def _data_tx_sign(self):
  210. fn = self.get_file_with_ext('rawtx')
  211. t = self.spawn('mmgen-txsign', ['-d', self.tmpdir, '--bob', fn])
  212. t.view_tx('v')
  213. t.passphrase(dfl_wcls.desc, rt_pw)
  214. t.do_comment(None)
  215. t.expect('(Y/n): ', 'y')
  216. t.written_to_file('Signed transaction')
  217. return t
  218. def data_tx1_send(self):
  219. return self._data_tx_send()
  220. def _data_tx_send(self):
  221. fn = self.get_file_with_ext('sigtx')
  222. t = self.spawn('mmgen-txsend', ['-q', '-d', self.tmpdir, '--bob', fn])
  223. t.expect('view: ', 'n')
  224. t.expect('(y/N): ', '\n')
  225. t.expect('to confirm: ', 'YES\n')
  226. t.written_to_file('Sent transaction')
  227. return t
  228. def data_tx1_chk(self):
  229. return self._data_tx_chk(sample1.encode().hex())
  230. def data_tx2_do(self):
  231. return self._data_tx_do('2', 'B:4', 'B:5', 'hexdata', sample2, 'v')
  232. def data_tx2_chk(self):
  233. return self._data_tx_chk(sample2)
  234. def _data_tx_do(self, src, dest, chg, pfx, sample, view):
  235. t = self.user_txdo(
  236. user = 'bob',
  237. fee = '30s',
  238. outputs_cl = [f'{self.sid}:{dest},1', f'{self.sid}:{chg}', f'{pfx}:{sample}'],
  239. outputs_list = src,
  240. add_comment = 'Transaction with OP_RETURN data',
  241. return_early = True)
  242. t.view_tx(view)
  243. if view == 'v':
  244. t.expect(sample)
  245. t.expect('amount:')
  246. t.passphrase(dfl_wcls.desc, rt_pw)
  247. t.written_to_file('Signed transaction')
  248. self._do_confirm_send(t)
  249. t.expect('Transaction sent')
  250. return t
  251. def _data_tx_chk(self, sample):
  252. mp = self._get_mempool(do_msg=True)
  253. assert len(mp) == 1
  254. self.write_to_tmpfile('data_tx1_id', mp[0]+'\n')
  255. tx_hex = self._do_cli(['getrawtransaction', mp[0]])
  256. tx = self._do_cli(['decoderawtransaction', tx_hex], decode_json=True)
  257. v0 = tx['vout'][0]
  258. assert v0['scriptPubKey']['hex'] == f'6a{(len(sample) // 2):02x}{sample}'
  259. assert v0['scriptPubKey']['type'] == 'nulldata'
  260. assert v0['value'] == "0.00000000"
  261. return 'ok'
  262. def generate3(self):
  263. return self.generate(3)
  264. def bob_listaddrs(self):
  265. t = self.spawn('mmgen-tool', ['--bob', 'listaddresses'])
  266. return t
  267. def setup_send_coin(self):
  268. self.user_sids = {}
  269. return self._setup(proto=self.protos[2], remove_datadir=True)
  270. def addrgen_bob_send(self):
  271. return self._addrgen_bob(2, ['C'])
  272. def addrimport_bob_send(self):
  273. return self.addrimport('bob', mmtypes=['C'], proto=self.protos[2])
  274. def fund_bob_send(self):
  275. return self._fund_bob(2, 'C', '5')
  276. def bob_bal_send(self):
  277. return self._bob_bal(2, '5')
  278. def setup_recv_coin(self):
  279. return self._setup(proto=self.protos[1], remove_datadir=False)
  280. def addrgen_bob_recv(self):
  281. return self._addrgen_bob(1, ['S', 'B'])
  282. def addrimport_bob_recv(self):
  283. return self._addrimport_bob(1)
  284. def fund_bob_recv1(self):
  285. return self._fund_bob(1, 'S', '5')
  286. def fund_bob_recv2(self):
  287. return self._fund_bob(1, 'B', '5')
  288. def addrgen_bob_recv_subwallet(self):
  289. return self._addrgen_bob(1, ['C', 'B'], subseed_idx='29L')
  290. def addrimport_bob_recv_subwallet(self):
  291. return self._subwallet_addrimport('bob', '29L', ['C', 'B'], proto=self.protos[1])
  292. def fund_bob_recv_subwallet(self, proto_idx=1, amt='5'):
  293. coin_arg = f'--coin={self.protos[proto_idx].coin}'
  294. t = self.spawn('mmgen-tool', ['--bob', coin_arg, 'listaddresses'])
  295. addr = [s for s in strip_ansi_escapes(t.read()).splitlines() if 'C:1 No' in s][0].split()[3]
  296. t = self.spawn('mmgen-regtest', [coin_arg, 'send', addr, str(amt)], no_passthru_opts=True, no_msg=True)
  297. return t
  298. def bob_bal_recv(self):
  299. return self._bob_bal(1, '15')
  300. def _swaptxcreate_ui_common(
  301. self,
  302. t,
  303. *,
  304. inputs = '1',
  305. interactive_fee = None,
  306. file_desc = 'Unsigned transaction',
  307. reload_quote = False,
  308. sign_and_send = False,
  309. expect = None):
  310. t.expect('abel:\b', 'q')
  311. t.expect('to spend: ', f'{inputs}\n')
  312. if reload_quote:
  313. t.expect('to continue: ', 'r') # reload swap quote
  314. t.expect('to continue: ', '\n') # exit swap quote view
  315. t.expect('(Y/n): ', 'y') # fee OK?
  316. t.expect('(Y/n): ', 'y') # change OK?
  317. t.expect('(y/N): ', 'n') # add comment?
  318. if reload_quote:
  319. t.expect('to continue: ', 'r') # reload swap quote
  320. t.expect('to continue: ', '\n') # exit swap quote view
  321. t.expect('view: ', 'y') # view TX
  322. if expect:
  323. t.expect(expect)
  324. t.expect('to continue: ', '\n')
  325. if sign_and_send:
  326. t.passphrase(dfl_wcls.desc, rt_pw)
  327. t.expect('to confirm: ', 'YES\n')
  328. else:
  329. t.expect('(y/N): ', 'y') # save?
  330. t.written_to_file(file_desc)
  331. return t
  332. def _swaptxcreate(self, args, *, action='txcreate', add_opts=[], exit_val=None):
  333. return self.spawn(
  334. f'mmgen-swap{action}',
  335. ['-q', '-d', self.tmpdir, '-B', '--bob']
  336. + add_opts
  337. + args,
  338. exit_val = exit_val)
  339. def swaptxcreate1(self, idx=3):
  340. return self._swaptxcreate_ui_common(
  341. self._swaptxcreate(
  342. ['BCH', '1.234', f'{self.sid}:C:{idx}', 'LTC', f'{self.sid}:B:3'],
  343. add_opts = ['--trade-limit=0%']),
  344. expect = ':3541e5/1/0')
  345. def swaptxcreate2(self):
  346. t = self._swaptxcreate(
  347. ['BCH', 'LTC'],
  348. add_opts = ['--no-quiet', '--trade-limit=3.337%'])
  349. t.expect('Enter a number> ', '1')
  350. t.expect('OK? (Y/n): ', 'y')
  351. return self._swaptxcreate_ui_common(t, reload_quote=True, expect=':1386e6/1/0')
  352. def swaptxcreate3(self):
  353. return self._swaptxcreate_ui_common(
  354. self._swaptxcreate(
  355. ['BCH', 'LTC', f'{self.sid}:B:3'],
  356. add_opts = ['--trade-limit=10.1%']),
  357. expect = ':1289e6/1/0')
  358. def swaptxcreate4(self):
  359. t = self._swaptxcreate(
  360. ['BCH', '1.234', 'C', 'LTC', 'B'],
  361. add_opts = ['--trade-limit=-1.123%'])
  362. t.expect('OK? (Y/n): ', 'y')
  363. t.expect('Enter a number> ', '1')
  364. t.expect('OK? (Y/n): ', 'y')
  365. return self._swaptxcreate_ui_common(t, expect=':358e6/1/0')
  366. def swaptxcreate5(self):
  367. t = self._swaptxcreate(
  368. ['BCH', '1.234', f'{self.sid}:C', 'LTC', f'{self.sid}:B'],
  369. add_opts = ['--trade-limit=3.6'])
  370. t.expect('OK? (Y/n): ', 'y')
  371. t.expect('OK? (Y/n): ', 'y')
  372. return self._swaptxcreate_ui_common(t, expect=':36e7/1/0')
  373. def swaptxcreate6(self):
  374. addr = make_burn_addr(self.protos[1], mmtype='bech32')
  375. t = self._swaptxcreate(
  376. ['BCH', '1.234', f'{self.sid}:C', 'LTC', addr],
  377. add_opts = ['--trade-limit=2.7%'])
  378. t.expect('OK? (Y/n): ', 'y')
  379. t.expect('to confirm: ', 'YES\n')
  380. return self._swaptxcreate_ui_common(t, expect=':3445e5/1/0')
  381. def swaptxcreate7(self):
  382. t = self._swaptxcreate(['BCH', '0.56789', 'LTC'])
  383. t.expect('OK? (Y/n): ', 'y')
  384. t.expect('Enter a number> ', '1')
  385. t.expect('OK? (Y/n): ', 'y')
  386. return self._swaptxcreate_ui_common(t, expect=':0/1/0')
  387. def _swaptxcreate_bad(self, args, *, exit_val=1, expect1=None, expect2=None):
  388. t = self._swaptxcreate(args, exit_val=exit_val)
  389. if expect1:
  390. t.expect(expect1)
  391. if expect2:
  392. t.expect(expect2)
  393. return t
  394. def swaptxcreate_bad1(self):
  395. t = self._swaptxcreate_bad(
  396. ['BCH', '1.234', f'{self.sid}:C:3', 'LTC', f'{self.sid}:S:1'],
  397. expect1 = 'Requested destination address',
  398. expect2 = 'Address reuse harms your privacy')
  399. t.expect('(y/N): ', 'n')
  400. return t
  401. def swaptxcreate_bad2(self):
  402. t = self._swaptxcreate_bad(
  403. ['BCH', '1.234', f'{self.sid}:C:1', 'LTC', f'{self.sid}:S:2'],
  404. expect1 = 'Requested change address',
  405. expect2 = 'Address reuse harms your privacy')
  406. t.expect('(y/N): ', 'n')
  407. return t
  408. def swaptxcreate_bad3(self):
  409. return self._swaptxcreate_bad(['RTC', 'LTC'], expect1='unsupported send coin')
  410. def swaptxcreate_bad4(self):
  411. return self._swaptxcreate_bad(['LTC', 'XTC'], expect1='unsupported receive coin')
  412. def swaptxcreate_bad5(self):
  413. return self._swaptxcreate_bad(['LTC'], expect1='USAGE:')
  414. def swaptxcreate_bad6(self):
  415. return self._swaptxcreate_bad(['LTC', '1.2345'], expect1='USAGE:')
  416. def swaptxcreate_bad7(self):
  417. t = self._swaptxcreate(['BCH', 'LTC'], exit_val=1)
  418. t.expect('Enter a number> ', '3')
  419. t.expect('Enter a number> ', '1')
  420. t.expect('OK? (Y/n): ', 'n')
  421. return t
  422. def swaptxcreate_bad8(self):
  423. addr = make_burn_addr(self.protos[2], mmtype='compressed')
  424. t = self._swaptxcreate_bad(['BCH', '1.234', addr, 'LTC', 'S'])
  425. t.expect('to confirm: ', 'NO\n')
  426. return t
  427. def swaptxcreate_bad9(self):
  428. return self._swaptxcreate_bad(['BCH', '1.234', 'S', 'LTC', 'B'], exit_val=2, expect1='invalid command-')
  429. def swaptxsign1_create(self):
  430. self.get_file_with_ext('rawtx', delete_all=True)
  431. return self._swaptxcreate_ui_common(
  432. self._swaptxcreate(['LTC', '4.321', f'{self.sid}:S:2', 'BCH', f'{self.sid}:C:2']))
  433. def swaptxsign1(self):
  434. return self._swaptxsign()
  435. def swaptxsend1(self):
  436. return self._swaptxsend1()
  437. def swaptxsend1_status(self):
  438. t = self._swaptxsend1(add_opts=['--status'], spawn_only=True)
  439. t.expect('in mempool')
  440. return t
  441. def _swaptxsend1(self, *, add_opts=[], spawn_only=False):
  442. return self._swaptxsend(
  443. add_opts = add_opts + [
  444. # test overriding host:port with coin-specific options:
  445. '--rpc-host=unreachable', # unreachable host
  446. '--ltc-rpc-host=localhost',
  447. '--rpc-port=46381', # bad port
  448. '--ltc-rpc-port=20680',
  449. ],
  450. spawn_only = spawn_only)
  451. def _swaptxsend(self, *, add_opts=[], spawn_only=False):
  452. fn = self.get_file_with_ext('sigtx')
  453. t = self.spawn('mmgen-txsend', add_opts + ['-q', '-d', self.tmpdir, '--bob', fn])
  454. if spawn_only:
  455. return t
  456. t.expect('view: ', 'v')
  457. t.expect('(y/N): ', 'n')
  458. t.expect('to confirm: ', 'YES\n')
  459. return t
  460. def _swaptxsign(self, *, add_opts=[], expect=None):
  461. self.get_file_with_ext('sigtx', delete_all=True)
  462. fn = self.get_file_with_ext('rawtx')
  463. t = self.spawn('mmgen-txsign', add_opts + ['-d', self.tmpdir, '--bob', fn])
  464. t.view_tx('t')
  465. if expect:
  466. t.expect(expect)
  467. t.passphrase(dfl_wcls.desc, rt_pw)
  468. t.do_comment(None)
  469. t.expect('(Y/n): ', 'y')
  470. t.written_to_file('Signed transaction')
  471. return t
  472. def swaptxsign2_create(self):
  473. self.get_file_with_ext('rawtx', delete_all=True)
  474. addr = make_burn_addr(self.protos[2], mmtype='compressed')
  475. t = self._swaptxcreate(['LTC', '4.56789', f'{self.sid}:S:3', 'BCH', addr])
  476. t.expect('to confirm: ', 'YES\n') # confirm non-MMGen destination
  477. return self._swaptxcreate_ui_common(t)
  478. def swaptxsign2(self):
  479. return self._swaptxsign(add_opts=['--allow-non-wallet-swap'], expect='swap to non-wallet address')
  480. def swaptxsend2(self):
  481. return self._swaptxsend()
  482. def swaptxbump1(self):
  483. return self._swaptxbump('20s', add_opts=['--allow-non-wallet-swap'])
  484. def swaptxbump2(self): # create one-output TX back to self to rescue funds
  485. return self._swaptxbump('40s', output_args=[f'{self.sid}:S:4'])
  486. def _swaptxbump(self, fee, *, add_opts=[], output_args=[], exit_val=None):
  487. self.get_file_with_ext('rawtx', delete_all=True)
  488. fn = self.get_file_with_ext('sigtx')
  489. t = self.spawn(
  490. 'mmgen-txbump',
  491. ['-q', '-d', self.tmpdir, '--bob'] + add_opts + output_args + [fn],
  492. exit_val = exit_val)
  493. return self._swaptxbump_ui_common(t, interactive_fee=fee, new_outputs=bool(output_args))
  494. def _swaptxbump_ui_common_new_outputs(self, t, *, inputs=None, interactive_fee=None, file_desc=None):
  495. return self._swaptxbump_ui_common(t, interactive_fee=interactive_fee, new_outputs=True)
  496. def _swaptxbump_ui_common(self, t, *, inputs=None, interactive_fee=None, file_desc=None, new_outputs=False):
  497. if new_outputs:
  498. t.expect('fee: ', interactive_fee + '\n')
  499. t.expect('(Y/n): ', 'y') # fee ok?
  500. t.expect('(Y/n): ', 'y') # change ok?
  501. else:
  502. t.expect('ENTER for the change output): ', '\n')
  503. t.expect('(Y/n): ', 'y') # confirm deduct from chg output
  504. t.expect('to continue: ', '\n') # exit swap quote
  505. t.expect('fee: ', interactive_fee + '\n')
  506. t.expect('(Y/n): ', 'y') # fee ok?
  507. t.expect('(y/N): ', 'n') # comment?
  508. t.expect('(y/N): ', 'y') # save?
  509. return t
  510. def swaptxsign3(self):
  511. return self.swaptxsign2()
  512. def swaptxsend3(self):
  513. return self._swaptxsend()
  514. def swaptxsign4(self):
  515. return self._swaptxsign()
  516. def swaptxsend4(self):
  517. return self._swaptxsend()
  518. def _generate_for_proto(self, proto_idx):
  519. return self.generate(num_blocks=1, add_opts=[f'--coin={self.protos[proto_idx].coin}'])
  520. def generate0(self):
  521. return self._generate_for_proto(0)
  522. def generate1(self):
  523. return self._generate_for_proto(1)
  524. def generate2(self):
  525. return self._generate_for_proto(2)
  526. def swap_bal1(self):
  527. return self._bob_bal(1, '10.67894238')
  528. def swap_bal2(self):
  529. return self._bob_bal(1, '8.90148152')
  530. def swap_bal3(self):
  531. return self._bob_bal(0, '999.99990407')
  532. def swaptxsign1_do(self):
  533. return self._swaptxcreate_ui_common(
  534. self._swaptxcreate(['LTC', '1.777444', f'{self.sid}:B:2', 'BCH', f'{self.sid}:C:2'], action='txdo'),
  535. sign_and_send = True,
  536. file_desc = 'Sent transaction')
  537. def swaptxsign_bad1_create(self):
  538. self.get_file_with_ext('rawtx', delete_all=True)
  539. return self.swaptxcreate6()
  540. def swaptxsign_bad1(self):
  541. self.get_file_with_ext('sigtx', delete_all=True)
  542. return self._swaptxsign_bad('non-wallet address forbidden')
  543. def _swaptxsign_bad(self, expect, *, add_opts=[], exit_val=1):
  544. fn = self.get_file_with_ext('rawtx')
  545. t = self.spawn('mmgen-txsign', add_opts + ['-d', self.tmpdir, '--bob', fn], exit_val=exit_val)
  546. t.expect('view: ', '\n')
  547. t.expect(expect)
  548. return t
  549. def swaptxsign_bad2_create(self):
  550. self.get_file_with_ext('rawtx', delete_all=True)
  551. return self.swaptxcreate1(idx=4)
  552. def swaptxsign_bad2(self):
  553. return self._swaptxsign()
  554. def swaptxsend_bad2(self):
  555. import json
  556. from mmgen.tx.file import json_dumps
  557. from mmgen.util import make_chksum_6
  558. fn = self.get_file_with_ext('sigtx')
  559. with open(fn) as fh:
  560. data = json.load(fh)
  561. data['MMGenTransaction']['swap_quote_expiry'] -= 2400
  562. data['chksum'] = make_chksum_6(json_dumps(data['MMGenTransaction']))
  563. with open(fn, 'w') as fh:
  564. json.dump(data, fh)
  565. t = self.spawn('mmgen-txsend', ['-d', self.tmpdir, '--bob', fn], exit_val=2)
  566. t.expect('expired')
  567. return t
  568. run_setup_bip39 = CmdTestAutosign.run_setup_bip39
  569. run_setup = CmdTestAutosign.run_setup
  570. def swap_wait_loop_start(self):
  571. return self.wait_loop_start(add_opts=['--allow-non-wallet-swap'])
  572. def autosign_swaptxcreate1(self):
  573. return self._user_txcreate(
  574. 'bob',
  575. progname = 'swaptxcreate',
  576. input_handler = self._swaptxcreate_ui_common,
  577. output_args = ['BTC', '8.88', f'{self.sid}:S:3', 'LTC', f'{self.sid}:S:3'])
  578. def autosign_swaptxsend1(self):
  579. return self._user_txsend('bob', need_rbf=True)
  580. def autosign_swaptxbump1(self):
  581. return self._user_txcreate(
  582. 'bob',
  583. progname = 'txbump',
  584. input_handler = self._swaptxbump_ui_common_new_outputs,
  585. output_args = [f'{self.sid}:S:3'])
  586. def autosign_swaptxsend2(self):
  587. return self._user_txsend('bob', need_rbf=True)
  588. # admin methods:
  589. def sleep(self):
  590. import time
  591. time.sleep(1000)
  592. return 'ok'
  593. def listaddresses0(self):
  594. return self._listaddresses(0)
  595. def listaddresses1(self):
  596. return self._listaddresses(1)
  597. def listaddresses2(self):
  598. return self._listaddresses(2)
  599. def _listaddresses(self, proto_idx):
  600. return self.user_bal('bob', None, proto=self.protos[proto_idx], skip_check=True)
  601. def mempool0(self):
  602. return self._mempool(0)
  603. def mempool1(self):
  604. return self._mempool(1)
  605. def mempool2(self):
  606. return self._mempool(2)
  607. def _mempool(self, proto_idx):
  608. self.spawn('', msg_only=True)
  609. data = self._do_cli(['getrawmempool'], add_opts=[f'--coin={self.protos[proto_idx].coin}'])
  610. assert data
  611. return 'ok'