From 5b59712cfa7589d156e8dc56796d093617711037 Mon Sep 17 00:00:00 2001 From: MMGen Date: Fri, 25 May 2018 07:50:51 +0000 Subject: [PATCH] Refactor listaddresses() Move the core code from tool.py to new class TwAddrList in tw.py --- mmgen/tool.py | 123 ++------------------------------------------------ mmgen/tw.py | 118 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 119 deletions(-) diff --git a/mmgen/tool.py b/mmgen/tool.py index ceab8101..2a0bbe40 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -650,29 +650,6 @@ def Listaddresses(addrs='',minconf=1, if not sort.issubset(sort_params): die(1,"The sort option takes the following parameters: '{}'".format("','".join(sort_params))) - rpc_init() - - def check_dup_mmid(acct_labels): - mmid_prev,err = None,False - for mmid in sorted(a.mmid for a in acct_labels if a): - if mmid == mmid_prev: - err = True - msg('Duplicate MMGen ID ({}) discovered in tracking wallet!\n'.format(mmid)) - mmid_prev = mmid - if err: rdie(3,'Tracking wallet is corrupted!') - - def check_addr_array_lens(acct_pairs): - err = False - for label,addrs in acct_pairs: - if not label: continue - if len(addrs) != 1: - err = True - if len(addrs) == 0: - msg("Label '{}': has no associated address!".format(label)) - else: - msg("'{}': more than one {} address in account!".format(addrs,g.coin)) - if err: rdie(3,'Tracking wallet is corrupted!') - usr_addr_list = [] if addrs: a = addrs.rsplit(':',1) @@ -681,103 +658,11 @@ def Listaddresses(addrs='',minconf=1, die(1,m.format(addrs)) usr_addr_list = [MMGenID('{}:{}'.format(a[0],i)) for i in AddrIdxList(a[1])] - class TwAddrList(dict,MMGenObject): pass - - addrs = TwAddrList() # reusing name! - total = g.proto.coin_amt('0') - - for d in g.rpch.listunspent(0): - if not 'account' in d: continue # skip coinbase outputs with missing account - if d['confirmations'] < minconf: continue - label = TwLabel(d['account'],on_fail='silent') - if label: - if usr_addr_list and (label.mmid not in usr_addr_list): continue - if label.mmid in addrs: - if addrs[label.mmid]['addr'] != d['address']: - die(2,'duplicate {} address ({}) for this MMGen address! ({})'.format( - g.coin,d['address'],addrs[label.mmid]['addr'])) - else: - addrs[label.mmid] = {'amt': g.proto.coin_amt('0'), - 'lbl': label, - 'addr': CoinAddr(d['address'])} - addrs[label.mmid]['lbl'].mmid.confs = d['confirmations'] - addrs[label.mmid]['amt'] += d['amount'] - total += d['amount'] - - # We use listaccounts only for empty addresses, as it shows false positive balances - if showempty or all_labels: - # for compatibility with old mmids, must use raw RPC rather than native data for matching - # args: minconf,watchonly, MUST use keys() so we get list, not dict - acct_list = g.rpch.listaccounts(0,True).keys() # raw list, no 'L' - acct_labels = MMGenList([TwLabel(a,on_fail='silent') for a in acct_list]) - check_dup_mmid(acct_labels) - acct_addrs = g.rpch.getaddressesbyaccount([[a] for a in acct_list],batch=True) # use raw list here - assert len(acct_list) == len(acct_addrs),'listaccounts() and getaddressesbyaccount() not equal in length' - addr_pairs = zip(acct_labels,acct_addrs) - check_addr_array_lens(addr_pairs) - for label,addr_arr in addr_pairs: - if not label: continue - if all_labels and not showempty and not label.comment: continue - if usr_addr_list and (label.mmid not in usr_addr_list): continue - if label.mmid not in addrs: - addrs[label.mmid] = { 'amt':g.proto.coin_amt('0'), 'lbl':label, 'addr':'' } - if showbtcaddrs: - addrs[label.mmid]['addr'] = CoinAddr(addr_arr[0]) - - if not addrs: + from mmgen.tw import TwAddrList + al = TwAddrList(usr_addr_list,minconf,showempty,showbtcaddrs,all_labels) + if not al: die(0,('No tracked addresses with balances!','No tracked addresses!')[showempty]) - - out = ([],[green('Chain: {}'.format(g.chain.upper()))])[g.chain in ('testnet','regtest')] - - fs = u'{{mid}}{} {{cmt}} {{amt}}{}'.format(('',' {addr}')[showbtcaddrs],('',' {age}')[show_age]) - mmaddrs = [k for k in addrs.keys() if k.type == 'mmgen'] - max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10 - max_cmt_len = max(max(screen_width(v['lbl'].comment) for v in addrs.values()),7) - addr_width = max(len(addrs[mmid]['addr']) for mmid in addrs) - -# pmsg([a.split('.')[1] for a in [str(v['amt']) for v in addrs.values()] if '.' in a]) - # fp: fractional part - max_fp_len = max([len(a.split('.')[1]) for a in [str(v['amt']) for v in addrs.values()] if '.' in a] or [1]) - out += [fs.format( - mid=MMGenID.fmtc('MMGenID',width=max_mmid_len), - addr=CoinAddr.fmtc('ADDRESS',width=addr_width), - cmt=TwComment.fmtc('COMMENT',width=max_cmt_len+1), - amt='BALANCE'.ljust(max_fp_len+4), - age=('CONFS','DAYS')[show_days], - )] - - def sort_algo(j): - if sort and 'age' in sort: - return '{}_{:>012}_{}'.format( - j.obj.rsplit(':',1)[0], - (1000000000-j.confs if hasattr(j,'confs') else 0), # Hack, but OK for the foreseeable future - j.sort_key) - else: - return j.sort_key - - al_id_save = None - confs_per_day = 60*60*24 / g.proto.secs_per_block - for mmid in sorted(addrs,key=sort_algo,reverse=bool(sort and 'reverse' in sort)): - if mmid.type == 'mmgen': - if al_id_save and al_id_save != mmid.obj.al_id: - out.append('') - al_id_save = mmid.obj.al_id - mmid_disp = mmid - else: - if al_id_save: - out.append('') - al_id_save = None - mmid_disp = 'Non-MMGen' - e = addrs[mmid] - out.append(fs.format( - mid=MMGenID.fmtc(mmid_disp,width=max_mmid_len,color=True), - addr=(e['addr'].fmt(color=True,width=addr_width) if showbtcaddrs else None), - cmt=e['lbl'].comment.fmt(width=max_cmt_len,color=True,nullrepl='-'), - amt=e['amt'].fmt('4.{}'.format(max(max_fp_len,3)),color=True), - age=mmid.confs / (1,confs_per_day)[show_days] if hasattr(mmid,'confs') else '-' - )) - out.append('\nTOTAL: {} {}'.format(total.hl(color=True),g.coin)) - o = '\n'.join(out) + o = al.format(showbtcaddrs,sort,show_age,show_days) return do_pager(o) if pager else Msg(o) def Getbalance(minconf=1,quiet=False,return_val=False): diff --git a/mmgen/tw.py b/mmgen/tw.py index f132044c..d36891e2 100755 --- a/mmgen/tw.py +++ b/mmgen/tw.py @@ -372,3 +372,121 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen @classmethod def remove_label(cls,mmaddr): cls.add_label(mmaddr,'') + +class TwAddrList(MMGenDict): + + def __init__(self,usr_addr_list,minconf,showempty,showbtcaddrs,all_labels): + + def check_dup_mmid(acct_labels): + mmid_prev,err = None,False + for mmid in sorted(a.mmid for a in acct_labels if a): + if mmid == mmid_prev: + err = True + msg('Duplicate MMGen ID ({}) discovered in tracking wallet!\n'.format(mmid)) + mmid_prev = mmid + if err: rdie(3,'Tracking wallet is corrupted!') + + def check_addr_array_lens(acct_pairs): + err = False + for label,addrs in acct_pairs: + if not label: continue + if len(addrs) != 1: + err = True + if len(addrs) == 0: + msg("Label '{}': has no associated address!".format(label)) + else: + msg("'{}': more than one {} address in account!".format(addrs,g.coin)) + if err: rdie(3,'Tracking wallet is corrupted!') + + self.total = g.proto.coin_amt('0') + rpc_init() + + for d in g.rpch.listunspent(0): + if not 'account' in d: continue # skip coinbase outputs with missing account + if d['confirmations'] < minconf: continue + label = TwLabel(d['account'],on_fail='silent') + if label: + if usr_addr_list and (label.mmid not in usr_addr_list): continue + if label.mmid in self: + if self[label.mmid]['addr'] != d['address']: + die(2,'duplicate {} address ({}) for this MMGen address! ({})'.format( + g.coin,d['address'],self[label.mmid]['addr'])) + else: + self[label.mmid] = {'amt': g.proto.coin_amt('0'), + 'lbl': label, + 'addr': CoinAddr(d['address'])} + self[label.mmid]['lbl'].mmid.confs = d['confirmations'] + self[label.mmid]['amt'] += d['amount'] + self.total += d['amount'] + + # We use listaccounts only for empty addresses, as it shows false positive balances + if showempty or all_labels: + # for compatibility with old mmids, must use raw RPC rather than native data for matching + # args: minconf,watchonly, MUST use keys() so we get list, not dict + acct_list = g.rpch.listaccounts(0,True).keys() # raw list, no 'L' + acct_labels = MMGenList([TwLabel(a,on_fail='silent') for a in acct_list]) + check_dup_mmid(acct_labels) + acct_addrs = g.rpch.getaddressesbyaccount([[a] for a in acct_list],batch=True) # use raw list here + assert len(acct_list) == len(acct_addrs),( + 'listaccounts() and getaddressesbyaccount() not equal in length') + addr_pairs = zip(acct_labels,acct_addrs) + check_addr_array_lens(addr_pairs) + for label,addr_arr in addr_pairs: + if not label: continue + if all_labels and not showempty and not label.comment: continue + if usr_addr_list and (label.mmid not in usr_addr_list): continue + if label.mmid not in self: + self[label.mmid] = { 'amt':g.proto.coin_amt('0'), 'lbl':label, 'addr':'' } + if showbtcaddrs: + self[label.mmid]['addr'] = CoinAddr(addr_arr[0]) + + def format(self,showbtcaddrs,sort,show_age,show_days): + out = ([],[green('Chain: {}'.format(g.chain.upper()))])[g.chain in ('testnet','regtest')] + fs = u'{{mid}}{} {{cmt}} {{amt}}{}'.format(('',' {addr}')[showbtcaddrs],('',' {age}')[show_age]) + mmaddrs = [k for k in self.keys() if k.type == 'mmgen'] + max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10 + max_cmt_len = max(max(screen_width(v['lbl'].comment) for v in self.values()),7) + addr_width = max(len(self[mmid]['addr']) for mmid in self) + + # fp: fractional part + max_fp_len = max([len(a.split('.')[1]) for a in [str(v['amt']) for v in self.values()] if '.' in a] or [1]) + out += [fs.format( + mid=MMGenID.fmtc('MMGenID',width=max_mmid_len), + addr=(CoinAddr.fmtc('ADDRESS',width=addr_width) if showbtcaddrs else None), + cmt=TwComment.fmtc('COMMENT',width=max_cmt_len+1), + amt='BALANCE'.ljust(max_fp_len+4), + age=('CONFS','DAYS')[show_days], + )] + + def sort_algo(j): + if sort and 'age' in sort: + return '{}_{:>012}_{}'.format( + j.obj.rsplit(':',1)[0], + (1000000000-j.confs if hasattr(j,'confs') else 0), # Hack, but OK for the foreseeable future + j.sort_key) + else: + return j.sort_key + + al_id_save = None + confs_per_day = 60*60*24 / g.proto.secs_per_block + for mmid in sorted(self,key=sort_algo,reverse=bool(sort and 'reverse' in sort)): + if mmid.type == 'mmgen': + if al_id_save and al_id_save != mmid.obj.al_id: + out.append('') + al_id_save = mmid.obj.al_id + mmid_disp = mmid + else: + if al_id_save: + out.append('') + al_id_save = None + mmid_disp = 'Non-MMGen' + e = self[mmid] + out.append(fs.format( + mid=MMGenID.fmtc(mmid_disp,width=max_mmid_len,color=True), + addr=(e['addr'].fmt(color=True,width=addr_width) if showbtcaddrs else None), + cmt=e['lbl'].comment.fmt(width=max_cmt_len,color=True,nullrepl='-'), + amt=e['amt'].fmt('4.{}'.format(max(max_fp_len,3)),color=True), + age=mmid.confs / (1,confs_per_day)[show_days] if hasattr(mmid,'confs') else '-' + )) + + return '\n'.join(out + ['\nTOTAL: {} {}'.format(self.total.hl(color=True),g.coin)])