Browse Source

mmgen-txbump: support transaction selection

Transaction selection logic is the same as with `mmgen-txsend --status`, except
ranges are not supported.

Examples (assumes --autosign):

    # Bump last sent transaction:
    $ mmgen-txbump

    # Same as above:
    $ mmgen-txbump 0

    # Bump next-to-last sent transaction:
    $ mmgen-txbump 1

Testing/demo:

    $ test/cmdtest.py -e -X alice_txbump6 autosign_automount
The MMGen Project 3 weeks ago
parent
commit
c4ec627153
4 changed files with 38 additions and 16 deletions
  1. 3 5
      mmgen/autosign.py
  2. 12 6
      mmgen/main_txbump.py
  3. 3 0
      mmgen/tx/online.py
  4. 20 5
      test/cmdtest_d/automount.py

+ 3 - 5
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!')
 

+ 12 - 6
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)

+ 3 - 0
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

+ 20 - 5
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])