diff --git a/mmgen/autosign.py b/mmgen/autosign.py index 080e07bb..9856a115 100755 --- a/mmgen/autosign.py +++ b/mmgen/autosign.py @@ -334,13 +334,13 @@ class Signable: shred_file(self.cfg, fn, iterations=15) sys.exit(0) - async def get_last_sent(self, *, tx_range=None): + async def get_last_sent(self, *, tx_range): return await self.get_last_created( # compat fallback - ‘sent_timestamp’ attr is missing in some old TX files: sort_key = lambda x: x.sent_timestamp or x.timestamp, tx_range = tx_range) - async def get_last_created(self, *, sort_key=lambda x: x.timestamp, tx_range=None): + async def get_last_created(self, *, tx_range, sort_key=lambda x: x.timestamp): from .tx import CompletedTX fns = [f for f in self.dir.iterdir() if f.name.endswith(self.subext)] files = sorted( @@ -348,9 +348,7 @@ class Signable: for txfile in fns], key = sort_key) if files: - return ( - files[-1] if tx_range is None else - files[len(files) - 1 - tx_range.last:len(files) - tx_range.first]) + return files[len(files) - 1 - tx_range.last:len(files) - tx_range.first] else: die(1, 'No sent automount transactions!') diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index 11869834..296e34f1 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -23,7 +23,7 @@ mmgen-txbump: Create, and optionally send and sign, a replacement transaction on import sys from .cfg import gc, Config -from .util import msg, msg_r, die, async_run +from .util import msg, msg_r, die, is_int, async_run from .color import green opts_data = { @@ -37,8 +37,8 @@ opts_data = { 'usage2': ( f'[opts] [{gc.proj_name} TX file] [seed source] ...', f'[opts] {{u_args}} [{gc.proj_name} TX file] [seed source] ...', - '--autosign [opts]', - '--autosign [opts] {u_args}', + '--autosign [opts] [index]', + '--autosign [opts] [index] {u_args}', ), 'options': """ -- -h, --help Print this help message @@ -96,8 +96,12 @@ opts_data = { """, 'notes': """ -With --autosign, the TX file argument is omitted, and the last submitted TX -file on the removable device will be used. +With --autosign, the TX file argument is omitted, and the last submitted +transaction on the removable device will be used. Or, if the first non-option +argument is a non-negative integer, it specifies an index into the list of +submitted transactions, in reverse chronological order, and that transaction +will be bumped. ‘0’ (the default) signifies the last sent transaction, ‘1’ +the next-to-last, and so on. If no outputs are specified, the original outputs will be used for the replacement transaction, otherwise a new transaction will be created with the @@ -151,6 +155,8 @@ seedfiles = pop_seedfiles(cfg, ignore_dfl_wallet=not cfg.send, empty_ok=not cfg. if cfg.autosign: if cfg.send: die(1, '--send cannot be used together with --autosign') + from .tx.online import CreatedTXRange + tx_range = CreatedTXRange(cfg._args.pop(0) if cfg._args and is_int(cfg._args[0]) else '0') else: tx_file = cfg._args.pop() from .fileutil import check_infile @@ -174,7 +180,7 @@ async def main(): 'Only sent transactions can be bumped with --autosign. Instead of bumping\n' f'your {state} transaction, abort it with ‘mmgen-txsend --abort’ and create\n' 'a new one.') - orig_tx = await si.get_last_created() + orig_tx = (await si.get_last_created(tx_range=tx_range))[0] sign_and_send = False else: orig_tx = await CompletedTX(cfg=cfg, filename=tx_file) diff --git a/mmgen/tx/online.py b/mmgen/tx/online.py index 7c1131af..46fb9991 100755 --- a/mmgen/tx/online.py +++ b/mmgen/tx/online.py @@ -24,6 +24,9 @@ class SentTXRange(MMGenRange): min_idx = 0 max_idx = 1_000_000 +class CreatedTXRange(SentTXRange): + pass + class OnlineSigned(Signed): @property diff --git a/test/cmdtest_d/automount.py b/test/cmdtest_d/automount.py index 7c22ae94..f8ce7eea 100755 --- a/test/cmdtest_d/automount.py +++ b/test/cmdtest_d/automount.py @@ -70,14 +70,14 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): ('alice_txsend_abort5', 'aborting the transaction again (error)'), ('generate', 'mining a block'), ('alice_txcreate4', 'creating a transaction'), - ('alice_txbump1', 'bumping the unsigned transaction (error)'), + ('alice_txbump1', 'bumping the unsigned transaction (error, idx=0)'), ('alice_txbump2', 'bumping the unsent transaction (error)'), ('alice_txsend2_dump_hex', 'dumping the transaction to hex'), ('alice_txsend2_cli', 'sending the transaction via cli'), ('alice_txsend2_mark_sent', 'marking the transaction sent'), ('alice_txbump3', 'bumping the transaction'), ('alice_txsend3', 'sending the bumped transaction'), - ('alice_txbump4', 'bumping the transaction (new outputs, fee too low)'), + ('alice_txbump4', 'bumping the transaction (new outputs, fee too low, idx=0)'), ('alice_txbump_abort1', 'aborting the transaction'), ('alice_txbump5', 'bumping the transaction (new outputs)'), ('alice_txsend5', 'sending the bumped transaction'), @@ -86,6 +86,7 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): ('alice_txstatus7', 'getting transaction status (tx_range=1, replaced)'), ('alice_txstatus8', 'getting transaction status (tx_range=3, 2 confirmations)'), ('alice_txstatus9', 'getting transaction status (tx_range=0-3)'), + ('alice_txbump6', 'bumping the next-to-last sent transaction (idx=1)'), ('generate', 'mining a block'), ('alice_bal2', 'checking Alice’s balance'), ('wait_loop_kill', 'stopping autosign wait loop'), @@ -293,7 +294,14 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): self.remove_device_online() return t - def _alice_txbump(self, fee_opt=None, output_args=[], bad_tx_expect=None, low_fee_fix=None): + def _alice_txbump( + self, + fee_opt = None, + output_args = [], + bad_tx_expect = None, + low_fee_fix = None, + orig_tx_expect = None, + idx = None): if not self.proto.cap('rbf'): return 'skip' self.insert_device_online() @@ -301,6 +309,7 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): 'mmgen-txbump', ['--alice', '--autosign'] + ([fee_opt] if fee_opt else []) + + ([] if idx is None else [str(idx)]) + output_args, exit_val = 1 if bad_tx_expect else None) if bad_tx_expect: @@ -308,6 +317,8 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): t.expect('Only sent transactions') t.expect(bad_tx_expect) else: + if orig_tx_expect: + t.expect(orig_tx_expect) 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? @@ -325,7 +336,7 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): return t def alice_txbump1(self): - return self._alice_txbump(bad_tx_expect='unsigned transaction') + return self._alice_txbump(bad_tx_expect='unsigned transaction', idx=0) def alice_txbump2(self): self._wait_signed('transaction') @@ -339,7 +350,8 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): return self._alice_txbump( fee_opt = '--fee=3s', output_args = [f'{self.burn_addr},7.654321', f'{sid}:C:1'], - low_fee_fix = '300s') + low_fee_fix = '300s', + idx = 0) def alice_txbump_abort1(self): if not self.proto.cap('rbf'): @@ -352,5 +364,8 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): fee_opt = '--fee=400s', output_args = ['data:message for posterity', f'{self.burn_addr},7.654321', f'{sid}:C:1']) + def alice_txbump6(self): + return self._alice_txbump(idx=1, fee_opt='--fee=250s', orig_tx_expect='1.23456') + def alice_bal2(self): return self.user_bal('alice', self.bal2_chk[self.coin])