Browse Source

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
The MMGen Project 8 months ago
parent
commit
0de5e47c3c

+ 4 - 0
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
 create    - create wallets for all or specified addresses in key-address file
 sync      - sync 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
 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
 label     - set a label for an address
 new       - create a new account in a wallet, or a new address in an account
 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
 transfer  - transfer specified XMR amount from specified wallet:account to

+ 2 - 2
mmgen/main_xmrwallet.py

@@ -39,7 +39,7 @@ opts_data = {
 		'desc': """Perform various Monero wallet and transacting operations for
 		'desc': """Perform various Monero wallet and transacting operations for
                    addresses in an MMGen XMR key-address file""",
                    addresses in an MMGen XMR key-address file""",
 		'usage2': [
 		'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] label    [xmr_keyaddrfile] LABEL_SPEC',
 			'[opts] new      [xmr_keyaddrfile] NEW_ADDRESS_SPEC',
 			'[opts] new      [xmr_keyaddrfile] NEW_ADDRESS_SPEC',
 			'[opts] transfer [xmr_keyaddrfile] TRANSFER_SPEC',
 			'[opts] transfer [xmr_keyaddrfile] TRANSFER_SPEC',
@@ -126,7 +126,7 @@ if op in ('relay','submit','resubmit'):
 		cfg._opts.usage()
 		cfg._opts.usage()
 elif op in ('txview','txlist'):
 elif op in ('txview','txlist'):
 	infile = [infile] + cmd_args
 	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:
 	if len(cmd_args) > 1:
 		cfg._opts.usage()
 		cfg._opts.usage()
 	wallets = cmd_args.pop(0) if cmd_args else None
 	wallets = cmd_args.pop(0) if cmd_args else None

+ 41 - 3
mmgen/xmrwallet.py

@@ -605,6 +605,8 @@ class MoneroWalletOps:
 		'create_offline',
 		'create_offline',
 		'sync',
 		'sync',
 		'list',
 		'list',
+		'view',
+		'listview',
 		'new',
 		'new',
 		'transfer',
 		'transfer',
 		'sweep',
 		'sweep',
@@ -624,6 +626,8 @@ class MoneroWalletOps:
 		'create',
 		'create',
 		'sync',
 		'sync',
 		'list',
 		'list',
+		'view',
+		'listview',
 		'label',
 		'label',
 		'new',
 		'new',
 		'transfer',
 		'transfer',
@@ -686,6 +690,8 @@ class MoneroWalletOps:
 
 
 			self.proto = init_proto( cfg, 'xmr', network=self.cfg.network, need_amt=True )
 			self.proto = init_proto( cfg, 'xmr', network=self.cfg.network, need_amt=True )
 
 
+			self.pre_init_action()
+
 		def check_uopts(self):
 		def check_uopts(self):
 
 
 			def check_pat_opt(name):
 			def check_pat_opt(name):
@@ -722,6 +728,9 @@ class MoneroWalletOps:
 				  Proxy: {blue(m[2] or 'None')}
 				  Proxy: {blue(m[2] or 'None')}
 				""",strip_char='\t',indent=indent))
 				""",strip_char='\t',indent=indent))
 
 
+		def pre_init_action(self):
+			pass
+
 		def post_main_success(self):
 		def post_main_success(self):
 			pass
 			pass
 
 
@@ -1298,7 +1307,8 @@ class MoneroWalletOps:
 
 
 			super().__init__(cfg,uarg_tuple)
 			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 = {}
 			self.accts_data = {}
 
 
@@ -1368,9 +1378,9 @@ class MoneroWalletOps:
 			d = self.accts_data
 			d = self.accts_data
 
 
 			for wnum,k in enumerate(d):
 			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='')
 					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} {} {} {}'
 					fs = '  {:2} {} {} {}'
 					msg('\n' + green(f'Wallet {k}:'))
 					msg('\n' + green(f'Wallet {k}:'))
 					for acct_num,acct in enumerate(d[k]['addrs']):
 					for acct_num,acct in enumerate(d[k]['addrs']):
@@ -1407,6 +1417,34 @@ class MoneroWalletOps:
 	class list(sync):
 	class list(sync):
 		stem = '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
 	class spec(wallet): # virtual class
 
 
 		def create_addr_data(self):
 		def create_addr_data(self):

+ 8 - 0
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'),
 		('check_tx_dirs',            'cleaning and checking signable file directories'),
 		('autosign_kill_thread',     'stopping autosign wait loop'),
 		('autosign_kill_thread',     'stopping autosign wait loop'),
 		('stop_daemons',             'stopping all wallet and coin daemons'),
 		('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):
 	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*$'
 		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'
 		assert re.search( pat, after, re.DOTALL ), f'regex search for {pat} failed'
 		return t
 		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')

+ 11 - 8
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
 		wlist = AddrIdxList(wallets) if wallets else MMGenRange(data.kal_range).items
 		for n,wnum in enumerate(wlist,1):
 		for n,wnum in enumerate(wlist,1):
-			t.expect('Syncing wallet {}/{} ({})'.format(
+			t.expect('ing wallet {}/{} ({})'.format(
 				n,
 				n,
 				len(wlist),
 				len(wlist),
 				os.path.basename(data.walletfile_fs.format(wnum)),
 				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
 		return t
 
 
 	def do_op(
 	def do_op(