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’
             or ‘transfer’ with the --no-relay option
 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
 txlist    - same as above, but display terse information in tabular format
 dump      - produce JSON dumps of wallet metadata (accounts, addresses and
@@ -326,18 +328,20 @@ differences that apply to autosigning:
                               Exporting Outputs
 
 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
-     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
 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
 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
 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
 

+ 3 - 2
mmgen/main_xmrwallet.py

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

+ 33 - 1
mmgen/xmrwallet.py

@@ -446,6 +446,7 @@ class MoneroMMGenTX:
 	class Submitted(ColdSigned):
 		desc = 'submitted transaction'
 		ext = 'subtx'
+		silent_load = True
 
 	class View(Completed):
 		silent_load = True
@@ -606,6 +607,7 @@ class MoneroWalletOps:
 		'label',
 		'sign',
 		'submit',
+		'resubmit',
 		'dump',
 		'restore',
 		'export_outputs',
@@ -1266,6 +1268,10 @@ class MoneroWalletOps:
 		opts = ('rescan_blockchain',)
 		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):
 
 			super().__init__(cfg,uarg_tuple)
@@ -1748,6 +1754,25 @@ class MoneroWalletOps:
 				ask_overwrite = not self.cfg.autosign )
 			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):
 
 		async def process_wallet(self,d,fn,last):
@@ -1764,10 +1789,17 @@ class MoneroWalletOps:
 	class export_outputs(wallet):
 		action = 'exporting outputs from'
 		stem = 'process'
+		opts = ('rescan_blockchain',)
 
 		async def process_wallet(self,d,fn,last):
 			h = self.rpc(self,d)
 			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)
 			for ftype in ('Unsigned','Signed'):
 				old_fn = getattr(MoneroWalletOutputsFile,ftype).find_fn_from_wallet_fn(
@@ -1803,7 +1835,7 @@ class MoneroWalletOps:
 			idata = res['num_imported']
 			bmsg('\n  {} output{} imported'.format( idata, suf(idata) ))
 			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(
 				parent    = self,
 				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'),
 		('sign_transfer_tx1',        'signing 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)'),
 		('sign_transfer_tx2',        'signing the transfer TX (for 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'),
 		('delete_wallets',           'deleting Alice’s wallets'),
 		('restore_wallets',          'creating online (watch-only) wallets for Alice'),
 		('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'),
 		('check_tx_dirs',            'cleaning and checking signable file directories'),
 	)
@@ -247,10 +252,14 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase):
 			bal_chk_func = bal_chk_func )
 
 	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 )
 		# 1.234567891234 - 0.124 - 0.257 = 0.853567891234 (minus fees)
 
-	sync_chkbal2 = sync_chkbal1
+	sync_chkbal3 = sync_chkbal2
 
 	def _mine_chk(self,desc):
 		bal_type = {'locked':'b','unlocked':'ub'}[desc]
@@ -262,6 +271,9 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase):
 	def submit_transfer_tx1(self):
 		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):
 		return self._submit_transfer_tx( relay_parm=self.tx_relay_daemon_parm )
 
@@ -288,14 +300,20 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase):
 			wallet_arg = wallet_arg,
 			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')
 
 	def _export_key_images(self,tx_count):
 		self.tx_count = tx_count
 		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)
 
 	def _import_key_images(self,wallet_arg):
@@ -304,7 +322,10 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase):
 			desc  = 'importing key images',
 			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)
 
 	def create_fake_tx_files(self):