mmgen-xmrwallet: new view and listview operations

- 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
This commit is contained in:
The MMGen Project 2024-02-11 09:50:20 +00:00
commit 0de5e47c3c
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
5 changed files with 66 additions and 13 deletions

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -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')

View file

@ -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(