Browse Source

rpc.py: new calls() method; tw.py: batch date RPC calls; related fixes/cleanups

The MMGen Project 4 years ago
parent
commit
744c7b0d54
3 changed files with 58 additions and 51 deletions
  1. 2 2
      mmgen/opts.py
  2. 24 10
      mmgen/rpc.py
  3. 32 39
      mmgen/tw.py

+ 2 - 2
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

+ 24 - 10
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',

+ 32 - 39
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)])