From a1986fe631f274be4903ca38a7eef628dd4c1811 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sun, 30 Apr 2023 09:36:01 +0000 Subject: [PATCH] mmgen-xmrwallet: new `txlist` operation Testing: $ test/test.py -e xmrwallet_txlist --- mmgen/data/version | 2 +- mmgen/help/xmrwallet.py | 3 +- mmgen/main_xmrwallet.py | 6 +-- mmgen/xmrwallet.py | 68 +++++++++++++++++++++++++++++-- test/test_py_d/ts_misc.py | 21 ++++++---- test/test_py_d/ts_xmr_autosign.py | 11 +++++ 6 files changed, 95 insertions(+), 16 deletions(-) diff --git a/mmgen/data/version b/mmgen/data/version index 27f7c1ad..7afb3d68 100644 --- a/mmgen/data/version +++ b/mmgen/data/version @@ -1 +1 @@ -13.3.dev49 +13.3.dev50 diff --git a/mmgen/help/xmrwallet.py b/mmgen/help/xmrwallet.py index db47ea59..50d18363 100755 --- a/mmgen/help/xmrwallet.py +++ b/mmgen/help/xmrwallet.py @@ -48,6 +48,7 @@ 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 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 labels) for a list or range of wallets restore - same as ‘create’, but additionally restore wallet metadata from @@ -134,7 +135,7 @@ wallets, creating the dumps is as easy as executing ‘mmgen-xmrwallet JSON and thus suitable for efficient incremental backup using git. - ‘TXVIEW’ OPERATION NOTES + ‘TXVIEW’ AND ‘TXLIST’ OPERATION NOTES Transactions are displayed in chronological order based on submit time or creation time. With --autosign, submitted transactions on the removable diff --git a/mmgen/main_xmrwallet.py b/mmgen/main_xmrwallet.py index 01e28701..153945de 100755 --- a/mmgen/main_xmrwallet.py +++ b/mmgen/main_xmrwallet.py @@ -49,7 +49,7 @@ opts_data = { '[opts] sweep [xmr_keyaddrfile] SWEEP_SPEC', '[opts] submit [TX_file]', '[opts] relay ', - '[opts] txview [TX_file] ...', + '[opts] txview | txlist [TX_file] ...', '[opts] export-outputs [wallets]', '[opts] import-key-images [wallets]', ], @@ -113,7 +113,7 @@ cmd_args = cfg._args if cmd_args and cfg.autosign and ( cmd_args[0] in ( MoneroWalletOps.kafile_arg_ops - + ('export-outputs','import-key-images','txview') + + ('export-outputs','import-key-images','txview','txlist') ) or len(cmd_args) == 1 and cmd_args[0] == 'submit' ): @@ -132,7 +132,7 @@ if op.replace('-','_') not in MoneroWalletOps.ops: if op in ('relay','submit'): if len(cmd_args) != 0: cfg._opts.usage() -elif op == 'txview': +elif op in ('txview','txlist'): infile = [infile] + cmd_args elif op in ('create','sync','list','dump','restore'): # kafile_arg_ops if len(cmd_args) > 1: diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index 58846eaa..31f4c3b0 100755 --- a/mmgen/xmrwallet.py +++ b/mmgen/xmrwallet.py @@ -26,7 +26,7 @@ from pathlib import PosixPath as Path from .objmethods import MMGenObject,Hilite,InitErrors from .obj import CoinTxID,Int -from .color import red,yellow,green,blue,cyan,pink,orange +from .color import red,yellow,green,blue,cyan,pink,orange,purple from .util import ( msg, msg_r, @@ -190,6 +190,7 @@ class MoneroMMGenTX: 'amount', 'fee', 'blob' } + oneline_fs = '{a:7} {b:8} {c:19} {d:13} {e:8} {f:6} {x:2} {g:6} {h:17} {j}' chksum_nchars = 6 xmrwallet_tx_data = namedtuple('xmrwallet_tx_data',[ 'op', @@ -218,7 +219,22 @@ class MoneroMMGenTX: def src_wallet_idx(self): return int(self.data.source.split(':')[0]) - def get_info(self,indent=''): + def get_info_oneline(self,indent='',cols=None): + d = self.data + return self.oneline_fs.format( + a = yellow(d.network), + b = d.seed_id.hl(), + c = make_timestr(d.submit_time if d.submit_time is not None else d.create_time), + d = orange(self.file_id), + e = purple(capfirst(d.op.ljust(8))), + f = red('{}:{}'.format(d.source.wallet,d.source.account).ljust(6)), + g = red('{}:{}'.format(d.dest.wallet,d.dest.account).ljust(6)) if d.dest else cyan('ext '), + h = d.amount.fmt( color=True, iwidth=4, prec=12 ), + j = d.dest_address.fmt(width=cols-95,color=True) if cols else d.dest_address.hl(), + x = '->' + ) + + def get_info(self,indent='',cols=None): d = self.data pmt_id = d.dest_address.parsed.payment_id fs = '\n'.join(list_gen( @@ -584,6 +600,7 @@ class MoneroWalletOps: 'sweep', 'relay', 'txview', + 'txlist', 'label', 'sign', 'submit', @@ -1819,10 +1836,15 @@ class MoneroWalletOps: die(1,'Exiting at user request') class txview(base): + view_method = 'get_info' opts = ('watch_only','autosign') + hdr = '' + col_hdr = '' + footer = '' + cols = None do_umount = False - async def main(self): + async def main(self,cols=None): if self.cfg.autosign: asi = get_autosign_obj(self.cfg,'xmr') files = [f for f in asi.xmr_tx_dir.iterdir() if f.name.endswith('.'+MoneroMMGenTX.Submitted.ext)] @@ -1836,5 +1858,43 @@ class MoneroWalletOps: if self.cfg.autosign: asi.do_umount() self.cfg._util.stdout_or_pager( - '\n'.join(tx.get_info() for tx in txs) + (self.hdr if len(files) > 1 else '') + + self.col_hdr + + '\n'.join(getattr(tx,self.view_method)(cols=cols) for tx in txs) + + self.footer ) + + class txlist(txview): + view_method = 'get_info_oneline' + add_nl = True + footer = '\n' + + @property + def hdr(self): + return ('SUBMITTED ' if self.cfg.autosign else '') + 'MONERO TRANSACTIONS\n' + + @property + def col_hdr(self): + return MoneroMMGenTX.View.oneline_fs.format( + a = 'Network', + b = 'Seed ID', + c = 'Submitted' if self.cfg.autosign else 'Date', + d = 'TxID', + e = 'Type', + f = 'Src', + g = 'Dest', + h = ' Amount', + j = 'Dest Address', + x = '', + ) + '\n' + + async def main(self): + if self.cfg.pager: + cols = None + else: + from .term import get_terminal_size + cols = self.cfg.columns or get_terminal_size().width + if cols < 106: + die(1, 'A terminal at least 106 columns wide is required to display this output' + + ' (or use --columns or --pager)' ) + await super().main(cols=cols) diff --git a/test/test_py_d/ts_misc.py b/test/test_py_d/ts_misc.py index 81fc22e9..21115b8e 100755 --- a/test/test_py_d/ts_misc.py +++ b/test/test_py_d/ts_misc.py @@ -34,6 +34,7 @@ class TestSuiteMisc(TestSuiteBase): cmd_group = ( ('rpc_backends', 'RPC backends'), ('xmrwallet_txview', "'mmgen-xmrwallet' txview"), + ('xmrwallet_txlist', "'mmgen-xmrwallet' txlist"), ('coin_daemon_info', "'examples/coin-daemon-info.py'"), ('term_echo', "term.set('echo')"), ('term_cleanup', 'term.register_cleanup()'), @@ -47,17 +48,23 @@ class TestSuiteMisc(TestSuiteBase): t = self.spawn_chk('mmgen-tool',[f'--rpc-backend={b}','daemon_version'],extra_desc=f'({b})') return t - def xmrwallet_txview(self): + def xmrwallet_txview(self,op='txview'): files = get_file_with_ext('test/ref/monero','tx',no_dot=True,delete=False,return_list=True) - t = self.spawn( f'mmgen-xmrwallet', ['txview'] + files ) + t = self.spawn( f'mmgen-xmrwallet', [op] + files ) res = strip_ansi_escapes(t.read()).replace('\r','') - for s in ( - 'Amount: 0.74 XMR', - 'Dest: 56VQ9M6k', - ): - assert s in res, f'{s} not in {res}' + if op == 'txview': + for s in ( + 'Amount: 0.74 XMR', + 'Dest: 56VQ9M6k', + ): + assert s in res, f'{s} not in {res}' + elif op == 'txlist': + assert re.search( '3EBD06-.*D94583-.*8BFA29-', res, re.DOTALL ) return t + def xmrwallet_txlist(self): + return self.xmrwallet_txview(op='txlist') + def coin_daemon_info(self): start_test_daemons('ltc','eth') t = self.spawn(f'examples/coin-daemon-info.py',['btc','ltc','eth'],cmd_dir='.') diff --git a/test/test_py_d/ts_xmr_autosign.py b/test/test_py_d/ts_xmr_autosign.py index 6ae891f0..5350180b 100755 --- a/test/test_py_d/ts_xmr_autosign.py +++ b/test/test_py_d/ts_xmr_autosign.py @@ -78,6 +78,7 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase): ('export_key_images', 'exporting signed key images from Alice’s offline wallets'), ('import_key_images', 'importing signed key images into Alice’s online wallets'), ('list_wallets', 'listing Alice’s wallets and checking balance'), + ('txlist', 'listing Alice’s submitted transactions'), ) def __init__(self,trunner,cfgs,spawn): @@ -358,3 +359,13 @@ class TestSuiteXMRAutosign(TestSuiteXMRWallet,TestSuiteAutosignBase): assert after + '\n' == fmt(chk), f'\n{after}\n!=\n{fmt(chk)}' return t + + def txlist(self): + t = self.spawn( 'mmgen-xmrwallet', self.autosign_opts + ['txlist'] ) + t.match_expect_list([ + 'SUBMITTED', + 'Network','Submitted', + 'Transfer 1:0','-> ext', + 'Transfer 1:0','-> ext' + ]) + return t