From 2b026f1ae7e8e3efd3af2d9124fed547129d98bb Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Wed, 9 Nov 2022 13:05:08 +0000 Subject: [PATCH] mmgen.tw.{common,addrs,txhistory,unspent}: cleanups --- mmgen/objmethods.py | 2 +- mmgen/proto/btc/tw/common.py | 1 + mmgen/proto/btc/tw/txhistory.py | 2 +- mmgen/tool/rpc.py | 14 +++++--- mmgen/tw/addrs.py | 4 +-- mmgen/tw/common.py | 34 ++++++++++-------- mmgen/tw/txhistory.py | 9 +++-- mmgen/tw/unspent.py | 44 +++++++++++++----------- mmgen/util.py | 4 +-- test/overlay/fakemods/mmgen/tw/common.py | 2 +- test/test_py_d/ts_ethdev.py | 17 ++++----- test/test_py_d/ts_regtest.py | 4 ++- 12 files changed, 75 insertions(+), 62 deletions(-) diff --git a/mmgen/objmethods.py b/mmgen/objmethods.py index 73d93298..4b0c2b08 100755 --- a/mmgen/objmethods.py +++ b/mmgen/objmethods.py @@ -71,7 +71,7 @@ class Hilite: assert trunc_ok, "If 'trunc_ok' is false, 'width' must be >= screen width of string" s = truncate_str(s,width-add_len) if s == '' and nullrepl: - s = nullrepl.center(width) + s = nullrepl.ljust(width) else: s = a+s+b if center: diff --git a/mmgen/proto/btc/tw/common.py b/mmgen/proto/btc/tw/common.py index ab0ad5fa..818cfc51 100755 --- a/mmgen/proto/btc/tw/common.py +++ b/mmgen/proto/btc/tw/common.py @@ -96,6 +96,7 @@ class BitcoinTwCommon: else: lm.confs = d['confirmations'] lm.txid = d['txid'] + lm.vout = d['vout'] lm.date = None data[lm] = { 'amt': self.proto.coin_amt('0'), diff --git a/mmgen/proto/btc/tw/txhistory.py b/mmgen/proto/btc/tw/txhistory.py index 9b792f8a..e01033f7 100755 --- a/mmgen/proto/btc/tw/txhistory.py +++ b/mmgen/proto/btc/tw/txhistory.py @@ -231,7 +231,7 @@ class BitcoinTwTxHistory(TwTxHistory,BitcoinTwCommon): prompt = """ Sort options: [t]xid, [a]mt, total a[m]t, [A]ge, [b]locknum, [r]everse Column options: toggle [D]ays/date/confs/block, tx[i]d, [T]otal amt -Filter options: show [u]nconfirmed +Filters: show [u]nconfirmed View/Print: pager [v]iew, full [V]iew, screen [p]rint, full [P]rint Actions: [q]uit, r[e]draw: """ diff --git a/mmgen/tool/rpc.py b/mmgen/tool/rpc.py index 745c4622..117cf8a7 100755 --- a/mmgen/tool/rpc.py +++ b/mmgen/tool/rpc.py @@ -97,12 +97,15 @@ class tool_cmd(tool_cmd_base): return await al.format( showcoinaddrs, sort, show_age, age_fmt or 'confs' ) async def twops(self, - obj,pager,reverse,detail,sort,age_fmt,interactive,show_mmid): + obj,pager,reverse,detail,sort,age_fmt,interactive, + **kwargs ): - obj.interactive = interactive obj.reverse = reverse obj.age_fmt = age_fmt - obj.show_mmid = show_mmid + obj.interactive = interactive + + for k,v in kwargs.items(): + setattr(obj,k,v) await obj.get_data(sort_key=sort,reverse_sort=reverse) @@ -128,7 +131,8 @@ class tool_cmd(tool_cmd_base): from ..tw.unspent import TwUnspentOutputs obj = await TwUnspentOutputs(self.proto,minconf=minconf) ret = await self.twops( - obj,pager,reverse,wide,sort,age_fmt,interactive,show_mmid) + obj,pager,reverse,wide,sort,age_fmt,interactive, + show_mmid = show_mmid ) del obj.wallet return ret @@ -144,7 +148,7 @@ class tool_cmd(tool_cmd_base): obj = await TwTxHistory(self.proto,sinceblock=sinceblock) return await self.twops( - obj,pager,reverse,detail,sort,age_fmt,interactive,show_mmid=None) + obj,pager,reverse,detail,sort,age_fmt,interactive ) async def add_label(self,mmgen_or_coin_addr:str,label:str): "add descriptive label for address in tracking wallet" diff --git a/mmgen/tw/addrs.py b/mmgen/tw/addrs.py index 2e4c373a..1fceb364 100755 --- a/mmgen/tw/addrs.py +++ b/mmgen/tw/addrs.py @@ -63,9 +63,7 @@ class TwAddrList(MMGenDict,TwCommon,metaclass=AsyncInit): mmids = sorted(self,key=sort_algo,reverse=bool(sort and 'reverse' in sort)) if show_age: - await self.set_dates( - self.rpc, - [o for o in mmids if hasattr(o,'confs')] ) + await self.set_dates( [o for o in mmids if hasattr(o,'confs')] ) def gen_output(): diff --git a/mmgen/tw/common.py b/mmgen/tw/common.py index 894a1ba3..017c5acb 100755 --- a/mmgen/tw/common.py +++ b/mmgen/tw/common.py @@ -32,6 +32,7 @@ from ..addr import MMGenID # mixin class for TwUnspentOutputs,TwAddrList,TwTxHistory: class TwCommon: + dates_set = False cols = None reverse = False group = False @@ -55,12 +56,21 @@ class TwCommon: 'days': lambda rpc,secs: (rpc.cur_date - secs) // 86400 if secs else 0, 'date': ( lambda rpc,secs: '{}-{:02}-{:02}'.format(*time.gmtime(secs)[:3])[2:] - if secs else '--------' ), + if secs else '- '), 'date_time': ( lambda rpc,secs: '{}-{:02}-{:02} {:02}:{:02}'.format(*time.gmtime(secs)[:5]) - if secs else '---------- -----' ), + if secs else '- '), } + tcols_errmsg = """ + --columns or MMGEN_COLUMNS value ({}) is too small to display the {}. + Minimum value for this configuration: {} + """ + twid_errmsg = """ + Screen is too narrow to display the {} + Please resize your screen to at least {} characters and hit any key: + """ + def age_disp(self,o,age_fmt): if age_fmt == 'confs': return o.confs @@ -86,14 +96,14 @@ class TwCommon: self.do_sort(key=sort_key,reverse=reverse_sort) - @staticmethod - async def set_dates(rpc,us): - if us and us[0].date is None: + async def set_dates(self,us): + if not self.dates_set: # 'blocktime' differs from 'time', is same as getblockheader['time'] dates = [ o.get('blocktime',0) - for o in await rpc.gathered_icall('gettransaction',[(o.txid,True,False) for o in us]) ] + for o in await self.rpc.gathered_icall('gettransaction',[(o.txid,True,False) for o in us]) ] for idx,o in enumerate(us): o.date = dates[idx] + self.dates_set = True @property def age_w(self): @@ -129,13 +139,9 @@ class TwCommon: return cols if sys.stdout.isatty(): if g.columns: - die(1, - f'\n--columns or MMGEN_COLUMNS value ({g.columns}) is too small to display the {self.desc}.\n' - + f'Minimum value for this configuration: {min_cols}' ) + die(1,'\n'+fmt(self.tcols_errmsg.format(g.columns,self.desc,min_cols),indent=' ')) else: - get_char_raw( - f'\nScreen is too narrow to display the {self.desc}\n' - + f'Please resize your screen to at least {min_cols} characters and hit any key: ' ) + get_char_raw('\n'+fmt(self.twid_errmsg.format(self.desc,min_cols),append='')) else: return min_cols @@ -175,7 +181,7 @@ class TwCommon: if not cached: data = self.data if self.has_age and self.age_fmt in self.age_fmts_date_dependent: - await self.set_dates(self.rpc,data) + await self.set_dates(data) if not getattr(self,'column_widths',None): self.set_column_params() @@ -202,7 +208,7 @@ class TwCommon: async def format_detail(self,color): if self.has_age: - await self.set_dates(self.rpc,self.data) + await self.set_dates(self.data) sep = self.detail_display_separator diff --git a/mmgen/tw/txhistory.py b/mmgen/tw/txhistory.py index 754833a5..0baadea3 100755 --- a/mmgen/tw/txhistory.py +++ b/mmgen/tw/txhistory.py @@ -95,7 +95,7 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit): freew[k] = min( total_freew - sum(freew[k2] for k2 in varcols-{k}), varw[k] ) self.column_widths = namedtuple('column_params', - ['col1','txid','addr1','amt','addr2','comment'])( + ['num','txid','addr1','amt','addr2','comment'])( col1_w, min( # max txid was reduced by txid_adj, so stretch to fill available space, if any @@ -118,14 +118,14 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit): yield '' hdr_fs = self.squeezed_hdr_fs_fs.format( - nw = cw.col1, + nw = cw.num, dw = self.age_w, txid_fs = f'{{i:{cw.txid}}} ' if self.show_txid else '', aw = cw.addr1, a2w = cw.addr2 ) fs = self.squeezed_fs_fs.format( - nw = cw.col1, + nw = cw.num, dw = self.age_w, txid_fs = f'{{i:{cw.txid}}} ' if self.show_txid else '' ) @@ -206,8 +206,7 @@ class TwTxHistory(MMGenObject,TwCommon,metaclass=AsyncInit): 'txid': lambda i: i.txid, } - @staticmethod - async def set_dates(rpc,us): + async def set_dates(self,us): pass @property diff --git a/mmgen/tw/unspent.py b/mmgen/tw/unspent.py index 6d4810fb..f5f08b82 100755 --- a/mmgen/tw/unspent.py +++ b/mmgen/tw/unspent.py @@ -126,50 +126,50 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit): self.column_widths = namedtuple( 'column_widths', - ['col1_w','mmid_w','addr_w','btaddr_w','comment_w','tx_w','txdots'] - )(col1_w, mmid_w, addr_w, btaddr_w, comment_w, tx_w, txdots) + ['num','mmid','addr','btaddr','comment','tx'] + )(col1_w, mmid_w, addr_w, btaddr_w, comment_w, tx_w) - def gen_squeezed_display(self,c,color): - fs = self.squeezed_fs_fs.format( cw=c.col1_w, tw=c.tx_w ) - hdr_fs = self.squeezed_hdr_fs_fs.format( cw=c.col1_w, tw=c.tx_w ) + def gen_squeezed_display(self,cw,color): + fs = self.squeezed_fs_fs.format( cw=cw.num, tw=cw.tx ) + hdr_fs = self.squeezed_hdr_fs_fs.format( cw=cw.num, tw=cw.tx ) yield hdr_fs.format( n = 'Num', - t = 'TXid'.ljust(c.tx_w - 2) + ' Vout', - a = 'Address'.ljust(c.addr_w), + t = 'TXid'.ljust(cw.tx - 2) + ' Vout', + a = 'Address'.ljust(cw.addr), A = f'Amt({self.proto.dcoin})'.ljust(self.disp_prec+5), A2 = f' Amt({self.proto.coin})'.ljust(self.disp_prec+4), c = self.age_hdr ).rstrip() for n,i in enumerate(self.data): - addr_dots = '|' + '.'*(c.addr_w-1) + addr_dots = '|' + '.'*(cw.addr-1) mmid_disp = MMGenID.fmtc( ( - '.'*c.mmid_w if i.skip == 'addr' else + '.'*cw.mmid if i.skip == 'addr' else i.twmmid if i.twmmid.type == 'mmgen' else f'Non-{g.proj_name}' ), - width = c.mmid_w, + width = cw.mmid, color = color ) if self.show_mmid: addr_out = '{} {}{}'.format(( - type(i.addr).fmtc(addr_dots,width=c.btaddr_w,color=color) if i.skip == 'addr' else - i.addr.fmt(width=c.btaddr_w,color=color) + type(i.addr).fmtc(addr_dots,width=cw.btaddr,color=color) if i.skip == 'addr' else + i.addr.fmt(width=cw.btaddr,color=color) ), mmid_disp, - (' ' + i.comment.fmt(width=c.comment_w,color=color)) if c.comment_w > 0 else '' + (' ' + i.comment.fmt(width=cw.comment,color=color)) if cw.comment > 0 else '' ) else: addr_out = ( - type(i.addr).fmtc(addr_dots,width=c.addr_w,color=color) if i.skip=='addr' else - i.addr.fmt(width=c.addr_w,color=color) ) + type(i.addr).fmtc(addr_dots,width=cw.addr,color=color) if i.skip=='addr' else + i.addr.fmt(width=cw.addr,color=color) ) yield fs.format( n = str(n+1)+')', t = ( '' if not i.txid else - ' ' * (c.tx_w-4) + '|...' if i.skip == 'txid' else - i.txid[:c.tx_w-len(c.txdots)] + c.txdots ), + ' ' * (cw.tx-4) + '|...' if i.skip == 'txid' else + i.txid.truncate(width=cw.tx,color=True) ), v = i.vout, a = addr_out, A = i.amt.fmt(color=color,prec=self.disp_prec), @@ -179,8 +179,10 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit): def gen_detail_display(self,color): - addr_w = max(len(i.addr) for i in self.data) - mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in self.data) or 12 # DEADBEEF:S:1 + data = self.data + + addr_w = max(len(i.addr) for i in data) + mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in data) or 12 # DEADBEEF:S:1 fs = self.wide_fs_fs.format( tw = self.txid_w + 3, @@ -199,9 +201,9 @@ class TwUnspentOutputs(MMGenObject,TwCommon,metaclass=AsyncInit): D = 'Date', l = 'Label' ) - max_comment_len = max([len(i.comment) for i in self.data if i.comment] or [2]) + max_comment_len = max([len(i.comment) for i in data if i.comment] or [2]) - for n,i in enumerate(self.data): + for n,i in enumerate(data): yield fs.format( n = str(n+1) + ')', t = '{},{}'.format( diff --git a/mmgen/util.py b/mmgen/util.py index 0f1ffad9..0f166514 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -144,9 +144,9 @@ def pp_fmt(d): def pp_msg(d): msg(pp_fmt(d)) -def fmt(s,indent='',strip_char=None): +def fmt(s,indent='',strip_char=None,append='\n'): "de-indent multiple lines of text, or indent with specified string" - return indent + ('\n'+indent).join([l.strip(strip_char) for l in s.strip().splitlines()]) + '\n' + return indent + ('\n'+indent).join([l.strip(strip_char) for l in s.strip().splitlines()]) + append def fmt_list(iterable,fmt='dfl',indent=''): "pretty-format a list" diff --git a/test/overlay/fakemods/mmgen/tw/common.py b/test/overlay/fakemods/mmgen/tw/common.py index 85d2b4d2..3326a828 100644 --- a/test/overlay/fakemods/mmgen/tw/common.py +++ b/test/overlay/fakemods/mmgen/tw/common.py @@ -17,7 +17,7 @@ if overlay_fake_os.getenv('MMGEN_BOGUS_UNSPENT_DATA'): class overlay_fake_data2: - async def set_dates(foo,rpc,us): + async def set_dates(foo,us): for o in us: o.date = 1831006505 - int(9.7 * 60 * (o.confs - 1)) diff --git a/test/test_py_d/ts_ethdev.py b/test/test_py_d/ts_ethdev.py index 807990d3..636b0695 100755 --- a/test/test_py_d/ts_ethdev.py +++ b/test/test_py_d/ts_ethdev.py @@ -605,7 +605,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): t.read() return t - def txsign(self,ni=False,ext='{}.regtest.rawtx',add_args=[]): + def txsign(self,ni=False,ext='{}.regtest.rawtx',add_args=[],dev_send=False): ext = ext.format('-α' if g.debug_utf8 else '') keyfile = joinpath(self.tmpdir,parity_devkey_fn) txfile = self.get_file_with_ext(ext,no_dot=True) @@ -616,7 +616,8 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): + ['--rpc-host=bad_host'] # ETH signing must work without RPC + add_args + ([],['--yes'])[ni] - + ['-k', keyfile, txfile, dfl_words_file] ) + + ([f'--keys-from-file={keyfile}'] if dev_send else []) + + [txfile, dfl_words_file] ) return self.txsign_ui_common(t,ni=ni,has_label=True) def txsend(self,ni=False,ext='{}.regtest.sigtx',add_args=[]): @@ -662,10 +663,10 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): return self.txcreate(args=args,menu=menu,acct='1',tweaks=['confirm_non_mmgen']) def txview1_raw(self): return self.txview(ext_fs='{}.regtest.rawtx') - def txsign1(self): return self.txsign(add_args=['--use-internal-keccak-module']) + def txsign1(self): return self.txsign(add_args=['--use-internal-keccak-module'],dev_send=True) def tx_status0_bad(self): return self.tx_status(ext='{}.regtest.sigtx',expect_str='neither in mempool nor blockchain',exit_val=1) - def txsign1_ni(self): return self.txsign(ni=True) + def txsign1_ni(self): return self.txsign(ni=True,dev_send=True) def txsend1(self): return self.txsend() def txview1_sig(self): # do after send so that TxID is displayed return self.txview(ext_fs='{}.regtest.sigtx') @@ -674,14 +675,14 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): def txcreate2(self): args = ['98831F3A:E:11,1.234'] return self.txcreate(args=args,acct='10',tweaks=['confirm_non_mmgen']) - def txsign2(self): return self.txsign(ni=True,ext='1.234,50000]{}.regtest.rawtx') + def txsign2(self): return self.txsign(ni=True,ext='1.234,50000]{}.regtest.rawtx',dev_send=True) def txsend2(self): return self.txsend(ext='1.234,50000]{}.regtest.sigtx') def bal2(self): return self.bal(n='2') def txcreate3(self): args = ['98831F3A:E:21,2.345'] return self.txcreate(args=args,acct='10',tweaks=['confirm_non_mmgen']) - def txsign3(self): return self.txsign(ni=True,ext='2.345,50000]{}.regtest.rawtx') + def txsign3(self): return self.txsign(ni=True,ext='2.345,50000]{}.regtest.rawtx',dev_send=True) def txsend3(self): return self.txsend(ext='2.345,50000]{}.regtest.sigtx') def bal3(self): return self.bal(n='3') @@ -797,14 +798,14 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared): t.expect('or gas price: ',fee+'\n') return t - def txsign4(self): return self.txsign(ni=True,ext='.45495,50000]{}.regtest.rawtx') + def txsign4(self): return self.txsign(ni=True,ext='.45495,50000]{}.regtest.rawtx',dev_send=True) def txsend4(self): return self.txsend(ext='.45495,50000]{}.regtest.sigtx') def bal4(self): return self.bal(n='4') def txcreate5(self): args = [burn_addr + ','+amt1] return self.txcreate(args=args,acct='10',tweaks=['confirm_non_mmgen']) - def txsign5(self): return self.txsign(ni=True,ext=amt1+',50000]{}.regtest.rawtx') + def txsign5(self): return self.txsign(ni=True,ext=amt1+',50000]{}.regtest.rawtx',dev_send=True) def txsend5(self): return self.txsend(ext=amt1+',50000]{}.regtest.sigtx') def bal5(self): return self.bal(n='5') diff --git a/test/test_py_d/ts_regtest.py b/test/test_py_d/ts_regtest.py index 18f5cf26..5d6c905b 100755 --- a/test/test_py_d/ts_regtest.py +++ b/test/test_py_d/ts_regtest.py @@ -1367,7 +1367,9 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared): def bob_msgverify_export_single(self): sid = self._user_sid('bob') mmid = f'{sid}:{self.dfl_mmtype}:1' - t = self.spawn('mmgen-tool', [ '--bob', '--color=0', 'listaddress', mmid ], no_msg=True) + args = [ '--bob', '--color=0', 'listaddress', mmid ] + imsg(f'Running mmgen-tool {fmt_list(args,fmt="bare")}') + t = self.spawn('mmgen-tool', args, no_msg=True) addr = t.expect_getend(mmid).split()[0] t.close() return self.bob_msgverify(