From ef5f6e4b22ef87e0b2c20f2ffcc410434c5abb7d Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 16 Feb 2025 14:42:32 +0000 Subject: [PATCH] mmgen-txbump: support new outputs in the replacement TX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- mmgen/data/version | 2 +- mmgen/main_txbump.py | 74 +++++++++++++++++++--------------- mmgen/proto/btc/tx/bump.py | 5 ++- mmgen/proto/btc/tx/new.py | 6 +-- mmgen/proto/eth/tx/bump.py | 3 ++ mmgen/proto/eth/tx/new.py | 2 +- mmgen/tx/base.py | 1 + mmgen/tx/bump.py | 45 ++++++++++++++++++++- mmgen/tx/new.py | 60 +++++++++++++++------------ mmgen/tx/sign.py | 10 ++--- test/cmdtest_d/ct_automount.py | 60 ++++++++++++++++++++++++--- test/cmdtest_d/ct_regtest.py | 29 +++++++++---- 12 files changed, 213 insertions(+), 84 deletions(-) diff --git a/mmgen/data/version b/mmgen/data/version index 7e14326e..6ad6249c 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -15.1.dev15 +15.1.dev16 diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index cd5dcf94..dc0ee1f9 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -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:')) diff --git a/mmgen/proto/btc/tx/bump.py b/mmgen/proto/btc/tx/bump.py index 251c12d8..661f578b 100755 --- a/mmgen/proto/btc/tx/bump.py +++ b/mmgen/proto/btc/tx/bump.py @@ -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( diff --git a/mmgen/proto/btc/tx/new.py b/mmgen/proto/btc/tx/new.py index 88c5a1b1..49cbf329 100755 --- a/mmgen/proto/btc/tx/new.py +++ b/mmgen/proto/btc/tx/new.py @@ -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 diff --git a/mmgen/proto/eth/tx/bump.py b/mmgen/proto/eth/tx/bump.py index 312caed3..0b97e8f6 100755 --- a/mmgen/proto/eth/tx/bump.py +++ b/mmgen/proto/eth/tx/bump.py @@ -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') diff --git a/mmgen/proto/eth/tx/new.py b/mmgen/proto/eth/tx/new.py index 3684dc27..e0cdeedf 100755 --- a/mmgen/proto/eth/tx/new.py +++ b/mmgen/proto/eth/tx/new.py @@ -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 diff --git a/mmgen/tx/base.py b/mmgen/tx/base.py index c7afcaa4..53af0f3f 100755 --- a/mmgen/tx/base.py +++ b/mmgen/tx/base.py @@ -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""" diff --git a/mmgen/tx/bump.py b/mmgen/tx/bump.py index 7d94f6fd..85f6cf00 100755 --- a/mmgen/tx/bump.py +++ b/mmgen/tx/bump.py @@ -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, diff --git a/mmgen/tx/new.py b/mmgen/tx/new.py index f339137e..6e736c69 100755 --- a/mmgen/tx/new.py +++ b/mmgen/tx/new.py @@ -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) diff --git a/mmgen/tx/sign.py b/mmgen/tx/sign.py index a3b1449e..d2a2cc68 100755 --- a/mmgen/tx/sign.py +++ b/mmgen/tx/sign.py @@ -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 diff --git a/test/cmdtest_d/ct_automount.py b/test/cmdtest_d/ct_automount.py index af174994..4eff5b6f 100755 --- a/test/cmdtest_d/ct_automount.py +++ b/test/cmdtest_d/ct_automount.py @@ -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)) diff --git a/test/cmdtest_d/ct_regtest.py b/test/cmdtest_d/ct_regtest.py index ba3f29a2..79ae48f0 100755 --- a/test/cmdtest_d/ct_regtest.py +++ b/test/cmdtest_d/ct_regtest.py @@ -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