MMGenTX: code cleanups, variable and method renames
This commit is contained in:
parent
8055c061b7
commit
ab8028ae64
4 changed files with 106 additions and 87 deletions
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
96
mmgen/tx.py
96
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+<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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue