Browse Source

tx.py: rename vars,funcs; refactor,move funcs. obj.py: ETHAmt bugfix

MMGen 7 years ago
parent
commit
11e02f3ab9
3 changed files with 81 additions and 62 deletions
  1. 1 1
      mmgen/main_txbump.py
  2. 2 2
      mmgen/obj.py
  3. 78 59
      mmgen/tx.py

+ 1 - 1
mmgen/main_txbump.py

@@ -114,7 +114,7 @@ fee = tx.get_usr_fee_interactive(tx_fee=opt.tx_fee,desc='User-selected')
 
 tx.update_output_amt(op_idx,tx.sum_inputs()-tx.sum_outputs(exclude=op_idx)-fee)
 
-d = tx.get_fee()
+d = tx.get_fee_from_tx()
 assert d == fee and d <= g.proto.max_tx_fee
 
 if not opt.yes:

+ 2 - 2
mmgen/obj.py

@@ -392,8 +392,8 @@ class ETHAmt(BTCAmt):
 			if from_unit:
 				assert from_unit in ('wei','szabo'),"'{}': unrecognized ETH denomination".format(from_unit)
 				assert type(num) in (int,long),'value is not an integer or long integer'
-				return super(cls,cls).__new__(cls,num * getattr(cls,from_unit))
-			return super(cls,cls).__new__(cls,num)
+				return super(cls,cls).__new__(cls,num * getattr(cls,from_unit),on_fail=on_fail)
+			return super(cls,cls).__new__(cls,num,on_fail=on_fail)
 		except Exception as e:
 			m = "{!r}: value cannot be converted to {} ({})"
 			return cls.init_fail(m.format(num,cls.__name__,e[0]),on_fail)

+ 78 - 59
mmgen/tx.py

@@ -225,7 +225,9 @@ class MMGenTX(MMGenObject):
 	txid_ext = 'txid'
 	desc     = 'transaction'
 	chg_fs   = 'Transaction produces {} {} in change'
+	fee_fail_fs = 'Network fee estimation for {c} confirmations failed ({t})'
 	no_chg_msg = 'Warning: Change address will be deleted as transaction produces no change'
+	rel_fee_desc = 'satoshis per byte'
 
 	class MMGenTxInput(MMGenListItem):
 		for k in txio_attrs: locals()[k] = txio_attrs[k] # in lieu of inheritance
@@ -371,6 +373,9 @@ class MMGenTX(MMGenObject):
 	def edit_comment(self):
 		return self.add_comment(self)
 
+	def get_fee_from_tx(self):
+		return self.sum_inputs() - self.sum_outputs()
+
 	def has_segwit_inputs(self):
 		return any(i.mmid and i.mmid.mmtype in ('S','B') for i in self.inputs)
 
@@ -459,18 +464,28 @@ class MMGenTX(MMGenObject):
 
 		return int(ret * float(opt.vsize_adj)) if hasattr(opt,'vsize_adj') and opt.vsize_adj else ret
 
-	def get_fee(self):
-		return self.sum_inputs() - self.sum_outputs()
-
-	def btc2spb(self,coin_fee):
-		return int(coin_fee/g.proto.coin_amt.min_coin_unit/self.estimate_size())
-
+	# coin-specific fee routines
 	def get_relay_fee(self):
 		kb_fee = g.proto.coin_amt(g.rpch.getnetworkinfo()['relayfee'])
 		ret = kb_fee * self.estimate_size() / 1024
 		vmsg('Relay fee: {} {c}/kB, for transaction: {} {c}'.format(kb_fee,ret,c=g.coin))
 		return ret
 
+	# convert absolute BTC fee to satoshis-per-byte
+	def fee_abs2rel(self,abs_fee):
+		return int(abs_fee/g.proto.coin_amt.min_coin_unit/self.estimate_size())
+
+	def get_rel_fee_from_network(self): # rel_fee is BTC/kB
+		try:
+			ret = g.rpch.estimatesmartfee(opt.tx_confs,on_fail='raise')
+			rel_fee = ret['feerate'] if 'feerate' in ret else -2
+			fe_type = 'estimatesmartfee'
+		except:
+			rel_fee = g.rpch.estimatefee(opt.tx_confs)
+			fe_type = 'estimatefee'
+
+		return rel_fee,fe_type
+
 	def convert_fee_spec(self,tx_fee,tx_size,on_fail='throw'):
 		if g.proto.coin_amt(tx_fee,on_fail='silent'):
 			return g.proto.coin_amt(tx_fee)
@@ -485,42 +500,76 @@ class MMGenTX(MMGenObject):
 			elif on_fail == 'throw':
 				assert False, "'{}': invalid tx-fee argument".format(tx_fee)
 
-	def get_usr_fee(self,tx_fee,desc='Missing description'):
-		coin_fee = self.convert_fee_spec(tx_fee,self.estimate_size(),on_fail='return')
-		if coin_fee == None:
+	# given network fee estimate in BTC/kB and tx size, calculate absolute fee in coin units
+	def calculate_fee(self,rel_fee,fe_type=None):
+		tx_size = self.estimate_size()
+		ret = g.proto.coin_amt(rel_fee) * opt.tx_fee_adj * tx_size / 1024
+		if opt.verbose:
+			msg('{} fee for {} confirmations: {} {}/kB'.format(fe_type.upper(),opt.tx_confs,rel_fee,g.coin))
+			msg('TX size (estimated): {}'.format(tx_size))
+		return ret
+
+	def convert_and_check_fee(self,tx_fee,desc='Missing description'):
+		abs_fee = self.convert_fee_spec(tx_fee,self.estimate_size(),on_fail='return')
+		if abs_fee == None:
 			# we shouldn't be calling this if tx size is unknown
 			m = "'{}': cannot convert satoshis-per-byte to {} because transaction size is unknown"
 			assert False, m.format(tx_fee,g.coin)
-		elif coin_fee == False:
+		elif abs_fee == False:
 			m = "'{}': invalid TX fee (not a {} amount or satoshis-per-byte specification)"
 			msg(m.format(tx_fee,g.coin))
 			return False
-		elif coin_fee > g.proto.max_tx_fee:
+		elif abs_fee > g.proto.max_tx_fee:
 			m = '{} {c}: {} fee too large (maximum fee: {} {c})'
-			msg(m.format(coin_fee,desc,g.proto.max_tx_fee,c=g.coin))
+			msg(m.format(abs_fee,desc,g.proto.max_tx_fee,c=g.coin))
 			return False
-		elif coin_fee < self.get_relay_fee():
+		elif abs_fee < self.get_relay_fee():
 			m = '{} {c}: {} fee too small (below relay fee of {} {c})'
-			msg(m.format(str(coin_fee),desc,str(self.get_relay_fee()),c=g.coin))
+			msg(m.format(str(abs_fee),desc,str(self.get_relay_fee()),c=g.coin))
 			return False
 		else:
-			return coin_fee
+			return abs_fee
 
+	# non-coin-specific fee routines
 	def get_usr_fee_interactive(self,tx_fee=None,desc='Starting'):
-		coin_fee = None
+		abs_fee = None
 		while True:
 			if tx_fee:
-				coin_fee = self.get_usr_fee(tx_fee,desc)
-			if coin_fee:
+				abs_fee = self.convert_and_check_fee(tx_fee,desc)
+			if abs_fee:
 				m = ('',' (after {}x adjustment)'.format(opt.tx_fee_adj))[opt.tx_fee_adj != 1]
-				p = '{} TX fee{}: {} {} ({} satoshis per byte)'.format(desc,m,
-					coin_fee.hl(),g.coin,pink(str(self.btc2spb(coin_fee))))
-				if opt.yes or keypress_confirm(p+'.  OK?',default_yes=True):
+				p = u'{} TX fee{}: {} {} ({} {})\n'.format(
+						desc,
+						m,
+						abs_fee.hl(),
+						g.coin,
+						pink(str(self.fee_abs2rel(abs_fee))),
+						self.rel_fee_desc)
+				if opt.yes or keypress_confirm(p+'OK?',default_yes=True):
 					if opt.yes: msg(p)
-					return coin_fee
+					return abs_fee
 			tx_fee = my_raw_input('Enter transaction fee: ')
 			desc = 'User-selected'
 
+	def get_fee_from_user(self,have_estimate_fail=[]):
+
+		if opt.tx_fee:
+			desc = 'User-selected'
+			start_fee = opt.tx_fee
+		else:
+			desc = 'Network-estimated'
+			rel_fee,fe_type = self.get_rel_fee_from_network()
+
+			if rel_fee < 0:
+				if not have_estimate_fail:
+					msg(self.fee_fail_fs.format(c=opt.tx_confs,t=fe_type))
+					have_estimate_fail.append(True)
+				start_fee = None
+			else:
+				start_fee = self.calculate_fee(rel_fee,fe_type)
+
+		return self.get_usr_fee_interactive(start_fee,desc=desc)
+
 	def delete_attrs(self,desc,attr):
 		for e in getattr(self,desc):
 			if hasattr(e,attr): delattr(e,attr)
@@ -806,9 +855,9 @@ class MMGenTX(MMGenObject):
 			m = 'Transaction has MMGen Segwit outputs, but this blockchain does not support Segwit'
 			die(2,m+' at the current height')
 
-		if self.get_fee() > g.proto.max_tx_fee:
+		if self.get_fee_from_tx() > g.proto.max_tx_fee:
 			die(2,'Transaction fee ({}) greater than {} max_tx_fee ({} {})!'.format(
-				self.get_fee(),g.proto.name.capitalize(),g.proto.max_tx_fee,g.coin.upper()))
+				self.get_fee_from_tx(),g.proto.name.capitalize(),g.proto.max_tx_fee,g.coin.upper()))
 
 		self.get_status()
 
@@ -863,7 +912,7 @@ class MMGenTX(MMGenObject):
 			self.txid,
 			('-'+g.coin,'')[g.coin=='BTC'],
 			self.send_amt,
-			('',',{}'.format(self.btc2spb(self.get_fee())))[self.is_rbf()],
+			('',',{}'.format(self.fee_abs2rel(self.get_fee_from_tx())))[self.is_rbf()],
 			('',',tl={}'.format(tl))[bool(tl)],
 			self.ext,
 			x=u'-α' if g.debug_utf8 else '')
@@ -988,7 +1037,7 @@ class MMGenTX(MMGenObject):
 
 		t_in,t_out = self.sum_inputs(),self.sum_outputs()
 		fee = t_in-t_out
-		out += fs.format(t_in.hl(),t_out.hl(),fee.hl(),pink(str(self.btc2spb(fee))),c=g.coin)
+		out += fs.format(t_in.hl(),t_out.hl(),fee.hl(),pink(str(self.fee_abs2rel(fee))),c=g.coin)
 
 		if opt.verbose:
 			ts = len(self.hex)/2 if self.hex else 'unknown'
@@ -1088,36 +1137,6 @@ class MMGenTX(MMGenObject):
 		if not self.chain and not self.inputs[0].addr.is_for_chain('testnet'):
 			self.chain = 'mainnet'
 
-	def get_fee_from_user(self,have_estimate_fail=[]):
-
-		if opt.tx_fee:
-			desc = 'User-selected'
-			start_fee = opt.tx_fee
-		else:
-			desc = 'Network-estimated'
-			try:
-				ret = g.rpch.estimatesmartfee(opt.tx_confs,on_fail='raise')
-			except:
-				fe_type = 'estimatefee'
-				fee_per_kb = g.rpch.estimatefee(opt.tx_confs)
-			else:
-				fe_type = 'estimatesmartfee'
-				fee_per_kb = ret['feerate'] if 'feerate' in ret else -2
-
-			if fee_per_kb < 0:
-				if not have_estimate_fail:
-					msg('Network fee estimation for {} confirmations failed ({})'.format(opt.tx_confs,fe_type))
-					have_estimate_fail.append(True)
-				start_fee = None
-			else:
-				start_fee = g.proto.coin_amt(fee_per_kb) * opt.tx_fee_adj * self.estimate_size() / 1024
-				if opt.verbose:
-					msg('{} fee for {} confirmations: {} {}/kB'.format(
-						fe_type.upper(),opt.tx_confs,fee_per_kb,g.coin))
-					msg('TX size (estimated): {}'.format(self.estimate_size()))
-
-		return self.get_usr_fee_interactive(start_fee,desc=desc)
-
 	def process_cmd_args(self,cmd_args,ad_f,ad_w):
 		for a in cmd_args:
 			if ',' in a:
@@ -1323,11 +1342,11 @@ class MMGenBumpTX(MMGenTX):
 	def set_min_fee(self):
 		self.min_fee = self.sum_inputs() - self.sum_outputs() + self.get_relay_fee()
 
-	def get_usr_fee(self,tx_fee,desc):
-		ret = super(type(self),self).get_usr_fee(tx_fee,desc)
+	def convert_and_check_fee(self,tx_fee,desc):
+		ret = super(type(self),self).convert_and_check_fee(tx_fee,desc)
 		if ret < self.min_fee:
 			msg('{} {c}: {} fee too small. Minimum fee: {} {c} ({} satoshis per byte)'.format(
-				ret,desc,self.min_fee,self.btc2spb(self.min_fee),c=g.coin))
+				ret,desc,self.min_fee,self.fee_abs2rel(self.min_fee),c=g.coin))
 			return False
 		output_amt = self.outputs[self.bump_output_idx].amt
 		if ret >= output_amt: