Browse Source

mmgen-xmrwallet: new `resubmit` operation

The MMGen Project 1 year ago
parent
commit
3b7a238259

+ 1 - 1
mmgen/data/release_date

@@ -1 +1 @@
-April 2023
+May 2023

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-13.3.dev50
+13.3.dev51

+ 14 - 5
mmgen/help/xmrwallet.py

@@ -47,6 +47,8 @@ sweep     - sweep funds in specified wallet:account to new address in same
 relay     - relay a transaction from a transaction file created using ‘sweep’
 relay     - relay a transaction from a transaction file created using ‘sweep’
             or ‘transfer’ with the --no-relay option
             or ‘transfer’ with the --no-relay option
 submit    - submit an autosigned transaction to a wallet and the network
 submit    - submit an autosigned transaction to a wallet and the network
+resubmit  - resubmit most recently submitted autosigned transaction (other
+            actions are required: see Exporting Outputs below)
 txview    - display detailed information about a transaction file or files
 txview    - display detailed information about a transaction file or files
 txlist    - same as above, but display terse information in tabular format
 txlist    - same as above, but display terse information in tabular format
 dump      - produce JSON dumps of wallet metadata (accounts, addresses and
 dump      - produce JSON dumps of wallet metadata (accounts, addresses and
@@ -326,18 +328,20 @@ differences that apply to autosigning:
                               Exporting Outputs
                               Exporting Outputs
 
 
 Exporting outputs from a watch-only wallet is generally required in only
 Exporting outputs from a watch-only wallet is generally required in only
-two cases:
+three cases:
 
 
-  a) at the start of each signing session (after ‘mmgen-autosign setup’); and
+  a) at the start of each signing session (after ‘mmgen-autosign setup’);
   b) after the wallet has received funds from an outside source or another
   b) after the wallet has received funds from an outside source or another
-     wallet.
+     wallet; and
+  c) after performing a ‘resubmit’ operation.
 
 
 You might also need to do it, however, if an offline wallet is unable to sign
 You might also need to do it, however, if an offline wallet is unable to sign
 a transaction due to missing outputs.
 a transaction due to missing outputs.
 
 
-Export outputs from a wallet as follows:
+Export outputs from a wallet as follows (note that the --rescan-blockchain
+option is required only after a ‘resubmit’ – otherwise it should be omitted):
 
 
-$ mmgen-xmrwallet --autosign export-outputs <wallet index>
+$ mmgen-xmrwallet --autosign --rescan-blockchain export-outputs <wallet index>
 
 
 At the start of a new signing session, you must export outputs from ALL
 At the start of a new signing session, you must export outputs from ALL
 wallets you intend to transact with.  This is necessary because the offline
 wallets you intend to transact with.  This is necessary because the offline
@@ -349,6 +353,11 @@ into the corresponding signing wallet(s) (and optionally redo any failed
 transaction signing operation).  The signing wallet(s) will also create
 transaction signing operation).  The signing wallet(s) will also create
 signed key images.
 signed key images.
 
 
+Following a ‘resubmit’, you must then import the signed key images into your
+online wallet as follows:
+
+$ mmgen-xmrwallet --autosign import-key-images
+
 
 
            Replacing Existing Hot Wallets with Watch-Only Wallets
            Replacing Existing Hot Wallets with Watch-Only Wallets
 
 

+ 3 - 2
mmgen/main_xmrwallet.py

@@ -49,6 +49,7 @@ opts_data = {
 			'[opts] sweep    [xmr_keyaddrfile] SWEEP_SPEC',
 			'[opts] sweep    [xmr_keyaddrfile] SWEEP_SPEC',
 			'[opts] submit   [TX_file]',
 			'[opts] submit   [TX_file]',
 			'[opts] relay    <TX_file>',
 			'[opts] relay    <TX_file>',
+			'[opts] resubmit',
 			'[opts] txview | txlist [TX_file] ...',
 			'[opts] txview | txlist [TX_file] ...',
 			'[opts] export-outputs    [wallets]',
 			'[opts] export-outputs    [wallets]',
 			'[opts] import-key-images [wallets]',
 			'[opts] import-key-images [wallets]',
@@ -112,7 +113,7 @@ if cmd_args and cfg.autosign and (
 			MoneroWalletOps.kafile_arg_ops
 			MoneroWalletOps.kafile_arg_ops
 			+ ('export-outputs','import-key-images','txview','txlist')
 			+ ('export-outputs','import-key-images','txview','txlist')
 		)
 		)
-		or len(cmd_args) == 1 and cmd_args[0] == 'submit'
+		or len(cmd_args) == 1 and cmd_args[0] in ('submit','resubmit')
 	):
 	):
 	cmd_args.insert(1,None)
 	cmd_args.insert(1,None)
 
 
@@ -126,7 +127,7 @@ wallets = spec = None
 if op.replace('-','_') not in MoneroWalletOps.ops:
 if op.replace('-','_') not in MoneroWalletOps.ops:
 	die(1,f'{op!r}: unrecognized operation')
 	die(1,f'{op!r}: unrecognized operation')
 
 
-if op in ('relay','submit'):
+if op in ('relay','submit','resubmit'):
 	if len(cmd_args) != 0:
 	if len(cmd_args) != 0:
 		cfg._opts.usage()
 		cfg._opts.usage()
 elif op in ('txview','txlist'):
 elif op in ('txview','txlist'):

+ 33 - 1
mmgen/xmrwallet.py

@@ -446,6 +446,7 @@ class MoneroMMGenTX:
 	class Submitted(ColdSigned):
 	class Submitted(ColdSigned):
 		desc = 'submitted transaction'
 		desc = 'submitted transaction'
 		ext = 'subtx'
 		ext = 'subtx'
+		silent_load = True
 
 
 	class View(Completed):
 	class View(Completed):
 		silent_load = True
 		silent_load = True
@@ -606,6 +607,7 @@ class MoneroWalletOps:
 		'label',
 		'label',
 		'sign',
 		'sign',
 		'submit',
 		'submit',
+		'resubmit',
 		'dump',
 		'dump',
 		'restore',
 		'restore',
 		'export_outputs',
 		'export_outputs',
@@ -1266,6 +1268,10 @@ class MoneroWalletOps:
 		opts = ('rescan_blockchain',)
 		opts = ('rescan_blockchain',)
 		test_monerod = True
 		test_monerod = True
 
 
+		def check_uopts(self):
+			if self.cfg.rescan_blockchain and self.cfg.watch_only:
+				die(1,f'Operation {self.name!r} does not support --rescan-blockchain with watch-only wallets')
+
 		def __init__(self,cfg,uarg_tuple):
 		def __init__(self,cfg,uarg_tuple):
 
 
 			super().__init__(cfg,uarg_tuple)
 			super().__init__(cfg,uarg_tuple)
@@ -1748,6 +1754,25 @@ class MoneroWalletOps:
 				ask_overwrite = not self.cfg.autosign )
 				ask_overwrite = not self.cfg.autosign )
 			return new_tx
 			return new_tx
 
 
+	class resubmit(submit):
+		action = 'resubmitting transaction with'
+
+		def check_uopts(self):
+			if not self.cfg.autosign:
+				die(1,f'--autosign is required for this operation')
+
+		def get_tx(self):
+			asi = get_autosign_obj(self.cfg,'xmr')
+			files = [f for f in asi.xmr_tx_dir.iterdir() if f.name.endswith('.'+MoneroMMGenTX.Submitted.ext)]
+			txs = sorted(
+				(MoneroMMGenTX.Submitted( self.cfg, Path(fn) ) for fn in files),
+					key = lambda x: getattr(x.data,'submit_time',None) or x.data.create_time
+			)
+			if txs:
+				return txs[-1]
+			else:
+				self.die_no_tx( 'submitted', 0, asi.xmr_tx_dir )
+
 	class dump(wallet):
 	class dump(wallet):
 
 
 		async def process_wallet(self,d,fn,last):
 		async def process_wallet(self,d,fn,last):
@@ -1764,10 +1789,17 @@ class MoneroWalletOps:
 	class export_outputs(wallet):
 	class export_outputs(wallet):
 		action = 'exporting outputs from'
 		action = 'exporting outputs from'
 		stem = 'process'
 		stem = 'process'
+		opts = ('rescan_blockchain',)
 
 
 		async def process_wallet(self,d,fn,last):
 		async def process_wallet(self,d,fn,last):
 			h = self.rpc(self,d)
 			h = self.rpc(self,d)
 			h.open_wallet('source')
 			h.open_wallet('source')
+
+			if self.cfg.rescan_blockchain:
+				gmsg_r(f'\n  Rescanning blockchain...')
+				self.c.call('rescan_blockchain')
+				gmsg('done')
+
 			self.head_msg(d.idx,h.fn)
 			self.head_msg(d.idx,h.fn)
 			for ftype in ('Unsigned','Signed'):
 			for ftype in ('Unsigned','Signed'):
 				old_fn = getattr(MoneroWalletOutputsFile,ftype).find_fn_from_wallet_fn(
 				old_fn = getattr(MoneroWalletOutputsFile,ftype).find_fn_from_wallet_fn(
@@ -1803,7 +1835,7 @@ class MoneroWalletOps:
 			idata = res['num_imported']
 			idata = res['num_imported']
 			bmsg('\n  {} output{} imported'.format( idata, suf(idata) ))
 			bmsg('\n  {} output{} imported'.format( idata, suf(idata) ))
 			data = m.data._asdict()
 			data = m.data._asdict()
-			data.update(self.c.call('export_key_images')) # for testing: all = True
+			data.update(self.c.call('export_key_images', all=True))
 			m = MoneroWalletOutputsFile.SignedNew(
 			m = MoneroWalletOutputsFile.SignedNew(
 				parent    = self,
 				parent    = self,
 				wallet_fn = m.get_wallet_fn(fn),
 				wallet_fn = m.get_wallet_fn(fn),

+ 30 - 9
test/test_py_d/ts_xmr_autosign.py

@@ -66,18 +66,23 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase):
 		('create_transfer_tx1',      'creating a transfer TX'),
 		('create_transfer_tx1',      'creating a transfer TX'),
 		('sign_transfer_tx1',        'signing the transfer TX'),
 		('sign_transfer_tx1',        'signing the transfer TX'),
 		('submit_transfer_tx1',      'submitting the transfer TX'),
 		('submit_transfer_tx1',      'submitting the transfer TX'),
+		('resubmit_transfer_tx1',    'resubmitting the transfer TX'),
+		('export_outputs1',          'exporting outputs from Alice’s watch-only wallet #1'),
+		('export_key_images1',       'exporting signed key images from Alice’s offline wallets'),
+		('import_key_images1',       'importing signed key images into Alice’s online wallets'),
+		('sync_chkbal1',             'syncing Alice’s wallet #1'),
 		('create_transfer_tx2',      'creating a transfer TX (for relaying via proxy)'),
 		('create_transfer_tx2',      'creating a transfer TX (for relaying via proxy)'),
 		('sign_transfer_tx2',        'signing the transfer TX (for relaying via proxy)'),
 		('sign_transfer_tx2',        'signing the transfer TX (for relaying via proxy)'),
 		('submit_transfer_tx2',      'submitting the transfer TX (relaying via proxy)'),
 		('submit_transfer_tx2',      'submitting the transfer TX (relaying via proxy)'),
-		('sync_chkbal1',             'syncing Alice’s wallets and checking balance'),
+		('sync_chkbal2',             'syncing Alice’s wallets and checking balance'),
 		('dump_wallets',             'dumping Alice’s wallets'),
 		('dump_wallets',             'dumping Alice’s wallets'),
 		('delete_wallets',           'deleting Alice’s wallets'),
 		('delete_wallets',           'deleting Alice’s wallets'),
 		('restore_wallets',          'creating online (watch-only) wallets for Alice'),
 		('restore_wallets',          'creating online (watch-only) wallets for Alice'),
 		('delete_dump_files',        'deleting Alice’s dump files'),
 		('delete_dump_files',        'deleting Alice’s dump files'),
-		('export_outputs',           'exporting outputs from Alice’s watch-only wallets'),
-		('export_key_images',        'exporting signed key images from Alice’s offline wallets'),
-		('import_key_images',        'importing signed key images into Alice’s online wallets'),
-		('sync_chkbal2',             'syncing Alice’s wallets and checking balance'),
+		('export_outputs2',          'exporting outputs from Alice’s watch-only wallets'),
+		('export_key_images2',       'exporting signed key images from Alice’s offline wallets'),
+		('import_key_images2',       'importing signed key images into Alice’s online wallets'),
+		('sync_chkbal3',             'syncing Alice’s wallets and checking balance'),
 		('txlist',                   'listing Alice’s submitted transactions'),
 		('txlist',                   'listing Alice’s submitted transactions'),
 		('check_tx_dirs',            'cleaning and checking signable file directories'),
 		('check_tx_dirs',            'cleaning and checking signable file directories'),
 	)
 	)
@@ -247,10 +252,14 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase):
 			bal_chk_func = bal_chk_func )
 			bal_chk_func = bal_chk_func )
 
 
 	def sync_chkbal1(self):
 	def sync_chkbal1(self):
+		return self._sync_chkbal( lambda n,b,ub: b == ub and 1 < b < 1.12 )
+		# 1.234567891234 - 0.124 = 1.110567891234 (minus fees)
+
+	def sync_chkbal2(self):
 		return self._sync_chkbal( lambda n,b,ub: b == ub and 0.8 < b < 0.86 )
 		return self._sync_chkbal( lambda n,b,ub: b == ub and 0.8 < b < 0.86 )
 		# 1.234567891234 - 0.124 - 0.257 = 0.853567891234 (minus fees)
 		# 1.234567891234 - 0.124 - 0.257 = 0.853567891234 (minus fees)
 
 
-	sync_chkbal2 = sync_chkbal1
+	sync_chkbal3 = sync_chkbal2
 
 
 	def _mine_chk(self,desc):
 	def _mine_chk(self,desc):
 		bal_type = {'locked':'b','unlocked':'ub'}[desc]
 		bal_type = {'locked':'b','unlocked':'ub'}[desc]
@@ -262,6 +271,9 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase):
 	def submit_transfer_tx1(self):
 	def submit_transfer_tx1(self):
 		return self._submit_transfer_tx( ext='sigtx' )
 		return self._submit_transfer_tx( ext='sigtx' )
 
 
+	def resubmit_transfer_tx1(self):
+		return self._submit_transfer_tx( relay_parm=self.tx_relay_daemon_proxy_parm, op='resubmit', check_bal=False )
+
 	def submit_transfer_tx2(self):
 	def submit_transfer_tx2(self):
 		return self._submit_transfer_tx( relay_parm=self.tx_relay_daemon_parm )
 		return self._submit_transfer_tx( relay_parm=self.tx_relay_daemon_parm )
 
 
@@ -288,14 +300,20 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase):
 			wallet_arg = wallet_arg,
 			wallet_arg = wallet_arg,
 			add_opts = add_opts )
 			add_opts = add_opts )
 
 
-	def export_outputs(self):
+	def export_outputs1(self):
+		return self._export_outputs('1',['--rescan-blockchain'])
+
+	def export_outputs2(self):
 		return self._export_outputs('1-2')
 		return self._export_outputs('1-2')
 
 
 	def _export_key_images(self,tx_count):
 	def _export_key_images(self,tx_count):
 		self.tx_count = tx_count
 		self.tx_count = tx_count
 		return self.do_sign(['--full-summary'],tx_name='Monero wallet outputs file')
 		return self.do_sign(['--full-summary'],tx_name='Monero wallet outputs file')
 
 
-	def export_key_images(self):
+	def export_key_images1(self):
+		return self._export_key_images(1)
+
+	def export_key_images2(self):
 		return self._export_key_images(2)
 		return self._export_key_images(2)
 
 
 	def _import_key_images(self,wallet_arg):
 	def _import_key_images(self,wallet_arg):
@@ -304,7 +322,10 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase):
 			desc  = 'importing key images',
 			desc  = 'importing key images',
 			wallet_arg = wallet_arg )
 			wallet_arg = wallet_arg )
 
 
-	def import_key_images(self):
+	def import_key_images1(self):
+		return self._import_key_images(None)
+
+	def import_key_images2(self):
 		return self._import_key_images(None)
 		return self._import_key_images(None)
 
 
 	def create_fake_tx_files(self):
 	def create_fake_tx_files(self):