tx.py,tw.py: cleanups, support tx inputs from cmdline
This commit is contained in:
parent
98d0fff70f
commit
fad573eccd
16 changed files with 537 additions and 247 deletions
|
|
@ -140,10 +140,9 @@ class EthereumTrackingWallet(TrackingWallet):
|
|||
m = "Address '{}' not found in '{}' section of tracking wallet"
|
||||
return ('rpcfail',(None,2,m.format(coinaddr,self.data_root_desc())))
|
||||
|
||||
# Use consistent naming, even though Ethereum doesn't have unspent outputs
|
||||
class EthereumTwUnspentOutputs(TwUnspentOutputs):
|
||||
|
||||
show_txid = False
|
||||
disp_type = 'eth'
|
||||
can_group = False
|
||||
hdr_fmt = 'TRACKED ACCOUNTS (sort order: {})\nTotal {}: {}'
|
||||
desc = 'account balances'
|
||||
|
|
@ -155,32 +154,36 @@ Display options: show [D]ays, show [m]mgen addr, r[e]draw screen
|
|||
|
||||
def do_sort(self,key=None,reverse=False):
|
||||
if key == 'txid': return
|
||||
super(type(self),self).do_sort(key=key,reverse=reverse)
|
||||
super(EthereumTwUnspentOutputs,self).do_sort(key=key,reverse=reverse)
|
||||
|
||||
def get_addr_bal(self,addr):
|
||||
return ETHAmt(int(g.rpch.eth_getBalance('0x'+addr),16),'wei')
|
||||
|
||||
def get_unspent_rpc(self):
|
||||
rpc_init()
|
||||
return map(lambda d: {
|
||||
'account': TwLabel(d['mmid']+' '+d['comment'],on_fail='raise'),
|
||||
'address': d['addr'],
|
||||
'amount': ETHAmt(int(g.rpch.eth_getBalance('0x'+d['addr']),16),'wei'),
|
||||
'amount': self.get_addr_bal(d['addr']),
|
||||
'confirmations': 0, # TODO
|
||||
}, TrackingWallet().sorted_list())
|
||||
|
||||
class EthereumTwAddrList(TwAddrList):
|
||||
|
||||
def __init__(self,usr_addr_list,minconf,showempty,showbtcaddrs,all_labels):
|
||||
tw = TrackingWallet().mmid_ordered_dict()
|
||||
self.total = g.proto.coin_amt('0')
|
||||
|
||||
rpc_init()
|
||||
# cur_blk = int(g.rpch.eth_blockNumber(),16)
|
||||
if g.token: self.token = Token(g.token)
|
||||
|
||||
tw = TrackingWallet().mmid_ordered_dict()
|
||||
self.total = g.proto.coin_amt('0')
|
||||
|
||||
from mmgen.obj import CoinAddr
|
||||
for mmid,d in tw.items():
|
||||
# if d['confirmations'] < minconf: continue
|
||||
label = TwLabel(mmid+' '+d['comment'],on_fail='raise')
|
||||
if usr_addr_list and (label.mmid not in usr_addr_list): continue
|
||||
bal = ETHAmt(int(g.rpch.eth_getBalance('0x'+d['addr']),16),'wei')
|
||||
bal = self.get_addr_balance(d['addr'])
|
||||
if bal == 0 and not showempty:
|
||||
if not label.comment: continue
|
||||
if not all_labels: continue
|
||||
|
|
@ -191,6 +194,9 @@ class EthereumTwAddrList(TwAddrList):
|
|||
self[label.mmid]['amt'] += bal
|
||||
self.total += bal
|
||||
|
||||
def get_addr_balance(self,addr):
|
||||
return ETHAmt(int(g.rpch.eth_getBalance('0x'+addr),16),'wei')
|
||||
|
||||
from mmgen.tw import TwGetBalance
|
||||
class EthereumTwGetBalance(TwGetBalance):
|
||||
|
||||
|
|
|
|||
|
|
@ -28,20 +28,47 @@ from mmgen.obj import *
|
|||
from mmgen.tx import MMGenTX,MMGenBumpTX,MMGenSplitTX,DeserializedTX,mmaddr2coinaddr
|
||||
class EthereumMMGenTX(MMGenTX):
|
||||
desc = 'Ethereum transaction'
|
||||
tx_gas = ETHAmt(21000,'wei') # tx_gas 21000 * gasPrice 50 Gwei = fee 0.00105
|
||||
chg_msg_fs = 'Transaction leaves {} {} in the account'
|
||||
tx_gas = ETHAmt(21000,'wei') # an approximate number, used for fee estimation purposes
|
||||
start_gas = ETHAmt(21000,'wei') # the actual startgas amt used in the transaction
|
||||
# for simple sends with no data, tx_gas = start_gas = 21000
|
||||
fee_fail_fs = 'Network fee estimation failed'
|
||||
no_chg_msg = 'Warning: Transaction leaves account with zero balance'
|
||||
rel_fee_desc = 'gas price'
|
||||
rel_fee_disp = 'gas price in Gwei'
|
||||
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_short = 'In {i} {d} - Out {o} {d}\nFee {a} {c}{r}\n'
|
||||
usr_fee_prompt = 'Enter transaction fee or gas price: '
|
||||
|
||||
fn_fee_unit = 'Mwei'
|
||||
usr_rel_fee = None # not in MMGenTX
|
||||
txobj_data = None # ""
|
||||
disable_fee_check = False
|
||||
txobj = None # ""
|
||||
data = HexStr('')
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
super(EthereumMMGenTX,self).__init__(*args,**kwargs)
|
||||
if hasattr(opt,'tx_gas') and opt.tx_gas:
|
||||
self.tx_gas = self.start_gas = ETHAmt(int(opt.tx_gas),'wei')
|
||||
if hasattr(opt,'contract_data') and opt.contract_data:
|
||||
self.data = HexStr(open(opt.contract_data).read().strip())
|
||||
self.disable_fee_check = True
|
||||
|
||||
@classmethod
|
||||
def get_receipt(cls,txid):
|
||||
return g.rpch.eth_getTransactionReceipt('0x'+txid)
|
||||
|
||||
@classmethod
|
||||
def get_exec_status(cls,txid):
|
||||
return int(g.rpch.eth_getTransactionReceipt('0x'+txid)['status'],16)
|
||||
|
||||
def is_replaceable(self): return True
|
||||
|
||||
def get_fee_from_tx(self):
|
||||
return self.fee
|
||||
|
||||
def check_fee(self):
|
||||
if self.disable_fee_check: return
|
||||
assert self.fee <= g.proto.max_tx_fee
|
||||
|
||||
def get_hex_locktime(self): return None # TODO
|
||||
|
|
@ -54,46 +81,65 @@ class EthereumMMGenTX(MMGenTX):
|
|||
return True
|
||||
return False
|
||||
|
||||
# hex data if signed, json if unsigned
|
||||
def check_tx_hex_data(self):
|
||||
# hex data if signed, json if unsigned: see create_raw()
|
||||
def check_txfile_hex_data(self):
|
||||
if self.check_sigs():
|
||||
from ethereum.transactions import Transaction
|
||||
import rlp
|
||||
etx = rlp.decode(self.hex.decode('hex'),Transaction)
|
||||
d = etx.to_dict()
|
||||
self.txobj_data = {
|
||||
'from': CoinAddr(d['sender'][2:]),
|
||||
'to': CoinAddr(d['to'][2:]),
|
||||
'amt': ETHAmt(d['value'],'wei'),
|
||||
'gasPrice': ETHAmt(d['gasprice'],'wei'),
|
||||
'nonce': ETHNonce(d['nonce'])
|
||||
}
|
||||
d = etx.to_dict() # ==> hex values have '0x' prefix, 0 is '0x'
|
||||
for k in ('sender','to','data'):
|
||||
if k in d: d[k] = d[k].replace('0x','',1)
|
||||
o = { 'from': CoinAddr(d['sender']),
|
||||
'to': CoinAddr(d['to']) if d['to'] else Str(''),
|
||||
'amt': ETHAmt(d['value'],'wei'),
|
||||
'gasPrice': ETHAmt(d['gasprice'],'wei'),
|
||||
'startGas': ETHAmt(d['startgas'],'wei'),
|
||||
'nonce': ETHNonce(d['nonce']),
|
||||
'data': HexStr(d['data']) }
|
||||
if o['data'] and not o['to']:
|
||||
self.token_addr = TokenAddr(etx.creates.encode('hex'))
|
||||
txid = CoinTxID(etx.hash.encode('hex'))
|
||||
assert txid == self.coin_txid,"txid in tx.hex doesn't match value in MMGen tx file"
|
||||
assert txid == self.coin_txid,"txid in tx.hex doesn't match value in MMGen transaction file"
|
||||
else:
|
||||
d = json.loads(self.hex)
|
||||
self.txobj_data = {
|
||||
'from': CoinAddr(d['from']),
|
||||
'to': CoinAddr(d['to']),
|
||||
'amt': ETHAmt(d['amt']),
|
||||
'gasPrice': ETHAmt(d['gasPrice']),
|
||||
'nonce': ETHNonce(d['nonce']),
|
||||
'chainId': d['chainId']
|
||||
}
|
||||
self.gasPrice = self.txobj_data['gasPrice']
|
||||
o = { 'from': CoinAddr(d['from']),
|
||||
'to': CoinAddr(d['to']) if d['to'] else Str(''),
|
||||
'amt': ETHAmt(d['amt']),
|
||||
'gasPrice': ETHAmt(d['gasPrice']),
|
||||
'startGas': ETHAmt(d['startGas']),
|
||||
'nonce': ETHNonce(d['nonce']),
|
||||
'chainId': Int(d['chainId']),
|
||||
'data': HexStr(d['data']) }
|
||||
self.tx_gas = o['startGas'] # approximate, but better than nothing
|
||||
self.data = o['data']
|
||||
if o['data'] and not o['to']: self.disable_fee_check = True
|
||||
self.fee = self.fee_rel2abs(o['gasPrice'].toWei())
|
||||
self.txobj = o
|
||||
return d # 'token_addr','decimals' required by subclass
|
||||
|
||||
def create_raw(self):
|
||||
for k in 'input','output':
|
||||
assert len(getattr(self,k+'s')) == 1,'Transaction has more than one {}!'.format(k)
|
||||
self.txobj_data = {
|
||||
def make_txobj(self): # create_raw
|
||||
self.txobj = {
|
||||
'from': self.inputs[0].addr,
|
||||
'to': self.outputs[0].addr,
|
||||
'amt': self.outputs[0].amt,
|
||||
'gasPrice': self.usr_rel_fee or self.fee_abs2rel(self.fee,in_eth=True),
|
||||
'to': self.outputs[0].addr if self.outputs else Str(''),
|
||||
'amt': self.outputs[0].amt if self.outputs else ETHAmt(0),
|
||||
'gasPrice': self.usr_rel_fee or self.fee_abs2rel(self.fee,to_unit='eth'),
|
||||
'startGas': self.start_gas,
|
||||
'nonce': ETHNonce(int(g.rpch.parity_nextNonce('0x'+self.inputs[0].addr),16)),
|
||||
'chainId': g.rpch.parity_chainId()
|
||||
'chainId': Int(g.rpch.parity_chainId(),16),
|
||||
'data': self.data,
|
||||
}
|
||||
self.hex = json.dumps(dict([(k,str(v))for k,v in self.txobj_data.items()]))
|
||||
|
||||
# Instead of serializing tx data as with BTC, just create a JSON dump.
|
||||
# This complicates things but means we avoid using the rlp library to deserialize the data,
|
||||
# thus removing an attack vector
|
||||
def create_raw(self):
|
||||
assert len(self.inputs) == 1,'Transaction has more than one input!'
|
||||
o_ok = (0,1) if self.data else (1,)
|
||||
o_num = len(self.outputs)
|
||||
assert o_num in o_ok,'Transaction has invalid number of outputs!'.format(o_num)
|
||||
self.make_txobj()
|
||||
self.hex = json.dumps(dict([(k,str(v))for k,v in self.txobj.items()]))
|
||||
self.update_txid()
|
||||
|
||||
def del_output(self,idx): pass
|
||||
|
|
@ -105,12 +151,15 @@ class EthereumMMGenTX(MMGenTX):
|
|||
self.txid = MMGenTxID(make_chksum_6(self.hex).upper())
|
||||
|
||||
def get_blockcount(self):
|
||||
return int(g.rpch.eth_blockNumber(),16)
|
||||
return Int(g.rpch.eth_blockNumber(),16)
|
||||
|
||||
def process_cmd_args(self,cmd_args,ad_f,ad_w):
|
||||
lc = len(cmd_args)
|
||||
if lc != 1:
|
||||
fs = '{} output{} specified, but Ethereum transactions must have only one'
|
||||
|
||||
if lc == 0 and self.data:
|
||||
return
|
||||
elif lc != 1:
|
||||
fs = '{} output{} specified, but Ethereum transactions must have exactly one'
|
||||
die(1,fs.format(lc,suf(lc)))
|
||||
|
||||
a = list(cmd_args)[0]
|
||||
|
|
@ -124,6 +173,9 @@ class EthereumMMGenTX(MMGenTX):
|
|||
else:
|
||||
die(2,'{}: invalid command-line argument'.format(a))
|
||||
|
||||
if not self.outputs:
|
||||
die(2,'At least one output must be specified on the command line')
|
||||
|
||||
def select_unspent(self,unspent):
|
||||
prompt = 'Enter an account to spend from: '
|
||||
while True:
|
||||
|
|
@ -142,13 +194,13 @@ class EthereumMMGenTX(MMGenTX):
|
|||
def get_relay_fee(self): return ETHAmt(0) # TODO
|
||||
|
||||
# given absolute fee in ETH, return gas price in Gwei using tx_gas
|
||||
def fee_abs2rel(self,abs_fee,in_eth=False): # in_eth not in MMGenTX
|
||||
def fee_abs2rel(self,abs_fee,to_unit='Gwei'):
|
||||
ret = ETHAmt(int(abs_fee.toWei() / self.tx_gas.toWei()),'wei')
|
||||
return ret if in_eth else ret.toGwei()
|
||||
return ret if to_unit == 'eth' else ret.to_unit(to_unit)
|
||||
|
||||
# get rel_fee (gas price) from network, return in native wei
|
||||
def get_rel_fee_from_network(self):
|
||||
return int(g.rpch.eth_gasPrice(),16),'eth_gasPrice' # ==> rel_fee,fe_type
|
||||
return Int(g.rpch.eth_gasPrice(),16),'eth_gasPrice' # ==> rel_fee,fe_type
|
||||
|
||||
# given rel fee and units, return absolute fee using tx_gas
|
||||
def convert_fee_spec(self,foo,units,amt,unit):
|
||||
|
|
@ -157,7 +209,7 @@ class EthereumMMGenTX(MMGenTX):
|
|||
|
||||
# given rel fee in wei, return absolute fee using tx_gas (not in MMGenTX)
|
||||
def fee_rel2abs(self,rel_fee):
|
||||
assert type(rel_fee) is int,"'{}': incorrect type for fee estimate (not an integer)".format(rel_fee)
|
||||
assert type(rel_fee) in (int,Int),"'{}': incorrect type for fee estimate (not an integer)".format(rel_fee)
|
||||
return ETHAmt(rel_fee * self.tx_gas.toWei(),'wei')
|
||||
|
||||
# given fee estimate (gas price) in wei, return absolute fee, adjusting by opt.tx_fee_adj
|
||||
|
|
@ -171,6 +223,8 @@ class EthereumMMGenTX(MMGenTX):
|
|||
abs_fee = self.process_fee_spec(tx_fee,None,on_fail='return')
|
||||
if abs_fee == False:
|
||||
return False
|
||||
elif self.disable_fee_check:
|
||||
return abs_fee
|
||||
elif abs_fee > g.proto.max_tx_fee:
|
||||
m = '{} {c}: {} fee too large (maximum fee: {} {c})'
|
||||
msg(m.format(abs_fee.hl(),desc,g.proto.max_tx_fee.hl(),c=g.coin))
|
||||
|
|
@ -181,25 +235,67 @@ class EthereumMMGenTX(MMGenTX):
|
|||
def format_view_body(self,blockcount,nonmm_str,max_mmwid,enl,terse):
|
||||
m = {}
|
||||
for k in ('in','out'):
|
||||
m[k] = getattr(self,k+'puts')[0].mmid
|
||||
m[k] = ' ' + m[k].hl() if m[k] else ' ' + MMGenID.hlc(nonmm_str)
|
||||
if len(getattr(self,k+'puts')):
|
||||
m[k] = getattr(self,k+'puts')[0].mmid if len(getattr(self,k+'puts')) else ''
|
||||
m[k] = ' ' + m[k].hl() if m[k] else ' ' + MMGenID.hlc(nonmm_str)
|
||||
fs = """From: {}{f_mmid}
|
||||
To: {}{t_mmid}
|
||||
Amount: {} ETH
|
||||
Amount: {} {c}
|
||||
Gas price: {g} Gwei
|
||||
Nonce: {}\n\n""".replace('\t','')
|
||||
Start gas: {G} Kwei
|
||||
Nonce: {}
|
||||
Data: {d}
|
||||
\n""".replace('\t','')
|
||||
keys = ('from','to','amt','nonce')
|
||||
return fs.format( *(self.txobj_data[k].hl() for k in keys),
|
||||
g=yellow(str(self.txobj_data['gasPrice'].toGwei())),
|
||||
t_mmid=m['out'],
|
||||
ld = len(self.txobj['data'])
|
||||
return fs.format( *((self.txobj[k] if self.txobj[k] != '' else Str('None')).hl() for k in keys),
|
||||
d='{}... ({} bytes)'.format(self.txobj['data'][:40],ld/2) if ld else Str('None'),
|
||||
c=g.dcoin if len(self.outputs) else '',
|
||||
g=yellow(str(self.txobj['gasPrice'].toGwei())),
|
||||
G=yellow(str(self.txobj['startGas'].toKwei())),
|
||||
t_mmid=m['out'] if len(self.outputs) else '',
|
||||
f_mmid=m['in'])
|
||||
|
||||
def format_view_abs_fee(self):
|
||||
return self.fee_rel2abs(self.txobj_data['gasPrice'].toWei()).hl()
|
||||
fee = self.fee_rel2abs(self.txobj['gasPrice'].toWei())
|
||||
note = ' (max)' if self.data else ''
|
||||
return fee.hl() + note
|
||||
|
||||
def format_view_rel_fee(self,terse): return ''
|
||||
def format_view_verbose_footer(self): return '' # TODO
|
||||
|
||||
def final_inputs_ok_msg(self,change_amt):
|
||||
m = "Transaction leaves {} {} in the sender's account"
|
||||
return m.format(g.proto.coin_amt(change_amt).hl(),g.coin)
|
||||
|
||||
def do_sign(self,d,wif,tx_num_str):
|
||||
|
||||
d_in = {'to': d['to'].decode('hex'),
|
||||
'startgas': d['startGas'].toWei(),
|
||||
'gasprice': d['gasPrice'].toWei(),
|
||||
'value': d['amt'].toWei() if d['amt'] else 0,
|
||||
'nonce': d['nonce'],
|
||||
'data': d['data'].decode('hex')}
|
||||
|
||||
msg_r('Signing transaction{}...'.format(tx_num_str))
|
||||
|
||||
try:
|
||||
from ethereum.transactions import Transaction
|
||||
etx = Transaction(**d_in)
|
||||
etx.sign(wif,d['chainId'])
|
||||
import rlp
|
||||
self.hex = rlp.encode(etx).encode('hex')
|
||||
self.coin_txid = CoinTxID(etx.hash.encode('hex'))
|
||||
msg('OK')
|
||||
if d['data']:
|
||||
self.token_addr = TokenAddr(etx.creates.encode('hex'))
|
||||
except Exception as e:
|
||||
m = "{!r}: transaction signing failed!"
|
||||
msg(m.format(e[0]))
|
||||
return False
|
||||
|
||||
return self.check_sigs()
|
||||
|
||||
def sign(self,tx_num_str,keys): # return true or false; don't exit
|
||||
|
||||
if self.marked_signed():
|
||||
|
|
@ -209,32 +305,7 @@ class EthereumMMGenTX(MMGenTX):
|
|||
if not self.check_correct_chain(on_fail='return'):
|
||||
return False
|
||||
|
||||
wif = keys[0].sec.wif
|
||||
d = self.txobj_data
|
||||
|
||||
out = { 'to': '0x'+d['to'],
|
||||
'startgas': self.tx_gas.toWei(),
|
||||
'gasprice': d['gasPrice'].toWei(),
|
||||
'value': d['amt'].toWei(),
|
||||
'nonce': d['nonce'],
|
||||
'data': ''}
|
||||
|
||||
msg_r('Signing transaction{}...'.format(tx_num_str))
|
||||
|
||||
try:
|
||||
from ethereum.transactions import Transaction
|
||||
etx = Transaction(**out)
|
||||
etx.sign(wif,int(d['chainId'],16))
|
||||
import rlp
|
||||
self.hex = rlp.encode(etx).encode('hex')
|
||||
self.coin_txid = CoinTxID(etx.hash.encode('hex'))
|
||||
msg('OK')
|
||||
except Exception as e:
|
||||
m = "{!r}: transaction signing failed!"
|
||||
msg(m.format(e[0]))
|
||||
return False
|
||||
|
||||
return self.check_sigs()
|
||||
return self.do_sign(self.txobj,keys[0].sec.wif,tx_num_str)
|
||||
|
||||
def get_status(self,status=False): pass # TODO
|
||||
|
||||
|
|
@ -247,9 +318,9 @@ class EthereumMMGenTX(MMGenTX):
|
|||
|
||||
bogus_send = os.getenv('MMGEN_BOGUS_SEND')
|
||||
|
||||
fee = self.fee_rel2abs(self.txobj_data['gasPrice'].toWei())
|
||||
fee = self.fee_rel2abs(self.txobj['gasPrice'].toWei())
|
||||
|
||||
if fee > g.proto.max_tx_fee:
|
||||
if not self.disable_fee_check and fee > g.proto.max_tx_fee:
|
||||
die(2,'Transaction fee ({}) greater than {} max_tx_fee ({} {})!'.format(
|
||||
fee,g.proto.name.capitalize(),g.proto.max_tx_fee,g.coin))
|
||||
|
||||
|
|
@ -275,6 +346,15 @@ class EthereumMMGenTX(MMGenTX):
|
|||
self.add_blockcount()
|
||||
return True
|
||||
|
||||
class EthereumMMGenBumpTX(MMGenBumpTX): pass
|
||||
class EthereumMMGenBumpTX(EthereumMMGenTX,MMGenBumpTX):
|
||||
|
||||
def choose_output(self): pass
|
||||
|
||||
def set_min_fee(self):
|
||||
self.min_fee = ETHAmt(self.fee * Decimal('1.101'))
|
||||
|
||||
def update_fee(self,foo,fee):
|
||||
self.fee = fee
|
||||
|
||||
class EthereumMMGenSplitTX(MMGenSplitTX): pass
|
||||
class EthereumDeserializedTX(DeserializedTX): pass
|
||||
|
|
|
|||
|
|
@ -102,20 +102,18 @@ if not silent:
|
|||
|
||||
tx.set_min_fee()
|
||||
|
||||
if not [o.amt for o in tx.outputs if o.amt >= tx.min_fee]:
|
||||
die(1,'Transaction cannot be bumped.' +
|
||||
'\nAll outputs have less than the minimum fee ({} {})'.format(tx.min_fee,g.coin))
|
||||
tx.check_bumpable()
|
||||
|
||||
msg('Creating new transaction')
|
||||
|
||||
op_idx = tx.choose_output()
|
||||
|
||||
if not silent:
|
||||
msg('Minimum fee for new transaction: {} {}'.format(tx.min_fee,g.coin))
|
||||
msg('Minimum fee for new transaction: {} {}'.format(tx.min_fee.hl(),g.coin))
|
||||
|
||||
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)
|
||||
tx.update_fee(op_idx,fee)
|
||||
|
||||
d = tx.get_fee_from_tx()
|
||||
assert d == fee and d <= g.proto.max_tx_fee
|
||||
|
|
|
|||
|
|
@ -28,26 +28,30 @@ opts_data = lambda: {
|
|||
'usage': '[opts] <addr,amt> ... [change addr] [addr file] ...',
|
||||
'sets': ( ('yes', True, 'quiet', True), ),
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-a, --tx-fee-adj= f Adjust transaction fee by factor 'f' (see below)
|
||||
-B, --no-blank Don't blank screen before displaying unspent outputs
|
||||
-c, --comment-file=f Source the transaction's comment from file 'f'
|
||||
-C, --tx-confs= c Desired number of confirmations (default: {g.tx_confs})
|
||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
||||
-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or as
|
||||
{fu} (an integer followed by {fl}).
|
||||
See FEE SPECIFICATION below. If omitted, fee will be
|
||||
calculated using network fee estimation.
|
||||
-i, --info Display unspent outputs and exit
|
||||
-L, --locktime= t Lock time (block height or unix seconds) (default: 0)
|
||||
-m, --minconf= n Minimum number of confirmations required to spend
|
||||
outputs (default: 1)
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-r, --rbf Make transaction BIP 125 replaceable (replace-by-fee)
|
||||
-v, --verbose Produce more verbose output
|
||||
-V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f'
|
||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-a, --tx-fee-adj= f Adjust transaction fee by factor 'f' (see below)
|
||||
-B, --no-blank Don't blank screen before displaying unspent outputs
|
||||
-c, --comment-file=f Source the transaction's comment from file 'f'
|
||||
-C, --tx-confs= c Desired number of confirmations (default: {g.tx_confs})
|
||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
||||
-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or as
|
||||
{fu} (an integer followed by {fl}).
|
||||
See FEE SPECIFICATION below. If omitted, fee will be
|
||||
calculated using network fee estimation.
|
||||
-g, --tx-gas= g Specify start gas amount in Wei (ETH only)
|
||||
-i, --info Display unspent outputs and exit
|
||||
-I, --inputs= i Specify transaction inputs (comma-separated list of
|
||||
MMGen IDs or coin addresses). Note that ALL unspent
|
||||
outputs associated with each address will be included.
|
||||
-L, --locktime= t Lock time (block height or unix seconds) (default: 0)
|
||||
-m, --minconf= n Minimum number of confirmations required to spend
|
||||
outputs (default: 1)
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-r, --rbf Make transaction BIP 125 replaceable (replace-by-fee)
|
||||
-v, --verbose Produce more verbose output
|
||||
-V, --vsize-adj= f Adjust transaction's estimated vsize by factor 'f'
|
||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||
""",
|
||||
'options_fmt_args': lambda: dict(
|
||||
g=g,cu=g.coin,
|
||||
|
|
|
|||
|
|
@ -41,9 +41,13 @@ opts_data = lambda: {
|
|||
{fu} (an integer followed by {fl}).
|
||||
See FEE SPECIFICATION below. If omitted, fee will be
|
||||
calculated using network fee estimation.
|
||||
-g, --tx-gas= g Specify start gas amount in Wei (ETH only)
|
||||
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
|
||||
'f' at offset 'o' (comma-separated)
|
||||
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
|
||||
-I, --inputs= i Specify transaction inputs (comma-separated list of
|
||||
MMGen IDs or coin addresses). Note that ALL unspent
|
||||
outputs associated with each address will be included.
|
||||
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option
|
||||
is required only for brainwallet and incognito inputs
|
||||
with non-standard (< {g.seed_len}-bit) seed lengths.
|
||||
|
|
@ -94,9 +98,13 @@ kl = get_keylist(opt)
|
|||
if kl and kal: kl.remove_dup_keys(kal)
|
||||
|
||||
tx = MMGenTX(caller='txdo')
|
||||
|
||||
tx.create(cmd_args,int(opt.locktime or 0))
|
||||
|
||||
txsign(tx,seed_files,kl,kal)
|
||||
|
||||
tx.write_to_file(ask_write=False)
|
||||
|
||||
tx.send(exit_on_fail=True)
|
||||
|
||||
tx.write_to_file(ask_overwrite=False,ask_write=False)
|
||||
|
|
|
|||
14
mmgen/obj.py
14
mmgen/obj.py
|
|
@ -336,13 +336,14 @@ class BTCAmt(Decimal,Hilite,InitErrors):
|
|||
m = "{!r}: value cannot be converted to {} ({})"
|
||||
return cls.init_fail(m.format(num,cls.__name__,e[0]),on_fail)
|
||||
|
||||
def toSatoshi(self): return int(Decimal(self) / self.satoshi)
|
||||
def toSatoshi(self): return int(Decimal(self) / self.satoshi)
|
||||
def to_unit(self,unit): return int(Decimal(self) / getattr(self,unit))
|
||||
|
||||
@classmethod
|
||||
def fmtc(cls):
|
||||
raise NotImplementedError
|
||||
|
||||
def fmt(self,fs=None,color=False,suf=''):
|
||||
def fmt(self,fs=None,color=False,suf='',prec=1000):
|
||||
if fs == None: fs = self.amt_fs
|
||||
s = str(int(self)) if int(self) == self else self.normalize().__format__('f')
|
||||
if '.' in fs:
|
||||
|
|
@ -350,9 +351,9 @@ class BTCAmt(Decimal,Hilite,InitErrors):
|
|||
ss = s.split('.',1)
|
||||
if len(ss) == 2:
|
||||
a,b = ss
|
||||
ret = a.rjust(p1) + '.' + (b+suf).ljust(p2+len(suf))
|
||||
ret = a.rjust(p1) + '.' + ((b+suf).ljust(p2+len(suf)))[:prec]
|
||||
else:
|
||||
ret = s.rjust(p1) + suf + ' ' * (p2+1)
|
||||
ret = s.rjust(p1) + suf + (' ' * (p2+1))[:prec+1-len(suf)]
|
||||
else:
|
||||
ret = s.ljust(int(fs))
|
||||
return self.colorize(ret,color=color)
|
||||
|
|
@ -424,7 +425,7 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
|
|||
def is_for_chain(self,chain):
|
||||
|
||||
from mmgen.globalvars import g
|
||||
if g.coin in ('ETH','ETC'):
|
||||
if g.proto.__name__[:8] == 'Ethereum':
|
||||
return True
|
||||
|
||||
def pfx_ok(pfx):
|
||||
|
|
@ -562,6 +563,9 @@ class HexStr(str,Hilite,InitErrors):
|
|||
m = "{!r}: value cannot be converted to {} (value is {})"
|
||||
return cls.init_fail(m.format(s,cls.__name__,e[0]),on_fail)
|
||||
|
||||
class Str(str,Hilite): pass
|
||||
class Int(int,Hilite): pass
|
||||
|
||||
class HexStrWithWidth(HexStr):
|
||||
color = 'nocolor'
|
||||
trunc_ok = False
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@ def opt_postproc_initializations():
|
|||
if g.platform == 'win': start_mscolor()
|
||||
|
||||
g.coin = g.coin.upper() # allow user to use lowercase
|
||||
g.dcoin = g.coin
|
||||
|
||||
def set_data_dir_root():
|
||||
g.data_dir_root = os.path.normpath(os.path.expanduser(opt.data_dir)) if opt.data_dir else \
|
||||
|
|
@ -152,7 +153,11 @@ def override_from_cfg_file(cfg_data):
|
|||
if name in g.cfg_file_opts:
|
||||
pfx,cfg_var = name.split('_',1)
|
||||
if pfx in CoinProtocol.coins:
|
||||
cls,attr = CoinProtocol(pfx,False),cfg_var
|
||||
tn = False
|
||||
cv1,cv2 = cfg_var.split('_',1)
|
||||
if cv1 in ('mainnet','testnet'):
|
||||
tn,cfg_var = (cv1 == 'testnet'),cv2
|
||||
cls,attr = CoinProtocol(pfx,tn),cfg_var
|
||||
else:
|
||||
cls,attr = g,name
|
||||
setattr(cls,attr,set_for_type(val,getattr(cls,attr),attr,src=g.cfg_file))
|
||||
|
|
@ -339,6 +344,7 @@ def init(opts_f,add_opts=[],opt_filter=None):
|
|||
def opt_is_tx_fee(val,desc):
|
||||
from mmgen.tx import MMGenTX
|
||||
ret = MMGenTX().process_fee_spec(val,224,on_fail='return')
|
||||
if opt.contract_data or opt.tx_gas: ret = None # Non-standard startgas: disable fee checking
|
||||
if ret == False:
|
||||
msg("'{}': invalid {}\n(not a {} amount or {} specification)".format(
|
||||
val,desc,g.coin.upper(),MMGenTX().rel_fee_desc))
|
||||
|
|
@ -495,7 +501,8 @@ def check_opts(usr_opts): # Returns false if any check fails
|
|||
if not opt_is_in_list(val.lower(),CoinProtocol.coins.keys(),'coin'): return False
|
||||
elif key == 'rbf':
|
||||
if not g.proto.cap('rbf'):
|
||||
die(1,'--rbf requested, but {} does not support replace-by-fee transactions'.format(g.coin))
|
||||
msg('--rbf requested, but {} does not support replace-by-fee transactions'.format(g.coin))
|
||||
return False
|
||||
elif key in ('bob','alice'):
|
||||
from mmgen.regtest import daemon_dir
|
||||
m = "Regtest (Bob and Alice) mode not set up yet. Run '{}-regtest setup' to initialize."
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ def _b58chk_decode(s):
|
|||
class BitcoinProtocol(MMGenObject):
|
||||
name = 'bitcoin'
|
||||
daemon_name = 'bitcoind'
|
||||
daemon_family = 'bitcoind'
|
||||
addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') }
|
||||
wif_ver_num = { 'std': '80' }
|
||||
mmtypes = ('L','C','S','B')
|
||||
|
|
@ -301,6 +302,7 @@ class EthereumProtocol(DummyWIF,BitcoinProtocolAddrgen):
|
|||
|
||||
data_subdir = ''
|
||||
daemon_name = 'parity'
|
||||
daemon_family = 'parity'
|
||||
rpc_port = 8545
|
||||
mmcaps = ('key','addr','rpc')
|
||||
coin_amt = ETHAmt
|
||||
|
|
|
|||
45
mmgen/tw.py
45
mmgen/tw.py
|
|
@ -32,11 +32,12 @@ class TwUnspentOutputs(MMGenObject):
|
|||
return MMGenObject.__new__(altcoin_subclass(cls,'tw','TwUnspentOutputs'),*args,**kwargs)
|
||||
|
||||
txid_w = 64
|
||||
show_txid = True
|
||||
disp_type = 'btc'
|
||||
can_group = True
|
||||
hdr_fmt = 'UNSPENT OUTPUTS (sort order: {}) Total {}: {}'
|
||||
desc = 'unspent outputs'
|
||||
dump_fn_pfx = 'listunspent'
|
||||
prompt_fs = 'Total to spend, excluding fees: {} {}\n\n'
|
||||
prompt = """
|
||||
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
|
||||
Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
||||
|
|
@ -49,6 +50,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
|||
txid = MMGenListItemAttr('txid','CoinTxID')
|
||||
vout = MMGenListItemAttr('vout',int,typeconv=False)
|
||||
amt = MMGenImmutableAttr('amt',g.proto.coin_amt.__name__)
|
||||
amt2 = MMGenListItemAttr('amt2',g.proto.coin_amt.__name__)
|
||||
label = MMGenListItemAttr('label','TwComment',reassign_ok=True)
|
||||
twmmid = MMGenImmutableAttr('twmmid','TwMMGenID')
|
||||
addr = MMGenImmutableAttr('addr','CoinAddr')
|
||||
|
|
@ -78,8 +80,10 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
self.sort_key = 'age'
|
||||
self.do_sort()
|
||||
self.total = self.get_total_coin()
|
||||
self.disp_prec = self.get_display_precision()
|
||||
|
||||
g.dcoin = g.dcoin or g.coin
|
||||
def get_display_precision(self):
|
||||
return g.proto.coin_amt.max_prec
|
||||
|
||||
def get_total_coin(self):
|
||||
return sum(i.amt for i in self.unspent)
|
||||
|
|
@ -157,7 +161,6 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
|
||||
def format_for_display(self):
|
||||
unsp = self.unspent
|
||||
# unsp.pdie()
|
||||
self.set_term_columns()
|
||||
|
||||
# allow for 7-digit confirmation nums
|
||||
|
|
@ -182,16 +185,16 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
|
||||
out = [self.hdr_fmt.format(' '.join(self.sort_info()),g.dcoin,self.total.hl())]
|
||||
if g.chain != 'mainnet': out += ['Chain: '+green(g.chain.upper())]
|
||||
if self.show_txid:
|
||||
fs = u' {n:%s} {t:%s} {v:2} {a} {A} {c:<}' % (col1_w,tx_w)
|
||||
else:
|
||||
fs = u' {n:%s} {a} {A} {c:<}' % col1_w
|
||||
fs = { 'btc': u' {n:%s} {t:%s} {v:2} {a} {A} {c:<}' % (col1_w,tx_w),
|
||||
'eth': u' {n:%s} {a} {A}' % col1_w }[self.disp_type]
|
||||
out += [fs.format( n='Num',
|
||||
t='TXid'.ljust(tx_w - 5) + ' Vout',
|
||||
v='',
|
||||
a='Address'.ljust(addr_w),
|
||||
A='Amt({})'.format(g.dcoin).ljust(g.proto.coin_amt.max_prec+4),
|
||||
c=('Confs','Age(d)')[self.show_days])]
|
||||
A='Amt({})'.format(g.dcoin).ljust(self.disp_prec+3),
|
||||
A2=' Amt({})'.format(g.coin).ljust(self.disp_prec+4),
|
||||
c=('Confs','Age(d)')[self.show_days]
|
||||
).rstrip()]
|
||||
|
||||
for n,i in enumerate(unsp):
|
||||
addr_dots = '|' + '.'*(addr_w-1)
|
||||
|
|
@ -214,26 +217,28 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
else i.txid[:tx_w-len(txdots)]+txdots,
|
||||
v=i.vout,
|
||||
a=addr_out,
|
||||
A=i.amt.fmt(color=True),
|
||||
c=i.days if self.show_days else i.confs))
|
||||
A=i.amt.fmt(color=True,prec=self.disp_prec),
|
||||
A2=(i.amt2.fmt(color=True,prec=self.disp_prec) if i.amt2 is not None else ''),
|
||||
c=i.days if self.show_days else i.confs
|
||||
).rstrip())
|
||||
|
||||
self.fmt_display = '\n'.join(out) + '\n'
|
||||
# unsp.pdie()
|
||||
return self.fmt_display
|
||||
|
||||
def format_for_printing(self,color=False):
|
||||
|
||||
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
|
||||
if self.show_txid:
|
||||
fs = ' {n:4} {t:%s} {a} {m} {A:%s} {c:<8} {g:<6} {l}' % (self.txid_w+3,g.proto.coin_amt.max_prec+4)
|
||||
else:
|
||||
fs = ' {n:4} {a} {m} {A:%s} {c:<8} {g:<6} {l}' % (g.proto.coin_amt.max_prec+4)
|
||||
amt_w = g.proto.coin_amt.max_prec + 4
|
||||
fs = { 'btc': u' {n:4} {t:%s} {a} {m} {A:%s} {c:<8} {g:<6} {l}' % (self.txid_w+3,amt_w),
|
||||
'eth': u' {n:4} {a} {m} {A:%s} {c:<8} {g:<6} {l}' % amt_w
|
||||
}[self.disp_type]
|
||||
out = [fs.format( n='Num',
|
||||
t='Tx ID,Vout',
|
||||
a='Address'.ljust(addr_w),
|
||||
m='MMGen ID'.ljust(mmid_w+1),
|
||||
A='Amount({})'.format(g.dcoin),
|
||||
A='Amount({})'.format(g.dcoin).ljust(amt_w+1),
|
||||
A2='Amount({})'.format(g.coin),
|
||||
c='Confs',
|
||||
g='Age(d)',
|
||||
l='Label')]
|
||||
|
|
@ -248,6 +253,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
m=MMGenID.fmtc(i.twmmid if i.twmmid.type=='mmgen'
|
||||
else 'Non-{}'.format(g.proj_name),width=mmid_w,color=color),
|
||||
A=i.amt.fmt(color=color),
|
||||
A2=(i.amt2.fmt(color=color) if i.amt2 is not None else ''),
|
||||
c=i.confs,
|
||||
g=i.days,
|
||||
l=i.label.hl(color=color) if i.label else
|
||||
|
|
@ -291,8 +297,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
return n,s
|
||||
|
||||
def view_and_sort(self,tx):
|
||||
fs = 'Total to spend, excluding fees: {} {}\n\n'
|
||||
txos = fs.format(tx.sum_outputs().hl(),g.dcoin) if tx.outputs else ''
|
||||
txos = self.prompt_fs.format(tx.sum_outputs().hl(),g.dcoin) if tx.outputs else ''
|
||||
prompt = txos + self.prompt.strip()
|
||||
self.display()
|
||||
msg(prompt)
|
||||
|
|
@ -465,7 +470,7 @@ class TwAddrList(MMGenDict):
|
|||
age=mmid.confs / (1,confs_per_day)[show_days] if hasattr(mmid,'confs') else '-'
|
||||
))
|
||||
|
||||
return '\n'.join(out + ['\nTOTAL: {} {}'.format(self.total.hl(color=True),g.coin)])
|
||||
return '\n'.join(out + ['\nTOTAL: {} {}'.format(self.total.hl(color=True),g.dcoin)])
|
||||
|
||||
class TrackingWallet(MMGenObject):
|
||||
|
||||
|
|
|
|||
125
mmgen/tx.py
125
mmgen/tx.py
|
|
@ -74,7 +74,7 @@ def mmaddr2coinaddr(mmaddr,ad_w,ad_f):
|
|||
coin_addr = ad_f.mmaddr2coinaddr(mmaddr)
|
||||
if coin_addr:
|
||||
msg(wmsg('addr_in_addrfile_only').format(mmaddr))
|
||||
if not keypress_confirm('Continue anyway?'):
|
||||
if not (opt.yes or keypress_confirm('Continue anyway?')):
|
||||
sys.exit(1)
|
||||
else:
|
||||
die(2,wmsg('addr_not_found').format(mmaddr))
|
||||
|
|
@ -212,7 +212,6 @@ class MMGenTX(MMGenObject):
|
|||
sig_ext = 'sigtx'
|
||||
txid_ext = 'txid'
|
||||
desc = 'transaction'
|
||||
chg_msg_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'
|
||||
|
|
@ -222,6 +221,8 @@ class MMGenTX(MMGenObject):
|
|||
txview_ftr_fs = 'Total input: {i} {d}\nTotal output: {o} {d}\nTX fee: {a} {c}{r}\n'
|
||||
txview_ftr_fs_short = 'In {i} {d} - Out {o} {d}\nFee {a} {c}{r}\n'
|
||||
usr_fee_prompt = 'Enter transaction fee: '
|
||||
fee_is_approximate = False
|
||||
fn_fee_unit = 'satoshi'
|
||||
|
||||
msg_low_coin = 'Selected outputs insufficient to fund this transaction ({} {} needed)'
|
||||
msg_no_change_output = """
|
||||
|
|
@ -291,8 +292,6 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
self.caller = caller
|
||||
self.locktime = None
|
||||
|
||||
g.dcoin = g.dcoin or g.coin
|
||||
|
||||
if filename:
|
||||
self.parse_tx_file(filename,coin_sym_only=coin_sym_only,silent_open=silent_open)
|
||||
if coin_sym_only: return
|
||||
|
|
@ -330,6 +329,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
self.outputs.pop(idx)
|
||||
|
||||
def sum_outputs(self,exclude=None):
|
||||
if not len(self.outputs): return g.proto.coin_amt(0)
|
||||
olist = self.outputs if exclude == None else \
|
||||
self.outputs[:exclude] + self.outputs[exclude+1:]
|
||||
return g.proto.coin_amt(sum(e.amt for e in olist))
|
||||
|
|
@ -484,8 +484,9 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
return ret
|
||||
|
||||
# convert absolute BTC fee to satoshis-per-byte using estimated size
|
||||
def fee_abs2rel(self,abs_fee):
|
||||
return int(abs_fee/g.proto.coin_amt.min_coin_unit/self.estimate_size())
|
||||
def fee_abs2rel(self,abs_fee,to_unit=None):
|
||||
unit = getattr(g.proto.coin_amt,to_unit or 'min_coin_unit')
|
||||
return int(abs_fee / unit / self.estimate_size())
|
||||
|
||||
def get_rel_fee_from_network(self): # rel_fee is in BTC/kB
|
||||
try:
|
||||
|
|
@ -559,9 +560,10 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
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 = u'{} TX fee{}: {} {} ({} {})\n'.format(
|
||||
p = u'{} TX fee{}: {}{} {} ({} {})\n'.format(
|
||||
desc,
|
||||
m,
|
||||
('',u'≈')[self.fee_is_approximate],
|
||||
abs_fee.hl(),
|
||||
g.coin,
|
||||
pink(str(self.fee_abs2rel(abs_fee))),
|
||||
|
|
@ -953,7 +955,9 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
self.txid,
|
||||
('-'+g.dcoin,'')[g.coin=='BTC'],
|
||||
self.send_amt,
|
||||
('',',{}'.format(self.fee_abs2rel(self.get_fee_from_tx())))[self.is_rbf()],
|
||||
('',',{}'.format(self.fee_abs2rel(
|
||||
self.get_fee_from_tx(),to_unit=self.fn_fee_unit))
|
||||
)[self.is_replaceable()],
|
||||
('',',tl={}'.format(tl))[bool(tl)],
|
||||
tn,self.ext,
|
||||
x=u'-α' if g.debug_utf8 else '')
|
||||
|
|
@ -991,11 +995,11 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
get_char('Press any key to continue: ')
|
||||
msg('')
|
||||
|
||||
# def is_rbf_from_rpc(self):
|
||||
# def is_replaceable_from_rpc(self):
|
||||
# dec_tx = g.rpch.decoderawtransaction(self.hex)
|
||||
# return None < dec_tx['vin'][0]['sequence'] <= g.max_int - 2
|
||||
|
||||
def is_rbf(self):
|
||||
def is_replaceable(self):
|
||||
return self.inputs[0].sequence == g.max_int - 2
|
||||
|
||||
def format_view_body(self,blockcount,nonmm_str,max_mmwid,enl,terse):
|
||||
|
|
@ -1075,14 +1079,14 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
a=self.send_amt.hl(),
|
||||
c=g.dcoin,
|
||||
t=self.timestamp,
|
||||
r=(red('False'),green('True'))[self.is_rbf()],
|
||||
r=(red('False'),green('True'))[self.is_replaceable()],
|
||||
s=self.marked_signed(color=True),
|
||||
l=(green('None'),orange(strfmt_locktime(self.locktime,terse=True)))[bool(self.locktime)])
|
||||
|
||||
if self.chain != 'mainnet':
|
||||
out += green('Chain: {}\n'.format(self.chain.upper()))
|
||||
if self.coin_txid:
|
||||
out += '{} TxID: {}\n'.format(g.dcoin,self.coin_txid.hl())
|
||||
out += '{} TxID: {}\n'.format(g.coin,self.coin_txid.hl())
|
||||
enl = ('\n','')[bool(terse)]
|
||||
out += enl
|
||||
if self.label:
|
||||
|
|
@ -1101,7 +1105,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
|
||||
return out # TX label might contain non-ascii chars
|
||||
|
||||
def check_tx_hex_data(self):
|
||||
def check_txfile_hex_data(self):
|
||||
self.hex = HexStr(self.hex,on_fail='raise')
|
||||
|
||||
def parse_tx_file(self,infile,coin_sym_only=False,silent_open=False):
|
||||
|
|
@ -1116,7 +1120,8 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
import re
|
||||
d = literal_eval(re.sub(r"[A-Za-z]+?\(('.+?')\)",r'\1',raw_data))
|
||||
assert type(d) == list,'{} data not a list!'.format(desc)
|
||||
assert len(d),'no {}!'.format(desc)
|
||||
if not (desc == 'outputs' and g.coin == 'ETH'): # ETH txs can have no outputs
|
||||
assert len(d),'no {}!'.format(desc)
|
||||
for e in d: e['amt'] = g.proto.coin_amt(e['amt'])
|
||||
io,io_list = (
|
||||
(MMGenTX.MMGenTxOutput,MMGenTX.MMGenTxOutputList),
|
||||
|
|
@ -1179,7 +1184,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
desc = 'block count in metadata'
|
||||
self.blockcount = int(blockcount)
|
||||
desc = 'transaction hex data'
|
||||
self.check_tx_hex_data()
|
||||
self.check_txfile_hex_data()
|
||||
# the following ops will all fail if g.coin doesn't match self.coin
|
||||
desc = 'coin type in metadata'
|
||||
assert self.coin == g.coin,self.coin
|
||||
|
|
@ -1194,6 +1199,8 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
if not self.chain and not self.inputs[0].addr.is_for_chain('testnet'):
|
||||
self.chain = 'mainnet'
|
||||
|
||||
if self.dcoin: self.set_g_token()
|
||||
|
||||
def process_cmd_args(self,cmd_args,ad_f,ad_w):
|
||||
for a in cmd_args:
|
||||
if ',' in a:
|
||||
|
|
@ -1219,6 +1226,9 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
fs = '{} Segwit address requested on the command line, but Segwit is not active on this chain'
|
||||
rdie(2,fs.format(g.proj_name))
|
||||
|
||||
if not self.outputs:
|
||||
die(2,'At least one output must be specified on the command line')
|
||||
|
||||
def get_outputs_from_cmdline(self,cmd_args):
|
||||
from mmgen.addr import AddrList,AddrData
|
||||
addrfiles = [a for a in cmd_args if get_extension(a) == AddrList.ext]
|
||||
|
|
@ -1233,9 +1243,6 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
|
||||
self.process_cmd_args(cmd_args,ad_f,ad_w)
|
||||
|
||||
if not self.outputs:
|
||||
die(2,'At least one output must be specified on the command line')
|
||||
|
||||
self.add_mmaddrs_to_outputs(ad_w,ad_f)
|
||||
self.check_dup_addrs('outputs')
|
||||
|
||||
|
|
@ -1250,9 +1257,9 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
return selected
|
||||
msg('Unspent output number must be <= {}'.format(len(unspent)))
|
||||
|
||||
def check_sufficient_funds(self,inputs,foo):
|
||||
if self.send_amt > inputs:
|
||||
msg(self.msg_low_coin.format(self.send_amt-inputs,g.coin))
|
||||
def check_sufficient_funds(self,inputs_sum,foo):
|
||||
if self.send_amt > inputs_sum:
|
||||
msg(self.msg_low_coin.format(self.send_amt-inputs_sum,g.coin))
|
||||
return False
|
||||
return True
|
||||
|
||||
|
|
@ -1262,23 +1269,55 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
def warn_insufficient_chg(self,change_amt):
|
||||
msg(self.msg_low_coin.format(g.proto.coin_amt(-change_amt).hl(),g.coin))
|
||||
|
||||
def final_inputs_ok_msg(self,change_amt):
|
||||
m = 'Transaction produces {} {} in change'
|
||||
return m.format(g.proto.coin_amt(change_amt).hl(),g.coin)
|
||||
|
||||
def select_unspent_cmdline(self,unspent):
|
||||
sel_nums = []
|
||||
for i in opt.inputs.split(','):
|
||||
ls = len(sel_nums)
|
||||
if is_mmgen_id(i):
|
||||
for j in range(len(unspent)):
|
||||
if unspent[j].twmmid == i:
|
||||
sel_nums.append(j+1)
|
||||
elif is_coin_addr(i):
|
||||
for j in range(len(unspent)):
|
||||
if unspent[j].addr == i:
|
||||
sel_nums.append(j+1)
|
||||
else:
|
||||
die(1,"'{}': not an MMGen ID or coin address".format(i))
|
||||
|
||||
ldiff = len(sel_nums) - ls
|
||||
if ldiff:
|
||||
sel_inputs = ','.join([str(i) for i in sel_nums[-ldiff:]])
|
||||
ul = unspent[sel_nums[-1]-1]
|
||||
mmid_disp = ' (' + ul.twmmid + ')' if ul.twmmid.type == 'mmgen' else ''
|
||||
msg('Adding input{}: {} {}{}'.format(suf(ldiff),sel_inputs,ul.addr,mmid_disp))
|
||||
else:
|
||||
die(1,"'{}': address not found in tracking wallet".format(i))
|
||||
|
||||
return set(sel_nums) # silently discard duplicates
|
||||
|
||||
def get_inputs_from_user(self,tw):
|
||||
|
||||
while True:
|
||||
sel_nums = self.select_unspent(tw.unspent)
|
||||
us_f = ('select_unspent','select_unspent_cmdline')[bool(opt.inputs)]
|
||||
sel_nums = getattr(self,us_f)(tw.unspent)
|
||||
|
||||
msg('Selected output{}: {}'.format(suf(sel_nums,'s'),' '.join(map(str,sel_nums))))
|
||||
|
||||
sel_unspent = tw.MMGenTwOutputList([tw.unspent[i-1] for i in sel_nums])
|
||||
|
||||
t_inputs = sum(s.amt for s in sel_unspent)
|
||||
if not self.check_sufficient_funds(t_inputs,sel_unspent):
|
||||
inputs_sum = sum(s.amt for s in sel_unspent)
|
||||
if not self.check_sufficient_funds(inputs_sum,sel_unspent):
|
||||
continue
|
||||
|
||||
non_mmaddrs = [i for i in sel_unspent if i.twmmid.type == 'non-mmgen']
|
||||
if non_mmaddrs and self.caller != 'txdo':
|
||||
msg(self.msg_non_mmgen_inputs.format(
|
||||
', '.join(set(sorted([a.addr.hl() for a in non_mmaddrs])))))
|
||||
if not keypress_confirm('Accept?'):
|
||||
if not (opt.yes or keypress_confirm('Accept?')):
|
||||
continue
|
||||
|
||||
self.copy_inputs_from_tw(sel_unspent) # makes self.inputs
|
||||
|
|
@ -1287,9 +1326,9 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
|
||||
change_amt = self.get_change_amt()
|
||||
|
||||
if change_amt >= 0:
|
||||
p = self.chg_msg_fs.format(change_amt.hl(),g.coin)
|
||||
if opt.yes or keypress_confirm(p+'. OK?',default_yes=True):
|
||||
if change_amt >= 0: # TODO: show both ETH and token amts remaining
|
||||
p = self.final_inputs_ok_msg(change_amt)
|
||||
if opt.yes or keypress_confirm(p+'. OK?',default_yes=True):
|
||||
if opt.yes: msg(p)
|
||||
return change_amt
|
||||
else:
|
||||
|
|
@ -1309,7 +1348,10 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
|
||||
from mmgen.tw import TwUnspentOutputs
|
||||
tw = TwUnspentOutputs(minconf=opt.minconf)
|
||||
tw.view_and_sort(self)
|
||||
|
||||
if not opt.inputs:
|
||||
tw.view_and_sort(self)
|
||||
|
||||
tw.display_total()
|
||||
|
||||
if do_info: sys.exit(0)
|
||||
|
|
@ -1334,7 +1376,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
else:
|
||||
self.update_output_amt(chg_idx,g.proto.coin_amt(change_amt))
|
||||
|
||||
if not self.send_amt:
|
||||
if not self.send_amt and len(self.outputs):
|
||||
self.send_amt = change_amt
|
||||
|
||||
if not opt.yes:
|
||||
|
|
@ -1360,15 +1402,18 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
|
||||
class MMGenBumpTX(MMGenTX):
|
||||
|
||||
def __new__(cls,*args,**kwargs):
|
||||
return MMGenTX.__new__(altcoin_subclass(cls,'tx','MMGenBumpTX'),*args,**kwargs)
|
||||
|
||||
min_fee = None
|
||||
bump_output_idx = None
|
||||
|
||||
def __init__(self,filename,send=False):
|
||||
|
||||
super(type(self),self).__init__(filename)
|
||||
super(MMGenBumpTX,self).__init__(filename)
|
||||
|
||||
if not self.is_rbf():
|
||||
die(1,"Transaction '{}' is not replaceable (RBF)".format(self.txid))
|
||||
if not self.is_replaceable():
|
||||
die(1,"Transaction '{}' is not replaceable".format(self.txid))
|
||||
|
||||
# If sending, require tx to have been signed
|
||||
if send:
|
||||
|
|
@ -1380,6 +1425,11 @@ class MMGenBumpTX(MMGenTX):
|
|||
self.coin_txid = ''
|
||||
self.mark_raw()
|
||||
|
||||
def check_bumpable(self):
|
||||
if not [o.amt for o in self.outputs if o.amt >= self.min_fee]:
|
||||
die(1,'Transaction cannot be bumped.' +
|
||||
'\nAll outputs have less than the minimum fee ({} {})'.format(self.min_fee,g.coin))
|
||||
|
||||
def choose_output(self):
|
||||
chg_idx = self.get_chg_output_idx()
|
||||
init_reply = opt.output_to_reduce
|
||||
|
|
@ -1412,15 +1462,18 @@ class MMGenBumpTX(MMGenTX):
|
|||
def set_min_fee(self):
|
||||
self.min_fee = self.sum_inputs() - self.sum_outputs() + self.get_relay_fee()
|
||||
|
||||
def update_fee(self,op_idx,fee):
|
||||
self.update_output_amt(op_idx,self.sum_inputs()-self.sum_outputs(exclude=op_idx)-fee)
|
||||
|
||||
def convert_and_check_fee(self,tx_fee,desc):
|
||||
ret = super(type(self),self).convert_and_check_fee(tx_fee,desc)
|
||||
ret = super(MMGenBumpTX,self).convert_and_check_fee(tx_fee,desc)
|
||||
if ret < self.min_fee:
|
||||
msg('{} {c}: {} fee too small. Minimum fee: {} {c} ({} {})'.format(
|
||||
ret,desc,self.min_fee,self.fee_abs2rel(self.min_fee),self.rel_fee_desc,c=g.coin))
|
||||
ret,desc,self.min_fee,self.fee_abs2rel(self.min_fee.hl()),self.rel_fee_desc,c=g.coin))
|
||||
return False
|
||||
output_amt = self.outputs[self.bump_output_idx].amt
|
||||
if ret >= output_amt:
|
||||
msg('{} {c}: {} fee too large. Maximum fee: <{} {c}'.format(ret,desc,output_amt,c=g.coin))
|
||||
msg('{} {c}: {} fee too large. Maximum fee: <{} {c}'.format(ret.hl(),desc,output_amt.hl(),c=g.coin))
|
||||
return False
|
||||
return ret
|
||||
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ i_eth='Ethereum'
|
|||
s_eth='Testing transaction and tracking wallet operations for Ethereum'
|
||||
t_eth=(
|
||||
"$test_py -On --coin=eth ref_tx_chk"
|
||||
"$test_py -On --coin=eth --testnet=1 ref_tx_chk"
|
||||
"$test_py -On ethdev"
|
||||
)
|
||||
f_eth='Ethereum tests completed'
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -111,7 +111,6 @@ setup(
|
|||
'mmgen.addr',
|
||||
'mmgen.altcoin',
|
||||
'mmgen.bech32',
|
||||
'mmgen.protocol',
|
||||
'mmgen.color',
|
||||
'mmgen.common',
|
||||
'mmgen.crypto',
|
||||
|
|
@ -123,6 +122,7 @@ setup(
|
|||
'mmgen.mn_tirosh',
|
||||
'mmgen.obj',
|
||||
'mmgen.opts',
|
||||
'mmgen.protocol',
|
||||
'mmgen.regtest',
|
||||
'mmgen.rpc',
|
||||
'mmgen.seed',
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ def my_send(p,t,delay=send_delay,s=False):
|
|||
return ret
|
||||
|
||||
def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False,silent=False):
|
||||
|
||||
quo = ('',"'")[type(s) == str]
|
||||
|
||||
if not silent:
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
5eb350
|
||||
ETH FOUNDATION BC79AB 0.123 20180530_125230 7513928
|
||||
{"nonce": "0", "chainId": "0x1", "from": "e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35", "to": "62ff8e4dbd251b98102e3fb5e4b14119e24cadde", "amt": "0.123", "gasPrice": "0.000000050"}
|
||||
0a7b6f
|
||||
ETH FOUNDATION 4ED554 0.123 20180530_125230 7513928
|
||||
{"nonce": "0", "chainId": "1", "from": "e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35", "to": "62ff8e4dbd251b98102e3fb5e4b14119e24cadde", "amt": "0.123", "gasPrice": "0.000000050"}
|
||||
[{'confs': 0, 'addr': 'e704b6cfd9f0edb2e6cfbd0c913438d37ede7b35', 'vout': 0, 'txid': '0000000000000000000000000000000000000000000000000000000000000000', 'label': u'', 'amt': '1.234567', 'mmid': '98831F3A:E:1'}]
|
||||
[{'mmid': '98831F3A:E:31', 'amt': '0.123', 'addr': '62ff8e4dbd251b98102e3fb5e4b14119e24cadde'}]
|
||||
qRHzrPVpZFYxnQvk3atLzUtp41bZupJ2UQNnKe3ZnmqFsEngS6vaCCvesKKy9khzVq6y2RqarVBcZLnjtXxMpbAcdEtyBWiBYmZdoU8SN4uAbroHT1c7gEbmUNVKKdqHD86ZRRqDNpdh1ztmLiMAy3ibM83puwJHNpGGHgUGjZ1RSEgyVKCs2rZ9wXN8rBMibDDPYo1LgtAst2FkB36Mgf4Vf7ekoRAdiRNGd5YZ3RXAVsSdnZcyn4rdeQDMDkCq7JJDoB25eNEuXQutZFUcf2fEfxkMbW1sXJDNFQq
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
8f7b85
|
||||
ETH KOVAN F04889 0.123 20180530_125230 7513928
|
||||
{"nonce": "0", "chainId": "0x2a", "from": "97ccc3a117b3696340c42561361054b1c9c793d5", "to": "07f575951e67f855ceffe512ee33a362e177924f", "amt": "0.123", "gasPrice": "0.000000008"}
|
||||
bc835b
|
||||
ETH KOVAN 7EE763 0.123 20180530_125230 7513928
|
||||
{"nonce": "0", "chainId": "42", "from": "97ccc3a117b3696340c42561361054b1c9c793d5", "to": "07f575951e67f855ceffe512ee33a362e177924f", "amt": "0.123", "gasPrice": "0.000000008"}
|
||||
[{'confs': 0, 'addr': '97ccc3a117b3696340c42561361054b1c9c793d5', 'label': u'', 'amt': '1.234567', 'mmid': '98831F3A:E:1'}]
|
||||
[{'mmid': '98831F3A:E:31', 'amt': '0.123', 'addr': '07f575951e67f855ceffe512ee33a362e177924f'}]
|
||||
qRHzrPVpZFYxnQvk3atLzUtp41bZupJ2UQNnKe3ZnmqFsEngS6vaCCvesKKy9khzVq6y2RqarVBcZLnjtXxMpbAcdEtyBWiBYmZdoU8SN4uAbroHT1c7gEbmUNVKKdqHD86ZRRqDNpdh1ztmLiMAy3ibM83puwJHNpGGHgUGjZ1RSEgyVKCs2rZ9wXN8rBMibDDPYo1LgtAst2FkB36Mgf4Vf7ekoRAdiRNGd5YZ3RXAVsSdnZcyn4rdeQDMDkCq7JJDoB25eNEuXQutZFUcf2fEfxkMbW1sXJDNFQq
|
||||
283
test/test.py
283
test/test.py
|
|
@ -20,7 +20,8 @@
|
|||
test/test.py: Test suite for the MMGen suite
|
||||
"""
|
||||
|
||||
import sys,os,subprocess,shutil,time,re
|
||||
import sys,os,subprocess,shutil,time,re,json
|
||||
from decimal import Decimal
|
||||
|
||||
repo_root = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.pardir)))
|
||||
os.chdir(repo_root)
|
||||
|
|
@ -29,7 +30,7 @@ sys.path.__setitem__(0,repo_root)
|
|||
# Import these _after_ local path's been added to sys.path
|
||||
from mmgen.common import *
|
||||
from mmgen.test import *
|
||||
from mmgen.protocol import CoinProtocol
|
||||
from mmgen.protocol import CoinProtocol,init_coin
|
||||
|
||||
set_debug_all()
|
||||
|
||||
|
|
@ -54,7 +55,7 @@ ref_wallet_brainpass = 'abc'
|
|||
ref_wallet_hash_preset = '1'
|
||||
ref_wallet_incog_offset = 123
|
||||
|
||||
from mmgen.obj import MMGenTXLabel,PrivKey
|
||||
from mmgen.obj import MMGenTXLabel,PrivKey,ETHAmt
|
||||
from mmgen.addr import AddrGenerator,KeyGenerator,AddrList,AddrData,AddrIdxList
|
||||
|
||||
ref_tx_label_jp = u'必要なのは、信用ではなく暗号化された証明に基づく電子取引システムであり、これにより希望する二者が信用できる第三者機関を介さずに直接取引できるよう' # 72 chars ('W'ide)
|
||||
|
|
@ -160,6 +161,8 @@ sys.argv = [sys.argv[0]] + ['--data-dir',data_dir] + sys.argv[1:]
|
|||
cmd_args = opts.init(opts_data)
|
||||
opt.popen_spawn = True # popen has issues, so use popen_spawn always
|
||||
|
||||
if not opt.system: os.environ['PYTHONPATH'] = repo_root
|
||||
|
||||
ref_subdir = '' if g.proto.base_coin == 'BTC' else g.proto.name
|
||||
altcoin_pfx = '' if g.proto.base_coin == 'BTC' else '-'+g.proto.base_coin
|
||||
tn_ext = ('','.testnet')[g.testnet]
|
||||
|
|
@ -183,6 +186,11 @@ rtBals = {
|
|||
'bch': ('499.9999484','399.9999194','399.9998972','399.9997692','6.79000000','993.20966920','999.99966920'),
|
||||
'ltc': ('5499.99744','5399.994425','5399.993885','5399.987535','13.00000000','10986.93753500','10999.93753500'),
|
||||
}[coin_sel]
|
||||
rtBals_gb = {
|
||||
'btc': ('116.77629233','283.22339537'),
|
||||
'bch': ('WIP'),
|
||||
'ltc': ('WIP'),
|
||||
}[coin_sel]
|
||||
rtBobOp3 = {'btc':'S:2','bch':'L:3','ltc':'S:2'}[coin_sel]
|
||||
|
||||
if opt.segwit and 'S' not in g.proto.mmtypes:
|
||||
|
|
@ -571,8 +579,8 @@ cfgs = {
|
|||
'359FD5-BCH[6.68868,tl=1320969600].testnet.rawtx'),
|
||||
'ltc': ('AF3CDF-LTC[620.76194,1453,tl=1320969600].rawtx',
|
||||
'A5A1E0-LTC[1454.64322,1453,tl=1320969600].testnet.rawtx'),
|
||||
'eth': ('BC79AB-ETH[0.123].rawtx',
|
||||
'F04889-ETH[0.123].testnet.rawtx'),
|
||||
'eth': ('4ED554-ETH[0.123].rawtx',
|
||||
'7EE763-ETH[0.123].testnet.rawtx'),
|
||||
},
|
||||
'ic_wallet': u'98831F3A-5482381C-18460FB1[256,1].mmincog',
|
||||
'ic_wallet_hex': u'98831F3A-1630A9F2-870376A9[256,1].mmincox',
|
||||
|
|
@ -613,6 +621,9 @@ dfl_words = os.path.join(ref_dir,cfgs['8']['seed_id']+'.mmwords')
|
|||
eth_addr = '00a329c0648769a73afac7f9381e08fb43dbea72'
|
||||
eth_key = '4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7'
|
||||
eth_args = [u'--outdir={}'.format(cfgs['22']['tmpdir']),'--coin=eth','--rpc-port=8549','--quiet']
|
||||
eth_burn_addr = 'deadbeef'*5
|
||||
eth_amt1 = '999999.12345689012345678'
|
||||
eth_amt2 = '888.111122223333444455'
|
||||
|
||||
from copy import deepcopy
|
||||
for a,b in (('6','11'),('7','12'),('8','13')):
|
||||
|
|
@ -651,7 +662,6 @@ cmd_group['main'] = OrderedDict([
|
|||
['passchg_usrlabel',(5,'password, label and hash preset change (interactive label)',[[['mmdat',pwfile],1]])],
|
||||
['walletchk_newpass',(5,'wallet check with new pw, label and hash preset',[[['mmdat',pwfile],5]])],
|
||||
['addrgen', (1,'address generation', [[['mmdat',pwfile],1]])],
|
||||
['addrimport', (1,'address import', [[['addrs'],1]])],
|
||||
['txcreate', (1,'transaction creation', [[['addrs'],1]])],
|
||||
['txbump', (1,'transaction fee bumping (no send)',[[['rawtx'],1]])],
|
||||
['txsign', (1,'transaction signing', [[['mmdat','rawtx',pwfile,'txbump'],1]])],
|
||||
|
|
@ -676,6 +686,8 @@ cmd_group['main'] = OrderedDict([
|
|||
['keyaddrgen', (1,'key-address file generation', [[['mmdat',pwfile],1]])],
|
||||
['txsign_keyaddr',(1,'transaction signing with key-address file', [[['akeys.mmenc','rawtx'],1]])],
|
||||
|
||||
['txcreate_ni', (1,'transaction creation (non-interactive)', [[['addrs'],1]])],
|
||||
|
||||
['walletgen2',(2,'wallet generation (2), 128-bit seed', [[['del_dw_run'],15]])],
|
||||
['addrgen2', (2,'address generation (2)', [[['mmdat'],2]])],
|
||||
['txcreate2', (2,'transaction creation (2)', [[['addrs'],2]])],
|
||||
|
|
@ -801,6 +813,7 @@ cmd_group['regtest'] = (
|
|||
('regtest_bob_split2', "splitting Bob's funds"),
|
||||
('regtest_generate', 'mining a block'),
|
||||
('regtest_bob_bal5', "Bob's balance"),
|
||||
('regtest_bob_bal5_getbalance',"Bob's balance"),
|
||||
('regtest_bob_send_non_mmgen', 'sending funds to Alice (from non-MMGen addrs)'),
|
||||
('regtest_generate', 'mining a block'),
|
||||
('regtest_bob_alice_bal', "Bob and Alice's balances"),
|
||||
|
|
@ -852,18 +865,38 @@ cmd_group['ethdev'] = (
|
|||
('ethdev_addrgen', 'generating addresses'),
|
||||
('ethdev_addrimport', 'importing addresses'),
|
||||
('ethdev_addrimport_dev_addr', "importing Parity dev address 'Ox00a329c..'"),
|
||||
('ethdev_txcreate', 'creating a transaction (spend from dev address)'),
|
||||
('ethdev_txsign', 'signing the transaction'),
|
||||
('ethdev_txsign_ni', 'signing the transaction (non-interactive)'),
|
||||
('ethdev_txsend', 'sending the transaction'),
|
||||
('ethdev_bal', 'the balance'),
|
||||
('ethdev_txcreate2', 'creating a transaction (spend from MMGen address)'),
|
||||
|
||||
('ethdev_txcreate1', 'creating a transaction (spend from dev address)'),
|
||||
('ethdev_txsign1', 'signing the transaction'),
|
||||
('ethdev_txsign1_ni', 'signing the transaction (non-interactive)'),
|
||||
('ethdev_txsend1', 'sending the transaction'),
|
||||
|
||||
('ethdev_txcreate2', 'creating a transaction (spend to address 11)'),
|
||||
('ethdev_txsign2', 'signing the transaction'),
|
||||
('ethdev_txsend2', 'sending the transaction'),
|
||||
('ethdev_bal2', 'the balance'),
|
||||
|
||||
('ethdev_txcreate3', 'creating a transaction (spend to address 21)'),
|
||||
('ethdev_txsign3', 'signing the transaction'),
|
||||
('ethdev_txsend3', 'sending the transaction'),
|
||||
|
||||
('ethdev_txcreate4', 'creating a transaction (spend from MMGen address, low TX fee)'),
|
||||
('ethdev_txbump', 'bumping the transaction fee'),
|
||||
|
||||
('ethdev_txsign4', 'signing the transaction'),
|
||||
('ethdev_txsend4', 'sending the transaction'),
|
||||
|
||||
('ethdev_txcreate5', 'creating a transaction (fund burn address)'),
|
||||
('ethdev_txsign5', 'signing the transaction'),
|
||||
('ethdev_txsend5', 'sending the transaction'),
|
||||
|
||||
('ethdev_addrimport_burn_addr',"importing burn address"),
|
||||
|
||||
('ethdev_bal1', 'the balance'),
|
||||
|
||||
('ethdev_add_label', 'adding a UTF-8 label'),
|
||||
('ethdev_chk_label', 'the label'),
|
||||
('ethdev_remove_label', 'removing the label'),
|
||||
|
||||
('ethdev_stop', 'stopping parity'),
|
||||
)
|
||||
|
||||
|
|
@ -993,7 +1026,7 @@ addrs_per_wallet = 8
|
|||
meta_cmds = OrderedDict([
|
||||
['gen', ('walletgen','addrgen')],
|
||||
['pass', ('passchg','walletchk_newpass')],
|
||||
['tx', ('addrimport','txcreate','txsign','txsend')],
|
||||
['tx', ('txcreate','txsign','txsend')],
|
||||
['export', [k for k in cmd_data if k[:7] == 'export_' and cmd_data[k][0] == 1]],
|
||||
['gen_sp', [k for k in cmd_data if k[:8] == 'addrgen_' and cmd_data[k][0] == 1]],
|
||||
['online', ('keyaddrgen','txsign_keyaddr')],
|
||||
|
|
@ -1040,6 +1073,8 @@ def get_segwit_arg(cfg):
|
|||
# Tell spawned programs they're running in the test suite
|
||||
os.environ['MMGEN_TEST_SUITE'] = '1'
|
||||
|
||||
def imsg(s): sys.stderr.write(s+'\n') # never gets redefined
|
||||
|
||||
if opt.exact_output:
|
||||
def msg(s): pass
|
||||
vmsg = vmsg_r = msg_r = msg
|
||||
|
|
@ -1145,7 +1180,6 @@ class MMGenExpect(MMGenPexpect):
|
|||
passthru_args = ['testnet','rpc_host','rpc_port','regtest','coin']
|
||||
|
||||
if not opt.system:
|
||||
os.environ['PYTHONPATH'] = repo_root
|
||||
mmgen_cmd = os.path.relpath(os.path.join(repo_root,'cmds',mmgen_cmd))
|
||||
elif g.platform == 'win':
|
||||
mmgen_cmd = os.path.join('/mingw64','opt','bin',mmgen_cmd)
|
||||
|
|
@ -1279,7 +1313,7 @@ def make_txcreate_cmdline(tx_data):
|
|||
for idx,mod in enumerate(mods):
|
||||
cfgs[k]['amts'][idx] = '{}.{}'.format(getrandnum(4) % mod, str(getrandnum(4))[:5])
|
||||
|
||||
cmd_args = ['-d',cfg['tmpdir']]
|
||||
cmd_args = ['--outdir='+cfg['tmpdir']]
|
||||
for num in tx_data:
|
||||
s = tx_data[num]
|
||||
cmd_args += [
|
||||
|
|
@ -1667,46 +1701,45 @@ class MMGenTestSuite(object):
|
|||
msg('Skipping non-Segwit address generation'); return True
|
||||
self.addrgen(name,wf,pf=pf,check_ref=True,mmtype='compressed')
|
||||
|
||||
def addrimport(self,name,addrfile):
|
||||
outfile = os.path.join(cfg['tmpdir'],u'addrfile_w_comments')
|
||||
add_comments_to_addr_file(addrfile,outfile)
|
||||
t = MMGenExpect(name,'mmgen-addrimport', [outfile])
|
||||
t.expect_getend(r'Checksum for address data .*\[.*\]: ',regex=True)
|
||||
t.expect("Type uppercase 'YES' to confirm: ",'\n')
|
||||
vmsg('This is a simulation, so no addresses were actually imported into the tracking\nwallet')
|
||||
t.ok(exit_val=1)
|
||||
|
||||
def txcreate_ui_common(self,t,name,
|
||||
menu=[],inputs='1',
|
||||
file_desc='Transaction',
|
||||
input_sels_prompt='to spend',
|
||||
bad_input_sels=False,non_mmgen_inputs=0,
|
||||
fee_desc='transaction fee',fee='',fee_res=None,
|
||||
add_comment='',view='t',save=True):
|
||||
interactive_fee='',
|
||||
fee_desc='transaction fee',fee_res=None,
|
||||
add_comment='',view='t',save=True,no_ok=False):
|
||||
for choice in menu + ['q']:
|
||||
t.expect(r"'q'=quit view, .*?:.",choice,regex=True)
|
||||
if bad_input_sels:
|
||||
for r in ('x','3-1','9999'):
|
||||
t.expect(input_sels_prompt+': ',r+'\n')
|
||||
t.expect(input_sels_prompt+': ',inputs+'\n')
|
||||
for i in range(non_mmgen_inputs):
|
||||
t.expect('Accept? (y/N): ','y')
|
||||
|
||||
if fee:
|
||||
t.expect(fee_desc+': ',fee+'\n')
|
||||
if not name[:4] == 'txdo':
|
||||
for i in range(non_mmgen_inputs):
|
||||
t.expect('Accept? (y/N): ','y')
|
||||
|
||||
have_est_fee = t.expect([fee_desc+': ','OK? (Y/n): ']) == 1
|
||||
if have_est_fee and not interactive_fee:
|
||||
t.send('y')
|
||||
else:
|
||||
if have_est_fee: t.send('n')
|
||||
t.send(interactive_fee+'\n')
|
||||
if fee_res: t.expect(fee_res)
|
||||
t.expect('OK? (Y/n): ','y') # fee OK?
|
||||
t.expect('OK? (Y/n): ','y')
|
||||
|
||||
t.expect('(Y/n): ','\n') # chg amt OK?
|
||||
t.do_comment(add_comment)
|
||||
t.view_tx(view)
|
||||
if not name[:4] == 'txdo':
|
||||
t.expect('(y/N): ',('n','y')[save])
|
||||
t.written_to_file(file_desc)
|
||||
t.ok()
|
||||
if not no_ok: t.ok()
|
||||
|
||||
def txsign_ui_common(self,t,name, view='t',add_comment='',
|
||||
ni=False,save=True,do_passwd=False,
|
||||
file_desc='Signed transaction'):
|
||||
file_desc='Signed transaction',no_ok=False):
|
||||
txdo = name[:4] == 'txdo'
|
||||
|
||||
if do_passwd:
|
||||
|
|
@ -1719,7 +1752,7 @@ class MMGenTestSuite(object):
|
|||
|
||||
t.written_to_file(file_desc)
|
||||
|
||||
if not txdo: t.ok()
|
||||
if not txdo and not no_ok: t.ok()
|
||||
|
||||
def do_confirm_send(self,t,quiet=False,confirm_send=True):
|
||||
t.expect('Are you sure you want to broadcast this')
|
||||
|
|
@ -1728,7 +1761,7 @@ class MMGenTestSuite(object):
|
|||
|
||||
def txsend_ui_common(self,t,name, view='n',add_comment='',
|
||||
confirm_send=True,bogus_send=True,quiet=False,
|
||||
file_desc='Sent transaction'):
|
||||
file_desc='Sent transaction',no_ok=False):
|
||||
|
||||
txdo = name[:4] == 'txdo'
|
||||
if not txdo:
|
||||
|
|
@ -1739,13 +1772,16 @@ class MMGenTestSuite(object):
|
|||
self.do_confirm_send(t,quiet=quiet,confirm_send=confirm_send)
|
||||
|
||||
if bogus_send:
|
||||
txid = ''
|
||||
t.expect('BOGUS transaction NOT sent')
|
||||
else:
|
||||
txid = t.expect_getend('Transaction sent: ')
|
||||
assert len(txid) == 64,"'{}': Incorrect txid length!".format(txid)
|
||||
|
||||
t.written_to_file(file_desc)
|
||||
if not txdo: t.ok()
|
||||
if not txdo and not no_ok: t.ok()
|
||||
|
||||
return txid
|
||||
|
||||
def txcreate_common(self,name,
|
||||
sources=['1'],
|
||||
|
|
@ -1755,7 +1791,8 @@ class MMGenTestSuite(object):
|
|||
add_args=[],
|
||||
view='n',
|
||||
addrs_per_wallet=addrs_per_wallet,
|
||||
non_mmgen_input_compressed=True):
|
||||
non_mmgen_input_compressed=True,
|
||||
cmdline_inputs=False):
|
||||
|
||||
if opt.verbose or opt.exact_output:
|
||||
sys.stderr.write(green('Generating fake tracking wallet info\n'))
|
||||
|
|
@ -1765,6 +1802,13 @@ class MMGenTestSuite(object):
|
|||
dfake = create_fake_unspent_data(ad,tx_data,non_mmgen_input,non_mmgen_input_compressed)
|
||||
write_fake_data_to_file(repr(dfake))
|
||||
cmd_args = make_txcreate_cmdline(tx_data)
|
||||
if cmdline_inputs:
|
||||
from mmgen.tx import TwLabel
|
||||
cmd_args = ['--inputs={},{},{},{},{},{}'.format(
|
||||
TwLabel(dfake[0]['account']).mmid,dfake[1]['address'],
|
||||
TwLabel(dfake[2]['account']).mmid,dfake[3]['address'],
|
||||
TwLabel(dfake[4]['account']).mmid,dfake[5]['address']
|
||||
),'--outdir='+trash_dir] + cmd_args[1:]
|
||||
end_silence()
|
||||
|
||||
if opt.verbose or opt.exact_output: sys.stderr.write('\n')
|
||||
|
|
@ -1773,6 +1817,12 @@ class MMGenTestSuite(object):
|
|||
'mmgen-'+('txcreate','txdo')[bool(txdo_args)],
|
||||
([],['--rbf'])[g.proto.cap('rbf')] +
|
||||
['-f',tx_fee,'-B'] + add_args + cmd_args + txdo_args)
|
||||
|
||||
if cmdline_inputs:
|
||||
t.written_to_file('Transaction')
|
||||
t.ok()
|
||||
return
|
||||
|
||||
t.license()
|
||||
|
||||
if txdo_args and add_args: # txdo4
|
||||
|
|
@ -1809,6 +1859,9 @@ class MMGenTestSuite(object):
|
|||
def txcreate(self,name,addrfile):
|
||||
self.txcreate_common(name,sources=['1'],add_args=['--vsize-adj=1.01'])
|
||||
|
||||
def txcreate_ni(self,name,addrfile):
|
||||
self.txcreate_common(name,sources=['1'],cmdline_inputs=True,add_args=['--yes'])
|
||||
|
||||
def txbump(self,name,txfile,prepend_args=[],seed_args=[]):
|
||||
if not g.proto.cap('rbf'):
|
||||
msg('Skipping RBF'); return True
|
||||
|
|
@ -2656,6 +2709,16 @@ class MMGenTestSuite(object):
|
|||
def regtest_bob_bal5(self,name):
|
||||
return self.regtest_user_bal(name,'bob',rtBals[3])
|
||||
|
||||
def regtest_bob_bal5_getbalance(self,name):
|
||||
t_ext,t_mmgen = rtBals_gb[0],rtBals_gb[1]
|
||||
assert Decimal(t_ext) + Decimal(t_mmgen) == Decimal(rtBals[3])
|
||||
t = MMGenExpect(name,'mmgen-tool',['--bob','getbalance'])
|
||||
t.expect(r'\n[0-9A-F]{8}: .* '+t_mmgen,regex=True)
|
||||
t.expect(r'\nNon-MMGen: .* '+t_ext,regex=True)
|
||||
t.expect(r'\nTOTAL: .* '+rtBals[3],regex=True)
|
||||
t.read()
|
||||
t.ok()
|
||||
|
||||
def regtest_bob_alice_bal(self,name):
|
||||
t = MMGenExpect(name,'mmgen-regtest',['get_balances'])
|
||||
t.expect('Switching')
|
||||
|
|
@ -2686,7 +2749,7 @@ class MMGenTestSuite(object):
|
|||
self.txcreate_ui_common(t,'txdo',
|
||||
menu=['M'],inputs=outputs_list,
|
||||
file_desc='Signed transaction',
|
||||
fee=(tx_fee,'')[bool(fee)],
|
||||
interactive_fee=(tx_fee,'')[bool(fee)],
|
||||
add_comment=ref_tx_label_jp,
|
||||
view='t',save=True)
|
||||
|
||||
|
|
@ -3046,89 +3109,144 @@ class MMGenTestSuite(object):
|
|||
pid = read_from_tmpfile(cfg,cfg['parity_pidfile'])
|
||||
ok()
|
||||
|
||||
def ethdev_addrgen(self,name):
|
||||
def ethdev_addrgen(self,name,addrs='1-3,11-13,21-23'):
|
||||
from mmgen.addr import MMGenAddrType
|
||||
t = MMGenExpect(name,'mmgen-addrgen', eth_args + [dfl_words,'1-10'])
|
||||
t = MMGenExpect(name,'mmgen-addrgen', eth_args + [dfl_words,addrs])
|
||||
t.written_to_file('Addresses')
|
||||
t.ok()
|
||||
|
||||
def ethdev_addrimport(self,name):
|
||||
fn = get_file_with_ext('addrs',cfg['tmpdir'])
|
||||
t = MMGenExpect(name,'mmgen-addrimport', eth_args[1:] + [fn])
|
||||
if g.debug: t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
|
||||
t.expect('Importing')
|
||||
t.expect('10/10')
|
||||
t.read()
|
||||
t.ok()
|
||||
|
||||
def ethdev_addrimport_dev_addr(self,name):
|
||||
t = MMGenExpect(name,'mmgen-addrimport', eth_args[1:] + ['--address='+eth_addr])
|
||||
def ethdev_addrimport(self,name,ext='21-23].addrs',expect='9/9',add_args=[]):
|
||||
fn = get_file_with_ext(ext,cfg['tmpdir'],no_dot=True,delete=False)
|
||||
t = MMGenExpect(name,'mmgen-addrimport', eth_args[1:] + add_args + [fn])
|
||||
if g.debug: t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
|
||||
t.expect('Importing')
|
||||
t.expect(expect)
|
||||
t.read()
|
||||
t.ok()
|
||||
|
||||
def ethdev_addrimport_one_addr(self,name,addr=None,extra_args=[]):
|
||||
t = MMGenExpect(name,'mmgen-addrimport', eth_args[1:] + extra_args + ['--address='+addr])
|
||||
t.expect('OK')
|
||||
t.ok()
|
||||
|
||||
def ethdev_txcreate(self,name,arg='98831F3A:E:1,123.456',acct='1',non_mmgen_inputs=1):
|
||||
t = MMGenExpect(name,'mmgen-txcreate', eth_args + ['-B',arg])
|
||||
def ethdev_addrimport_dev_addr(self,name):
|
||||
self.ethdev_addrimport_one_addr(name,addr=eth_addr)
|
||||
|
||||
def ethdev_addrimport_burn_addr(self,name):
|
||||
self.ethdev_addrimport_one_addr(name,addr=eth_burn_addr)
|
||||
|
||||
def ethdev_txcreate(self,name,args=[],menu=[],acct='1',non_mmgen_inputs=0,
|
||||
interactive_fee='50G',
|
||||
fee_res='0.00105 ETH (50 gas price in Gwei)',
|
||||
fee_desc = 'gas price'):
|
||||
t = MMGenExpect(name,'mmgen-txcreate', eth_args + ['-B'] + args)
|
||||
t.expect(r"'q'=quit view, .*?:.",'p', regex=True)
|
||||
t.written_to_file('Account balances listing')
|
||||
self.txcreate_ui_common(t,name,
|
||||
menu=['a','d','A','r','M','D','e','m','m'],
|
||||
menu=menu,
|
||||
input_sels_prompt='to spend from',
|
||||
inputs=acct,file_desc='Ethereum transaction',
|
||||
bad_input_sels=True,non_mmgen_inputs=non_mmgen_inputs,
|
||||
fee_desc='gas price',fee='50G',fee_res='0.00105 ETH (50 gas price in Gwei)')
|
||||
interactive_fee=interactive_fee,fee_res=fee_res,fee_desc=fee_desc)
|
||||
|
||||
def ethdev_txsign(self,name,ni=False,ext='.rawtx'):
|
||||
def ethdev_txsign(self,name,ni=False,ext='.rawtx',add_args=[]):
|
||||
key_fn = get_tmpfile_fn(cfg,cfg['parity_keyfile'])
|
||||
write_to_tmpfile(cfg,cfg['parity_keyfile'],eth_key+'\n')
|
||||
tx_fn = get_file_with_ext(ext,cfg['tmpdir'],no_dot=True)
|
||||
t = MMGenExpect(name,'mmgen-txsign',eth_args + ([],['--yes'])[ni] + ['-k',key_fn,tx_fn,dfl_words])
|
||||
t = MMGenExpect(name,'mmgen-txsign',eth_args+add_args + ([],['--yes'])[ni] + ['-k',key_fn,tx_fn,dfl_words])
|
||||
self.txsign_ui_common(t,name,ni=ni)
|
||||
|
||||
def ethdev_txsign_ni(self,name):
|
||||
self.ethdev_txsign(name,ni=True)
|
||||
|
||||
def ethdev_txsend(self,name,ni=False,bogus_send=False,ext='.sigtx'):
|
||||
def ethdev_txsend(self,name,ni=False,bogus_send=False,ext='.sigtx',add_args=[]):
|
||||
tx_fn = get_file_with_ext(ext,cfg['tmpdir'],no_dot=True)
|
||||
if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = ''
|
||||
t = MMGenExpect(name,'mmgen-txsend', eth_args + [tx_fn])
|
||||
t = MMGenExpect(name,'mmgen-txsend', eth_args+add_args + [tx_fn])
|
||||
if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = '1'
|
||||
self.txsend_ui_common(t,name,quiet=True,bogus_send=bogus_send)
|
||||
|
||||
def ethdev_bal(self,name):
|
||||
t = MMGenExpect(name,'mmgen-tool', eth_args + ['twview'])
|
||||
t.expect(r'98831F3A:E:1\s+123\.456\s+',regex=True)
|
||||
t.ok()
|
||||
def ethdev_txcreate1(self,name):
|
||||
menu = ['a','d','A','r','M','D','e','m','m']
|
||||
args = ['98831F3A:E:1,123.456']
|
||||
return self.ethdev_txcreate(name,args=args,menu=menu,acct='1',non_mmgen_inputs=1)
|
||||
|
||||
def ethdev_txsign1(self,name): self.ethdev_txsign(name)
|
||||
def ethdev_txsign1_ni(self,name): self.ethdev_txsign(name,ni=True)
|
||||
def ethdev_txsend1(self,name): self.ethdev_txsend(name)
|
||||
|
||||
def ethdev_txcreate2(self,name):
|
||||
return self.ethdev_txcreate(name,arg='98831F3A:E:2,23.45495',acct='11',non_mmgen_inputs=0)
|
||||
args = ['98831F3A:E:11,1.234']
|
||||
return self.ethdev_txcreate(name,args=args,acct='10',non_mmgen_inputs=1)
|
||||
def ethdev_txsign2(self,name): self.ethdev_txsign(name,ni=True,ext='1.234,50000].rawtx')
|
||||
def ethdev_txsend2(self,name): self.ethdev_txsend(name,ext='1.234,50000].sigtx')
|
||||
|
||||
def ethdev_txsign2(self,name):
|
||||
self.ethdev_txsign(name,ext='.45495].rawtx',ni=True)
|
||||
def ethdev_txcreate3(self,name):
|
||||
args = ['98831F3A:E:21,2.345']
|
||||
return self.ethdev_txcreate(name,args=args,acct='10',non_mmgen_inputs=1)
|
||||
def ethdev_txsign3(self,name): self.ethdev_txsign(name,ni=True,ext='2.345,50000].rawtx')
|
||||
def ethdev_txsend3(self,name): self.ethdev_txsend(name,ext='2.345,50000].sigtx')
|
||||
|
||||
def ethdev_txsend2(self,name):
|
||||
self.ethdev_txsend(name,ni=True,ext='.45495].sigtx')
|
||||
def ethdev_txcreate4(self,name):
|
||||
args = ['98831F3A:E:2,23.45495']
|
||||
interactive_fee='40G'
|
||||
fee_res='0.00084 ETH (40 gas price in Gwei)'
|
||||
return self.ethdev_txcreate(name,args=args,acct='1',non_mmgen_inputs=0,
|
||||
interactive_fee=interactive_fee,fee_res=fee_res)
|
||||
|
||||
def ethdev_bal2(self,name):
|
||||
t = MMGenExpect(name,'mmgen-tool', eth_args + ['twview'])
|
||||
t.expect(r'98831F3A:E:1\s+100\s+',regex=True)
|
||||
t.expect(r'98831F3A:E:2\s+23\.45495\s+',regex=True)
|
||||
def ethdev_txbump(self,name,ext=',40000].rawtx',fee='50G',add_args=[]):
|
||||
tx_fn = get_file_with_ext(ext,cfg['tmpdir'],no_dot=True)
|
||||
t = MMGenExpect(name,'mmgen-txbump', eth_args + add_args + ['--yes',tx_fn])
|
||||
t.expect('or gas price: ',fee+'\n')
|
||||
t.read()
|
||||
t.ok()
|
||||
|
||||
def ethdev_add_label(self,name,addr='98831F3A:E:10',lbl=utf8_label):
|
||||
def ethdev_txsign4(self,name): self.ethdev_txsign(name,ni=True,ext='.45495,50000].rawtx')
|
||||
def ethdev_txsend4(self,name): self.ethdev_txsend(name,ext='.45495,50000].sigtx')
|
||||
|
||||
def ethdev_txcreate5(self,name):
|
||||
args = [eth_burn_addr + ','+eth_amt1]
|
||||
return self.ethdev_txcreate(name,args=args,acct='10',non_mmgen_inputs=1)
|
||||
def ethdev_txsign5(self,name): self.ethdev_txsign(name,ni=True,ext=eth_amt1+',50000].rawtx')
|
||||
def ethdev_txsend5(self,name): self.ethdev_txsend(name,ext=eth_amt1+',50000].sigtx')
|
||||
|
||||
def ethdev_bal(self,name,expect_str=''):
|
||||
t = MMGenExpect(name,'mmgen-tool', eth_args + ['twview'])
|
||||
t.expect(expect_str,regex=True)
|
||||
t.read()
|
||||
t.ok()
|
||||
|
||||
def ethdev_bal_getbalance(self,name,t_non_mmgen='',t_mmgen='',extra_args=[]):
|
||||
t = MMGenExpect(name,'mmgen-tool', eth_args + extra_args + ['getbalance'])
|
||||
t.expect(r'\n[0-9A-F]{8}: .* '+t_mmgen,regex=True)
|
||||
t.expect(r'\nNon-MMGen: .* '+t_non_mmgen,regex=True)
|
||||
total = t.expect_getend(r'\nTOTAL:\s+',regex=True).split()[0]
|
||||
t.read()
|
||||
assert Decimal(t_non_mmgen) + Decimal(t_mmgen) == Decimal(total)
|
||||
t.ok()
|
||||
|
||||
def ethdev_bal1(self,name,expect_str=''):
|
||||
self.ethdev_bal(name,expect_str=r'98831F3A:E:2\s+23\.45495\s+')
|
||||
|
||||
def ethdev_add_label(self,name,addr='98831F3A:E:3',lbl=utf8_label):
|
||||
t = MMGenExpect(name,'mmgen-tool', eth_args + ['add_label',addr,lbl])
|
||||
t.expect('Added label.*in tracking wallet',regex=True)
|
||||
t.ok()
|
||||
|
||||
def ethdev_chk_label(self,name,addr='98831F3A:E:10',label_pat=utf8_label_pat):
|
||||
def ethdev_chk_label(self,name,addr='98831F3A:E:3',label_pat=utf8_label_pat):
|
||||
t = MMGenExpect(name,'mmgen-tool', eth_args + ['listaddresses','all_labels=1'])
|
||||
t.expect(r'{}\s+\S{{30}}\S+\s+{}\s+'.format(addr,(label_pat or label).encode('utf8')),regex=True)
|
||||
t.ok()
|
||||
|
||||
def ethdev_remove_label(self,name,addr='98831F3A:E:10'):
|
||||
def ethdev_remove_label(self,name,addr='98831F3A:E:3'):
|
||||
t = MMGenExpect(name,'mmgen-tool', eth_args + ['remove_label',addr])
|
||||
t.expect('Removed label.*in tracking wallet',regex=True)
|
||||
t.ok()
|
||||
|
||||
def init_ethdev_common(self):
|
||||
g.testnet = True
|
||||
init_coin('eth')
|
||||
g.proto.rpc_port = 8549
|
||||
rpc_init()
|
||||
|
||||
def ethdev_stop(self,name):
|
||||
MMGenExpect(name,'',msg_only=True)
|
||||
pid = read_from_tmpfile(cfg,cfg['parity_pidfile'])
|
||||
|
|
@ -3265,9 +3383,12 @@ try:
|
|||
except KeyboardInterrupt:
|
||||
die(1,'\nExiting at user request')
|
||||
except opt.traceback and Exception:
|
||||
with open('my.err') as f:
|
||||
t = f.readlines()
|
||||
if t: msg_r('\n'+yellow(''.join(t[:-1]))+red(t[-1]))
|
||||
try:
|
||||
os.stat('my.err')
|
||||
with open('my.err') as f:
|
||||
t = f.readlines()
|
||||
if t: msg_r('\n'+yellow(''.join(t[:-1]))+red(t[-1]))
|
||||
except: pass
|
||||
die(1,blue('Test script exited with error'))
|
||||
except:
|
||||
sys.stderr = stderr_save
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue