object-oriented tracking wallet code
updates for oo tx code updates for test/test.py
This commit is contained in:
parent
15ab8d9e21
commit
5ca6412b23
13 changed files with 393 additions and 332 deletions
|
|
@ -31,6 +31,12 @@ from mmgen.obj import *
|
|||
pnm = g.proj_name
|
||||
|
||||
addrmsgs = {
|
||||
'too_many_acct_addresses': """
|
||||
ERROR: More than one address found for account: '%s'.
|
||||
Your 'wallet.dat' file appears to have been altered by a non-{pnm} program.
|
||||
Please restore your tracking wallet from a backup or create a new one and
|
||||
re-import your addresses.
|
||||
""".strip().format(pnm=pnm),
|
||||
'addrfile_header': """
|
||||
# {pnm} address file
|
||||
#
|
||||
|
|
@ -228,13 +234,14 @@ class AddrInfoList(MMGenObject):
|
|||
vmsg_r('Getting account data from wallet...')
|
||||
accts = c.listaccounts(0,True)
|
||||
data,i = {},0
|
||||
for acct in accts:
|
||||
alists = c.getaddressesbyaccount([[k] for k in accts],batch=True)
|
||||
for acct,addrlist in zip(accts,alists):
|
||||
ma,comment = parse_mmgen_label(acct)
|
||||
if ma:
|
||||
i += 1
|
||||
addrlist = c.getaddressesbyaccount(acct)
|
||||
# addrlist = c.getaddressesbyaccount(acct)
|
||||
if len(addrlist) != 1:
|
||||
die(2,wmsg['too_many_acct_addresses'] % acct)
|
||||
die(2,addrmsgs['too_many_acct_addresses'] % acct)
|
||||
seed_id,idx = ma.split(':')
|
||||
if seed_id not in data:
|
||||
data[seed_id] = []
|
||||
|
|
@ -380,7 +387,6 @@ class AddrInfo(MMGenObject):
|
|||
|
||||
# Header
|
||||
out = []
|
||||
from mmgen.addr import addrmsgs
|
||||
k = ('addrfile_header','keyfile_header')[status[0]==0]
|
||||
out.append(addrmsgs[k]+'\n')
|
||||
if self.checksum:
|
||||
|
|
|
|||
|
|
@ -39,8 +39,7 @@ opts_data = {
|
|||
'notes': """
|
||||
|
||||
COMMANDS:{}
|
||||
Type '{} usage <command> for usage information on a particular
|
||||
command
|
||||
Type '{} help <command> for help on a particular command
|
||||
""".format(tool.cmd_help,g.prog_name)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ from decimal import Decimal
|
|||
|
||||
from mmgen.common import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.term import get_char
|
||||
from mmgen.tw import *
|
||||
|
||||
pnm = g.proj_name
|
||||
|
||||
|
|
@ -68,12 +68,6 @@ one address with no amount on the command line.
|
|||
}
|
||||
|
||||
wmsg = {
|
||||
'too_many_acct_addresses': """
|
||||
ERROR: More than one address found for account: '%s'.
|
||||
Your 'wallet.dat' file appears to have been altered by a non-{pnm} program.
|
||||
Please restore your tracking wallet from a backup or create a new one and
|
||||
re-import your addresses.
|
||||
""".strip().format(pnm=pnm),
|
||||
'addr_in_addrfile_only': """
|
||||
Warning: output address {mmgenaddr} is not in the tracking wallet, which means
|
||||
its balance will not be tracked. You're strongly advised to import the address
|
||||
|
|
@ -88,10 +82,6 @@ tracking wallet, or supply an address file for it on the command line.
|
|||
No data for {pnm} address {mmgenaddr} could be found in the tracking wallet.
|
||||
Please import this address into your tracking wallet or supply an address file
|
||||
for it on the command line.
|
||||
""".strip(),
|
||||
'no_spendable_outputs': """
|
||||
No spendable outputs found! Import addresses with balances into your
|
||||
watch-only wallet using '{pnm}-addrimport' and then re-run this program.
|
||||
""".strip(),
|
||||
'mixed_inputs': """
|
||||
NOTE: This transaction uses a mixture of both {pnm} and non-{pnm} inputs, which
|
||||
|
|
@ -110,163 +100,6 @@ was specified.
|
|||
""".strip(),
|
||||
}
|
||||
|
||||
def format_unspent_outputs_for_printing(out,sort_info,total):
|
||||
|
||||
pfs = ' %-4s %-67s %-34s %-14s %-12s %-8s %-6s %s'
|
||||
pout = [pfs % ('Num','Tx ID,Vout','Address','{pnm} ID'.format(pnm=pnm),
|
||||
'Amount(BTC)','Conf.','Age(d)', 'Comment')]
|
||||
|
||||
for n,i in enumerate(out):
|
||||
addr = '=' if i['skip'] == 'addr' and 'grouped' in sort_info else i['address']
|
||||
tx = ' ' * 63 + '=' \
|
||||
if i['skip'] == 'txid' and 'grouped' in sort_info else str(i['txid'])
|
||||
|
||||
s = pfs % (str(n+1)+')', tx+','+str(i['vout']),addr,
|
||||
i['mmid'],i['amt'].strip(),i['confirmations'],i['days'],i['comment'])
|
||||
pout.append(s.rstrip())
|
||||
|
||||
return \
|
||||
'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n'.format(
|
||||
make_timestr(), ' '.join(sort_info), '\n'.join(pout), normalize_btc_amt(total)
|
||||
)
|
||||
|
||||
|
||||
def sort_and_view(unspent):
|
||||
|
||||
def s_amt(i): return i['amount']
|
||||
def s_txid(i): return '%s %03s' % (i['txid'],i['vout'])
|
||||
def s_addr(i): return i['address']
|
||||
def s_age(i): return i['confirmations']
|
||||
def s_mmgen(i):
|
||||
if i['mmid']:
|
||||
return '{}:{:>0{w}}'.format(
|
||||
*i['mmid'].split(':'), w=g.mmgen_idx_max_digits)
|
||||
else: return 'G' + i['comment']
|
||||
|
||||
sort,group,show_days,show_mmaddr,reverse = 'age',False,False,True,True
|
||||
unspent.sort(key=s_age,reverse=reverse) # Reverse age sort by default
|
||||
|
||||
total = sum([i['amount'] for i in unspent])
|
||||
max_acct_len = max([len(i['mmid']+' '+i['comment']) for i in unspent])
|
||||
|
||||
hdr_fmt = 'UNSPENT OUTPUTS (sort order: %s) Total BTC: %s'
|
||||
options_msg = """
|
||||
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
|
||||
""".strip()
|
||||
prompt = \
|
||||
"('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
|
||||
|
||||
mmid_w = max(len(i['mmid']) for i in unspent)
|
||||
from copy import deepcopy
|
||||
from mmgen.term import get_terminal_size
|
||||
|
||||
written_to_file_msg = ''
|
||||
msg('')
|
||||
|
||||
while True:
|
||||
cols = get_terminal_size()[0]
|
||||
if cols < g.min_screen_width:
|
||||
die(2,
|
||||
'{pnl}-txcreate requires a screen at least {w} characters wide'.format(
|
||||
pnl=pnm.lower(),w=g.min_screen_width))
|
||||
|
||||
addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
|
||||
acct_w = min(max_acct_len, max(24,int(addr_w-10)))
|
||||
btaddr_w = addr_w - acct_w - 1
|
||||
tx_w = max(11,min(64, cols-addr_w-32))
|
||||
txdots = ('','...')[tx_w < 64]
|
||||
fs = ' %-4s %-' + str(tx_w) + 's %-2s %-' + str(addr_w) + 's %-13s %-s'
|
||||
table_hdr = fs % ('Num','TX id Vout','','Address','Amount (BTC)',
|
||||
('Conf.','Age(d)')[show_days])
|
||||
|
||||
unsp = deepcopy(unspent)
|
||||
for i in unsp: i['skip'] = ''
|
||||
if group and (sort == 'address' or sort == 'txid'):
|
||||
for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]:
|
||||
if sort == 'address' and a['address'] == b['address']: b['skip'] = 'addr'
|
||||
elif sort == 'txid' and a['txid'] == b['txid']: b['skip'] = 'txid'
|
||||
|
||||
for i in unsp:
|
||||
amt = str(normalize_btc_amt(i['amount']))
|
||||
lfill = 3 - len(amt.split('.')[0]) if '.' in amt else 3 - len(amt)
|
||||
i['amt'] = ' '*lfill + amt
|
||||
i['days'] = int(i['confirmations'] * g.mins_per_block / (60*24))
|
||||
i['age'] = i['days'] if show_days else i['confirmations']
|
||||
|
||||
addr_disp = (i['address'],'|' + '.'*33)[i['skip']=='addr']
|
||||
mmid_disp = (i['mmid'],'.'*len(i['mmid']))[i['skip']=='addr']
|
||||
|
||||
if show_mmaddr:
|
||||
dots = ('','..')[btaddr_w < len(i['address'])]
|
||||
i['addr'] = '%s%s %s' % (
|
||||
addr_disp[:btaddr_w-len(dots)],
|
||||
dots, (
|
||||
('{:<{w}} '.format(mmid_disp,w=mmid_w) if i['mmid'] else '')
|
||||
+ i['comment'])[:acct_w]
|
||||
)
|
||||
else:
|
||||
i['addr'] = addr_disp
|
||||
|
||||
i['tx'] = ' ' * (tx_w-4) + '|...' if i['skip'] == 'txid' \
|
||||
else i['txid'][:tx_w-len(txdots)]+txdots
|
||||
|
||||
sort_info = ([],['reverse'])[reverse]
|
||||
sort_info.append(sort if sort else 'unsorted')
|
||||
if group and (sort == 'address' or sort == 'txid'):
|
||||
sort_info.append('grouped')
|
||||
|
||||
out = [hdr_fmt % (' '.join(sort_info), normalize_btc_amt(total)), table_hdr]
|
||||
out += [fs % (str(n+1)+')',i['tx'],i['vout'],i['addr'],i['amt'],i['age'])
|
||||
for n,i in enumerate(unsp)]
|
||||
|
||||
msg('\n'.join(out) +'\n\n' + written_to_file_msg + options_msg)
|
||||
written_to_file_msg = ''
|
||||
|
||||
skip_prompt = False
|
||||
|
||||
while True:
|
||||
reply = get_char(prompt, immed_chars='atDdAMrgmeqpvw')
|
||||
|
||||
if reply == 'a': unspent.sort(key=s_amt); sort = 'amount'
|
||||
elif reply == 't': unspent.sort(key=s_txid); sort = 'txid'
|
||||
elif reply == 'D': show_days = not show_days
|
||||
elif reply == 'd': unspent.sort(key=s_addr); sort = 'address'
|
||||
elif reply == 'A': unspent.sort(key=s_age); sort = 'age'
|
||||
elif reply == 'M':
|
||||
unspent.sort(key=s_mmgen); sort = 'mmgen'
|
||||
show_mmaddr = True
|
||||
elif reply == 'r':
|
||||
unspent.reverse()
|
||||
reverse = not reverse
|
||||
elif reply == 'g': group = not group
|
||||
elif reply == 'm': show_mmaddr = not show_mmaddr
|
||||
elif reply == 'e': pass
|
||||
elif reply == 'q': pass
|
||||
elif reply == 'p':
|
||||
d = format_unspent_outputs_for_printing(unsp,sort_info,total)
|
||||
of = 'listunspent[%s].out' % ','.join(sort_info)
|
||||
write_data_to_file(of,d,'unspent outputs listing')
|
||||
written_to_file_msg = "Data written to '%s'\n\n" % of
|
||||
elif reply == 'v':
|
||||
do_pager('\n'.join(out))
|
||||
continue
|
||||
elif reply == 'w':
|
||||
data = format_unspent_outputs_for_printing(unsp,sort_info,total)
|
||||
do_pager(data)
|
||||
continue
|
||||
else:
|
||||
msg('\nInvalid input')
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
msg('\n')
|
||||
if reply == 'q': break
|
||||
|
||||
return tuple(unspent)
|
||||
|
||||
|
||||
def select_outputs(unspent,prompt):
|
||||
|
||||
while True:
|
||||
|
|
@ -285,20 +118,7 @@ def select_outputs(unspent,prompt):
|
|||
return selected
|
||||
|
||||
|
||||
def mmaddr2btcaddr_unspent(unspent,mmaddr):
|
||||
vmsg_r('Searching for {pnm} address {m} in wallet...'.format(pnm=pnm,m=mmaddr))
|
||||
m = [u for u in unspent if u['mmid'] == mmaddr]
|
||||
if len(m) == 0:
|
||||
vmsg('not found')
|
||||
return '',''
|
||||
elif len(m) > 1:
|
||||
die(2,wmsg['too_many_acct_addresses'] % acct)
|
||||
else:
|
||||
vmsg('success (%s)' % m[0].address)
|
||||
return m[0].address, m[0].comment
|
||||
|
||||
|
||||
def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f):
|
||||
def mmaddr2baddr(c,mmaddr,ail_w,ail_f):
|
||||
|
||||
# assume mmaddr has already been checked
|
||||
btc_addr = ail_w.mmaddr2btcaddr(mmaddr)
|
||||
|
|
@ -369,7 +189,7 @@ if not opt.info:
|
|||
if is_btc_addr(a1):
|
||||
btc_addr = a1
|
||||
elif is_mmgen_addr(a1):
|
||||
btc_addr = mmaddr2btcaddr(c,a1,ail_w,ail_f)
|
||||
btc_addr = mmaddr2baddr(c,a1,ail_w,ail_f)
|
||||
else:
|
||||
die(2,"%s: unrecognized subargument in argument '%s'" % (a1,a))
|
||||
|
||||
|
|
@ -382,7 +202,7 @@ if not opt.info:
|
|||
if tx.change_addr:
|
||||
die(2,'ERROR: More than one change address specified: %s, %s' %
|
||||
(change_addr, a))
|
||||
tx.change_addr = a if is_btc_addr(a) else mmaddr2btcaddr(c,a,ail_w,ail_f)
|
||||
tx.change_addr = a if is_btc_addr(a) else mmaddr2baddr(c,a,ail_w,ail_f)
|
||||
tx.add_output(tx.change_addr,Decimal('0'))
|
||||
else:
|
||||
die(2,'%s: unrecognized argument' % a)
|
||||
|
|
@ -395,24 +215,10 @@ if not opt.info:
|
|||
|
||||
fee_estimate = get_fee_estimate()
|
||||
|
||||
tw = MMGenTrackingWallet()
|
||||
tw.view_and_sort()
|
||||
tw.display_total()
|
||||
|
||||
if g.bogus_wallet_data: # for debugging purposes only
|
||||
us = eval(get_data_from_file(g.bogus_wallet_data))
|
||||
else:
|
||||
us = c.listunspent()
|
||||
# write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data')
|
||||
# sys.exit()
|
||||
|
||||
if not us:
|
||||
die(2,wmsg['no_spendable_outputs'])
|
||||
for o in us:
|
||||
o['mmid'],o['comment'] = parse_mmgen_label(o['account'])
|
||||
del o['account']
|
||||
unspent = sort_and_view(us)
|
||||
|
||||
total = sum([i['amount'] for i in unspent])
|
||||
|
||||
msg('Total unspent: %s BTC (%s outputs)' % (normalize_btc_amt(total), len(unspent)))
|
||||
if opt.info: sys.exit()
|
||||
|
||||
tx.send_amt = tx.sum_outputs()
|
||||
|
|
@ -420,13 +226,13 @@ tx.send_amt = tx.sum_outputs()
|
|||
msg('Total amount to spend: %s' % ('Unknown','%s BTC'%tx.send_amt,)[bool(tx.send_amt)])
|
||||
|
||||
while True:
|
||||
sel_nums = select_outputs(unspent,
|
||||
sel_nums = select_outputs(tw.unspent,
|
||||
'Enter a range or space-separated list of outputs to spend: ')
|
||||
msg('Selected output%s: %s' % (
|
||||
('s','')[len(sel_nums)==1],
|
||||
' '.join(str(i) for i in sel_nums)
|
||||
))
|
||||
sel_unspent = [unspent[i-1] for i in sel_nums]
|
||||
sel_unspent = [tw.unspent[i-1] for i in sel_nums]
|
||||
|
||||
mmaddrs = set([i['mmid'] for i in sel_unspent])
|
||||
|
||||
|
|
|
|||
|
|
@ -44,9 +44,7 @@ else: opts.usage()
|
|||
|
||||
do_license_msg()
|
||||
|
||||
tx = MMGenTX()
|
||||
|
||||
tx.parse_tx_file(infile,'signed transaction data')
|
||||
tx = MMGenTX(infile)
|
||||
|
||||
c = bitcoin_connection()
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ from mmgen.seed import SeedSource
|
|||
|
||||
pnm = g.proj_name
|
||||
|
||||
# Unneeded, as MMGen transactions cannot contain non-MMGen inputs
|
||||
# -w is unneeded - use bitcoin-cli walletdump instead
|
||||
# -w, --use-wallet-dat Get keys from a running bitcoind
|
||||
opts_data = {
|
||||
'desc': 'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnm.lower()),
|
||||
|
|
@ -147,32 +147,32 @@ def generate_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
|
|||
d += [('{}:{}'.format(seed_id,e.idx),e.addr,e.wif) for e in ai.addrdata]
|
||||
return d
|
||||
|
||||
# function unneeded, as MMGen transactions cannot contain non-MMGen inputs
|
||||
def sign_tx_with_bitcoind_wallet(c,tx,tx_num_str,keys):
|
||||
ok = tx.sign(c,tx_num_str,keys) # returns false on failure
|
||||
if ok:
|
||||
return ok
|
||||
else:
|
||||
msg('Using keys in wallet.dat as per user request')
|
||||
prompt = 'Enter passphrase for bitcoind wallet: '
|
||||
while True:
|
||||
passwd = get_bitcoind_passphrase(prompt)
|
||||
ret = c.walletpassphrase(passwd, 9999,ret_on_error=True)
|
||||
if rpc_error(ret):
|
||||
if rpc_errmsg(ret,'unencrypted wallet, but walletpassphrase was called'):
|
||||
msg('Wallet is unencrypted'); break
|
||||
else:
|
||||
msg('Passphrase OK'); break
|
||||
|
||||
ok = tx.sign(c,tx_num_str,keys)
|
||||
|
||||
msg('Locking wallet')
|
||||
ret = c.walletlock(ret_on_error=True)
|
||||
if rpc_error(ret):
|
||||
msg('Failed to lock wallet')
|
||||
|
||||
return ok
|
||||
|
||||
# # function unneeded - use bitcoin-cli walletdump instead
|
||||
# def sign_tx_with_bitcoind_wallet(c,tx,tx_num_str,keys):
|
||||
# ok = tx.sign(c,tx_num_str,keys) # returns false on failure
|
||||
# if ok:
|
||||
# return ok
|
||||
# else:
|
||||
# msg('Using keys in wallet.dat as per user request')
|
||||
# prompt = 'Enter passphrase for bitcoind wallet: '
|
||||
# while True:
|
||||
# passwd = get_bitcoind_passphrase(prompt)
|
||||
# ret = c.walletpassphrase(passwd, 9999,ret_on_error=True)
|
||||
# if rpc_error(ret):
|
||||
# if rpc_errmsg(ret,'unencrypted wallet, but walletpassphrase was called'):
|
||||
# msg('Wallet is unencrypted'); break
|
||||
# else:
|
||||
# msg('Passphrase OK'); break
|
||||
#
|
||||
# ok = tx.sign(c,tx_num_str,keys)
|
||||
#
|
||||
# msg('Locking wallet')
|
||||
# ret = c.walletlock(ret_on_error=True)
|
||||
# if rpc_error(ret):
|
||||
# msg('Failed to lock wallet')
|
||||
#
|
||||
# return ok
|
||||
#
|
||||
def missing_keys_errormsg(addrs):
|
||||
Msg("""
|
||||
A key file must be supplied (or use the '--use-wallet-dat' option)
|
||||
|
|
@ -241,7 +241,7 @@ def check_maps_from_seeds(io_map,desc,infiles,saved_seeds,return_keys=False):
|
|||
al,bl = 'generated seed:','tx file:'
|
||||
die(3,wmsg['mm2btc_mapping_error'] % (al,a,m[a],bl,b,io_map[b]))
|
||||
if return_keys:
|
||||
vmsg('Added %s wif key%s from seeds' % (len(d),suf(ret,'k')))
|
||||
vmsg('Added %s wif key%s from seeds' % (len(d),suf(d,'k')))
|
||||
return [e[2] for e in d]
|
||||
|
||||
def get_keys_from_keylist(kldata,addrs):
|
||||
|
|
@ -287,8 +287,7 @@ for tx_num,tx_file in enumerate(tx_files,1):
|
|||
msg('\nTransaction #%s of %s:' % (tx_num,len(tx_files)))
|
||||
tx_num_str = ' #%s' % tx_num
|
||||
|
||||
tx = MMGenTX()
|
||||
tx.parse_tx_file(tx_file,('transaction data','')[bool(opt.tx_id)])
|
||||
tx = MMGenTX(tx_file)
|
||||
|
||||
if tx.check_signed(c):
|
||||
die(1,'Transaction is already signed!')
|
||||
|
|
@ -335,6 +334,6 @@ for tx_num,tx_file in enumerate(tx_files,1):
|
|||
|
||||
if ok:
|
||||
tx.add_comment() # edits an existing comment
|
||||
tx.write_to_file(ask_write_default_yes=True)
|
||||
tx.write_to_file(ask_write_default_yes=True,add_desc=tx_num_str)
|
||||
else:
|
||||
die(3,'failed\nSome keys were missing. Transaction %scould not be signed.' % tx_num_str)
|
||||
|
|
|
|||
11
mmgen/rpc.py
11
mmgen/rpc.py
|
|
@ -78,8 +78,17 @@ class BitcoinRPCConnection(object):
|
|||
dmsg('=== rpc.py debug ===')
|
||||
dmsg(' RPC POST data ==> %s\n' % p)
|
||||
|
||||
from decimal import Decimal
|
||||
class JSONDecEncoder(json.JSONEncoder):
|
||||
def default(self, obj):
|
||||
if isinstance(obj, Decimal):
|
||||
return str(obj)
|
||||
return json.JSONEncoder.default(self, obj)
|
||||
|
||||
# pp_msg(json.dumps(p,cls=JSONDecEncoder))
|
||||
|
||||
try:
|
||||
c.request('POST', '/', json.dumps(p), {
|
||||
c.request('POST', '/', json.dumps(p,cls=JSONDecEncoder), {
|
||||
'Host': self.host,
|
||||
'Authorization': 'Basic ' + base64.b64encode(self.auth_str)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@ cmd_data = OrderedDict([
|
|||
('listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=False]']),
|
||||
('getbalance', ['minconf [int=1]']),
|
||||
('txview', ['<{} TX file> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]']),
|
||||
('twview', ["sort [str='age']",'reverse [bool=False]','wide [bool=False]','pager [bool=False]']),
|
||||
|
||||
('add_label', ['<{} address> [str]'.format(pnm),'<label> [str]']),
|
||||
('remove_label', ['<{} address> [str]'.format(pnm)]),
|
||||
|
|
@ -105,6 +106,7 @@ cmd_help = """
|
|||
spendable/unspendable balances for individual {pnm} wallets
|
||||
listaddresses - list {pnm} addresses and their balances
|
||||
txview - show raw/signed {pnm} transaction in human-readable form
|
||||
twview - view tracking wallet
|
||||
|
||||
General utilities:
|
||||
hexdump - encode data into formatted hexadecimal form (file or stdin)
|
||||
|
|
@ -157,6 +159,10 @@ cmd_help = """
|
|||
|
||||
def tool_usage(prog_name, command):
|
||||
if command in cmd_data:
|
||||
for line in cmd_help.split('\n'):
|
||||
if ' ' + command in line:
|
||||
c,h = line.split('-',1)
|
||||
Msg('{}: {}'.format(c.strip(),h.strip()))
|
||||
Msg('USAGE: %s %s %s' % (prog_name, command, ' '.join(cmd_data[command])))
|
||||
else:
|
||||
Msg("'%s': no such tool command" % command)
|
||||
|
|
@ -393,7 +399,7 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
|
|||
die(2,'duplicate BTC address ({}) for this MMGen address! ({})'.format(
|
||||
(d['address'], addrs[key][2])))
|
||||
else:
|
||||
addrs[key] = [0,comment,d['address']]
|
||||
addrs[key] = [Decimal('0'),comment,d['address']]
|
||||
addrs[key][0] += d['amount']
|
||||
total += d['amount']
|
||||
|
||||
|
|
@ -408,7 +414,7 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
|
|||
key = mmaddr.replace(':','_')
|
||||
if key not in addrs:
|
||||
if showbtcaddrs: save_a.append([acct])
|
||||
addrs[key] = [0,comment,'']
|
||||
addrs[key] = [Decimal('0'),comment,'']
|
||||
|
||||
for acct,addr in zip(save_a,c.getaddressesbyaccount(save_a,batch=True)):
|
||||
if len(addr) != 1:
|
||||
|
|
@ -468,10 +474,16 @@ def getbalance(minconf=1):
|
|||
|
||||
def txview(infile,pager=False,terse=False):
|
||||
c = bitcoin_connection()
|
||||
tx = MMGenTX()
|
||||
tx.parse_tx_file(infile,'transaction data')
|
||||
tx = MMGenTX(infile)
|
||||
tx.view(pager,pause=False,terse=terse)
|
||||
|
||||
def twview(pager=False,reverse=False,wide=False,sort='age'):
|
||||
from mmgen.tw import MMGenTrackingWallet
|
||||
tw = MMGenTrackingWallet()
|
||||
tw.do_sort(sort,reverse=reverse)
|
||||
out = tw.format(wide=wide)
|
||||
do_pager(out) if pager else sys.stdout.write(out)
|
||||
|
||||
def add_label(mmaddr,label,remove=False):
|
||||
if not is_mmgen_addr(mmaddr):
|
||||
die(1,'{a}: not a valid {pnm} address'.format(pnm=pnm,a=mmaddr))
|
||||
|
|
|
|||
220
mmgen/tw.py
Executable file
220
mmgen/tw.py
Executable file
|
|
@ -0,0 +1,220 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2016 Philemon <mmgen-py@yandex.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
tw: Tracking wallet methods for the MMGen suite
|
||||
"""
|
||||
|
||||
from mmgen.common import *
|
||||
from mmgen.obj import *
|
||||
from mmgen.tx import parse_mmgen_label,normalize_btc_amt
|
||||
from mmgen.term import get_char
|
||||
|
||||
class MMGenTrackingWallet(MMGenObject):
|
||||
|
||||
wmsg = {
|
||||
'no_spendable_outputs': """
|
||||
No spendable outputs found! Import addresses with balances into your
|
||||
watch-only wallet using '{}-addrimport' and then re-run this program.
|
||||
""".strip().format(g.proj_name)
|
||||
}
|
||||
|
||||
sort_keys = 'address','age','amount','txid','mmaddr'
|
||||
def s_address(self,i): return i['address']
|
||||
def s_age(self,i): return 0 - i['confirmations']
|
||||
def s_amount(self,i): return i['amount']
|
||||
def s_txid(self,i): return '%s %03s' % (i['txid'],i['vout'])
|
||||
def s_mmaddr(self,i):
|
||||
if i['mmid']:
|
||||
return '{}:{:>0{w}}'.format(
|
||||
*i['mmid'].split(':'), w=g.mmgen_idx_max_digits)
|
||||
else: return 'G' + i['comment']
|
||||
|
||||
def do_sort(self,key,reverse=None):
|
||||
if key not in self.sort_keys:
|
||||
fs = "'{}': invalid sort key. Valid keys: [{}]"
|
||||
die(2,fs.format(key,' '.join(self.sort_keys)))
|
||||
if reverse == None: reverse = self.reverse
|
||||
self.sort = key
|
||||
self.unspent.sort(key=getattr(self,'s_'+key),reverse=reverse)
|
||||
|
||||
def sort_info(self,include_group=True):
|
||||
ret = ([],['reverse'])[self.reverse]
|
||||
ret.append(self.sort)
|
||||
if include_group and self.group and (self.sort in ('address','txid')):
|
||||
ret.append('grouped')
|
||||
return ret
|
||||
|
||||
def __init__(self):
|
||||
if g.bogus_wallet_data: # for debugging purposes only
|
||||
us = eval(get_data_from_file(g.bogus_wallet_data))
|
||||
else:
|
||||
us = bitcoin_connection().listunspent()
|
||||
# write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data')
|
||||
# sys.exit()
|
||||
|
||||
if not us: die(2,self.wmsg['no_spendable_outputs'])
|
||||
for o in us:
|
||||
o['mmid'],o['comment'] = parse_mmgen_label(o['account'])
|
||||
del o['account']
|
||||
o['skip'] = ''
|
||||
amt = str(normalize_btc_amt(o['amount']))
|
||||
lfill = 3 - len(amt.split('.')[0]) if '.' in amt else 3 - len(amt)
|
||||
o['amt_fmt'] = ' '*lfill + amt
|
||||
o['days'] = int(o['confirmations'] * g.mins_per_block / (60*24))
|
||||
|
||||
self.unspent = us
|
||||
self.fmt_display = ''
|
||||
self.fmt_print = ''
|
||||
self.cols = None
|
||||
self.reverse = False
|
||||
self.group = False
|
||||
self.show_days = True
|
||||
self.show_mmaddr = True
|
||||
self.do_sort('age')
|
||||
self.total = sum([i['amount'] for i in self.unspent])
|
||||
|
||||
def set_cols(self):
|
||||
from mmgen.term import get_terminal_size
|
||||
self.cols = get_terminal_size()[0]
|
||||
if self.cols < g.min_screen_width:
|
||||
m = 'A screen at least {} characters wide is required to display the tracking wallet'
|
||||
die(2,m.format(g.min_screen_width))
|
||||
|
||||
def display(self):
|
||||
msg(self.format_for_display())
|
||||
|
||||
def format(self,wide=False):
|
||||
return self.format_for_printing() if wide else self.format_for_display()
|
||||
|
||||
def format_for_display(self):
|
||||
unspent = self.unspent
|
||||
total = sum([i['amount'] for i in unspent])
|
||||
mmid_w = max(len(i['mmid']) for i in unspent)
|
||||
self.set_cols()
|
||||
|
||||
max_acct_len = max([len(i['mmid']+i['comment'])+1 for i in self.unspent])
|
||||
addr_w = min(34+((1+max_acct_len) if self.show_mmaddr else 0),self.cols-46)
|
||||
acct_w = min(max_acct_len, max(24,int(addr_w-10)))
|
||||
btaddr_w = addr_w - acct_w - 1
|
||||
tx_w = max(11,min(64, self.cols-addr_w-32))
|
||||
txdots = ('','...')[tx_w < 64]
|
||||
fs = ' %-4s %-' + str(tx_w) + 's %-2s %-' + str(addr_w) + 's %-13s %-s'
|
||||
table_hdr = fs % ('Num','TX id Vout','','Address','Amount (BTC)',
|
||||
('Conf.','Age(d)')[self.show_days])
|
||||
|
||||
from copy import deepcopy
|
||||
unsp = deepcopy(unspent)
|
||||
for i in unsp: i['skip'] = ''
|
||||
if self.group and (self.sort in ('address','txid')):
|
||||
for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]:
|
||||
if self.sort == 'address' and a['address'] == b['address']: b['skip'] = 'addr'
|
||||
elif self.sort == 'txid' and a['txid'] == b['txid']: b['skip'] = 'txid'
|
||||
|
||||
for i in unsp:
|
||||
addr_disp = (i['address'],'|' + '.'*33)[i['skip']=='addr']
|
||||
mmid_disp = (i['mmid'],'.'*len(i['mmid']))[i['skip']=='addr']
|
||||
if self.show_mmaddr:
|
||||
dots = ('','..')[btaddr_w < len(i['address'])]
|
||||
i['addr'] = '%s%s %s' % (
|
||||
addr_disp[:btaddr_w-len(dots)],
|
||||
dots, (
|
||||
('{:<{w}} '.format(mmid_disp,w=mmid_w) if i['mmid'] else '')
|
||||
+ i['comment'])[:acct_w]
|
||||
)
|
||||
else:
|
||||
i['addr'] = addr_disp
|
||||
|
||||
i['tx'] = ' ' * (tx_w-4) + '|...' if i['skip'] == 'txid' \
|
||||
else i['txid'][:tx_w-len(txdots)]+txdots
|
||||
|
||||
hdr_fmt = 'UNSPENT OUTPUTS (sort order: %s) Total BTC: %s'
|
||||
out = [hdr_fmt % (' '.join(self.sort_info()), normalize_btc_amt(total)), table_hdr]
|
||||
out += [fs % (str(n+1)+')',i['tx'],i['vout'],i['addr'],i['amt_fmt'],
|
||||
i['days'] if self.show_days else i['confirmations'])
|
||||
for n,i in enumerate(unsp)]
|
||||
self.fmt_display = '\n'.join(out)
|
||||
return self.fmt_display
|
||||
|
||||
def format_for_printing(self):
|
||||
|
||||
total = sum([i['amount'] for i in self.unspent])
|
||||
fs = ' %-4s %-67s %-34s %-14s %-12s %-8s %-6s %s'
|
||||
out = [fs % ('Num','Tx ID,Vout','Address','{} ID'.format(g.proj_name),
|
||||
'Amount(BTC)','Conf.','Age(d)', 'Comment')]
|
||||
|
||||
for n,i in enumerate(self.unspent):
|
||||
addr = '=' if i['skip'] == 'addr' and self.group else i['address']
|
||||
tx = ' ' * 63 + '=' if i['skip'] == 'txid' and self.group else str(i['txid'])
|
||||
s = fs % (str(n+1)+')', tx+','+str(i['vout']),addr,
|
||||
i['mmid'],i['amt_fmt'].strip(),i['confirmations'],i['days'],i['comment'])
|
||||
out.append(s.rstrip())
|
||||
|
||||
fs = 'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n'
|
||||
self.fmt_print = fs.format(
|
||||
make_timestr(),
|
||||
' '.join(self.sort_info(include_group=False)),
|
||||
'\n'.join(out),
|
||||
normalize_btc_amt(total))
|
||||
return self.fmt_print
|
||||
|
||||
def display_total(self):
|
||||
fs = '\nTotal unspent: %s BTC (%s outputs)'
|
||||
msg(fs % (normalize_btc_amt(self.total), len(self.unspent)))
|
||||
|
||||
def view_and_sort(self):
|
||||
from mmgen.term import do_pager
|
||||
s = """
|
||||
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
|
||||
""".strip()
|
||||
self.display()
|
||||
msg(s)
|
||||
|
||||
p = "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
|
||||
while True:
|
||||
reply = get_char(p, immed_chars='atDdAMrgmeqpvw')
|
||||
if reply == 'a': self.do_sort('amount')
|
||||
elif reply == 't': self.do_sort('txid')
|
||||
elif reply == 'D': self.show_days = not self.show_days
|
||||
elif reply == 'd': self.do_sort('address')
|
||||
elif reply == 'A': self.do_sort('age')
|
||||
elif reply == 'M': self.do_sort('mmaddr'); self.show_mmaddr = True
|
||||
elif reply == 'r': self.unspent.reverse(); self.reverse = not self.reverse
|
||||
elif reply == 'g': self.group = not self.group
|
||||
elif reply == 'm': self.show_mmaddr = not self.show_mmaddr
|
||||
elif reply == 'e': msg("\n%s\n%s\n%s" % (self.fmt_display,s,p))
|
||||
elif reply == 'q': return self.unspent
|
||||
elif reply == 'p':
|
||||
of = 'listunspent[%s].out' % ','.join(self.sort_info(include_group=False))
|
||||
write_data_to_file(of,self.format_for_printing(),'unspent outputs listing')
|
||||
m = yellow("Data written to '%s'" % of)
|
||||
msg('\n%s\n\n%s\n\n%s' % (self.fmt_display,m,s))
|
||||
continue
|
||||
elif reply == 'v':
|
||||
do_pager(self.fmt_display)
|
||||
continue
|
||||
elif reply == 'w':
|
||||
do_pager(self.format_for_printing())
|
||||
continue
|
||||
else:
|
||||
msg('\nInvalid input')
|
||||
continue
|
||||
|
||||
self.display()
|
||||
msg(s)
|
||||
43
mmgen/tx.py
43
mmgen/tx.py
|
|
@ -176,6 +176,9 @@ def mmaddr2btcaddr_addrdata(mmaddr,addr_data,source=''):
|
|||
from mmgen.obj import *
|
||||
|
||||
class MMGenTX(MMGenObject):
|
||||
ext = g.rawtx_ext
|
||||
desc = 'transaction'
|
||||
|
||||
def __init__(self,filename=None):
|
||||
self.inputs = []
|
||||
self.outputs = {}
|
||||
|
|
@ -191,7 +194,10 @@ class MMGenTX(MMGenObject):
|
|||
self.chksum = ''
|
||||
self.fmt_data = ''
|
||||
self.blockcount = 0
|
||||
self.isSigned = False
|
||||
if filename:
|
||||
if get_extension(filename) == g.sigtx_ext:
|
||||
self.mark_signed()
|
||||
self.parse_tx_file(filename)
|
||||
|
||||
def add_output(self,btcaddr,amt):
|
||||
self.outputs[btcaddr] = (amt,)
|
||||
|
|
@ -261,17 +267,8 @@ class MMGenTX(MMGenObject):
|
|||
return sum([i['amount'] for i in self.inputs])
|
||||
|
||||
def create_raw(self,c):
|
||||
inputs,outputs = [],{}
|
||||
def dec2str(d):
|
||||
tmp = {}
|
||||
for k in d:
|
||||
tmp[k] = str(d[k]) if type(d[k]) is Decimal else d[k]
|
||||
return tmp
|
||||
inputs = [dec2str(d) for d in self.inputs]
|
||||
for k in self.outputs:
|
||||
outputs[k] = str(self.outputs[k][0])
|
||||
# mdie(inputs,outputs)
|
||||
self.hex = c.createrawtransaction(inputs,outputs)
|
||||
o = dict([(k,v[0]) for k,v in self.outputs.items()])
|
||||
self.hex = c.createrawtransaction(self.inputs,o)
|
||||
self.txid = make_chksum_6(unhexlify(self.hex)).upper()
|
||||
|
||||
# def make_b2m_map(self,ail_w,ail_f):
|
||||
|
|
@ -327,17 +324,22 @@ class MMGenTX(MMGenObject):
|
|||
if sig_tx['complete']:
|
||||
msg('OK')
|
||||
self.hex = sig_tx['hex']
|
||||
self.isSigned = True
|
||||
self.mark_signed()
|
||||
return True
|
||||
else:
|
||||
msg('failed\nBitcoind returned the following errors:')
|
||||
pp_msg(sig_tx['errors'])
|
||||
return False
|
||||
|
||||
def mark_signed(self):
|
||||
self.desc = 'signed transaction'
|
||||
self.ext = g.sigtx_ext
|
||||
|
||||
def check_signed(self,c):
|
||||
d = c.decoderawtransaction(self.hex)
|
||||
self.isSigned = bool(d['vin'][0]['scriptSig']['hex'])
|
||||
return self.isSigned
|
||||
ret = bool(d['vin'][0]['scriptSig']['hex'])
|
||||
if ret: self.mark_signed()
|
||||
return ret
|
||||
|
||||
def send(self,c,bogus=False):
|
||||
if bogus:
|
||||
|
|
@ -354,13 +356,12 @@ class MMGenTX(MMGenObject):
|
|||
ask_write=ask_write,
|
||||
ask_write_default_yes=ask_write_default_yes)
|
||||
|
||||
def write_to_file(self,ask_write=True,ask_write_default_yes=False):
|
||||
def write_to_file(self,add_desc='',ask_write=True,ask_write_default_yes=False):
|
||||
if ask_write == False:
|
||||
ask_write_default_yes=True
|
||||
self.format()
|
||||
fn = '%s[%s].%s' % (self.txid,self.send_amt,
|
||||
(g.rawtx_ext,g.sigtx_ext)[self.isSigned])
|
||||
write_data_to_file(fn,self.fmt_data,'transaction',
|
||||
fn = '%s[%s].%s' % (self.txid,self.send_amt,self.ext)
|
||||
write_data_to_file(fn,self.fmt_data,self.desc+add_desc,
|
||||
ask_write=ask_write,
|
||||
ask_write_default_yes=ask_write_default_yes)
|
||||
|
||||
|
|
@ -453,9 +454,9 @@ class MMGenTX(MMGenObject):
|
|||
|
||||
return out
|
||||
|
||||
def parse_tx_file(self,infile,desc):
|
||||
def parse_tx_file(self,infile):
|
||||
|
||||
self.parse_tx_data(get_lines_from_file(infile,desc))
|
||||
self.parse_tx_data(get_lines_from_file(infile,self.desc+' data'))
|
||||
|
||||
def parse_tx_data(self,tx_data):
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,18 @@ def die(ev,s):
|
|||
def Die(ev,s):
|
||||
sys.stdout.write(s+'\n'); sys.exit(ev)
|
||||
|
||||
def pp_format(d):
|
||||
import pprint
|
||||
return pprint.PrettyPrinter(indent=4).pformat(d)
|
||||
|
||||
def pp_die(d):
|
||||
import pprint
|
||||
die(1,pprint.PrettyPrinter(indent=4).pformat(d))
|
||||
|
||||
def pp_msg(d):
|
||||
import pprint
|
||||
msg(pprint.PrettyPrinter(indent=4).pformat(d))
|
||||
|
||||
def is_mmgen_wallet_label(s):
|
||||
if len(s) > g.max_wallet_label_len:
|
||||
msg('ERROR: wallet label length (%s chars) > maximum allowed (%s chars)' % (len(s),g.max_wallet_label_len))
|
||||
|
|
@ -804,15 +816,3 @@ def bitcoin_connection():
|
|||
import mmgen.rpc
|
||||
return mmgen.rpc.BitcoinRPCConnection(
|
||||
host,port,cfg[user],cfg[passwd],auth_cookie=auth_cookie)
|
||||
|
||||
def pp_format(d):
|
||||
import pprint
|
||||
return pprint.PrettyPrinter(indent=4).pformat(d)
|
||||
|
||||
def pp_die(d):
|
||||
import pprint
|
||||
die(1,pprint.PrettyPrinter(indent=4).pformat(d))
|
||||
|
||||
def pp_msg(d):
|
||||
import pprint
|
||||
msg(pprint.PrettyPrinter(indent=4).pformat(d))
|
||||
|
|
|
|||
|
|
@ -99,18 +99,17 @@ tx.send_amt = Decimal(send_amt)
|
|||
|
||||
c = bitcoin_connection()
|
||||
|
||||
tx.blockcount = find_block_by_time(c,tx.timestamp)
|
||||
tx.copy_inputs(inputs)
|
||||
if tx.check_signed(c):
|
||||
msg('Transaction is signed')
|
||||
|
||||
dec_tx = c.decoderawtransaction(tx.hex)
|
||||
|
||||
tx.copy_inputs(inputs)
|
||||
tx.outputs = dict([(i['scriptPubKey']['addresses'][0],(i['value'],)) for i in dec_tx['vout']])
|
||||
|
||||
tx.blockcount = find_block_by_time(c,tx.timestamp)
|
||||
|
||||
for k in tx.outputs:
|
||||
if k in b2m_map:
|
||||
tx.outputs[k] += b2m_map[k]
|
||||
|
||||
#tx.view_with_prompt('View decoded transaction?')
|
||||
#tx.format()
|
||||
#print(str(tx))
|
||||
# do_pager('OLD:\n' + pp_format(inputs) + '\nNEW:\n' + pp_format(tx.inputs))
|
||||
tx.write_to_file(ask_write=False)
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -47,6 +47,7 @@ setup(
|
|||
'mmgen.test',
|
||||
'mmgen.tool',
|
||||
'mmgen.tx',
|
||||
'mmgen.tw',
|
||||
'mmgen.util',
|
||||
|
||||
'mmgen.main',
|
||||
|
|
|
|||
99
test/test.py
99
test/test.py
|
|
@ -77,8 +77,8 @@ cfgs = {
|
|||
pwfile: 'walletgen',
|
||||
'mmdat': 'walletgen',
|
||||
'addrs': 'addrgen',
|
||||
'raw': 'txcreate',
|
||||
'sig': 'txsign',
|
||||
'rawtx': 'txcreate',
|
||||
'sigtx': 'txsign',
|
||||
'mmwords': 'export_mnemonic',
|
||||
'mmseed': 'export_seed',
|
||||
'mmincog': 'export_incog',
|
||||
|
|
@ -96,8 +96,8 @@ cfgs = {
|
|||
'dep_generators': {
|
||||
'mmdat': 'walletgen2',
|
||||
'addrs': 'addrgen2',
|
||||
'raw': 'txcreate2',
|
||||
'sig': 'txsign2',
|
||||
'rawtx': 'txcreate2',
|
||||
'sigtx': 'txsign2',
|
||||
'mmwords': 'export_mnemonic2',
|
||||
},
|
||||
},
|
||||
|
|
@ -108,8 +108,8 @@ cfgs = {
|
|||
'dep_generators': {
|
||||
'mmdat': 'walletgen3',
|
||||
'addrs': 'addrgen3',
|
||||
'raw': 'txcreate3',
|
||||
'sig': 'txsign3'
|
||||
'rawtx': 'txcreate3',
|
||||
'sigtx': 'txsign3'
|
||||
},
|
||||
},
|
||||
'4': {
|
||||
|
|
@ -121,8 +121,8 @@ cfgs = {
|
|||
'mmdat': 'walletgen4',
|
||||
'mmbrain': 'walletgen4',
|
||||
'addrs': 'addrgen4',
|
||||
'raw': 'txcreate4',
|
||||
'sig': 'txsign4',
|
||||
'rawtx': 'txcreate4',
|
||||
'sigtx': 'txsign4',
|
||||
},
|
||||
'bw_filename': 'brainwallet.mmbrain',
|
||||
'bw_params': '192,1',
|
||||
|
|
@ -203,7 +203,7 @@ cfgs = {
|
|||
'ref_keyaddrfile_chksum': '9F2D D781 1812 8BAD C396 9DEB',
|
||||
|
||||
# 'ref_fake_unspent_data':'98831F3A_unspent.json',
|
||||
'ref_tx_file': 'tx_FFB367[1.234].raw',
|
||||
'ref_tx_file': 'FFB367[1.234].rawtx',
|
||||
'ic_wallet': '98831F3A-5482381C-18460FB1[256,1].mmincog',
|
||||
'ic_wallet_hex': '98831F3A-1630A9F2-870376A9[256,1].mmincox',
|
||||
|
||||
|
|
@ -256,8 +256,8 @@ cmd_group['main'] = OrderedDict([
|
|||
['addrgen', (1,'address generation', [[['mmdat',pwfile],1]],1)],
|
||||
['addrimport', (1,'address import', [[['addrs'],1]],1)],
|
||||
['txcreate', (1,'transaction creation', [[['addrs'],1]],1)],
|
||||
['txsign', (1,'transaction signing', [[['mmdat','raw',pwfile],1]],1)],
|
||||
['txsend', (1,'transaction sending', [[['sig'],1]])],
|
||||
['txsign', (1,'transaction signing', [[['mmdat','rawtx',pwfile],1]],1)],
|
||||
['txsend', (1,'transaction sending', [[['sigtx'],1]])],
|
||||
|
||||
['export_seed', (1,'seed export to mmseed format', [[['mmdat'],1]])],
|
||||
['export_mnemonic', (1,'seed export to mmwords format', [[['mmdat'],1]])],
|
||||
|
|
@ -272,23 +272,23 @@ cmd_group['main'] = OrderedDict([
|
|||
['addrgen_incog_hidden',(1,'address generation from hidden mmincog file', [[[hincog_fn,'addrs'],1]])],
|
||||
|
||||
['keyaddrgen', (1,'key-address file generation', [[['mmdat',pwfile],1]])],
|
||||
['txsign_keyaddr',(1,'transaction signing with key-address file', [[['akeys.mmenc','raw'],1]])],
|
||||
['txsign_keyaddr',(1,'transaction signing with key-address file', [[['akeys.mmenc','rawtx'],1]])],
|
||||
|
||||
['walletgen2',(2,'wallet generation (2), 128-bit seed', [])],
|
||||
['addrgen2', (2,'address generation (2)', [[['mmdat'],2]])],
|
||||
['txcreate2', (2,'transaction creation (2)', [[['addrs'],2]])],
|
||||
['txsign2', (2,'transaction signing, two transactions',[[['mmdat','raw'],1],[['mmdat','raw'],2]])],
|
||||
['txsign2', (2,'transaction signing, two transactions',[[['mmdat','rawtx'],1],[['mmdat','rawtx'],2]])],
|
||||
['export_mnemonic2', (2,'seed export to mmwords format (2)',[[['mmdat'],2]])],
|
||||
|
||||
['walletgen3',(3,'wallet generation (3)', [])],
|
||||
['addrgen3', (3,'address generation (3)', [[['mmdat'],3]])],
|
||||
['txcreate3', (3,'tx creation with inputs and outputs from two wallets', [[['addrs'],1],[['addrs'],3]])],
|
||||
['txsign3', (3,'tx signing with inputs and outputs from two wallets',[[['mmdat'],1],[['mmdat','raw'],3]])],
|
||||
['txsign3', (3,'tx signing with inputs and outputs from two wallets',[[['mmdat'],1],[['mmdat','rawtx'],3]])],
|
||||
|
||||
['walletgen4',(4,'wallet generation (4) (brainwallet)', [])],
|
||||
['addrgen4', (4,'address generation (4)', [[['mmdat'],4]])],
|
||||
['txcreate4', (4,'tx creation with inputs and outputs from four seed sources, plus non-MMGen inputs and outputs', [[['addrs'],1],[['addrs'],2],[['addrs'],3],[['addrs'],4]])],
|
||||
['txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet and brainwallet, plus non-MMGen inputs and outputs', [[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['mmbrain','raw'],4]])],
|
||||
['txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet and brainwallet, plus non-MMGen inputs and outputs', [[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['mmbrain','rawtx'],4]])],
|
||||
])
|
||||
|
||||
cmd_group['tool'] = OrderedDict([
|
||||
|
|
@ -428,6 +428,7 @@ meta_cmds = OrderedDict([
|
|||
])
|
||||
|
||||
del cmd_group
|
||||
log_file = 'test.py_log'
|
||||
|
||||
opts_data = {
|
||||
# 'sets': [('non_interactive',bool,'verbose',None)],
|
||||
|
|
@ -441,15 +442,17 @@ opts_data = {
|
|||
debugging only).
|
||||
-e, --exact-output Show the exact output of the MMGen script(s) being run.
|
||||
-l, --list-cmds List and describe the commands in the test suite.
|
||||
-L, --log Log commands to file {lf}
|
||||
-n, --names Display command names instead of descriptions.
|
||||
-I, --non-interactive Non-interactive operation (MS Windows mode)
|
||||
-p, --pause Pause between tests, resuming on keypress.
|
||||
-q, --quiet Produce minimal output. Suppress dependency info.
|
||||
-s, --system Test scripts and modules installed on system rather
|
||||
than those in the repo root.
|
||||
-S, --skip-deps Skip dependency checking for command
|
||||
-t, --traceback Run the command inside the '{tb_cmd}' script.
|
||||
-v, --verbose Produce more verbose output.
|
||||
""".format(tb_cmd=tb_cmd),
|
||||
""".format(tb_cmd=tb_cmd,lf=log_file),
|
||||
'notes': """
|
||||
|
||||
If no command is given, the whole suite of tests is run.
|
||||
|
|
@ -457,6 +460,9 @@ If no command is given, the whole suite of tests is run.
|
|||
}
|
||||
|
||||
cmd_args = opts.init(opts_data)
|
||||
if opt.log:
|
||||
log_fd = open(log_file,'a')
|
||||
log_fd.write('\nLog started: %s\n' % make_timestr())
|
||||
|
||||
if opt.system: sys.path.pop(0)
|
||||
ni = bool(opt.non_interactive)
|
||||
|
|
@ -616,11 +622,11 @@ class MMGenExpect(object):
|
|||
mmgen_cmd = os.path.join(os.curdir,mmgen_cmd)
|
||||
desc = (cmd_data[name][1],name)[bool(opt.names)]
|
||||
if extra_desc: desc += ' ' + extra_desc
|
||||
cmd_str = mmgen_cmd + ' ' + ' '.join(cmd_args)
|
||||
if opt.log:
|
||||
log_fd.write(cmd_str+'\n')
|
||||
if opt.verbose or opt.exact_output:
|
||||
sys.stderr.write(
|
||||
green('Testing: %s\nExecuting ' % desc) +
|
||||
cyan("'%s %s'\n" % (mmgen_cmd,' '.join(cmd_args)))
|
||||
)
|
||||
sys.stderr.write(green('Testing: %s\nExecuting %s\n' % (desc,cyan(cmd_str))))
|
||||
else:
|
||||
m = 'Testing %s: ' % desc
|
||||
msg_r((m,yellow(m))[ni])
|
||||
|
|
@ -845,7 +851,8 @@ def check_needs_rerun(
|
|||
if rerun:
|
||||
for fn in fns:
|
||||
if not root: os.unlink(fn)
|
||||
ts.do_cmd(cmd)
|
||||
if not (dpy and opt.skip_deps):
|
||||
ts.do_cmd(cmd)
|
||||
if not root: do_between()
|
||||
else:
|
||||
# If prog produces multiple files:
|
||||
|
|
@ -888,6 +895,7 @@ def check_deps(cmds):
|
|||
|
||||
|
||||
def clean(usr_dirs=[]):
|
||||
if opt.skip_deps: return
|
||||
all_dirs = MMGenTestSuite().list_tmp_dirs()
|
||||
dirs = (usr_dirs or all_dirs)
|
||||
for d in sorted(dirs):
|
||||
|
|
@ -1149,14 +1157,15 @@ class MMGenTestSuite(object):
|
|||
t.written_to_file('Transaction')
|
||||
ok()
|
||||
|
||||
def txsign_end(self,t,tnum=None):
|
||||
def txsign_end(self,t,tnum=None,has_comment=False):
|
||||
t.expect('Signing transaction')
|
||||
t.expect('Edit transaction comment? (y/N): ','\n')
|
||||
t.expect('Save signed transaction? (Y/n): ','y')
|
||||
cprompt = ('Add a comment to transaction','Edit transaction comment')[has_comment]
|
||||
t.expect('%s? (y/N): ' % cprompt,'\n')
|
||||
t.expect('Save signed transaction.*?\? \(Y/n\): ','y',regex=True)
|
||||
add = ' #' + tnum if tnum else ''
|
||||
t.written_to_file('Signed transaction' + add, oo=True)
|
||||
|
||||
def txsign(self,name,txfile,wf,pf='',save=True):
|
||||
def txsign(self,name,txfile,wf,pf='',save=True,has_comment=False):
|
||||
add_args = ([],['-q','-P',pf])[ni]
|
||||
if ni:
|
||||
m = '\nAnswer the interactive prompts as follows:\n ENTER, ENTER, ENTER'
|
||||
|
|
@ -1167,9 +1176,10 @@ class MMGenTestSuite(object):
|
|||
t.tx_view()
|
||||
t.passphrase('MMGen wallet',cfg['wpasswd'])
|
||||
if save:
|
||||
self.txsign_end(t)
|
||||
self.txsign_end(t,has_comment=has_comment)
|
||||
else:
|
||||
t.expect('Edit transaction comment? (y/N): ','\n')
|
||||
cprompt = ('Add a comment to transaction','Edit transaction comment')[has_comment]
|
||||
t.expect('%s? (y/N): ' % cprompt,'\n')
|
||||
t.close()
|
||||
ok()
|
||||
|
||||
|
|
@ -1177,7 +1187,7 @@ class MMGenTestSuite(object):
|
|||
t = MMGenExpect(name,'mmgen-txsend', ['-d',cfg['tmpdir'],sigfile])
|
||||
t.license()
|
||||
t.tx_view()
|
||||
t.expect('Edit transaction comment? (y/N): ','\n')
|
||||
t.expect('Add a comment to transaction? (y/N): ','\n')
|
||||
t.expect('broadcast this transaction to the network?')
|
||||
t.expect("'YES, I REALLY WANT TO DO THIS' to confirm: ",'\n')
|
||||
t.expect('Exiting at user request')
|
||||
|
|
@ -1601,7 +1611,7 @@ class MMGenTestSuite(object):
|
|||
wf = os.path.join(ref_dir,cfg['ref_wallet'])
|
||||
write_to_tmpfile(cfg,pwfile,cfg['wpasswd'])
|
||||
pf = get_tmpfile_fn(cfg,pwfile)
|
||||
self.txsign(name,tf,wf,pf,save=False)
|
||||
self.txsign(name,tf,wf,pf,save=False,has_comment=True)
|
||||
|
||||
def ref_tool_decrypt(self,name):
|
||||
f = os.path.join(ref_dir,ref_enc_fn)
|
||||
|
|
@ -1760,21 +1770,22 @@ ts = MMGenTestSuite()
|
|||
|
||||
# Laggy flash media cause pexpect to crash, so read and write all temporary
|
||||
# files to volatile memory in '/dev/shm'
|
||||
if sys.platform[:3] == 'win':
|
||||
for cfg in sorted(cfgs): mk_tmpdir(cfgs[cfg])
|
||||
else:
|
||||
d,pfx = '/dev/shm','mmgen-test-'
|
||||
try:
|
||||
import subprocess
|
||||
subprocess.call('rm -rf %s/%s*'%(d,pfx),shell=True)
|
||||
except Exception as e:
|
||||
die(2,'Unable to delete directory tree %s/%s* (%s)'%(d,pfx,e))
|
||||
try:
|
||||
import tempfile
|
||||
shm_dir = tempfile.mkdtemp('',pfx,d)
|
||||
except Exception as e:
|
||||
die(2,'Unable to create temporary directory in %s (%s)'%(d,e))
|
||||
for cfg in sorted(cfgs): mk_tmpdir_path(shm_dir,cfgs[cfg])
|
||||
if not opt.skip_deps:
|
||||
if sys.platform[:3] == 'win':
|
||||
for cfg in sorted(cfgs): mk_tmpdir(cfgs[cfg])
|
||||
else:
|
||||
d,pfx = '/dev/shm','mmgen-test-'
|
||||
try:
|
||||
import subprocess
|
||||
subprocess.call('rm -rf %s/%s*'%(d,pfx),shell=True)
|
||||
except Exception as e:
|
||||
die(2,'Unable to delete directory tree %s/%s* (%s)'%(d,pfx,e))
|
||||
try:
|
||||
import tempfile
|
||||
shm_dir = tempfile.mkdtemp('',pfx,d)
|
||||
except Exception as e:
|
||||
die(2,'Unable to create temporary directory in %s (%s)'%(d,e))
|
||||
for cfg in sorted(cfgs): mk_tmpdir_path(shm_dir,cfgs[cfg])
|
||||
|
||||
try:
|
||||
if cmd_args:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue