Refactor listaddresses()

Move the core code from tool.py to new class TwAddrList in tw.py
This commit is contained in:
The MMGen Project 2018-05-25 07:50:51 +00:00
commit 5b59712cfa
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
2 changed files with 122 additions and 119 deletions

View file

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

View file

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