diff --git a/mmgen/autosign.py b/mmgen/autosign.py index 589b9a5d..3cf09d47 100755 --- a/mmgen/autosign.py +++ b/mmgen/autosign.py @@ -334,19 +334,22 @@ class Signable: shred_file(self.cfg, fn, iterations=15) sys.exit(0) - async def get_last_sent(self): + async def get_last_sent(self, *, idx=0): 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) + sort_key = lambda x: x.sent_timestamp or x.timestamp, + idx = idx) - async def get_last_created(self, *, sort_key=lambda x: x.timestamp): + async def get_last_created(self, *, sort_key=lambda x: x.timestamp, idx=0): from .tx import CompletedTX fns = [f for f in self.dir.iterdir() if f.name.endswith(self.subext)] files = sorted( [await CompletedTX(cfg=self.cfg, filename=str(txfile), quiet_open=True) for txfile in fns], key = sort_key) - return files[-1] + if not (0 <= idx < len(files)): + die(2, f'{idx}: invalid transaction index (must be less than {len(files)})') + return files[-1 - idx] class xmr_signable: # mixin class automount = True diff --git a/mmgen/data/version b/mmgen/data/version index 4e1b80df..10ec5e99 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -16.1.dev27 +16.1.dev28 diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index eb1463da..e33f3036 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -23,7 +23,7 @@ mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network import sys from .cfg import gc, Config -from .util import async_run, die +from .util import async_run, die, is_int opts_data = { 'sets': [ @@ -35,6 +35,7 @@ opts_data = { 'usage2': [ '[opts] ', '[opts] --autosign', + '[opts] --autosign (--status | --receipt) [IDX]', ], 'options': """ -h, --help Print this help message @@ -68,6 +69,12 @@ opts_data = { Use special value ‘env’ to honor *_PROXY environment vars instead. -y, --yes Answer 'yes' to prompts, suppress non-essential output +""", + 'notes': """ +With --autosign, combined with --status or --receipt, the optional IDX arg +represents an index into the list of sent transaction files on the removable +device, in reverse chronological order. ‘0’ (the default) specifies the +last sent transaction, ‘1’ the next-to-last, and so on. """ }, 'code': { @@ -95,8 +102,8 @@ post_send_op = cfg.status or cfg.receipt asi = None -def init_autosign(): - global asi, si, infile +def init_autosign(arg): + global asi, si, infile, tx_idx from .tx.util import mount_removable_device from .autosign import Signable asi = mount_removable_device(cfg) @@ -108,13 +115,18 @@ def init_autosign(): die(1, 'Transaction is unsent') if si.unsigned: die(1, 'Transaction is unsigned') + if not is_int(arg): + die(2, f'{arg}: invalid transaction index (must be a non-negative integer)') + tx_idx = int(arg) else: infile = si.get_unsent() cfg._util.qmsg(f'Got signed transaction file ‘{infile}’') match cfg._args: + case [arg] if cfg.autosign and post_send_op: + init_autosign(arg) case [] if cfg.autosign: - init_autosign() + init_autosign(0) case [infile]: from .fileutil import check_infile check_infile(infile) @@ -132,7 +144,7 @@ async def main(): global cfg if cfg.autosign and post_send_op: - tx = await si.get_last_sent() + tx = await si.get_last_sent(idx=tx_idx) else: tx = await OnlineSignedTX( cfg = cfg, diff --git a/test/cmdtest_d/automount.py b/test/cmdtest_d/automount.py index c3bca797..f84c48b1 100755 --- a/test/cmdtest_d/automount.py +++ b/test/cmdtest_d/automount.py @@ -82,6 +82,9 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): ('alice_txbump5', 'bumping the transaction (new outputs)'), ('alice_txsend5', 'sending the bumped transaction'), ('alice_txstatus5', 'getting transaction status (in mempool)'), + ('alice_txstatus6', 'getting transaction status (idx=0, in mempool)'), + ('alice_txstatus7', 'getting transaction status (idx=1, replaced)'), + ('alice_txstatus8', 'getting transaction status (idx=3, 2 confirmations)'), ('generate', 'mining a block'), ('alice_bal2', 'checking Alice’s balance'), ('wait_loop_kill', 'stopping autosign wait loop'), @@ -221,7 +224,7 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): def alice_txsend5(self): return self._user_txsend('alice', need_rbf=True) - def _alice_txstatus(self, expect, exit_val=None, need_rbf=False): + def _alice_txstatus(self, expect, exit_val=None, need_rbf=False, idx=None): if need_rbf and not self.proto.cap('rbf'): return 'skip' @@ -229,7 +232,8 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): self.insert_device_online() t = self.spawn( 'mmgen-txsend', - ['--alice', '--autosign', '--status', '--verbose'], + ['--alice', '--autosign', '--status', '--verbose'] + + ([] if idx is None else [str(idx)]), no_passthru_opts = ['coin'], exit_val = exit_val) t.expect(expect) @@ -255,6 +259,15 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest): def alice_txstatus5(self): return self._alice_txstatus('in mempool', need_rbf=True) + def alice_txstatus6(self): + return self._alice_txstatus('in mempool', need_rbf=True, idx=0) + + def alice_txstatus7(self): + return self._alice_txstatus('replaced', need_rbf=True, idx=1) + + def alice_txstatus8(self): + return self._alice_txstatus('2 confirmations', need_rbf=True, idx=3) + def alice_txsend_bad_no_unsent(self): self.insert_device_online() t = self.spawn('mmgen-txsend', ['--quiet', '--autosign'], exit_val=2, no_passthru_opts=['coin'])