From ab8028ae647c64334d4a1fd93f3c178ec67e1ded Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 24 Jun 2021 17:15:22 +0000 Subject: [PATCH] MMGenTX: code cleanups, variable and method renames --- mmgen/altcoins/eth/tx.py | 79 +++++++++++++++++++-------------- mmgen/main_txbump.py | 12 ++--- mmgen/opts.py | 2 +- mmgen/tx.py | 96 +++++++++++++++++++++------------------- 4 files changed, 104 insertions(+), 85 deletions(-) diff --git a/mmgen/altcoins/eth/tx.py b/mmgen/altcoins/eth/tx.py index cb71a197..a869a8e1 100755 --- a/mmgen/altcoins/eth/tx.py +++ b/mmgen/altcoins/eth/tx.py @@ -55,8 +55,8 @@ class EthereumMMGenTX: def get_hex_locktime(self): return None # TODO - # given rel fee in wei, return absolute fee using tx_gas (not in MMGenTX) - def fee_rel2abs(self,rel_fee): + # given rel fee (gasPrice) in wei, return absolute fee using tx_gas (not in MMGenTX) + def fee_gasPrice2abs(self,rel_fee): assert isinstance(rel_fee,int),"'{}': incorrect type for fee estimate (not an integer)".format(rel_fee) return ETHAmt(rel_fee * self.tx_gas.toWei(),'wei') @@ -158,22 +158,25 @@ class EthereumMMGenTX: return Int(await self.rpc.call('eth_gasPrice'),16),'eth_gasPrice' # ==> rel_fee,fe_type def check_fee(self): - assert self.disable_fee_check or (self.fee <= self.proto.max_tx_fee) + if not self.disable_fee_check: + assert self.usr_fee <= self.proto.max_tx_fee # given rel fee and units, return absolute fee using tx_gas - def convert_fee_spec(self,foo,units,amt,unit): - self.usr_rel_fee = ETHAmt(int(amt),units[unit]) - return ETHAmt(self.usr_rel_fee.toWei() * self.tx_gas.toWei(),'wei') + def fee_rel2abs(self,tx_size,units,amt,unit): + return ETHAmt( + ETHAmt(amt,units[unit]).toWei() * self.tx_gas.toWei(), + from_unit='wei' + ) # given fee estimate (gas price) in wei, return absolute fee, adjusting by opt.tx_fee_adj def fee_est2abs(self,rel_fee,fe_type=None): - ret = self.fee_rel2abs(rel_fee) * opt.tx_fee_adj + ret = self.fee_gasPrice2abs(rel_fee) * opt.tx_fee_adj if opt.verbose: msg('Estimated fee: {} ETH'.format(ret)) return ret def convert_and_check_fee(self,tx_fee,desc='Missing description'): - abs_fee = self.process_fee_spec(tx_fee,None) + abs_fee = self.feespec2abs(tx_fee,None) if abs_fee == False: return False elif not self.disable_fee_check and (abs_fee > self.proto.max_tx_fee): @@ -183,9 +186,9 @@ class EthereumMMGenTX: else: return abs_fee - def update_change_output(self,change_amt): + def update_change_output(self,funds_left): if self.outputs and self.outputs[0].is_chg: - self.update_output_amt(0,ETHAmt(change_amt)) + self.update_output_amt(0,ETHAmt(funds_left)) def update_send_amt(self): if self.outputs: @@ -212,16 +215,22 @@ class EthereumMMGenTX: die(1,"'{}': not an MMGen ID or coin address".format(i)) return ret - def final_inputs_ok_msg(self,change_amt): - m = "Transaction leaves {} {} in the sender's account" - chg = '0' if (self.outputs and self.outputs[0].is_chg) else change_amt - return m.format(ETHAmt(chg).hl(),self.proto.coin) + def final_inputs_ok_msg(self,funds_left): + chg = '0' if (self.outputs and self.outputs[0].is_chg) else funds_left + return "Transaction leaves {} {} in the sender's account".format( + ETHAmt(chg).hl(), + self.proto.coin + ) class Completed(Base,MMGenTX.Completed): fn_fee_unit = 'Mwei' txview_hdr_fs = 'TRANSACTION DATA\n\nID={i} ({a} {c}) UTC={t} Sig={s} Locktime={l}\n' txview_hdr_fs_short = 'TX {i} ({a} {c}) UTC={t} Sig={s} Locktime={l}\n' - txview_ftr_fs = 'Total in account: {i} {d}\nTotal to spend: {o} {d}\nTX fee: {a} {c}{r}\n' + txview_ftr_fs = fmt(""" + Total in account: {i} {d} + Total to spend: {o} {d} + TX fee: {a} {c}{r} + """) fmt_keys = ('from','to','amt','nonce') def check_txfile_hex_data(self): @@ -253,9 +262,8 @@ class EthereumMMGenTX: f_mmid = m['inputs'] ) def format_view_abs_fee(self): - fee = self.fee_rel2abs(self.txobj['gasPrice'].toWei()) - note = ' (max)' if self.txobj['data'] else '' - return fee.hl() + note + fee = self.fee_gasPrice2abs(self.txobj['gasPrice'].toWei()) + return fee.hl() + (' (max)' if self.txobj['data'] else '') def format_view_rel_fee(self,terse): return '' @@ -292,7 +300,7 @@ class EthereumMMGenTX: 'chainId': Int(d['chainId']), 'data': HexStr(d['data']) } self.tx_gas = o['startGas'] # approximate, but better than nothing - self.fee = self.fee_rel2abs(o['gasPrice'].toWei()) + self.fee = self.fee_gasPrice2abs(o['gasPrice'].toWei()) self.txobj = o return d # 'token_addr','decimals' required by Token subclass @@ -371,7 +379,7 @@ class EthereumMMGenTX: txid = CoinTxID(etx.hash.hex()) assert txid == self.coin_txid,"txid in tx.hex doesn't match value in MMGen transaction file" self.tx_gas = o['startGas'] # approximate, but better than nothing - self.fee = self.fee_rel2abs(o['gasPrice'].toWei()) + self.fee = self.fee_gasPrice2abs(o['gasPrice'].toWei()) self.txobj = o return d # 'token_addr','decimals' required by Token subclass @@ -413,7 +421,7 @@ class EthereumMMGenTX: self.check_correct_chain() - fee = self.fee_rel2abs(self.txobj['gasPrice'].toWei()) + fee = self.fee_gasPrice2abs(self.txobj['gasPrice'].toWei()) if not self.disable_fee_check and (fee > self.proto.max_tx_fee): die(2,'Transaction fee ({}) greater than {} max_tx_fee ({} {})!'.format( @@ -463,7 +471,7 @@ class EthereumMMGenTX: def min_fee(self): return ETHAmt(self.fee * Decimal('1.101')) - def update_fee(self,foo,fee): + def bump_fee(self,idx,fee): self.fee = fee async def get_nonce(self): @@ -489,28 +497,33 @@ class EthereumTokenMMGenTX: o['token_to'] = o['to'] o['data'] = t.create_data(o['token_to'],o['amt']) - def update_change_output(self,change_amt): + def update_change_output(self,funds_left): if self.outputs[0].is_chg: self.update_output_amt(0,self.inputs[0].amt) # token transaction, so check both eth and token balances # TODO: add test with insufficient funds - async def precheck_sufficient_funds(self,inputs_sum,sel_unspent): + async def precheck_sufficient_funds(self,inputs_sum,sel_unspent,outputs_sum): eth_bal = await self.tw.get_eth_balance(sel_unspent[0].addr) if eth_bal == 0: # we don't know the fee yet msg('This account has no ether to pay for the transaction fee!') return False - return await super().precheck_sufficient_funds(inputs_sum,sel_unspent) + return await super().precheck_sufficient_funds(inputs_sum,sel_unspent,outputs_sum) - async def get_change_amt(self): # here we know the fee - eth_bal = await self.tw.get_eth_balance(self.inputs[0].addr) - return eth_bal - self.fee + async def get_funds_left(self,fee,outputs_sum): + return ( await self.tw.get_eth_balance(self.inputs[0].addr) ) - fee - def final_inputs_ok_msg(self,change_amt): - token_bal = ( ETHAmt('0') if self.outputs[0].is_chg else - self.inputs[0].amt - self.outputs[0].amt ) - m = "Transaction leaves ≈{} {} and {} {} in the sender's account" - return m.format( change_amt.hl(), self.proto.coin, token_bal.hl(), self.proto.dcoin ) + def final_inputs_ok_msg(self,funds_left): + token_bal = ( + ETHAmt('0') if self.outputs[0].is_chg + else self.inputs[0].amt - self.outputs[0].amt + ) + return "Transaction leaves ≈{} {} and {} {} in the sender's account".format( + funds_left.hl(), + self.proto.coin, + token_bal.hl(), + self.proto.dcoin + ) class Completed(Base,EthereumMMGenTX.Completed): fmt_keys = ('from','token_to','amt','nonce') diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index 758f1f71..6bc29ded 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -140,22 +140,22 @@ async def main(): from .rpc import rpc_init tx.rpc = await rpc_init(tx.proto) - tx.check_bumpable() # needs cached networkinfo['relayfee'] - msg('Creating replacement transaction') - op_idx = tx.choose_output() + tx.check_sufficient_funds_for_bump() + + output_idx = tx.choose_output() if not silent: msg('Minimum fee for new transaction: {} {}'.format(tx.min_fee.hl(),tx.proto.coin)) - fee = tx.get_usr_fee_interactive(tx_fee=opt.tx_fee,desc='User-selected') + tx.usr_fee = tx.get_usr_fee_interactive(tx_fee=opt.tx_fee,desc='User-selected') - tx.update_fee(op_idx,fee) + tx.bump_fee(output_idx,tx.usr_fee) tx.update_send_amt() d = tx.get_fee() - assert d == fee and d <= tx.proto.max_tx_fee + assert d == tx.usr_fee and d <= tx.proto.max_tx_fee if tx.proto.base_proto == 'Bitcoin': tx.outputs.sort_bip69() # output amts have changed, so re-sort diff --git a/mmgen/opts.py b/mmgen/opts.py index 092298da..810a3262 100755 --- a/mmgen/opts.py +++ b/mmgen/opts.py @@ -405,7 +405,7 @@ def opt_is_tx_fee(key,val,desc): # 'key' must remain a placeholder tx = MMGenTX.New(init_proto_from_opts()) # Size of 224 is just a ball-park figure to eliminate the most extreme cases at startup # This check will be performed again once we know the true size - ret = tx.process_fee_spec(val,224) + ret = tx.feespec2abs(val,224) if ret == False: raise UserOptError('{!r}: invalid {}\n(not a {} amount or {} specification)'.format( diff --git a/mmgen/tx.py b/mmgen/tx.py index 372ad1ae..bb825686 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -576,10 +576,12 @@ class MMGenTX: return fee_per_kb,fe_type # given tx size, rel fee and units, return absolute fee - def convert_fee_spec(self,tx_size,units,amt,unit): + def fee_rel2abs(self,tx_size,units,amt,unit): self.usr_rel_fee = None # TODO - return self.proto.coin_amt(int(amt)*tx_size*getattr(self.proto.coin_amt,units[unit])) \ - if tx_size else None + if tx_size: + return self.proto.coin_amt(amt * tx_size * getattr(self.proto.coin_amt,units[unit])) + else: + return None # given network fee estimate in BTC/kB, return absolute fee using estimated tx size def fee_est2abs(self,fee_per_kb,fe_type=None): @@ -596,7 +598,7 @@ class MMGenTX: return ret def convert_and_check_fee(self,tx_fee,desc='Missing description'): - abs_fee = self.process_fee_spec(tx_fee,self.estimate_size()) + abs_fee = self.feespec2abs(tx_fee,self.estimate_size()) if abs_fee == None: raise ValueError(f'{tx_fee}: cannot convert {self.rel_fee_desc} to {self.coin}' + ' because transaction size is unknown') @@ -615,7 +617,7 @@ class MMGenTX: # given tx size and absolute fee or fee spec, return absolute fee # relative fee is N+ - def process_fee_spec(self,tx_fee,tx_size): + def feespec2abs(self,tx_fee,tx_size): fee = get_obj(self.proto.coin_amt,num=tx_fee,silent=True) if fee: return fee @@ -625,7 +627,7 @@ class MMGenTX: pat = re.compile(r'([1-9][0-9]*)({})'.format('|'.join(units))) if pat.match(tx_fee): amt,unit = pat.match(tx_fee).groups() - return self.convert_fee_spec(tx_size,units,amt,unit) + return self.fee_rel2abs(tx_size,units,int(amt),unit) return False def get_usr_fee_interactive(self,tx_fee=None,desc='Starting'): @@ -769,12 +771,12 @@ class MMGenTX: return set(get_uo_nums()) # silently discard duplicates # we don't know fee yet, so perform preliminary check with fee == 0 - async def precheck_sufficient_funds(self,inputs_sum,sel_unspent): - if self.twuo.total < self.send_amt: - msg(self.msg_wallet_low_coin.format(self.send_amt-inputs_sum,self.dcoin)) + async def precheck_sufficient_funds(self,inputs_sum,sel_unspent,outputs_sum): + if self.twuo.total < outputs_sum: + msg(self.msg_wallet_low_coin.format(outputs_sum-inputs_sum,self.dcoin)) return False - if inputs_sum < self.send_amt: - msg(self.msg_low_coin.format(self.send_amt-inputs_sum,self.dcoin)) + if inputs_sum < outputs_sum: + msg(self.msg_low_coin.format(outputs_sum-inputs_sum,self.dcoin)) return False return True @@ -789,16 +791,19 @@ class MMGenTX: yield i self.inputs = MMGenTxInputList(self,list(gen_inputs())) - async def get_change_amt(self): - return self.sum_inputs() - self.send_amt - self.fee + async def get_funds_left(self,fee,outputs_sum): + return self.sum_inputs() - outputs_sum - fee - def final_inputs_ok_msg(self,change_amt): - return f'Transaction produces {self.proto.coin_amt(change_amt).hl()} {self.coin} in change' + def final_inputs_ok_msg(self,funds_left): + return 'Transaction produces {} {} in change'.format( + self.proto.coin_amt(funds_left).hl(), + self.coin + ) - def warn_insufficient_chg(self,change_amt): - msg(self.msg_low_coin.format(self.proto.coin_amt(-change_amt).hl(),self.coin)) + def warn_insufficient_funds(self,funds_left): + msg(self.msg_low_coin.format(self.proto.coin_amt(-funds_left).hl(),self.coin)) - async def get_inputs_from_user(self): + async def get_inputs_from_user(self,outputs_sum): while True: us_f = self.select_unspent_cmdline if opt.inputs else self.select_unspent @@ -808,31 +813,31 @@ class MMGenTX: sel_unspent = self.twuo.MMGenTwOutputList([self.twuo.unspent[i-1] for i in sel_nums]) inputs_sum = sum(s.amt for s in sel_unspent) - if not await self.precheck_sufficient_funds(inputs_sum,sel_unspent): + if not await self.precheck_sufficient_funds(inputs_sum,sel_unspent,outputs_sum): continue self.copy_inputs_from_tw(sel_unspent) # makes self.inputs - self.fee = await self.get_fee_from_user() + self.usr_fee = await self.get_fee_from_user() - change_amt = await self.get_change_amt() + funds_left = await self.get_funds_left(self.usr_fee,outputs_sum) - if change_amt >= 0: - p = self.final_inputs_ok_msg(change_amt) + if funds_left >= 0: + p = self.final_inputs_ok_msg(funds_left) if opt.yes or keypress_confirm(p+'. OK?',default_yes=True): if opt.yes: msg(p) - return change_amt + return funds_left else: - self.warn_insufficient_chg(change_amt) + self.warn_insufficient_funds(funds_left) - def update_change_output(self,change_amt): + def update_change_output(self,funds_left): chg_idx = self.get_chg_output_idx() - if change_amt == 0: + if funds_left == 0: msg(self.no_chg_msg) self.del_output(chg_idx) else: - self.update_output_amt(chg_idx,self.proto.coin_amt(change_amt)) + self.update_output_amt(chg_idx,self.proto.coin_amt(funds_left)) def update_send_amt(self): self.send_amt = self.sum_outputs(exclude = @@ -884,17 +889,17 @@ class MMGenTX: del self.twuo.wallet sys.exit(0) - self.send_amt = self.sum_outputs() + outputs_sum = self.sum_outputs() - msg_r('Total amount to spend: ') - msg(f'{self.send_amt.hl()} {self.dcoin}' if self.send_amt else 'Unknown') + msg('Total amount to spend: {}'.format( + f'{outputs_sum.hl()} {self.dcoin}' if outputs_sum else 'Unknown' + )) - change_amt = await self.get_inputs_from_user() + funds_left = await self.get_inputs_from_user(outputs_sum) self.check_non_mmgen_inputs(caller) - self.update_change_output(change_amt) - self.update_send_amt() + self.update_change_output(funds_left) if self.proto.base_proto == 'Bitcoin': self.inputs.sort_bip69() @@ -942,8 +947,8 @@ class MMGenTX: txview_hdr_fs_short = 'TX {i} ({a} {c}) UTC={t} RBF={r} Sig={s} Locktime={l}\n' txview_ftr_fs = fmt(""" Input amount: {i} {d} - Change: {C} {d} Spend amount: {s} {d} + Change: {C} {d} Fee: {a} {c}{r} """) parsed_hex = None @@ -1497,7 +1502,7 @@ class MMGenTX: self.coin_txid = '' - def check_bumpable(self): + def check_sufficient_funds_for_bump(self): if not [o.amt for o in self.outputs if o.amt >= self.min_fee]: die(1, 'Transaction cannot be bumped.\n' + @@ -1549,9 +1554,11 @@ class MMGenTX: def min_fee(self): return self.sum_inputs() - self.sum_outputs() + self.relay_fee - def update_fee(self,op_idx,fee): - amt = self.sum_inputs() - self.sum_outputs(exclude=op_idx) - fee - self.update_output_amt(op_idx,amt) + def bump_fee(self,idx,fee): + self.update_output_amt( + idx, + self.sum_inputs() - self.sum_outputs(exclude=idx) - fee + ) def convert_and_check_fee(self,tx_fee,desc): ret = super().convert_and_check_fee(tx_fee,desc) @@ -1607,18 +1614,17 @@ class MMGenTX: # await self.get_outputs_from_cmdline(mmid) # # while True: -# change_amt = self.sum_inputs() - self.get_split_fee_from_user() -# if change_amt >= 0: -# p = 'Transaction produces {} {} in change'.format(change_amt.hl(),self.coin) +# funds_left = self.sum_inputs() - self.get_split_fee_from_user() +# if funds_left >= 0: +# p = 'Transaction produces {} {} in change'.format(funds_left.hl(),self.coin) # if opt.yes or keypress_confirm(p+'. OK?',default_yes=True): # if opt.yes: # msg(p) # break # else: -# self.warn_insufficient_chg(change_amt) +# self.warn_insufficient_funds(funds_left) # -# self.update_output_amt(0,change_amt) -# self.send_amt = change_amt +# self.update_output_amt(0,funds_left) # # if not opt.yes: # self.add_comment() # edits an existing comment