From 3b7a238259d95abe060ce7119df4687666f154a9 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 4 May 2023 19:04:35 +0000 Subject: [PATCH] mmgen-xmrwallet: new `resubmit` operation --- mmgen/data/release_date | 2 +- mmgen/data/version | 2 +- mmgen/help/xmrwallet.py | 19 +++++++++++---- mmgen/main_xmrwallet.py | 5 ++-- mmgen/xmrwallet.py | 34 ++++++++++++++++++++++++++- test/test_py_d/ts_xmr_autosign.py | 39 ++++++++++++++++++++++++------- 6 files changed, 82 insertions(+), 19 deletions(-) diff --git a/mmgen/data/release_date b/mmgen/data/release_date index 40f7e134..10f4da01 100644 --- a/mmgen/data/release_date +++ b/mmgen/data/release_date @@ -1 +1 @@ -April 2023 +May 2023 diff --git a/mmgen/data/version b/mmgen/data/version index 7afb3d68..f1ccf325 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.3.dev50 +13.3.dev51 diff --git a/mmgen/help/xmrwallet.py b/mmgen/help/xmrwallet.py index b9c20e59..335725f8 100755 --- a/mmgen/help/xmrwallet.py +++ b/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 +$ mmgen-xmrwallet --autosign --rescan-blockchain export-outputs 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 diff --git a/mmgen/main_xmrwallet.py b/mmgen/main_xmrwallet.py index 9b25d6c3..a2f33602 100755 --- a/mmgen/main_xmrwallet.py +++ b/mmgen/main_xmrwallet.py @@ -49,6 +49,7 @@ opts_data = { '[opts] sweep [xmr_keyaddrfile] SWEEP_SPEC', '[opts] submit [TX_file]', '[opts] relay ', + '[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'): diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index a8e1e07f..200f278b 100755 --- a/mmgen/xmrwallet.py +++ b/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), diff --git a/test/test_py_d/ts_xmr_autosign.py b/test/test_py_d/ts_xmr_autosign.py index 65f866d3..715e9057 100755 --- a/test/test_py_d/ts_xmr_autosign.py +++ b/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):