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)
 				shred_file(self.cfg, fn, iterations=15)
 			sys.exit(0)
 			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(
 			return await self.get_last_created(
 				# compat fallback - ‘sent_timestamp’ attr is missing in some old TX files:
 				# 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,
 				tx_range = tx_range)
 				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
 			from .tx import CompletedTX
 			fns = [f for f in self.dir.iterdir() if f.name.endswith(self.subext)]
 			fns = [f for f in self.dir.iterdir() if f.name.endswith(self.subext)]
 			files = sorted(
 			files = sorted(
@@ -348,9 +348,7 @@ class Signable:
 					for txfile in fns],
 					for txfile in fns],
 				key = sort_key)
 				key = sort_key)
 			if files:
 			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:
 			else:
 				die(1, 'No sent automount transactions!')
 				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
 import sys
 
 
 from .cfg import gc, Config
 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
 from .color import green
 
 
 opts_data = {
 opts_data = {
@@ -37,8 +37,8 @@ opts_data = {
 		'usage2':   (
 		'usage2':   (
 			f'[opts] [{gc.proj_name} TX file] [seed source] ...',
 			f'[opts] [{gc.proj_name} TX file] [seed source] ...',
 			f'[opts] {{u_args}} [{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': """
 		'options': """
 			-- -h, --help             Print this help message
 			-- -h, --help             Print this help message
@@ -96,8 +96,12 @@ opts_data = {
 """,
 """,
 	'notes': """
 	'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
 If no outputs are specified, the original outputs will be used for the
 replacement transaction, otherwise a new transaction will be created with 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.autosign:
 	if cfg.send:
 	if cfg.send:
 		die(1, '--send cannot be used together with --autosign')
 		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:
 else:
 	tx_file = cfg._args.pop()
 	tx_file = cfg._args.pop()
 	from .fileutil import check_infile
 	from .fileutil import check_infile
@@ -174,7 +180,7 @@ async def main():
 				'Only sent transactions can be bumped with --autosign.  Instead of bumping\n'
 				'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'
 				f'your {state} transaction, abort it with ‘mmgen-txsend --abort’ and create\n'
 				'a new one.')
 				'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
 		sign_and_send = False
 	else:
 	else:
 		orig_tx = await CompletedTX(cfg=cfg, filename=tx_file)
 		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
 	min_idx = 0
 	max_idx = 1_000_000
 	max_idx = 1_000_000
 
 
+class CreatedTXRange(SentTXRange):
+	pass
+
 class OnlineSigned(Signed):
 class OnlineSigned(Signed):
 
 
 	@property
 	@property

+ 20 - 5
test/cmdtest_d/automount.py

@@ -70,14 +70,14 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 		('alice_txsend_abort5',              'aborting the transaction again (error)'),
 		('alice_txsend_abort5',              'aborting the transaction again (error)'),
 		('generate',                         'mining a block'),
 		('generate',                         'mining a block'),
 		('alice_txcreate4',                  'creating a transaction'),
 		('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_txbump2',                    'bumping the unsent transaction (error)'),
 		('alice_txsend2_dump_hex',           'dumping the transaction to hex'),
 		('alice_txsend2_dump_hex',           'dumping the transaction to hex'),
 		('alice_txsend2_cli',                'sending the transaction via cli'),
 		('alice_txsend2_cli',                'sending the transaction via cli'),
 		('alice_txsend2_mark_sent',          'marking the transaction sent'),
 		('alice_txsend2_mark_sent',          'marking the transaction sent'),
 		('alice_txbump3',                    'bumping the transaction'),
 		('alice_txbump3',                    'bumping the transaction'),
 		('alice_txsend3',                    'sending the bumped 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_txbump_abort1',              'aborting the transaction'),
 		('alice_txbump5',                    'bumping the transaction (new outputs)'),
 		('alice_txbump5',                    'bumping the transaction (new outputs)'),
 		('alice_txsend5',                    'sending the bumped transaction'),
 		('alice_txsend5',                    'sending the bumped transaction'),
@@ -86,6 +86,7 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 		('alice_txstatus7',                  'getting transaction status (tx_range=1, replaced)'),
 		('alice_txstatus7',                  'getting transaction status (tx_range=1, replaced)'),
 		('alice_txstatus8',                  'getting transaction status (tx_range=3, 2 confirmations)'),
 		('alice_txstatus8',                  'getting transaction status (tx_range=3, 2 confirmations)'),
 		('alice_txstatus9',                  'getting transaction status (tx_range=0-3)'),
 		('alice_txstatus9',                  'getting transaction status (tx_range=0-3)'),
+		('alice_txbump6',                    'bumping the next-to-last sent transaction (idx=1)'),
 		('generate',                         'mining a block'),
 		('generate',                         'mining a block'),
 		('alice_bal2',                       'checking Alice’s balance'),
 		('alice_bal2',                       'checking Alice’s balance'),
 		('wait_loop_kill',                   'stopping autosign wait loop'),
 		('wait_loop_kill',                   'stopping autosign wait loop'),
@@ -293,7 +294,14 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 		self.remove_device_online()
 		self.remove_device_online()
 		return t
 		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'):
 		if not self.proto.cap('rbf'):
 			return 'skip'
 			return 'skip'
 		self.insert_device_online()
 		self.insert_device_online()
@@ -301,6 +309,7 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 				'mmgen-txbump',
 				'mmgen-txbump',
 				['--alice', '--autosign']
 				['--alice', '--autosign']
 				+ ([fee_opt] if fee_opt else [])
 				+ ([fee_opt] if fee_opt else [])
+				+ ([] if idx is None else [str(idx)])
 				+ output_args,
 				+ output_args,
 				exit_val = 1 if bad_tx_expect else None)
 				exit_val = 1 if bad_tx_expect else None)
 		if bad_tx_expect:
 		if bad_tx_expect:
@@ -308,6 +317,8 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 			t.expect('Only sent transactions')
 			t.expect('Only sent transactions')
 			t.expect(bad_tx_expect)
 			t.expect(bad_tx_expect)
 		else:
 		else:
+			if orig_tx_expect:
+				t.expect(orig_tx_expect)
 			if not output_args:
 			if not output_args:
 				t.expect(r'to deduct the fee from .* change output\): ', '\n', regex=True)
 				t.expect(r'to deduct the fee from .* change output\): ', '\n', regex=True)
 				t.expect(r'(Y/n): ', 'y')  # output OK?
 				t.expect(r'(Y/n): ', 'y')  # output OK?
@@ -325,7 +336,7 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 		return t
 		return t
 
 
 	def alice_txbump1(self):
 	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):
 	def alice_txbump2(self):
 		self._wait_signed('transaction')
 		self._wait_signed('transaction')
@@ -339,7 +350,8 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 		return self._alice_txbump(
 		return self._alice_txbump(
 			fee_opt = '--fee=3s',
 			fee_opt = '--fee=3s',
 			output_args = [f'{self.burn_addr},7.654321', f'{sid}:C:1'],
 			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):
 	def alice_txbump_abort1(self):
 		if not self.proto.cap('rbf'):
 		if not self.proto.cap('rbf'):
@@ -352,5 +364,8 @@ class CmdTestAutosignAutomount(CmdTestAutosignThreaded, CmdTestRegtest):
 			fee_opt = '--fee=400s',
 			fee_opt = '--fee=400s',
 			output_args = ['data:message for posterity', f'{self.burn_addr},7.654321', f'{sid}:C:1'])
 			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):
 	def alice_bal2(self):
 		return self.user_bal('alice', self.bal2_chk[self.coin])
 		return self.user_bal('alice', self.bal2_chk[self.coin])