From 0de5e47c3cfd30194537c080fa36c35c40f0dccf Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 11 Feb 2024 09:50:20 +0000 Subject: [PATCH] mmgen-xmrwallet: new `view` and `listview` operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - view: display a summary of accounts and balances in offline mode. May be invoked without a running monerod - listview: same as ‘view’, but also list detailed address info for accounts --- mmgen/help/xmrwallet.py | 4 +++ mmgen/main_xmrwallet.py | 4 +-- mmgen/xmrwallet.py | 44 ++++++++++++++++++++++++++-- test/cmdtest_py_d/ct_xmr_autosign.py | 8 +++++ test/cmdtest_py_d/ct_xmrwallet.py | 19 +++++++----- 5 files changed, 66 insertions(+), 13 deletions(-) diff --git a/mmgen/help/xmrwallet.py b/mmgen/help/xmrwallet.py index 5bdfa082..3efb08a2 100755 --- a/mmgen/help/xmrwallet.py +++ b/mmgen/help/xmrwallet.py @@ -37,7 +37,11 @@ plain HTTP is not supported. create - create wallets for all or specified addresses in key-address file sync - sync wallets for all or specified addresses in key-address file + and display a summary of accounts and balances list - same as ‘sync’, but also list detailed address info for accounts +view - display a summary of accounts and balances in offline mode. May + be invoked without a running monerod +listview - same as ‘view’, but also list detailed address info for accounts label - set a label for an address new - create a new account in a wallet, or a new address in an account transfer - transfer specified XMR amount from specified wallet:account to diff --git a/mmgen/main_xmrwallet.py b/mmgen/main_xmrwallet.py index 16b6e594..e1fe45cd 100755 --- a/mmgen/main_xmrwallet.py +++ b/mmgen/main_xmrwallet.py @@ -39,7 +39,7 @@ opts_data = { 'desc': """Perform various Monero wallet and transacting operations for addresses in an MMGen XMR key-address file""", 'usage2': [ - '[opts] create | sync | list | dump | restore [xmr_keyaddrfile] [wallets]', + '[opts] create | sync | list | view | listview | dump | restore [xmr_keyaddrfile] [wallets]', '[opts] label [xmr_keyaddrfile] LABEL_SPEC', '[opts] new [xmr_keyaddrfile] NEW_ADDRESS_SPEC', '[opts] transfer [xmr_keyaddrfile] TRANSFER_SPEC', @@ -126,7 +126,7 @@ if op in ('relay','submit','resubmit'): cfg._opts.usage() elif op in ('txview','txlist'): infile = [infile] + cmd_args -elif op in ('create','sync','list','dump','restore'): # kafile_arg_ops +elif op in ('create','sync','list','view','listview','dump','restore'): # kafile_arg_ops if len(cmd_args) > 1: cfg._opts.usage() wallets = cmd_args.pop(0) if cmd_args else None diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index c2e67a31..762cd61e 100755 --- a/mmgen/xmrwallet.py +++ b/mmgen/xmrwallet.py @@ -605,6 +605,8 @@ class MoneroWalletOps: 'create_offline', 'sync', 'list', + 'view', + 'listview', 'new', 'transfer', 'sweep', @@ -624,6 +626,8 @@ class MoneroWalletOps: 'create', 'sync', 'list', + 'view', + 'listview', 'label', 'new', 'transfer', @@ -686,6 +690,8 @@ class MoneroWalletOps: self.proto = init_proto( cfg, 'xmr', network=self.cfg.network, need_amt=True ) + self.pre_init_action() + def check_uopts(self): def check_pat_opt(name): @@ -722,6 +728,9 @@ class MoneroWalletOps: Proxy: {blue(m[2] or 'None')} """,strip_char='\t',indent=indent)) + def pre_init_action(self): + pass + def post_main_success(self): pass @@ -1298,7 +1307,8 @@ class MoneroWalletOps: super().__init__(cfg,uarg_tuple) - self.dc = self.get_coin_daemon_rpc() + if not self.wallet_offline: + self.dc = self.get_coin_daemon_rpc() self.accts_data = {} @@ -1368,9 +1378,9 @@ class MoneroWalletOps: d = self.accts_data for wnum,k in enumerate(d): - if self.name == 'sync': + if self.name in ('sync','view'): self.rpc(self,self.addr_data[wnum]).print_accts( d[k]['accts'], d[k]['addrs'], indent='') - elif self.name == 'list': + elif self.name in ('list','listview'): fs = ' {:2} {} {} {}' msg('\n' + green(f'Wallet {k}:')) for acct_num,acct in enumerate(d[k]['addrs']): @@ -1407,6 +1417,34 @@ class MoneroWalletOps: class list(sync): stem = 'sync' + class view(sync): + stem = 'open' + opts = () + wallet_offline = True + + def pre_init_action(self): + ymsg('WARNING: Running in offline mode. Balances and other info may be out of date!') + + async def process_wallet(self,d,fn,last): + + self.c.call( + 'open_wallet', + filename = fn.name, + password = d.wallet_passwd) + + wallet_height = self.c.call('get_height')['height'] + msg(f' Wallet height: {wallet_height}') + a,b = self.rpc(self, d).get_accts(print=False) + self.accts_data[fn.name] = {'accts': a, 'addrs': b} + + if not last: + self.c.call('close_wallet') + + return True + + class listview(view): + pass + class spec(wallet): # virtual class def create_addr_data(self): diff --git a/test/cmdtest_py_d/ct_xmr_autosign.py b/test/cmdtest_py_d/ct_xmr_autosign.py index 38001d4e..29b04c06 100755 --- a/test/cmdtest_py_d/ct_xmr_autosign.py +++ b/test/cmdtest_py_d/ct_xmr_autosign.py @@ -102,6 +102,8 @@ class CmdTestXMRAutosign(CmdTestXMRWallet,CmdTestAutosignBase): ('check_tx_dirs', 'cleaning and checking signable file directories'), ('autosign_kill_thread', 'stopping autosign wait loop'), ('stop_daemons', 'stopping all wallet and coin daemons'), + ('view', 'viewing Alice’s wallet in offline mode (wallet #1)'), + ('listview', 'list-viewing Alice’s wallet in offline mode (wallet #2)'), ) def __init__(self,trunner,cfgs,spawn): @@ -488,3 +490,9 @@ class CmdTestXMRAutosign(CmdTestXMRWallet,CmdTestAutosignBase): pat = r'xmr/tx: \s*\S+\.subtx \S+\.subtx\s+xmr/outputs:\s*$' assert re.search( pat, after, re.DOTALL ), f'regex search for {pat} failed' return t + + def view(self): + return self.sync_wallets('alice', op='view', wallets='1') + + def listview(self): + return self.sync_wallets('alice', op='listview', wallets='2') diff --git a/test/cmdtest_py_d/ct_xmrwallet.py b/test/cmdtest_py_d/ct_xmrwallet.py index 7d4cce7b..15f41ced 100755 --- a/test/cmdtest_py_d/ct_xmrwallet.py +++ b/test/cmdtest_py_d/ct_xmrwallet.py @@ -500,18 +500,21 @@ class CmdTestXMRWallet(CmdTestBase): ) wlist = AddrIdxList(wallets) if wallets else MMGenRange(data.kal_range).items for n,wnum in enumerate(wlist,1): - t.expect('Syncing wallet {}/{} ({})'.format( + t.expect('ing wallet {}/{} ({})'.format( n, len(wlist), os.path.basename(data.walletfile_fs.format(wnum)), )) - t.expect('Chain height: ') - t.expect('Wallet height: ') - res = strip_ansi_escapes(t.expect_getend('Balance: ')) - if bal_chk_func: - m = re.match( r'(\S+) Unlocked balance: (\S+)', res, re.DOTALL ) - amts = [XMRAmt(amt) for amt in m.groups()] - assert bal_chk_func(n,*amts), f'balance check for wallet {n} failed!' + if op in ('view','listview'): + t.expect('Wallet height: ') + else: + t.expect('Chain height: ') + t.expect('Wallet height: ') + res = strip_ansi_escapes(t.expect_getend('Balance: ')) + if bal_chk_func: + m = re.match( r'(\S+) Unlocked balance: (\S+)', res, re.DOTALL ) + amts = [XMRAmt(amt) for amt in m.groups()] + assert bal_chk_func(n,*amts), f'balance check for wallet {n} failed!' return t def do_op(