From 744c7b0d545da5300ea08f01f89da08fe76f2291 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Fri, 1 May 2020 08:28:22 +0000 Subject: [PATCH] rpc.py: new calls() method; tw.py: batch date RPC calls; related fixes/cleanups --- mmgen/opts.py | 4 +-- mmgen/rpc.py | 34 ++++++++++++++++-------- mmgen/tw.py | 71 +++++++++++++++++++++++---------------------------- 3 files changed, 58 insertions(+), 51 deletions(-) diff --git a/mmgen/opts.py b/mmgen/opts.py index 95fac7b8..88758797 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -164,8 +164,8 @@ common_opts_data = { --, --no-license Suppress the GPL license prompt --, --rpc-host=h Communicate with {dn} running on host 'h' --, --rpc-port=p Communicate with {dn} listening on port 'p' ---, --rpc-user=user Override 'rpcuser' in {pn}.conf ---, --rpc-password=pass Override 'rpcpassword' in {pn}.conf +--, --rpc-user=user Override 'rpc_user' in mmgen.cfg +--, --rpc-password=pass Override 'rpc_password' in mmgen.cfg --, --monero-wallet-rpc-host=host Override 'monero_wallet_rpc_host' in mmgen.cfg --, --monero-wallet-rpc-user=user Override 'monero_wallet_rpc_user' in mmgen.cfg --, --monero-wallet-rpc-password=pass Override 'monero_wallet_rpc_password' in mmgen.cfg diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 50a68330..eb44bb71 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -46,13 +46,11 @@ class RPCConnection(MMGenObject): except: raise SocketError('Unable to connect to {}:{}'.format(host,port)) - if not self.auth: + if user and passwd: # user/pass overrides cookie pass - elif user and passwd: - self.auth_str = '{}:{}'.format(user,passwd) elif auth_cookie: - self.auth_str = auth_cookie - else: + user,passwd = auth_cookie.split(':') + elif self.auth: msg('Error: no {} RPC authentication method found'.format(g.proto.name.capitalize())) if passwd: die(1,"'rpcuser' entry not found in {}.conf or mmgen.cfg".format(g.proto.name)) elif user: die(1,"'rpcpassword' entry not found in {}.conf or mmgen.cfg".format(g.proto.name)) @@ -67,10 +65,11 @@ class RPCConnection(MMGenObject): pnm=g.proj_name)) if self.auth: - fs = ' RPC AUTHORIZATION data ==> raw: [{}]\n{:>31}enc: [Basic {}]\n' - as_enc = base64.b64encode(self.auth_str.encode()).decode() - dmsg_rpc(fs.format(self.auth_str,'',as_enc)) - self.http_hdrs.update({ 'Host':host, 'Authorization':'Basic {}'.format(as_enc) }) + fs = ' RPC AUTHORIZATION data ==> raw: [{}]\n{:>31}enc: [{}]\n' + auth_str = f'{user}:{passwd}' + auth_str_b64 = 'Basic ' + base64.b64encode(auth_str.encode()).decode() + dmsg_rpc(fs.format(auth_str,'',auth_str_b64)) + self.http_hdrs.update({ 'Host': host, 'Authorization': auth_str_b64 }) self.host = host self.port = port @@ -81,6 +80,20 @@ class RPCConnection(MMGenObject): exec('{c}.{m} = lambda self,*args,**kwargs: self.request("{m}",*args,**kwargs)'.format( c=type(self).__name__,m=method)) + def calls(self,method,args_list): + """ + Perform a list of RPC calls, returning results in a list + + Can be called two ways: + 1) method = methodname, args_list = [args_tuple1, args_tuple2,...] + 2) method = None, args_list = [(methodname1,args_tuple1), (methodname2,args_tuple2), ...] + """ + + cmd_list = args_list if method == None else tuple(zip([method] * len(args_list), args_list)) + + if True: + return [self.request(method,*params) for method,params in cmd_list] + # Normal mode: call with arg list unrolled, exactly as with cli # Batch mode: call with list of arg lists as first argument # kwargs are for local use and are not passed to server @@ -199,7 +212,7 @@ class RPCConnection(MMGenObject): for k,v in self.http_hdrs.items(): exec_cmd += ['--header', '{}: {}'.format(k,v)] if self.auth: - exec_cmd += ['--user', self.auth_str] + exec_cmd += ['--user', self.user + ':' + self.passwd] exec_cmd += ['http://{}:{}/'.format(self.host,self.port)] cp = run(exec_cmd,stdout=PIPE,check=True) @@ -233,6 +246,7 @@ class RPCConnection(MMGenObject): 'getblockcount', 'getblockhash', 'getblockheader', + 'getblockstats', # mmgen-node-tools 'getmempoolinfo', 'getmempoolentry', 'getnettotals', diff --git a/mmgen/tw.py b/mmgen/tw.py index bea3070e..2f9db474 100755 --- a/mmgen/tw.py +++ b/mmgen/tw.py @@ -34,33 +34,28 @@ def get_tw_label(s): except BadTwComment: raise except: return None -_date2days = lambda date: (g.rpch.cur_date - date) // 86400 +_date_formatter = { + 'days': lambda secs: (g.rpch.cur_date - secs) // 86400, + 'date': lambda secs: '{}-{:02}-{:02}'.format(*time.gmtime(secs)[:3])[2:], + 'date_time': lambda secs: '{}-{:02}-{:02} {:02}:{:02}'.format(*time.gmtime(secs)[:5]), +} + _confs2date_approx = lambda o: g.rpch.cur_date - int(g.proto.avg_bdi * (o.confs - 1)) -_confs2date_exact = lambda o: ( -# g.rpch.getblockheader(g.rpch.getblockhash(g.rpch.blockcount - (o.confs - 1)))['time'] - g.rpch.gettransaction(o.txid)['blocktime'] # same as above, differs from 'time' - if o.confs - else g.rpch.cur_date ) + +def _set_dates(us): + if us and us[0].date is None: + # 'blocktime' differs from 'time', is same as getblockheader['time'] + dates = [o['blocktime'] for o in g.rpch.calls('gettransaction',[(o.txid,) for o in us])] + for o,date in zip(us,dates): + o.date = date if os.getenv('MMGEN_BOGUS_WALLET_DATA'): # 1831006505 (09 Jan 2028) = projected time of block 1000000 - _date2days = lambda date: (1831006505 - date) // 86400 + _date_formatter['days'] = lambda date: (1831006505 - date) // 86400 _confs2date_approx = lambda o: 1831006505 - (10 * 60 * (o.confs - 1)) - _confs2date_exact = lambda o: 1831006505 - int(9.7 * 60 * (o.confs - 1)) - -def _format_date(secs): - t = time.gmtime(secs) - return '{}-{:02}-{:02}'.format(str(t.tm_year)[2:],t.tm_mon,t.tm_mday) - -def _format_date_time(secs): - t = time.gmtime(secs) - return '{}-{:02}-{:02} {:02}:{:02}'.format( - t.tm_year, - t.tm_mon, - t.tm_mday, - t.tm_hour, - t.tm_min, - ) + def _set_dates(us): + for o in us: + o.date = 1831006505 - int(9.7 * 60 * (o.confs - 1)) class TwUnspentOutputs(MMGenObject): @@ -86,7 +81,8 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, add [l]abel: 'q':'a_quit','p':'a_print','v':'a_view','w':'a_view_wide','l':'a_lbl_add' } col_adj = 38 age_fmts = ('confs','block','days','date','date_time') - age_fmts_ia = ('confs','block','days','date') + age_fmts_date_dependent = ('days','date','date_time') + age_fmts_interactive = ('confs','block','days','date') _age_fmt = 'confs' age_precs = ('approx','exact') age_prec = 'approx' @@ -227,6 +223,8 @@ watch-only wallet using '{}-addrimport' and then re-run this program. def format_for_display(self): unsp = self.unspent + if self.age_fmt in self.age_fmts_date_dependent and self.age_prec == 'exact': + _set_dates(unsp) self.set_term_columns() # allow for 7-digit confirmation nums @@ -301,7 +299,8 @@ watch-only wallet using '{}-addrimport' and then re-run this program. return self.fmt_display def format_for_printing(self,color=False,show_confs=True): - + if self.age_fmt in self.age_fmts_date_dependent and self.age_prec == 'exact': + _set_dates(self.unspent) addr_w = max(len(i.addr) for i in self.unspent) mmid_w = max(len(('',i.twmmid)[i.twmmid.type=='mmgen']) for i in self.unspent) or 12 # DEADBEEF:S:1 amt_w = g.proto.coin_amt.max_prec + 5 @@ -404,7 +403,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program. self.do_sort(action[2:]) if action == 's_twmmid': self.show_mmid = True elif action == 'd_days': - af = self.age_fmts_ia + af = self.age_fmts_interactive self.age_fmt = af[(af.index(self.age_fmt) + 1) % len(af)] elif action == 'd_mmid': self.show_mmid = not self.show_mmid elif action == 'd_group': @@ -467,22 +466,13 @@ watch-only wallet using '{}-addrimport' and then re-run this program. elif age_fmt == 'block': return g.rpch.blockcount - (o.confs - 1) else: - if self.age_prec == 'approx': - date = _confs2date_approx(o) - else: - if o.date == None: - o.date = _confs2date_exact(o) - date = o.date - return { - 'days': _date2days(date), - 'date': _format_date(date), - 'date_time': _format_date_time(date), - }[age_fmt] + return _date_formatter[age_fmt](_confs2date_approx(o) if self.age_prec == 'approx' else o.date) class TwAddrList(MMGenDict): age_fmts = TwUnspentOutputs.age_fmts age_disp = TwUnspentOutputs.age_disp + age_prec_disp = TwUnspentOutputs.age_prec_disp def __new__(cls,*args,**kwargs): return MMGenDict.__new__(altcoin_subclass(cls,'tw','TwAddrList'),*args,**kwargs) @@ -584,7 +574,7 @@ class TwAddrList(MMGenDict): addr=(CoinAddr.fmtc('ADDRESS',width=addr_width) if showbtcaddrs else None), cmt=TwComment.fmtc('COMMENT',width=max_cmt_width+1), amt='BALANCE'.ljust(max_fp_len+4), - age=age_fmt.upper() + age=age_fmt.upper()+self.age_prec_disp[self.age_prec], ).rstrip()] def sort_algo(j): @@ -598,7 +588,10 @@ class TwAddrList(MMGenDict): return j.sort_key al_id_save = None - for mmid in sorted(self,key=sort_algo,reverse=bool(sort and 'reverse' in sort)): + mmids = sorted(self,key=sort_algo,reverse=bool(sort and 'reverse' in sort)) + if show_age and self.age_prec == 'exact': + _set_dates([o for o in mmids if hasattr(o,'confs')]) + for mmid in mmids: if mmid.type == 'mmgen': if al_id_save and al_id_save != mmid.obj.al_id: out.append('') @@ -615,7 +608,7 @@ class TwAddrList(MMGenDict): addr=(e['addr'].fmt(color=True,width=addr_width) if showbtcaddrs else None), cmt=e['lbl'].comment.fmt(width=max_cmt_width,color=True,nullrepl='-'), amt=e['amt'].fmt('4.{}'.format(max(max_fp_len,3)),color=True), - age=self.age_disp(mmid,age_fmt) if hasattr(mmid,'confs') and mmid.confs != None else '-' + age=self.age_disp(mmid,age_fmt) if show_age and hasattr(mmid,'confs') else '-' ).rstrip()) return '\n'.join(out + ['\nTOTAL: {} {}'.format(self.total.hl(color=True),g.dcoin)])