mmgen-txbump: support new outputs in the replacement TX
The former behavior permitted only increasing the transaction fee. Now the
replacement TX can contain entirely new outputs.
From mmgen-txbump --help:
If no outputs are specified, the original outputs will be used for the
replacement transaction, otherwise a new transaction will be created with
the outputs listed on the command line. The syntax for the output
arguments is identical to that of ‘mmgen-txcreate’.
Testing:
$ test/cmdtest.py regtest_legacy.main autosign_automount
$ test/cmdtest.py --coin=eth ethdev.main
This commit is contained in:
parent
d3b5ba23f3
commit
ef5f6e4b22
12 changed files with 213 additions and 84 deletions
|
|
@ -1 +1 @@
|
|||
15.1.dev15
|
||||
15.1.dev16
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@ opts_data = {
|
|||
Create, and optionally send and sign, a replacement transaction
|
||||
on networks that support replace-by-fee (RBF)
|
||||
""",
|
||||
'usage': f'[opts] [{gc.proj_name} TX file] [seed source] ...',
|
||||
'usage2': (
|
||||
f'[opts] [{gc.proj_name} TX file] [seed source] ...',
|
||||
f'[opts] {{u_args}} [{gc.proj_name} TX file] [seed source] ...',
|
||||
),
|
||||
'options': """
|
||||
-- -h, --help Print this help message
|
||||
-- --, --longhelp Print help message for long (global) options
|
||||
|
|
@ -80,6 +83,18 @@ opts_data = {
|
|||
-- -z, --show-hash-presets Show information on available hash presets
|
||||
""",
|
||||
'notes': """
|
||||
|
||||
With --autosign, the TX file argument is omitted, and the last submitted TX
|
||||
file on the removable device will be used.
|
||||
|
||||
If no outputs are specified, the original outputs will be used for the
|
||||
replacement transaction, otherwise a new transaction will be created with the
|
||||
outputs listed on the command line. The syntax for the output arguments is
|
||||
identical to that of ‘mmgen-txcreate’.
|
||||
|
||||
The user should take care to select a fee sufficient to ensure the original
|
||||
transaction is replaced in the mempool.
|
||||
|
||||
{e}{s}
|
||||
Seed source files must have the canonical extensions listed in the 'FileExt'
|
||||
column below:
|
||||
|
|
@ -88,6 +103,8 @@ column below:
|
|||
"""
|
||||
},
|
||||
'code': {
|
||||
'usage': lambda cfg, proto, help_notes, s: s.format(
|
||||
u_args = help_notes('txcreate_args', 'tx')),
|
||||
'options': lambda cfg, help_notes, proto, s: s.format(
|
||||
cfg = cfg,
|
||||
gc = gc,
|
||||
|
|
@ -108,15 +125,22 @@ column below:
|
|||
|
||||
cfg = Config(opts_data=opts_data)
|
||||
|
||||
if not cfg.autosign:
|
||||
tx_file = cfg._args.pop(0)
|
||||
from .fileutil import check_infile
|
||||
check_infile(tx_file)
|
||||
|
||||
from .tx import CompletedTX, BumpTX, UnsignedTX, OnlineSignedTX
|
||||
from .tx.sign import txsign, get_seed_files, get_keyaddrlist, get_keylist
|
||||
|
||||
seed_files = get_seed_files(cfg, cfg._args) if (cfg._args or cfg.send) else None
|
||||
seed_files = get_seed_files(
|
||||
cfg,
|
||||
cfg._args,
|
||||
ignore_dfl_wallet = not cfg.send,
|
||||
empty_ok = not cfg.send)
|
||||
|
||||
if cfg.autosign:
|
||||
if cfg.send:
|
||||
die(1, '--send cannot be used together with --autosign')
|
||||
else:
|
||||
tx_file = cfg._args.pop()
|
||||
from .fileutil import check_infile
|
||||
check_infile(tx_file)
|
||||
|
||||
from .ui import do_license_msg
|
||||
do_license_msg(cfg)
|
||||
|
|
@ -158,33 +182,17 @@ async def main():
|
|||
check_sent = cfg.autosign or sign_and_send,
|
||||
twctl = await TwCtl(cfg, orig_tx.proto) if orig_tx.proto.tokensym else None)
|
||||
|
||||
from .rpc import rpc_init
|
||||
tx.rpc = await rpc_init(cfg, tx.proto)
|
||||
tx.orig_rel_fee = tx.get_orig_rel_fee()
|
||||
|
||||
msg('Creating replacement transaction')
|
||||
|
||||
tx.check_sufficient_funds_for_bump()
|
||||
|
||||
output_idx = tx.choose_output()
|
||||
|
||||
if not silent:
|
||||
msg(f'Minimum fee for new transaction: {tx.min_fee.hl()} {tx.proto.coin}')
|
||||
|
||||
tx.usr_fee = tx.get_usr_fee_interactive(fee=cfg.fee, desc='User-selected')
|
||||
|
||||
tx.bump_fee(output_idx, tx.usr_fee)
|
||||
|
||||
assert tx.fee <= tx.proto.max_tx_fee
|
||||
|
||||
if not cfg.yes:
|
||||
tx.add_comment() # edits an existing comment
|
||||
|
||||
await tx.create_serialized(bump=True)
|
||||
|
||||
tx.add_timestamp()
|
||||
tx.add_blockcount()
|
||||
|
||||
cfg._util.qmsg('Fee successfully increased')
|
||||
if cfg._args:
|
||||
tx.new_outputs = True
|
||||
tx.is_swap = False
|
||||
tx.outputs = tx.OutputList(tx)
|
||||
tx.cfg = cfg # NB: with --automount, must use current cfg opts, not those from orig_tx
|
||||
await tx.create(cfg._args, caller='txdo' if sign_and_send else 'txcreate')
|
||||
else:
|
||||
tx.new_outputs = False
|
||||
await tx.create_feebump(silent=silent)
|
||||
|
||||
if not silent:
|
||||
msg(green('\nREPLACEMENT TRANSACTION:'))
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ from .unsigned import AutomountUnsigned
|
|||
class Bump(Completed, New, TxBase.Bump):
|
||||
desc = 'fee-bumped transaction'
|
||||
|
||||
def get_orig_rel_fee(self):
|
||||
return self.fee_abs2rel(self.sum_inputs() - self.sum_outputs())
|
||||
|
||||
@property
|
||||
def min_fee(self):
|
||||
return self.sum_inputs() - self.sum_outputs() + self.relay_fee
|
||||
|
|
@ -33,7 +36,7 @@ class Bump(Completed, New, TxBase.Bump):
|
|||
|
||||
def convert_and_check_fee(self, fee, desc):
|
||||
ret = super().convert_and_check_fee(fee, desc)
|
||||
if ret is False:
|
||||
if ret is False or self.new_outputs:
|
||||
return ret
|
||||
if ret < self.min_fee:
|
||||
msg('{} {c}: {} fee too small. Minimum fee: {} {c} ({} {})'.format(
|
||||
|
|
|
|||
|
|
@ -135,9 +135,9 @@ class New(Base, TxNew):
|
|||
if len(self.nondata_outputs) > 1 and not self.chg_output.mmid:
|
||||
do_err()
|
||||
|
||||
async def create_serialized(self, locktime=None, bump=None):
|
||||
async def create_serialized(self, locktime=None):
|
||||
|
||||
if not bump:
|
||||
if not self.is_bump:
|
||||
# Set all sequence numbers to the same value, in conformity with the behavior of most modern wallets:
|
||||
do_rbf = self.proto.cap('rbf') and not self.cfg.no_rbf
|
||||
seqnum_val = self.proto.max_int - (2 if do_rbf else 1 if locktime else 0)
|
||||
|
|
@ -158,7 +158,7 @@ class New(Base, TxNew):
|
|||
|
||||
ret = await self.rpc.call('createrawtransaction', inputs_list, outputs_dict)
|
||||
|
||||
if locktime and not bump:
|
||||
if locktime and not self.is_bump:
|
||||
msg(f'Setting nLockTime to {self.info.strfmt_locktime(locktime)}!')
|
||||
assert isinstance(locktime, int), 'locktime value not an integer'
|
||||
self.locktime = locktime
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ from .new import New, TokenNew
|
|||
class Bump(Completed, New, TxBase.Bump):
|
||||
desc = 'fee-bumped transaction'
|
||||
|
||||
def get_orig_rel_fee(self): # disable this check for ETH
|
||||
return 0
|
||||
|
||||
@property
|
||||
def min_fee(self):
|
||||
return self.fee * Decimal('1.101')
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ class New(Base, TxBase.New):
|
|||
# Instead of serializing tx data as with BTC, just create a JSON dump.
|
||||
# This complicates things but means we avoid using the rlp library to deserialize the data,
|
||||
# thus removing an attack vector
|
||||
async def create_serialized(self, locktime=None, bump=None):
|
||||
async def create_serialized(self, locktime=None):
|
||||
assert len(self.inputs) == 1, 'Transaction has more than one input!'
|
||||
o_num = len(self.outputs)
|
||||
o_ok = 0 if self.usr_contract_data else 1
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ class Base(MMGenObject):
|
|||
locktime = None
|
||||
chain = None
|
||||
signed = False
|
||||
is_bump = False
|
||||
is_swap = False
|
||||
file_format = 'json'
|
||||
non_mmgen_inputs_msg = f"""
|
||||
|
|
|
|||
|
|
@ -14,12 +14,13 @@ tx.bump: transaction bump class
|
|||
|
||||
from .new import New
|
||||
from .completed import Completed
|
||||
from ..util import msg, is_int, die
|
||||
from ..util import msg, ymsg, is_int, die
|
||||
|
||||
class Bump(Completed, New):
|
||||
desc = 'fee-bumped transaction'
|
||||
ext = 'rawtx'
|
||||
bump_output_idx = None
|
||||
is_bump = True
|
||||
|
||||
def __init__(self, check_sent, *args, **kwargs):
|
||||
|
||||
|
|
@ -35,6 +36,48 @@ class Bump(Completed, New):
|
|||
self.coin_txid = ''
|
||||
self.sent_timestamp = None
|
||||
|
||||
async def get_inputs(self, outputs_sum):
|
||||
return True
|
||||
|
||||
def check_bumped_fee_ok(self, abs_fee):
|
||||
orig = int(self.orig_rel_fee)
|
||||
new = int(self.fee_abs2rel(abs_fee))
|
||||
if new <= orig:
|
||||
ymsg('New fee ({b} {d}) <= original fee ({a} {d}). Please choose a higher fee'.format(
|
||||
a=orig, b=new, d=self.rel_fee_disp))
|
||||
return False
|
||||
return True
|
||||
|
||||
async def create_feebump(self, silent):
|
||||
|
||||
from ..rpc import rpc_init
|
||||
self.rpc = await rpc_init(self.cfg, self.proto)
|
||||
|
||||
msg('Creating replacement transaction')
|
||||
|
||||
self.check_sufficient_funds_for_bump()
|
||||
|
||||
output_idx = self.choose_output()
|
||||
|
||||
if not silent:
|
||||
msg(f'Minimum fee for new transaction: {self.min_fee.hl()} {self.proto.coin}')
|
||||
|
||||
self.usr_fee = self.get_usr_fee_interactive(fee=self.cfg.fee, desc='User-selected')
|
||||
|
||||
self.bump_fee(output_idx, self.usr_fee)
|
||||
|
||||
assert self.fee <= self.proto.max_tx_fee
|
||||
|
||||
if not self.cfg.yes:
|
||||
self.add_comment() # edits an existing comment
|
||||
|
||||
await self.create_serialized()
|
||||
|
||||
self.add_timestamp()
|
||||
self.add_blockcount()
|
||||
|
||||
self.cfg._util.qmsg('Fee successfully increased')
|
||||
|
||||
def check_sufficient_funds_for_bump(self):
|
||||
if not [o.amt for o in self.outputs if o.amt >= self.min_fee]:
|
||||
die(1,
|
||||
|
|
|
|||
|
|
@ -137,21 +137,24 @@ class New(Base):
|
|||
if fee:
|
||||
abs_fee = self.convert_and_check_fee(fee, desc)
|
||||
if abs_fee:
|
||||
prompt = '{a} TX fee{b}: {c}{d} {e} ({f} {g})\n'.format(
|
||||
a = desc,
|
||||
b = (f' (after {self.cfg.fee_adjust:.2f}X adjustment)'
|
||||
if self.cfg.fee_adjust != 1 and desc.startswith('Network-estimated')
|
||||
else ''),
|
||||
c = ('', '≈')[self.fee_is_approximate],
|
||||
d = abs_fee.hl(),
|
||||
e = self.coin,
|
||||
f = pink(self.fee_abs2rel(abs_fee)),
|
||||
g = self.rel_fee_disp)
|
||||
from ..ui import keypress_confirm
|
||||
if self.cfg.yes or keypress_confirm(self.cfg, prompt+'OK?', default_yes=True):
|
||||
if self.cfg.yes:
|
||||
msg(prompt)
|
||||
return abs_fee
|
||||
if self.is_bump and not self.check_bumped_fee_ok(abs_fee):
|
||||
pass
|
||||
else:
|
||||
prompt = '{a} TX fee{b}: {c}{d} {e} ({f} {g})\n'.format(
|
||||
a = desc,
|
||||
b = (f' (after {self.cfg.fee_adjust:.2f}X adjustment)'
|
||||
if self.cfg.fee_adjust != 1 and desc.startswith('Network-estimated')
|
||||
else ''),
|
||||
c = ('', '≈')[self.fee_is_approximate],
|
||||
d = abs_fee.hl(),
|
||||
e = self.coin,
|
||||
f = pink(self.fee_abs2rel(abs_fee)),
|
||||
g = self.rel_fee_disp)
|
||||
from ..ui import keypress_confirm
|
||||
if self.cfg.yes or keypress_confirm(self.cfg, prompt+'OK?', default_yes=True):
|
||||
if self.cfg.yes:
|
||||
msg(prompt)
|
||||
return abs_fee
|
||||
fee = line_input(self.cfg, self.usr_fee_prompt)
|
||||
desc = 'User-selected'
|
||||
|
||||
|
|
@ -431,21 +434,22 @@ class New(Base):
|
|||
self.get_addrdata_from_files(self.proto, addrfile_args),
|
||||
await TwAddrData(self.cfg, self.proto, twctl=self.twctl))
|
||||
|
||||
self.twuo = await TwUnspentOutputs(
|
||||
self.cfg,
|
||||
self.proto,
|
||||
minconf = self.cfg.minconf,
|
||||
addrs = await self.get_input_addrs_from_inputs_opt())
|
||||
|
||||
await self.twuo.get_data()
|
||||
if not self.is_bump:
|
||||
self.twuo = await TwUnspentOutputs(
|
||||
self.cfg,
|
||||
self.proto,
|
||||
minconf = self.cfg.minconf,
|
||||
addrs = await self.get_input_addrs_from_inputs_opt())
|
||||
await self.twuo.get_data()
|
||||
|
||||
from ..ui import do_license_msg
|
||||
do_license_msg(self.cfg)
|
||||
|
||||
if not self.cfg.inputs:
|
||||
if not (self.is_bump or self.cfg.inputs):
|
||||
await self.twuo.view_filter_and_sort()
|
||||
|
||||
self.twuo.display_total()
|
||||
if not self.is_bump:
|
||||
self.twuo.display_total()
|
||||
|
||||
if do_info:
|
||||
del self.twuo.twctl
|
||||
|
|
@ -461,7 +465,10 @@ class New(Base):
|
|||
while True:
|
||||
if not await self.get_inputs(outputs_sum):
|
||||
continue
|
||||
if funds_left := await self.get_fee(self.cfg.fee, outputs_sum):
|
||||
fee_hint = None
|
||||
if self.is_swap:
|
||||
fee_hint = self.update_vault_output(self.vault_output.amt or self.sum_inputs())
|
||||
if funds_left := await self.get_fee(fee_hint or self.cfg.fee, outputs_sum):
|
||||
break
|
||||
|
||||
self.check_non_mmgen_inputs(caller)
|
||||
|
|
@ -482,6 +489,9 @@ class New(Base):
|
|||
|
||||
self.cfg._util.qmsg('Transaction successfully created')
|
||||
|
||||
if self.is_bump:
|
||||
return
|
||||
|
||||
from . import UnsignedTX
|
||||
new = UnsignedTX(cfg=self.cfg, data=self.__dict__, automount=self.cfg.autosign)
|
||||
|
||||
|
|
|
|||
|
|
@ -128,15 +128,15 @@ def get_tx_files(cfg, args):
|
|||
die(1, 'You must specify a raw transaction file!')
|
||||
return ret
|
||||
|
||||
def get_seed_files(cfg, args):
|
||||
def get_seed_files(cfg, args, ignore_dfl_wallet=False, empty_ok=False):
|
||||
# favor unencrypted seed sources first, as they don't require passwords
|
||||
ret = _pop_matching_fns(args, get_wallet_extensions('unenc'))
|
||||
from ..filename import find_file_in_dir
|
||||
wf = find_file_in_dir(get_wallet_cls('mmgen'), cfg.data_dir) # Make this the first encrypted ss in the list
|
||||
if wf:
|
||||
ret.append(wf)
|
||||
if not ignore_dfl_wallet: # Make this the first encrypted ss in the list
|
||||
if wf := find_file_in_dir(get_wallet_cls('mmgen'), cfg.data_dir):
|
||||
ret.append(wf)
|
||||
ret += _pop_matching_fns(args, get_wallet_extensions('enc'))
|
||||
if not (ret or cfg.mmgen_keys_from_file or cfg.keys_from_file): # or cfg.use_wallet_dat
|
||||
if not (ret or empty_ok or cfg.mmgen_keys_from_file or cfg.keys_from_file): # or cfg.use_wallet_dat
|
||||
die(1, 'You must specify a seed or key source!')
|
||||
return ret
|
||||
|
||||
|
|
|
|||
|
|
@ -61,6 +61,13 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtestBDBWallet)
|
|||
('alice_txsend2', 'sending the transaction'),
|
||||
('alice_txbump3', 'bumping the transaction'),
|
||||
('alice_txsend3', 'sending the bumped transaction'),
|
||||
('alice_txbump4', 'bumping the transaction (new outputs, fee too low)'),
|
||||
('alice_txbump_abort1', 'aborting the transaction'),
|
||||
('alice_txbump5', 'bumping the transaction (new outputs)'),
|
||||
('alice_txsend5', 'sending the bumped transaction'),
|
||||
('alice_txstatus5', 'getting transaction status (in mempool)'),
|
||||
('generate', 'mining a block'),
|
||||
('alice_bal2', 'checking Alice’s balance'),
|
||||
('wait_loop_kill', 'stopping autosign wait loop'),
|
||||
('stop', 'stopping regtest daemon'),
|
||||
('txview', 'viewing transactions'),
|
||||
|
|
@ -176,6 +183,9 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtestBDBWallet)
|
|||
def alice_txsend3(self):
|
||||
return self._alice_txsend(need_rbf=True)
|
||||
|
||||
def alice_txsend5(self):
|
||||
return self._alice_txsend(need_rbf=True)
|
||||
|
||||
def _alice_txstatus(self, expect, exit_val=None, need_rbf=False):
|
||||
|
||||
if need_rbf and not self.proto.cap('rbf'):
|
||||
|
|
@ -204,6 +214,9 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtestBDBWallet)
|
|||
def alice_txstatus4(self):
|
||||
return self._alice_txstatus('1 confirmation', 0)
|
||||
|
||||
def alice_txstatus5(self):
|
||||
return self._alice_txstatus('in mempool', need_rbf=True)
|
||||
|
||||
def _alice_txsend(self, comment=None, no_wait=False, need_rbf=False):
|
||||
|
||||
if need_rbf and not self.proto.cap('rbf'):
|
||||
|
|
@ -211,6 +224,7 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtestBDBWallet)
|
|||
|
||||
if not no_wait:
|
||||
self._wait_signed('transaction')
|
||||
|
||||
self.insert_device_online()
|
||||
t = self.spawn('mmgen-txsend', ['--alice', '--quiet', '--autosign'])
|
||||
t.view_tx('t')
|
||||
|
|
@ -229,22 +243,30 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtestBDBWallet)
|
|||
self.remove_device_online()
|
||||
return t
|
||||
|
||||
def _alice_txbump(self, bad_tx_expect=None):
|
||||
if cfg.coin == 'BCH':
|
||||
def _alice_txbump(self, fee_opt=None, output_args=[], bad_tx_expect=None, low_fee_fix=None):
|
||||
if not self.proto.cap('rbf'):
|
||||
return 'skip'
|
||||
self.insert_device_online()
|
||||
t = self.spawn(
|
||||
'mmgen-txbump',
|
||||
['--alice', '--autosign'],
|
||||
['--alice', '--autosign']
|
||||
+ ([fee_opt] if fee_opt else [])
|
||||
+ output_args,
|
||||
exit_val = 1 if bad_tx_expect else None)
|
||||
if bad_tx_expect:
|
||||
time.sleep(0.5)
|
||||
t.expect('Only sent transactions')
|
||||
t.expect(bad_tx_expect)
|
||||
else:
|
||||
t.expect(r'to deduct the fee from .* change output\): ', '\n', regex=True)
|
||||
t.expect(r'(Y/n): ', 'y') # output OK?
|
||||
t.expect('transaction fee: ', '200s\n')
|
||||
if not output_args:
|
||||
t.expect(r'to deduct the fee from .* change output\): ', '\n', regex=True)
|
||||
t.expect(r'(Y/n): ', 'y') # output OK?
|
||||
if low_fee_fix or not fee_opt:
|
||||
if low_fee_fix:
|
||||
t.expect('Please choose a higher fee')
|
||||
t.expect('transaction fee: ', (low_fee_fix or '200s') + '\n')
|
||||
if output_args:
|
||||
t.expect(r'(Y/n): ', 'y')
|
||||
t.expect(r'(Y/n): ', 'y') # fee OK?
|
||||
t.expect(r'(y/N): ', '\n') # add comment?
|
||||
t.expect(r'(y/N): ', 'y') # save?
|
||||
|
|
@ -261,3 +283,29 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtestBDBWallet)
|
|||
|
||||
def alice_txbump3(self):
|
||||
return self._alice_txbump()
|
||||
|
||||
def alice_txbump4(self):
|
||||
sid = self._user_sid('alice')
|
||||
return self._alice_txbump(
|
||||
fee_opt = '--fee=3s',
|
||||
output_args = [f'{self.burn_addr},7.654321', f'{sid}:C:1'],
|
||||
low_fee_fix = '300s')
|
||||
|
||||
def alice_txbump_abort1(self):
|
||||
if not self.proto.cap('rbf'):
|
||||
return 'skip'
|
||||
return self._alice_txsend_abort(shred_expect=['Shredding .*arawtx'])
|
||||
|
||||
def alice_txbump5(self):
|
||||
sid = self._user_sid('alice')
|
||||
return self._alice_txbump(
|
||||
fee_opt = '--fee=400s',
|
||||
output_args = ['data:message for posterity', f'{self.burn_addr},7.654321', f'{sid}:C:1'])
|
||||
|
||||
def alice_bal2(self):
|
||||
bals = {
|
||||
'btc': '491.11002204',
|
||||
'ltc': '5491.11002204',
|
||||
'bch': '498.7653392',
|
||||
}
|
||||
return self.user_bal('alice', bals.get(self.coin, None))
|
||||
|
|
|
|||
|
|
@ -71,9 +71,9 @@ rt_data = {
|
|||
'tx_fee': {'btc':'0.0001', 'bch':'0.001', 'ltc':'0.01'},
|
||||
'rtFundAmt': {'btc':'500', 'bch':'500', 'ltc':'5500'},
|
||||
'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': {
|
||||
'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)'),
|
||||
('get_mempool1', 'mempool (before RBF bump)'),
|
||||
('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)'),
|
||||
('bob_rbf_bump', 'bumping RBF transaction'),
|
||||
('get_mempool3', 'mempool (after RBF bump)'),
|
||||
('bob_rbf_status2', 'getting status of transaction after replacement'),
|
||||
('bob_rbf_status3', 'getting status of replacement transaction (mempool)'),
|
||||
('generate', 'mining a block'),
|
||||
|
|
@ -1173,10 +1175,18 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|||
t.written_to_file('Fee-bumped transaction')
|
||||
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):
|
||||
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 '')
|
||||
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=[]):
|
||||
int(num_blocks)
|
||||
|
|
@ -1203,6 +1213,9 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|||
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):
|
||||
if do_msg:
|
||||
self.spawn('', msg_only=True)
|
||||
|
|
@ -1243,19 +1256,19 @@ class CmdTestRegtest(CmdTestBase, CmdTestShared):
|
|||
return self._bob_rbf_status(rtFee[1])
|
||||
|
||||
def bob_rbf_status2(self):
|
||||
return self._bob_rbf_status(rtFee[1], txid='rbf_txid2')
|
||||
return self._bob_rbf_status(rtFee[1], txid='rbf_txid3')
|
||||
|
||||
def bob_rbf_status3(self):
|
||||
return self._bob_rbf_status(rtFee[2])
|
||||
|
||||
def bob_rbf_status4(self):
|
||||
return self._bob_rbf_status(rtFee[1], txid='rbf_txid2', confirmations=1, exit_val=0)
|
||||
return self._bob_rbf_status(rtFee[1], txid='rbf_txid3', confirmations=1, exit_val=0)
|
||||
|
||||
def bob_rbf_status5(self):
|
||||
return self._bob_rbf_status(rtFee[2], confirmations=1, exit_val=0)
|
||||
|
||||
def bob_rbf_status6(self):
|
||||
return self._bob_rbf_status(rtFee[1], txid='rbf_txid2', confirmations=2, exit_val=0)
|
||||
return self._bob_rbf_status(rtFee[1], txid='rbf_txid3', confirmations=2, exit_val=0)
|
||||
|
||||
def _gen_pairs(self, n):
|
||||
from mmgen.tool.api import tool_api
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue