|
@@ -1,7 +1,7 @@
|
|
|
#!/usr/bin/env python3
|
|
#!/usr/bin/env python3
|
|
|
#
|
|
#
|
|
|
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
|
# MMGen Wallet, a terminal-based cryptocurrency wallet
|
|
|
-# Copyright (C)2013-2024 The MMGen Project <mmgen@tuta.io>
|
|
|
|
|
|
|
+# Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
|
|
|
#
|
|
#
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
# it under the terms of the GNU General Public License as published by
|
|
@@ -54,7 +54,8 @@ from .common import (
|
|
|
tw_comment_lat_cyr_gr,
|
|
tw_comment_lat_cyr_gr,
|
|
|
tw_comment_zh,
|
|
tw_comment_zh,
|
|
|
tx_comment_jp,
|
|
tx_comment_jp,
|
|
|
- get_env_without_debug_vars)
|
|
|
|
|
|
|
+ get_env_without_debug_vars,
|
|
|
|
|
+ rt_pw)
|
|
|
|
|
|
|
|
from .ct_base import CmdTestBase
|
|
from .ct_base import CmdTestBase
|
|
|
from .ct_shared import CmdTestShared
|
|
from .ct_shared import CmdTestShared
|
|
@@ -66,14 +67,13 @@ dfl_wcls = get_wallet_cls('mmgen')
|
|
|
|
|
|
|
|
tx_fee = rtFundAmt = rtFee = rtBals = rtBals_gb = rtBobOp3 = rtAmts = None # ruff
|
|
tx_fee = rtFundAmt = rtFee = rtBals = rtBals_gb = rtBobOp3 = rtAmts = None # ruff
|
|
|
|
|
|
|
|
-rt_pw = 'abc-α'
|
|
|
|
|
rt_data = {
|
|
rt_data = {
|
|
|
'tx_fee': {'btc':'0.0001', 'bch':'0.001', 'ltc':'0.01'},
|
|
'tx_fee': {'btc':'0.0001', 'bch':'0.001', 'ltc':'0.01'},
|
|
|
'rtFundAmt': {'btc':'500', 'bch':'500', 'ltc':'5500'},
|
|
'rtFundAmt': {'btc':'500', 'bch':'500', 'ltc':'5500'},
|
|
|
'rtFee': {
|
|
'rtFee': {
|
|
|
- 'btc': ('20s', '10s', '60s', '31s', '10s', '20s'),
|
|
|
|
|
- 'bch': ('20s', '10s', '60s', '0.0001', '10s', '20s'),
|
|
|
|
|
- 'ltc': ('1000s', '500s', '1500s', '0.05', '400s', '1000s')
|
|
|
|
|
|
|
+ 'btc': ('20s', '10s', '60s', '31s', '10s', '20s', '40s'),
|
|
|
|
|
+ 'bch': ('20s', '10s', '60s', '0.0001', '10s', '20s', '40s'),
|
|
|
|
|
+ 'ltc': ('1000s', '500s', '1500s', '0.05', '400s', '1000s', '1200s')
|
|
|
},
|
|
},
|
|
|
'rtBals': {
|
|
'rtBals': {
|
|
|
'btc': ('499.9999488', '399.9998282', '399.9998147', '399.9996877',
|
|
'btc': ('499.9999488', '399.9998282', '399.9998147', '399.9996877',
|
|
@@ -291,8 +291,10 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|
|
('bob_send_maybe_rbf', 'sending funds to Alice (RBF, if supported)'),
|
|
('bob_send_maybe_rbf', 'sending funds to Alice (RBF, if supported)'),
|
|
|
('get_mempool1', 'mempool (before RBF bump)'),
|
|
('get_mempool1', 'mempool (before RBF bump)'),
|
|
|
('bob_rbf_status1', 'getting status of transaction'),
|
|
('bob_rbf_status1', 'getting status of transaction'),
|
|
|
- ('bob_rbf_bump', 'bumping RBF transaction'),
|
|
|
|
|
|
|
+ ('bob_rbf_bump_newoutputs', 'bumping RBF transaction (new outputs)'),
|
|
|
('get_mempool2', 'mempool (after RBF bump)'),
|
|
('get_mempool2', 'mempool (after RBF bump)'),
|
|
|
|
|
+ ('bob_rbf_bump', 'bumping RBF transaction'),
|
|
|
|
|
+ ('get_mempool3', 'mempool (after RBF bump)'),
|
|
|
('bob_rbf_status2', 'getting status of transaction after replacement'),
|
|
('bob_rbf_status2', 'getting status of transaction after replacement'),
|
|
|
('bob_rbf_status3', 'getting status of replacement transaction (mempool)'),
|
|
('bob_rbf_status3', 'getting status of replacement transaction (mempool)'),
|
|
|
('generate', 'mining a block'),
|
|
('generate', 'mining a block'),
|
|
@@ -1173,10 +1175,18 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|
|
t.written_to_file('Fee-bumped transaction')
|
|
t.written_to_file('Fee-bumped transaction')
|
|
|
return t
|
|
return t
|
|
|
|
|
|
|
|
|
|
+ def bob_rbf_bump_newoutputs(self):
|
|
|
|
|
+ return self._bob_rbf_bump(
|
|
|
|
|
+ ['--send', 'data:embedded forever', f'{self.burn_addr},0.1', f'{self._user_sid("bob")}:C:5'],
|
|
|
|
|
+ rtFee[6])
|
|
|
|
|
+
|
|
|
def bob_rbf_bump(self):
|
|
def bob_rbf_bump(self):
|
|
|
|
|
+ return self._bob_rbf_bump(['--send'], rtFee[2])
|
|
|
|
|
+
|
|
|
|
|
+ def _bob_rbf_bump(self, add_args, fee):
|
|
|
ext = ',{}]{x}.regtest.sigtx'.format(rtFee[1][:-1], x='-α' if cfg.debug_utf8 else '')
|
|
ext = ',{}]{x}.regtest.sigtx'.format(rtFee[1][:-1], x='-α' if cfg.debug_utf8 else '')
|
|
|
txfile = self.get_file_with_ext(ext, delete=False, no_dot=True)
|
|
txfile = self.get_file_with_ext(ext, delete=False, no_dot=True)
|
|
|
- return self.user_txbump('bob', self.tmpdir, txfile, rtFee[2], add_args=['--send'])
|
|
|
|
|
|
|
+ return self.user_txbump('bob', self.tmpdir, txfile, fee, add_args=add_args)
|
|
|
|
|
|
|
|
def generate(self, num_blocks=1, add_opts=[]):
|
|
def generate(self, num_blocks=1, add_opts=[]):
|
|
|
int(num_blocks)
|
|
int(num_blocks)
|
|
@@ -1197,77 +1207,68 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|
|
).read().strip()
|
|
).read().strip()
|
|
|
return json.loads(ret) if decode_json else ret
|
|
return json.loads(ret) if decode_json else ret
|
|
|
|
|
|
|
|
|
|
+ def get_mempool1(self):
|
|
|
|
|
+ return self._get_mempool_compare_txid(None, 'rbf_txid1')
|
|
|
|
|
+
|
|
|
|
|
+ def get_mempool2(self):
|
|
|
|
|
+ return self._get_mempool_compare_txid('rbf_txid1', 'rbf_txid2')
|
|
|
|
|
+
|
|
|
|
|
+ def get_mempool3(self):
|
|
|
|
|
+ return self._get_mempool_compare_txid('rbf_txid2', 'rbf_txid3')
|
|
|
|
|
+
|
|
|
def _get_mempool(self, do_msg=False):
|
|
def _get_mempool(self, do_msg=False):
|
|
|
if do_msg:
|
|
if do_msg:
|
|
|
self.spawn('', msg_only=True)
|
|
self.spawn('', msg_only=True)
|
|
|
return self._do_mmgen_regtest(['mempool'], decode_json=True)
|
|
return self._do_mmgen_regtest(['mempool'], decode_json=True)
|
|
|
|
|
|
|
|
- def get_mempool1(self):
|
|
|
|
|
|
|
+ def _get_mempool_compare_txid(self, txid1, txid2):
|
|
|
|
|
+ if not self.proto.cap('rbf'):
|
|
|
|
|
+ return 'skip'
|
|
|
mp = self._get_mempool(do_msg=True)
|
|
mp = self._get_mempool(do_msg=True)
|
|
|
if len(mp) != 1:
|
|
if len(mp) != 1:
|
|
|
die(4, 'Mempool has more or less than one TX!')
|
|
die(4, 'Mempool has more or less than one TX!')
|
|
|
- self.write_to_tmpfile('rbf_txid', mp[0]+'\n')
|
|
|
|
|
|
|
+ if txid1:
|
|
|
|
|
+ chk = self.read_from_tmpfile(txid1)
|
|
|
|
|
+ if chk.strip() == mp[0]:
|
|
|
|
|
+ die(4, 'TX in mempool has not changed! RBF bump failed')
|
|
|
|
|
+ self.write_to_tmpfile(txid2, mp[0]+'\n')
|
|
|
return 'ok'
|
|
return 'ok'
|
|
|
|
|
|
|
|
- def bob_rbf_status(self, fee, exp1, exp2='', exit_val=None):
|
|
|
|
|
|
|
+ def _bob_rbf_status(self, fee, exit_val=None, txid=None, confirmations=0):
|
|
|
if not self.proto.cap('rbf'):
|
|
if not self.proto.cap('rbf'):
|
|
|
return 'skip'
|
|
return 'skip'
|
|
|
|
|
+ if txid:
|
|
|
|
|
+ txid = self.read_from_tmpfile(txid).strip()
|
|
|
|
|
+ if confirmations:
|
|
|
|
|
+ r1 = f'Replacement transaction has {confirmations} confirmation'
|
|
|
|
|
+ r2 = rf'Replacing transactions:.*{txid}'
|
|
|
|
|
+ else:
|
|
|
|
|
+ r1, r2 = ('Transaction has been replaced', f'{txid} in mempool')
|
|
|
|
|
+ elif confirmations:
|
|
|
|
|
+ r1, r2 = (f'Transaction has {confirmations} confirmation', '')
|
|
|
|
|
+ else:
|
|
|
|
|
+ r1, r2 = ('in mempool, replaceable', '')
|
|
|
ext = ',{}]{x}.regtest.sigtx'.format(fee[:-1], x='-α' if cfg.debug_utf8 else '')
|
|
ext = ',{}]{x}.regtest.sigtx'.format(fee[:-1], x='-α' if cfg.debug_utf8 else '')
|
|
|
txfile = self.get_file_with_ext(ext, delete=False, no_dot=True)
|
|
txfile = self.get_file_with_ext(ext, delete=False, no_dot=True)
|
|
|
- return self.user_txsend_status('bob', txfile, exp1, exp2, exit_val=exit_val)
|
|
|
|
|
|
|
+ return self.user_txsend_status('bob', txfile, r1, r2, exit_val=exit_val)
|
|
|
|
|
|
|
|
def bob_rbf_status1(self):
|
|
def bob_rbf_status1(self):
|
|
|
- if not self.proto.cap('rbf'):
|
|
|
|
|
- return 'skip'
|
|
|
|
|
- return self.bob_rbf_status(rtFee[1], 'in mempool, replaceable')
|
|
|
|
|
-
|
|
|
|
|
- def get_mempool2(self):
|
|
|
|
|
- if not self.proto.cap('rbf'):
|
|
|
|
|
- return 'skip'
|
|
|
|
|
- mp = self._get_mempool(do_msg=True)
|
|
|
|
|
- if len(mp) != 1:
|
|
|
|
|
- die(4, 'Mempool has more or less than one TX!')
|
|
|
|
|
- chk = self.read_from_tmpfile('rbf_txid')
|
|
|
|
|
- if chk.strip() == mp[0]:
|
|
|
|
|
- die(4, 'TX in mempool has not changed! RBF bump failed')
|
|
|
|
|
- self.write_to_tmpfile('rbf_txid2', mp[0]+'\n')
|
|
|
|
|
- return 'ok'
|
|
|
|
|
|
|
+ return self._bob_rbf_status(rtFee[1])
|
|
|
|
|
|
|
|
def bob_rbf_status2(self):
|
|
def bob_rbf_status2(self):
|
|
|
- if not self.proto.cap('rbf'):
|
|
|
|
|
- return 'skip'
|
|
|
|
|
- new_txid = self.read_from_tmpfile('rbf_txid2').strip()
|
|
|
|
|
- return self.bob_rbf_status(
|
|
|
|
|
- rtFee[1],
|
|
|
|
|
- 'Transaction has been replaced',
|
|
|
|
|
- f'{new_txid} in mempool',
|
|
|
|
|
- exit_val = 0)
|
|
|
|
|
|
|
+ return self._bob_rbf_status(rtFee[1], txid='rbf_txid3')
|
|
|
|
|
|
|
|
def bob_rbf_status3(self):
|
|
def bob_rbf_status3(self):
|
|
|
- if not self.proto.cap('rbf'):
|
|
|
|
|
- return 'skip'
|
|
|
|
|
- return self.bob_rbf_status(rtFee[2], 'status: in mempool, replaceable')
|
|
|
|
|
|
|
+ return self._bob_rbf_status(rtFee[2])
|
|
|
|
|
|
|
|
def bob_rbf_status4(self):
|
|
def bob_rbf_status4(self):
|
|
|
- if not self.proto.cap('rbf'):
|
|
|
|
|
- return 'skip'
|
|
|
|
|
- new_txid = self.read_from_tmpfile('rbf_txid2').strip()
|
|
|
|
|
- return self.bob_rbf_status(rtFee[1],
|
|
|
|
|
- 'Replacement transaction has 1 confirmation',
|
|
|
|
|
- rf'Replacing transactions:\s+{new_txid}')
|
|
|
|
|
|
|
+ return self._bob_rbf_status(rtFee[1], txid='rbf_txid3', confirmations=1, exit_val=0)
|
|
|
|
|
|
|
|
def bob_rbf_status5(self):
|
|
def bob_rbf_status5(self):
|
|
|
- if not self.proto.cap('rbf'):
|
|
|
|
|
- return 'skip'
|
|
|
|
|
- return self.bob_rbf_status(rtFee[2], 'Transaction has 1 confirmation')
|
|
|
|
|
|
|
+ return self._bob_rbf_status(rtFee[2], confirmations=1, exit_val=0)
|
|
|
|
|
|
|
|
def bob_rbf_status6(self):
|
|
def bob_rbf_status6(self):
|
|
|
- if not self.proto.cap('rbf'):
|
|
|
|
|
- return 'skip'
|
|
|
|
|
- new_txid = self.read_from_tmpfile('rbf_txid2').strip()
|
|
|
|
|
- return self.bob_rbf_status(rtFee[1],
|
|
|
|
|
- 'Replacement transaction has 2 confirmations',
|
|
|
|
|
- rf'Replacing transactions:\s+{new_txid}')
|
|
|
|
|
|
|
+ return self._bob_rbf_status(rtFee[1], txid='rbf_txid3', confirmations=2, exit_val=0)
|
|
|
|
|
|
|
|
def _gen_pairs(self, n):
|
|
def _gen_pairs(self, n):
|
|
|
from mmgen.tool.api import tool_api
|
|
from mmgen.tool.api import tool_api
|