MMGenTX: code cleanups, variable and method renames

This commit is contained in:
The MMGen Project 2021-06-24 17:15:22 +00:00
commit ab8028ae64
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 106 additions and 87 deletions

View file

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

View file

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

View file

@ -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(

View file

@ -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+<first letter of unit name>
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