New object-oriented TX code
This commit is contained in:
parent
7b0b1cfcbb
commit
3f2a866c47
12 changed files with 745 additions and 434 deletions
|
|
@ -35,6 +35,8 @@ no_license = os.getenv('MMGEN_NOLICENSE')
|
|||
bogus_wallet_data = os.getenv('MMGEN_BOGUS_WALLET_DATA')
|
||||
disable_hold_protect = os.getenv('MMGEN_DISABLE_HOLD_PROTECT')
|
||||
|
||||
btc_amt_decimal_places = 8
|
||||
|
||||
from decimal import Decimal
|
||||
tx_fee = Decimal('0.0003')
|
||||
max_tx_fee = Decimal('0.01')
|
||||
|
|
@ -77,8 +79,9 @@ seedfile_exts = (
|
|||
wallet_ext, seed_ext, mn_ext, brain_ext, incog_ext, incog_hex_ext
|
||||
)
|
||||
|
||||
rawtx_ext = 'raw'
|
||||
sigtx_ext = 'sig'
|
||||
rawtx_ext = 'rawtx'
|
||||
sigtx_ext = 'sigtx'
|
||||
txid_ext = 'txid'
|
||||
addrfile_ext = 'addrs'
|
||||
addrfile_chksum_ext = 'chk'
|
||||
keyfile_ext = 'keys'
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ def format_unspent_outputs_for_printing(out,sort_info,total):
|
|||
|
||||
return \
|
||||
'Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n'.format(
|
||||
make_timestr(), ' '.join(sort_info), '\n'.join(pout), total
|
||||
make_timestr(), ' '.join(sort_info), '\n'.join(pout), normalize_btc_amt(total)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -146,7 +146,7 @@ def sort_and_view(unspent):
|
|||
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 = trim_exponent(sum([i['amount'] for i in unspent]))
|
||||
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'
|
||||
|
|
@ -188,7 +188,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
|||
elif sort == 'txid' and a['txid'] == b['txid']: b['skip'] = 'txid'
|
||||
|
||||
for i in unsp:
|
||||
amt = str(trim_exponent(i['amount']))
|
||||
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))
|
||||
|
|
@ -216,7 +216,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
|||
if group and (sort == 'address' or sort == 'txid'):
|
||||
sort_info.append('grouped')
|
||||
|
||||
out = [hdr_fmt % (' '.join(sort_info), total), table_hdr]
|
||||
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)]
|
||||
|
||||
|
|
@ -301,12 +301,12 @@ def mmaddr2btcaddr_unspent(unspent,mmaddr):
|
|||
def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f):
|
||||
|
||||
# assume mmaddr has already been checked
|
||||
btcaddr = ail_w.mmaddr2btcaddr(mmaddr)
|
||||
btc_addr = ail_w.mmaddr2btcaddr(mmaddr)
|
||||
|
||||
if not btcaddr:
|
||||
if not btc_addr:
|
||||
if ail_f:
|
||||
btcaddr = ail_f.mmaddr2btcaddr(mmaddr)
|
||||
if btcaddr:
|
||||
btc_addr = ail_f.mmaddr2btcaddr(mmaddr)
|
||||
if btc_addr:
|
||||
msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr))
|
||||
if not keypress_confirm('Continue anyway?'):
|
||||
sys.exit(1)
|
||||
|
|
@ -315,15 +315,31 @@ def mmaddr2btcaddr(c,mmaddr,ail_w,ail_f):
|
|||
else:
|
||||
die(2,wmsg['addr_not_found_no_addrfile'].format(pnm=pnm,mmgenaddr=mmaddr))
|
||||
|
||||
return btcaddr
|
||||
return btc_addr
|
||||
|
||||
|
||||
def make_b2m_map(inputs_data,tx_out,ail_w,ail_f):
|
||||
d = dict([(d['address'], (d['mmid'],d['comment']))
|
||||
for d in inputs_data if d['mmid']])
|
||||
d.update(ail_w.make_reverse_dict(tx_out.keys()))
|
||||
d.update(ail_f.make_reverse_dict(tx_out.keys()))
|
||||
return d
|
||||
def get_fee_estimate():
|
||||
if 'tx_fee' in opt.set_by_user:
|
||||
return None
|
||||
else:
|
||||
ret = c.estimatefee(opt.tx_confs)
|
||||
if ret != -1:
|
||||
return ret
|
||||
else:
|
||||
m = """
|
||||
Fee estimation failed!
|
||||
Your possible courses of action (from best to worst):
|
||||
1) Re-run script with a different '--tx-confs' parameter (now '{c}')
|
||||
2) Re-run script with the '--tx-fee' option (specify fee manually)
|
||||
3) Accept the global default fee of {f} BTC
|
||||
Accept the global default fee of {f} BTC?
|
||||
""".format(c=opt.tx_confs,f=opt.tx_fee).strip()
|
||||
if keypress_confirm(m):
|
||||
return None
|
||||
else:
|
||||
die(1,'Exiting at user request')
|
||||
|
||||
# main(): execution begins here
|
||||
|
||||
def get_fee_estimate():
|
||||
if 'tx_fee' in opt.set_by_user:
|
||||
|
|
@ -372,16 +388,15 @@ def get_tx_size_and_fee(inputs,outputs):
|
|||
|
||||
cmd_args = opts.init(opts_data)
|
||||
|
||||
if opt.comment_file:
|
||||
comment = get_tx_comment_from_file(opt.comment_file)
|
||||
tx = MMGenTX()
|
||||
|
||||
if opt.comment_file: tx.add_comment(opt.comment_file)
|
||||
|
||||
c = bitcoin_connection()
|
||||
|
||||
if not opt.info:
|
||||
do_license_msg(immed=True)
|
||||
|
||||
tx_out,change_addr = {},''
|
||||
|
||||
addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext]
|
||||
cmd_args = set(cmd_args) - set(addrfiles)
|
||||
|
||||
|
|
@ -397,27 +412,27 @@ if not opt.info:
|
|||
if ',' in a:
|
||||
a1,a2 = split2(a,',')
|
||||
if is_btc_addr(a1):
|
||||
btcaddr = a1
|
||||
btc_addr = a1
|
||||
elif is_mmgen_addr(a1):
|
||||
btcaddr = mmaddr2btcaddr(c,a1,ail_w,ail_f)
|
||||
btc_addr = mmaddr2btcaddr(c,a1,ail_w,ail_f)
|
||||
else:
|
||||
die(2,"%s: unrecognized subargument in argument '%s'" % (a1,a))
|
||||
|
||||
ret = normalize_btc_amt(a2)
|
||||
if ret:
|
||||
tx_out[btcaddr] = ret
|
||||
btc_amt = convert_to_btc_amt(a2)
|
||||
if btc_amt:
|
||||
tx.add_output(btc_addr,btc_amt)
|
||||
else:
|
||||
die(2,"%s: invalid amount in argument '%s'" % (a2,a))
|
||||
elif is_mmgen_addr(a) or is_btc_addr(a):
|
||||
if change_addr:
|
||||
if tx.change_addr:
|
||||
die(2,'ERROR: More than one change address specified: %s, %s' %
|
||||
(change_addr, a))
|
||||
change_addr = a if is_btc_addr(a) else mmaddr2btcaddr(c,a,ail_w,ail_f)
|
||||
tx_out[change_addr] = 0
|
||||
tx.change_addr = a if is_btc_addr(a) else mmaddr2btcaddr(c,a,ail_w,ail_f)
|
||||
tx.add_output(tx.change_addr,Decimal('0'))
|
||||
else:
|
||||
die(2,'%s: unrecognized argument' % a)
|
||||
|
||||
if not tx_out:
|
||||
if not tx.outputs:
|
||||
die(2,'At least one output must be specified on the command line')
|
||||
|
||||
if opt.tx_fee > g.max_tx_fee:
|
||||
|
|
@ -440,13 +455,14 @@ for o in us:
|
|||
del o['account']
|
||||
unspent = sort_and_view(us)
|
||||
|
||||
total = trim_exponent(sum([i['amount'] for i in unspent]))
|
||||
total = sum([i['amount'] for i in unspent])
|
||||
|
||||
msg('Total unspent: %s BTC (%s outputs)' % (total, len(unspent)))
|
||||
msg('Total unspent: %s BTC (%s outputs)' % (normalize_btc_amt(total), len(unspent)))
|
||||
if opt.info: sys.exit()
|
||||
|
||||
send_amt = sum([tx_out[i] for i in tx_out.keys()])
|
||||
msg('Total amount to spend: %s' % ('%s BTC'%send_amt,'Unknown')[bool(send_amt)])
|
||||
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,
|
||||
|
|
@ -465,53 +481,42 @@ while True:
|
|||
if not keypress_confirm('Accept?'):
|
||||
continue
|
||||
|
||||
total_in = trim_exponent(sum([i['amount'] for i in sel_unspent]))
|
||||
tx_size,tx_fee = get_tx_size_and_fee(sel_unspent,tx_out)
|
||||
change = trim_exponent(total_in - (send_amt + tx_fee))
|
||||
tx.copy_inputs(sel_unspent) # makes tx.inputs
|
||||
|
||||
if change >= 0:
|
||||
prompt = 'Transaction produces %s BTC in change. OK?' % change
|
||||
tx.calculate_size_and_fee(fee_estimate) # sets tx.size, tx.fee
|
||||
|
||||
change_amt = tx.sum_inputs() - tx.send_amt - tx.fee
|
||||
|
||||
if change_amt >= 0:
|
||||
prompt = 'Transaction produces %s BTC in change. OK?' % change_amt
|
||||
if keypress_confirm(prompt,default_yes=True):
|
||||
break
|
||||
else:
|
||||
msg(wmsg['not_enough_btc'] % change)
|
||||
msg(wmsg['not_enough_btc'] % change_amt)
|
||||
|
||||
if change > 0 and not change_addr:
|
||||
die(2,wmsg['throwaway_change'] % change)
|
||||
|
||||
if change_addr in tx_out and not change:
|
||||
if change_amt > 0:
|
||||
if not tx.change_addr:
|
||||
die(2,wmsg['throwaway_change'] % change_amt)
|
||||
tx.add_output(tx.change_addr,change_amt)
|
||||
elif tx.change_addr:
|
||||
msg('Warning: Change address will be unused as transaction produces no change')
|
||||
del tx_out[change_addr]
|
||||
tx.del_output(tx.change_addr)
|
||||
|
||||
for k,v in tx_out.items(): tx_out[k] = float(v)
|
||||
if not tx.send_amt:
|
||||
tx.send_amt = change_amt
|
||||
|
||||
if change > 0: tx_out[change_addr] = float(change)
|
||||
dmsg('tx: %s' % tx)
|
||||
|
||||
tx_in = [{'txid':i['txid'], 'vout':i['vout']} for i in sel_unspent]
|
||||
tx.add_comment() # edits an existing comment
|
||||
tx.create_raw(c) # creates tx.hex, tx.txid
|
||||
tx.add_mmaddrs_to_outputs(ail_w,ail_f)
|
||||
tx.add_timestamp()
|
||||
tx.add_blockcount(c)
|
||||
|
||||
dmsg('tx_in: %s\ntx_out: %s' % (repr(tx_in),repr(tx_out)))
|
||||
|
||||
if opt.comment_file:
|
||||
if keypress_confirm('Edit comment?',False):
|
||||
comment = get_tx_comment_from_user(comment)
|
||||
else:
|
||||
if keypress_confirm('Add a comment to transaction?',False):
|
||||
comment = get_tx_comment_from_user()
|
||||
else: comment = False
|
||||
|
||||
tx_hex = c.createrawtransaction(tx_in,tx_out)
|
||||
qmsg('Transaction successfully created')
|
||||
|
||||
amt = send_amt or change
|
||||
tx_id = make_chksum_6(unhexlify(tx_hex)).upper()
|
||||
metadata = tx_id, amt, make_timestamp()
|
||||
dmsg('TX (final): %s' % tx)
|
||||
|
||||
b2m_map = make_b2m_map(sel_unspent,tx_out,ail_w,ail_f)
|
||||
tx.view_with_prompt('View decoded transaction?')
|
||||
|
||||
prompt_and_view_tx_data(c,'View decoded transaction?',
|
||||
sel_unspent,tx_hex,b2m_map,comment,metadata)
|
||||
|
||||
outfile = 'tx_%s[%s].%s' % (tx_id,amt,g.rawtx_ext)
|
||||
data = make_tx_data('{} {} {}'.format(*metadata),
|
||||
tx_hex,sel_unspent,b2m_map,comment)
|
||||
write_data_to_file(outfile,data,'transaction',ask_write_default_yes=False)
|
||||
tx.write_to_file(ask_write_default_yes=False)
|
||||
|
|
|
|||
|
|
@ -44,39 +44,32 @@ else: opts.usage()
|
|||
|
||||
do_license_msg()
|
||||
|
||||
tx_data = get_lines_from_file(infile,'signed transaction data')
|
||||
tx = MMGenTX()
|
||||
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,infile)
|
||||
|
||||
qmsg("Signed transaction file '%s' is valid" % infile)
|
||||
tx.parse_tx_file(infile,'signed transaction data')
|
||||
|
||||
c = bitcoin_connection()
|
||||
|
||||
prompt_and_view_tx_data(c,'View transaction data?',
|
||||
inputs_data,tx_hex,b2m_map,comment,metadata)
|
||||
if not tx.check_signed(c):
|
||||
die(1,'Transaction has no signature!')
|
||||
|
||||
if keypress_confirm('Edit transaction comment?'):
|
||||
comment = get_tx_comment_from_user(comment)
|
||||
data = make_tx_data('{} {} {}'.format(*metadata), tx_hex,
|
||||
inputs_data, b2m_map, comment)
|
||||
write_data_to_file(infile,data,'signed transaction with edited comment')
|
||||
qmsg("Signed transaction file '%s' is valid" % infile)
|
||||
|
||||
tx.view_with_prompt('View transaction data?')
|
||||
|
||||
if tx.add_comment(): # edits an existing comment, returns true if changed
|
||||
tx.write_to_file(ask_write_default_yes=True)
|
||||
|
||||
warn = "Once this transaction is sent, there's no taking it back!"
|
||||
action = 'broadcast this transaction to the network'
|
||||
expect = 'YES, I REALLY WANT TO DO THIS'
|
||||
expect = 'YES, I REALLY WANT TO DO THIS'
|
||||
|
||||
if opt.quiet: warn,expect = '','YES'
|
||||
|
||||
confirm_or_exit(warn, action, expect)
|
||||
confirm_or_exit(warn,action,expect)
|
||||
|
||||
msg('Sending transaction')
|
||||
|
||||
try:
|
||||
tx_id = c.sendrawtransaction(tx_hex)
|
||||
except:
|
||||
die(3,'Unable to send transaction')
|
||||
tx.send(c,bogus=True)
|
||||
|
||||
msg('Transaction sent: %s' % tx_id)
|
||||
|
||||
of = 'tx_{}[{}].txid'.format(*metadata[:2])
|
||||
write_data_to_file(of, tx_id+'\n','transaction ID',ask_overwrite=True)
|
||||
tx.write_txid_to_file()
|
||||
|
|
|
|||
|
|
@ -25,10 +25,11 @@ from mmgen.tx import *
|
|||
from mmgen.seed import SeedSource
|
||||
|
||||
pnm = g.proj_name
|
||||
pnl = pnm.lower()
|
||||
|
||||
# Unneeded, as MMGen transactions cannot contain non-MMGen inputs
|
||||
# -w, --use-wallet-dat Get keys from a running bitcoind
|
||||
opts_data = {
|
||||
'desc': 'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnl),
|
||||
'desc': 'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnm.lower()),
|
||||
'usage': '[opts] <transaction file>... [seed source]...',
|
||||
'options': """
|
||||
-h, --help Print this help message.
|
||||
|
|
@ -64,8 +65,7 @@ opts_data = {
|
|||
-I, --info Display information about the transaction and exit.
|
||||
-t, --terse-info Like '--info', but produce more concise output.
|
||||
-v, --verbose Produce more verbose output
|
||||
-w, --use-wallet-dat Get keys from a running bitcoind
|
||||
""".format(g=g,pnm=pnm,pnl=pnl),
|
||||
""".format(g=g,pnm=pnm,pnl=pnm.lower()),
|
||||
'notes': """
|
||||
|
||||
Transactions with either {pnm} or non-{pnm} input addresses may be signed.
|
||||
|
|
@ -97,7 +97,7 @@ FMT CODES:
|
|||
{f}
|
||||
""".format(
|
||||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||
g=g,pnm=pnm,pnl=pnl
|
||||
g=g,pnm=pnm,pnl=pnm.lower()
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
|
||||
def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
|
||||
|
||||
if seed_id in saved_seeds.keys():
|
||||
if seed_id in saved_seeds:
|
||||
return saved_seeds[seed_id]
|
||||
|
||||
while True:
|
||||
|
|
@ -131,8 +131,7 @@ def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
|
|||
|
||||
if ss.seed.sid == seed_id: return ss.seed.data
|
||||
|
||||
|
||||
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
|
||||
def generate_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
|
||||
|
||||
seed_ids = set([i[:8] for i in mmgen_addrs])
|
||||
vmsg('Need seed%s: %s' % (suf(seed_ids,'k'),' '.join(seed_ids)))
|
||||
|
|
@ -148,70 +147,31 @@ def get_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
|
||||
|
||||
|
||||
def sign_transaction(c,tx_hex,tx_num_str,sig_data,keys=None):
|
||||
|
||||
if keys:
|
||||
qmsg('Passing %s key%s to bitcoind' % (len(keys),suf(keys,'k')))
|
||||
dmsg('Keys:\n %s' % '\n '.join(keys))
|
||||
|
||||
msg_r('Signing transaction{}...'.format(tx_num_str))
|
||||
# from mmgen.rpc import exceptions
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
|
||||
# except exceptions.InvalidAddressOrKey:
|
||||
except: # TODO
|
||||
die(3,'failed\nInvalid address or key')
|
||||
|
||||
return sig_tx
|
||||
|
||||
|
||||
def sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys):
|
||||
|
||||
try:
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
except:
|
||||
# from mmgen.rpc import exceptions
|
||||
# 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)
|
||||
|
||||
try:
|
||||
c.walletpassphrase(passwd, 9999)
|
||||
# except exceptions.WalletPassphraseIncorrect:
|
||||
except: # TODO
|
||||
msg('Passphrase incorrect (or some other error)')
|
||||
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
|
||||
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
ok = tx.sign(c,tx_num_str,keys)
|
||||
|
||||
msg('Locking wallet')
|
||||
try:
|
||||
c.walletlock()
|
||||
except:
|
||||
ret = c.walletlock(ret_on_error=True)
|
||||
if rpc_error(ret):
|
||||
msg('Failed to lock wallet')
|
||||
|
||||
return sig_tx
|
||||
|
||||
|
||||
def check_maps_from_seeds(maplist,desc,infiles,saved_seeds,return_keys=False):
|
||||
|
||||
if not maplist: return []
|
||||
qmsg('Checking {pnm} -> BTC address mappings for {w}s (from seed(s))'.format(
|
||||
pnm=pnm,w=desc))
|
||||
d = get_keys_for_mmgen_addrs(maplist.keys(),infiles,saved_seeds)
|
||||
# 0=mmaddr 1=addr 2=wif
|
||||
m = dict([(e[0],e[1]) for e in d])
|
||||
for a,b in zip(sorted(m),sorted(maplist)):
|
||||
if a != b:
|
||||
al,bl = 'generated seed:','tx file:'
|
||||
die(3,wmsg['mm2btc_mapping_error'] % (al,a,m[a],bl,b,maplist[b]))
|
||||
if return_keys:
|
||||
ret = [e[2] for e in d]
|
||||
vmsg('Added %s wif key%s from seeds' % (len(ret),suf(ret,'k')))
|
||||
return ret
|
||||
return ok
|
||||
|
||||
def missing_keys_errormsg(addrs):
|
||||
Msg("""
|
||||
|
|
@ -219,7 +179,6 @@ A key file must be supplied (or use the '--use-wallet-dat' option)
|
|||
for the following non-{pnm} address{suf}:\n {l}""".format(
|
||||
pnm=pnm, suf=suf(addrs,'a'), l='\n '.join(addrs)).strip())
|
||||
|
||||
|
||||
def parse_mmgen_keyaddr_file():
|
||||
from mmgen.addr import AddrInfo
|
||||
ai = AddrInfo(opt.mmgen_keys_from_file,has_keys=True)
|
||||
|
|
@ -229,21 +188,18 @@ def parse_mmgen_keyaddr_file():
|
|||
return dict(
|
||||
[('%s:%s'%(ai.seed_id,e.idx), (e.addr,e.wif)) for e in ai.addrdata])
|
||||
|
||||
|
||||
def parse_keylist(from_file):
|
||||
def parse_keylist(key_data):
|
||||
fn = opt.keys_from_file
|
||||
from mmgen.crypto import mmgen_decrypt_file_maybe
|
||||
dec = mmgen_decrypt_file_maybe(fn,'non-{} keylist file'.format(pnm))
|
||||
keys_all = remove_comments(dec.splitlines()) # DOS-safe
|
||||
# Key list could be bitcoind dump, so remove first space and everything following
|
||||
keys_all = [k.split()[0] for k in keys_all]
|
||||
keys_all = set(keys_all) # Remove possible dups
|
||||
keys_all = set([line.split()[0] for line in remove_comments(dec.splitlines())]) # DOS-safe
|
||||
dmsg(repr(keys_all))
|
||||
d = from_file['mmdata']
|
||||
kawifs = [d[k][1] for k in d.keys()]
|
||||
keys = [k for k in keys_all if k not in kawifs]
|
||||
ka_keys = [d[k][1] for k in key_data['kafile']]
|
||||
keys = [k for k in keys_all if k not in ka_keys]
|
||||
removed = len(keys_all) - len(keys)
|
||||
if removed: vmsg(wmsg['removed_dups'] % (removed,suf(removed,'k')))
|
||||
if removed:
|
||||
vmsg(wmsg['removed_dups'] % (removed,suf(removed,'k')))
|
||||
addrs = []
|
||||
wif2addr_f = get_wif2addr_f()
|
||||
for n,k in enumerate(keys,1):
|
||||
|
|
@ -253,41 +209,55 @@ def parse_keylist(from_file):
|
|||
|
||||
return dict(zip(addrs,keys))
|
||||
|
||||
|
||||
# Check inputs and outputs maps against key-address file, deleting entries:
|
||||
def check_maps_from_kafile(imap,desc,kadata,return_keys=False):
|
||||
def check_maps_from_kafile(io_map,desc,kadata,return_keys=False):
|
||||
if not kadata: return []
|
||||
qmsg('Checking {pnm} -> BTC address mappings for {w}s (from key-address file)'.format(pnm=pnm,w=desc))
|
||||
ret = []
|
||||
for k in imap.keys():
|
||||
if k in kadata.keys():
|
||||
if kadata[k][0] == imap[k]:
|
||||
del imap[k]
|
||||
for k in io_map.keys():
|
||||
if k in kadata:
|
||||
if kadata[k][0] == io_map[k]:
|
||||
del io_map[k]
|
||||
ret += [kadata[k][1]]
|
||||
else:
|
||||
kl,il = 'key-address file:','tx file:'
|
||||
die(2,wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,imap[k]))
|
||||
die(2,wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,io_map[k]))
|
||||
if ret: vmsg('Removed %s address%s from %ss map' % (len(ret),suf(ret,'a'),desc))
|
||||
if return_keys:
|
||||
vmsg('Added %s wif key%s from %ss map' % (len(ret),suf(ret,'k'),desc))
|
||||
return ret
|
||||
|
||||
# Check inputs and outputs maps against values generated from seeds
|
||||
def check_maps_from_seeds(io_map,desc,infiles,saved_seeds,return_keys=False):
|
||||
|
||||
def get_keys_from_keylist(kldata,other_addrs):
|
||||
if not io_map: return []
|
||||
qmsg('Checking {pnm} -> BTC address mappings for {w}s (from seed(s))'.format(
|
||||
pnm=pnm,w=desc))
|
||||
d = generate_keys_for_mmgen_addrs(io_map.keys(),infiles,saved_seeds)
|
||||
# 0=mmaddr 1=addr 2=wif
|
||||
m = dict([(e[0],e[1]) for e in d])
|
||||
for a,b in zip(sorted(m),sorted(io_map)):
|
||||
if a != b:
|
||||
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')))
|
||||
return [e[2] for e in d]
|
||||
|
||||
def get_keys_from_keylist(kldata,addrs):
|
||||
ret = []
|
||||
for addr in other_addrs[:]:
|
||||
if addr in kldata.keys():
|
||||
for addr in addrs[:]:
|
||||
if addr in kldata:
|
||||
ret += [kldata[addr]]
|
||||
other_addrs.remove(addr)
|
||||
addrs.remove(addr)
|
||||
vmsg('Added %s wif key%s from user-supplied keylist' %
|
||||
(len(ret),suf(ret,'k')))
|
||||
return ret
|
||||
|
||||
# main(): execution begins here
|
||||
|
||||
infiles = opts.init(opts_data,add_opts=['b16'])
|
||||
|
||||
#if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True
|
||||
|
||||
if not infiles: opts.usage()
|
||||
for i in infiles: check_infile(i)
|
||||
|
||||
|
|
@ -295,16 +265,21 @@ c = bitcoin_connection()
|
|||
|
||||
saved_seeds = {}
|
||||
tx_files = [i for i in infiles if get_extension(i) == g.rawtx_ext]
|
||||
seed_files = [i for i in infiles if get_extension(i) != g.rawtx_ext]
|
||||
seed_files = [i for i in infiles if get_extension(i) in g.seedfile_exts]
|
||||
|
||||
if not tx_files:
|
||||
die(1,'You must specify a raw transaction file!')
|
||||
if not (seed_files or opt.mmgen_keys_from_file or opt.keys_from_file or opt.use_wallet_dat):
|
||||
die(1,'You must specify a seed or key source!')
|
||||
|
||||
if not opt.info and not opt.terse_info:
|
||||
do_license_msg(immed=True)
|
||||
|
||||
from_file = { 'mmdata':{}, 'kldata':{} }
|
||||
key_data = { 'kafile':{}, 'klfile':{} }
|
||||
if opt.mmgen_keys_from_file:
|
||||
from_file['mmdata'] = parse_mmgen_keyaddr_file() or {}
|
||||
key_data['kafile'] = parse_mmgen_keyaddr_file() or {}
|
||||
if opt.keys_from_file:
|
||||
from_file['kldata'] = parse_keylist(from_file) or {}
|
||||
key_data['klfile'] = parse_keylist(key_data) or {}
|
||||
|
||||
tx_num_str = ''
|
||||
for tx_num,tx_file in enumerate(tx_files,1):
|
||||
|
|
@ -312,72 +287,54 @@ 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
|
||||
|
||||
m = ('transaction data','')[bool(opt.tx_id)]
|
||||
tx_data = get_lines_from_file(tx_file,m)
|
||||
tx = MMGenTX()
|
||||
tx.parse_tx_file(tx_file,('transaction data','')[bool(opt.tx_id)])
|
||||
|
||||
if tx.check_signed(c):
|
||||
die(1,'Transaction is already signed!')
|
||||
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,tx_file)
|
||||
vmsg("Successfully opened transaction file '%s'" % tx_file)
|
||||
|
||||
if opt.tx_id: die(0,metadata[0])
|
||||
if opt.tx_id: die(0,tx.txid)
|
||||
|
||||
if opt.info or opt.terse_info:
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pause=False,
|
||||
terse=opt.terse_info)
|
||||
tx.view(pause=False,terse=opt.terse_info)
|
||||
sys.exit()
|
||||
|
||||
prompt_and_view_tx_data(c,'View data for transaction{}?'.format(tx_num_str),
|
||||
inputs_data,tx_hex,b2m_map,comment,metadata)
|
||||
tx.view_with_prompt('View data for transaction%s?' % tx_num_str)
|
||||
|
||||
# Start
|
||||
other_addrs = list(set([i['address'] for i in inputs_data if not i['mmid']]))
|
||||
other_addrs = list(set([i['address'] for i in tx.inputs if not i['mmid']]))
|
||||
|
||||
keys = get_keys_from_keylist(from_file['kldata'],other_addrs)
|
||||
# should remove all elements from other_addrs
|
||||
keys = get_keys_from_keylist(key_data['klfile'],other_addrs)
|
||||
|
||||
if other_addrs and not opt.use_wallet_dat:
|
||||
missing_keys_errormsg(other_addrs)
|
||||
sys.exit(2)
|
||||
|
||||
imap = dict([(i['mmid'],i['address']) for i in inputs_data if i['mmid']])
|
||||
omap = dict([(j[0],i) for i,j in b2m_map.items()])
|
||||
sids = set([i[:8] for i in imap.keys()])
|
||||
imap = dict([(i['mmid'],i['address']) for i in tx.inputs if i['mmid']])
|
||||
omap = dict([(tx.outputs[k][1],k) for k in tx.outputs if len(tx.outputs[k]) > 1])
|
||||
sids = set([i[:8] for i in imap])
|
||||
|
||||
keys += check_maps_from_kafile(imap,'input',from_file['mmdata'],True)
|
||||
check_maps_from_kafile(omap,'output',from_file['mmdata'])
|
||||
keys += check_maps_from_kafile(imap,'input',key_data['kafile'],True)
|
||||
check_maps_from_kafile(omap,'output',key_data['kafile'])
|
||||
|
||||
keys += check_maps_from_seeds(imap,'input',seed_files,saved_seeds,True)
|
||||
check_maps_from_seeds(omap,'output',seed_files,saved_seeds)
|
||||
|
||||
extra_sids = set(saved_seeds.keys()) - sids
|
||||
extra_sids = set(saved_seeds) - sids
|
||||
if extra_sids:
|
||||
msg('Unused Seed ID%s: %s' %
|
||||
(suf(extra_sids,'k'),' '.join(extra_sids)))
|
||||
|
||||
# Begin signing
|
||||
sig_data = [
|
||||
{'txid':i['txid'],'vout':i['vout'],'scriptPubKey':i['scriptPubKey']}
|
||||
for i in inputs_data]
|
||||
# if opt.use_wallet_dat:
|
||||
# ok = sign_tx_with_bitcoind_wallet(c,tx,tx_num_str,keys)
|
||||
# else:
|
||||
ok = tx.sign(c,tx_num_str,keys)
|
||||
|
||||
if opt.use_wallet_dat:
|
||||
sig_tx = sign_tx_with_bitcoind_wallet(
|
||||
c,tx_hex,tx_num_str,sig_data,keys)
|
||||
if ok:
|
||||
tx.add_comment() # edits an existing comment
|
||||
tx.write_to_file(ask_write_default_yes=True)
|
||||
else:
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
|
||||
if sig_tx['complete']:
|
||||
msg('OK')
|
||||
if keypress_confirm('Edit transaction comment?'):
|
||||
comment = get_tx_comment_from_user(comment)
|
||||
outfile = 'tx_%s[%s].%s' % (metadata[0],metadata[1],g.sigtx_ext)
|
||||
data = make_tx_data(
|
||||
'{} {} {t}'.format(*metadata[:2],
|
||||
t=make_timestamp()),
|
||||
sig_tx['hex'], inputs_data, b2m_map, comment
|
||||
)
|
||||
write_data_to_file(
|
||||
outfile,data,
|
||||
'signed transaction{}'.format(tx_num_str),
|
||||
ask_write_prompt='Save signed transaction?'
|
||||
)
|
||||
else:
|
||||
msg_r('failed\nSome keys were missing. ')
|
||||
die(3,'Transaction %scould not be signed.' % tx_num_str)
|
||||
die(3,'failed\nSome keys were missing. Transaction %scould not be signed.' % tx_num_str)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
obj.py: The MMGenObject class and methods
|
||||
"""
|
||||
import mmgen.globalvars as g
|
||||
from decimal import Decimal
|
||||
|
||||
lvl = 0
|
||||
|
||||
|
|
@ -38,16 +39,16 @@ class MMGenObject(object):
|
|||
|
||||
def conv(v,col_w):
|
||||
vret = ''
|
||||
if type(v) == str:
|
||||
if type(v) in (str,unicode):
|
||||
if not (set(list(v)) <= set(list(g.printable))):
|
||||
vret = repr(v)
|
||||
else:
|
||||
vret = fix_linebreaks(v,fixed_indent=0)
|
||||
elif type(v) == int or type(v) == long:
|
||||
elif type(v) in (int,long,Decimal):
|
||||
vret = str(v)
|
||||
elif type(v) == dict:
|
||||
sep = '\n{}{}'.format(indent,' '*4)
|
||||
cw = max(len(k) for k in v) + 2
|
||||
cw = (max(len(k) for k in v) if v else 0) + 2
|
||||
t = sep.join(['{:<{w}}: {}'.format(
|
||||
repr(k),
|
||||
(fix_linebreaks(v[k],fixed_indent=0) if type(v[k]) == str else v[k]),
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ def init(opts_data,add_opts=[],opt_filter=None):
|
|||
for k in 'prog_name','desc','usage','options','notes':
|
||||
if k in opts_data: del opts_data[k]
|
||||
|
||||
# Transfer uopts into opt, setting required opts to None if not set by user
|
||||
# Transfer uopts into opt, setting program's opts + required opts to None if not set by user
|
||||
for o in [s.rstrip('=') for s in long_opts] + \
|
||||
g.required_opts + add_opts + skipped_opts:
|
||||
setattr(opt,o,uopts[o] if o in uopts else None)
|
||||
|
|
|
|||
64
mmgen/rpc.py
64
mmgen/rpc.py
|
|
@ -52,9 +52,12 @@ class BitcoinRPCConnection(object):
|
|||
# Normal mode: call with arg list unrolled, exactly as with 'bitcoin-cli'
|
||||
# Batch mode: call with list of arg lists as first argument
|
||||
# kwargs are for local use and are not passed to server
|
||||
|
||||
# By default, dies with an error msg on all errors and exceptions
|
||||
# With ret_on_error=True, returns 'rpcfail',(resp_object,(die_args))
|
||||
def request(self,cmd,*args,**kwargs):
|
||||
|
||||
cf = { 'timeout': g.http_timeout, 'batch': False }
|
||||
cf = { 'timeout':g.http_timeout, 'batch':False, 'ret_on_error':False }
|
||||
|
||||
for k in cf:
|
||||
if k in kwargs and kwargs[k]: cf[k] = kwargs[k]
|
||||
|
|
@ -66,6 +69,12 @@ class BitcoinRPCConnection(object):
|
|||
else:
|
||||
p = {'method':cmd,'params':args,'id':1}
|
||||
|
||||
def die_maybe(*args):
|
||||
if cf['ret_on_error']:
|
||||
return 'rpcfail',args
|
||||
else:
|
||||
die(*args[1:])
|
||||
|
||||
dmsg('=== rpc.py debug ===')
|
||||
dmsg(' RPC POST data ==> %s\n' % p)
|
||||
|
||||
|
|
@ -75,34 +84,33 @@ class BitcoinRPCConnection(object):
|
|||
'Authorization': 'Basic ' + base64.b64encode(self.auth_str)
|
||||
})
|
||||
except Exception as e:
|
||||
die(2,'%s\nUnable to connect to bitcoind' % e)
|
||||
return die_maybe(None,2,'%s\nUnable to connect to bitcoind' % e)
|
||||
|
||||
r = c.getresponse() # returns HTTPResponse instance
|
||||
|
||||
if r.status == 401:
|
||||
m1 = 'RPC authentication error'
|
||||
m2 = 'Check that rpcuser/rpcpassword in Bitcoin config file are correct'
|
||||
m3 = '(or, alternatively, copy the authentication cookie to Bitcoin data dir'
|
||||
m4 = 'if {} and Bitcoin are running as different users)'.format(g.proj_name)
|
||||
die(1,'\n'.join((m1,m2,m3,m4)))
|
||||
elif r.status != 200:
|
||||
die(1,'RPC error: %s %s\n%s' % (r.status, r.reason, r.read()))
|
||||
if r.status != 200:
|
||||
e1 = r.read()
|
||||
try:
|
||||
e2 = json.loads(e1)['error']['message']
|
||||
except:
|
||||
e2 = str(e1)
|
||||
return die_maybe(r,1,e2)
|
||||
|
||||
r2 = r.read()
|
||||
|
||||
dmsg(' RPC REPLY data ==> %s\n' % r2)
|
||||
|
||||
if not r2:
|
||||
die(2,'Error: empty reply')
|
||||
return die_maybe(r,2,'Error: empty reply')
|
||||
|
||||
r3 = json.loads(r2.decode('utf8'), parse_float=decimal.Decimal)
|
||||
ret = []
|
||||
|
||||
for resp in r3 if cf['batch'] else [r3]:
|
||||
if 'error' in resp and resp['error'] != None:
|
||||
die(1,'Bitcoind returned an error: %s' % resp['error'])
|
||||
return die_maybe(r,1,'Bitcoind returned an error: %s' % resp['error'])
|
||||
elif 'result' not in resp:
|
||||
die(1, 'Missing JSON-RPC result\n' + repr(resps))
|
||||
return die_maybe(r,1, 'Missing JSON-RPC result\n' + repr(resps))
|
||||
else:
|
||||
ret.append(resp['result'])
|
||||
|
||||
|
|
@ -110,20 +118,28 @@ class BitcoinRPCConnection(object):
|
|||
|
||||
|
||||
rpcmethods = (
|
||||
'estimatefee',
|
||||
'getinfo',
|
||||
'getbalance',
|
||||
'getaddressesbyaccount',
|
||||
'listunspent',
|
||||
'listaccounts',
|
||||
'importaddress',
|
||||
'decoderawtransaction',
|
||||
'createrawtransaction',
|
||||
'signrawtransaction',
|
||||
'backupwallet',
|
||||
'decoderawtransaction',
|
||||
'estimatefee',
|
||||
'getaddressesbyaccount',
|
||||
'getbalance',
|
||||
'getblock',
|
||||
'getblockcount',
|
||||
'getblockhash',
|
||||
'getinfo',
|
||||
'importaddress',
|
||||
'listaccounts',
|
||||
'listunspent',
|
||||
'sendrawtransaction',
|
||||
'walletpassphrase',
|
||||
'walletlock',
|
||||
'signrawtransaction',
|
||||
)
|
||||
|
||||
for name in rpcmethods:
|
||||
exec "def {n}(self,*a,**k):return self.request('{n}',*a,**k)\n".format(n=name)
|
||||
|
||||
def rpc_error(ret):
|
||||
return ret is list and ret and ret[0] == 'rpcfail'
|
||||
|
||||
def rpc_errmsg(ret,e):
|
||||
return (False,True)[ret[1][2].find(e) == -1]
|
||||
|
|
|
|||
|
|
@ -432,10 +432,10 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
|
|||
for k in sorted(addrs,key=s_mmgen):
|
||||
if out and k.split('_')[0] != out[-1].split(':')[0]: out.append('')
|
||||
baddr = ' ' + addrs[k][2] if showbtcaddrs else ''
|
||||
out.append(fs % (k.replace('_',':'), baddr, addrs[k][1], trim_exponent(addrs[k][0])))
|
||||
out.append(fs % (k.replace('_',':'), baddr, addrs[k][1], normalize_btc_amt(addrs[k][0])))
|
||||
|
||||
o = (fs + '\n%s\nTOTAL: %s BTC') % (
|
||||
'ADDRESS','','COMMENT','BALANCE', '\n'.join(out), trim_exponent(total)
|
||||
'ADDRESS','','COMMENT','BALANCE', '\n'.join(out), normalize_btc_amt(total)
|
||||
)
|
||||
if pager: do_pager(o)
|
||||
else: Msg(o)
|
||||
|
|
@ -444,7 +444,9 @@ def listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=Fa
|
|||
def getbalance(minconf=1):
|
||||
|
||||
accts = {}
|
||||
for d in bitcoin_connection().listunspent(0):
|
||||
us = bitcoin_connection().listunspent(0)
|
||||
# pp_die(us)
|
||||
for d in us:
|
||||
ma = split2(d['account'])[0]
|
||||
keys = ['TOTAL']
|
||||
if d['spendable']: keys += ['SPENDABLE']
|
||||
|
|
@ -453,7 +455,7 @@ def getbalance(minconf=1):
|
|||
i = (1,2)[confs >= minconf]
|
||||
|
||||
for key in keys:
|
||||
if key not in accts: accts[key] = [0,0,0]
|
||||
if key not in accts: accts[key] = [Decimal('0')] * 3
|
||||
for j in ([],[0])[confs==0] + [i]:
|
||||
accts[key][j] += d['amount']
|
||||
|
||||
|
|
@ -461,15 +463,14 @@ def getbalance(minconf=1):
|
|||
mc,lbl = str(minconf),'confirms'
|
||||
Msg(fs.format('Wallet','Unconfirmed','<%s %s'%(mc,lbl),'>=%s %s'%(mc,lbl)))
|
||||
for key in sorted(accts.keys()):
|
||||
Msg(fs.format(key+':', *[str(trim_exponent(a))+' BTC'
|
||||
for a in accts[key]]))
|
||||
line = [str(normalize_btc_amt(a))+' BTC' for a in accts[key]]
|
||||
Msg(fs.format(key+':', *line))
|
||||
|
||||
def txview(infile,pager=False,terse=False):
|
||||
c = bitcoin_connection()
|
||||
tx_data = get_lines_from_file(infile,'transaction data')
|
||||
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,infile)
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager,pause=False,terse=terse)
|
||||
tx = MMGenTX()
|
||||
tx.parse_tx_file(infile,'transaction data')
|
||||
tx.view(pager,pause=False,terse=terse)
|
||||
|
||||
def add_label(mmaddr,label,remove=False):
|
||||
if not is_mmgen_addr(mmaddr):
|
||||
|
|
|
|||
538
mmgen/tx.py
538
mmgen/tx.py
|
|
@ -22,40 +22,61 @@ tx.py: Bitcoin transaction routines
|
|||
|
||||
import sys, os
|
||||
from stat import *
|
||||
from binascii import unhexlify
|
||||
from binascii import hexlify,unhexlify
|
||||
from decimal import Decimal
|
||||
from collections import OrderedDict
|
||||
|
||||
from mmgen.common import *
|
||||
from mmgen.term import do_pager
|
||||
|
||||
def trim_exponent(n):
|
||||
def normalize_btc_amt(amt):
|
||||
'''Remove exponent and trailing zeros.
|
||||
'''
|
||||
d = Decimal(n)
|
||||
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
|
||||
# to_integral() needed to keep ints > 9 from being shown in exp. notation
|
||||
if is_btc_amt(amt):
|
||||
return amt.quantize(Decimal(1)) if amt == amt.to_integral() else amt.normalize()
|
||||
else:
|
||||
die(2,'%s: not a BTC amount' % amt)
|
||||
|
||||
def normalize_btc_amt(amt):
|
||||
def is_btc_amt(amt):
|
||||
|
||||
if type(amt) is not Decimal:
|
||||
msg('%s: not a decimal number' % amt)
|
||||
return False
|
||||
|
||||
if amt.as_tuple()[-1] < -g.btc_amt_decimal_places:
|
||||
msg('%s: Too many decimal places in amount' % amt)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def convert_to_btc_amt(amt,return_on_fail=False):
|
||||
# amt must be a string!
|
||||
|
||||
from decimal import Decimal
|
||||
try:
|
||||
ret = Decimal(amt)
|
||||
except:
|
||||
msg('%s: Invalid BTC amount' % amt)
|
||||
return False
|
||||
m = '%s: amount cannot be converted to decimal' % amt
|
||||
if return_on_fail:
|
||||
msg(m); return False
|
||||
else:
|
||||
die(2,m)
|
||||
|
||||
dmsg('Decimal(amt): %s\nAs tuple: %s' % (amt,repr(ret.as_tuple())))
|
||||
dmsg('Decimal(amt): %s' % repr(amt))
|
||||
|
||||
if ret.as_tuple()[-1] < -8:
|
||||
msg('%s: Too many decimal places in amount' % amt)
|
||||
return False
|
||||
if ret.as_tuple()[-1] < -g.btc_amt_decimal_places:
|
||||
m = '%s: Too many decimal places in amount' % amt
|
||||
if return_on_fail:
|
||||
msg(m); return False
|
||||
else:
|
||||
die(2,m)
|
||||
|
||||
if ret == 0:
|
||||
msg('Requested zero BTC amount')
|
||||
return False
|
||||
msg('WARNING: BTC amount is zero')
|
||||
|
||||
return ret
|
||||
|
||||
return trim_exponent(ret)
|
||||
|
||||
def parse_mmgen_label(s,check_label_len=False):
|
||||
l = split2(s)
|
||||
|
|
@ -129,140 +150,6 @@ Only ASCII printable characters are permitted.
|
|||
""".strip() % (ch,label))
|
||||
sys.exit(3)
|
||||
|
||||
def prompt_and_view_tx_data(c,prompt,inputs_data,tx_hex,adata,comment,metadata):
|
||||
|
||||
prompt += ' (y)es, (N)o, pager (v)iew, (t)erse view'
|
||||
|
||||
reply = prompt_and_get_char(prompt,'YyNnVvTt',enter_ok=True)
|
||||
|
||||
if reply and reply in 'YyVvTt':
|
||||
view_tx_data(c,inputs_data,tx_hex,adata,comment,metadata,
|
||||
pager=reply in 'Vv',terse=reply in 'Tt')
|
||||
|
||||
|
||||
def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False,pause=True,terse=False):
|
||||
|
||||
td = c.decoderawtransaction(tx_hex)
|
||||
|
||||
fs = (
|
||||
'TRANSACTION DATA\n\nHeader: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n',
|
||||
'Transaction {} - {} BTC - {} UTC\n'
|
||||
)[bool(terse)]
|
||||
|
||||
out = fs.format(*metadata)
|
||||
|
||||
enl = ('\n','')[bool(terse)]
|
||||
if comment: out += 'Comment: %s\n%s' % (comment,enl)
|
||||
out += 'Inputs:\n' + enl
|
||||
|
||||
nonmm_str = 'non-{pnm} address'.format(pnm=g.proj_name)
|
||||
|
||||
total_in = 0
|
||||
for n,i in enumerate(td['vin']):
|
||||
for j in inputs_data:
|
||||
if j['txid'] == i['txid'] and j['vout'] == i['vout']:
|
||||
days = int(j['confirmations'] * g.mins_per_block / (60*24))
|
||||
total_in += j['amount']
|
||||
if not j['mmid']: j['mmid'] = nonmm_str
|
||||
mmid_fmt = ' ({:>{l}})'.format(j['mmid'],l=34-len(j['address']))
|
||||
if terse:
|
||||
out += ' %s: %-54s %s BTC' % (n+1,j['address'] + mmid_fmt,
|
||||
trim_exponent(j['amount']))
|
||||
else:
|
||||
for d in (
|
||||
(n+1, 'tx,vout:', '%s,%s' % (i['txid'], i['vout'])),
|
||||
('', 'address:', j['address'] + mmid_fmt),
|
||||
('', 'comment:', j['comment']),
|
||||
('', 'amount:', '%s BTC' % trim_exponent(j['amount'])),
|
||||
('', 'confirmations:', '%s (around %s days)' % (j['confirmations'], days))
|
||||
):
|
||||
if d[2]: out += ('%3s %-8s %s\n' % d)
|
||||
out += '\n'
|
||||
|
||||
break
|
||||
total_out = 0
|
||||
out += 'Outputs:\n' + enl
|
||||
for n,i in enumerate(td['vout']):
|
||||
btcaddr = i['scriptPubKey']['addresses'][0]
|
||||
mmid,comment=b2m_map[btcaddr] if btcaddr in b2m_map else (nonmm_str,'')
|
||||
mmid_fmt = ' ({:>{l}})'.format(mmid,l=34-len(j['address']))
|
||||
total_out += i['value']
|
||||
if terse:
|
||||
out += ' %s: %-54s %s BTC' % (n+1,btcaddr + mmid_fmt,
|
||||
trim_exponent(i['value']))
|
||||
else:
|
||||
for d in (
|
||||
(n+1, 'address:', btcaddr + mmid_fmt),
|
||||
('', 'comment:', comment),
|
||||
('', 'amount:', trim_exponent(i['value']))
|
||||
):
|
||||
if d[2]: out += ('%3s %-8s %s\n' % d)
|
||||
out += '\n'
|
||||
|
||||
fs = (
|
||||
'Total input: %s BTC\nTotal output: %s BTC\nTX fee: %s BTC\n',
|
||||
'In %s BTC - Out %s BTC - Fee %s BTC\n'
|
||||
)[bool(terse)]
|
||||
|
||||
out += fs % (
|
||||
trim_exponent(total_in),
|
||||
trim_exponent(total_out),
|
||||
trim_exponent(total_in-total_out)
|
||||
)
|
||||
|
||||
o = out.encode('utf8')
|
||||
if pager: do_pager(o)
|
||||
else:
|
||||
sys.stdout.write(o)
|
||||
from mmgen.term import get_char
|
||||
if pause:
|
||||
get_char('Press any key to continue: ')
|
||||
msg('')
|
||||
|
||||
|
||||
def parse_tx_file(tx_data,infile):
|
||||
|
||||
err_str,err_fmt = '','Invalid %s in transaction file'
|
||||
|
||||
if len(tx_data) == 5:
|
||||
metadata,tx_hex,inputs_data,outputs_data,comment = tx_data
|
||||
elif len(tx_data) == 4:
|
||||
metadata,tx_hex,inputs_data,outputs_data = tx_data
|
||||
comment = ''
|
||||
else:
|
||||
err_str = 'number of lines'
|
||||
|
||||
if not err_str:
|
||||
if len(metadata.split()) != 3:
|
||||
err_str = 'metadata'
|
||||
else:
|
||||
try: unhexlify(tx_hex)
|
||||
except: err_str = 'hex data'
|
||||
else:
|
||||
try: inputs_data = eval(inputs_data)
|
||||
except: err_str = 'inputs data'
|
||||
else:
|
||||
try: outputs_data = eval(outputs_data)
|
||||
except: err_str = 'mmgen-to-btc address map data'
|
||||
else:
|
||||
if comment:
|
||||
from mmgen.bitcoin import b58decode
|
||||
comment = b58decode(comment)
|
||||
if comment == False:
|
||||
err_str = 'encoded comment (not base58)'
|
||||
else:
|
||||
if is_valid_tx_comment(comment):
|
||||
comment = comment.decode('utf8')
|
||||
else:
|
||||
err_str = 'comment'
|
||||
|
||||
if err_str:
|
||||
msg(err_fmt % err_str)
|
||||
sys.exit(2)
|
||||
else:
|
||||
return metadata.split(),tx_hex,inputs_data,outputs_data,comment
|
||||
|
||||
|
||||
def wiftoaddr_keyconv(wif):
|
||||
if wif[0] == '5':
|
||||
from subprocess import check_output
|
||||
|
|
@ -276,30 +163,6 @@ def get_wif2addr_f():
|
|||
return (wiftoaddr,wiftoaddr_keyconv)[bool(test_for_keyconv())]
|
||||
|
||||
|
||||
def get_tx_comment_from_file(infile):
|
||||
s = get_data_from_file(infile,'transaction comment')
|
||||
if is_valid_tx_comment(s):
|
||||
return s.decode('utf8').strip()
|
||||
else:
|
||||
sys.exit(2)
|
||||
|
||||
def get_tx_comment_from_user(comment=''):
|
||||
try:
|
||||
while True:
|
||||
s = my_raw_input('Comment: ',insert_txt=comment.encode('utf8'))
|
||||
if s == '': return False
|
||||
if is_valid_tx_comment(s):
|
||||
return s.decode('utf8')
|
||||
except KeyboardInterrupt:
|
||||
msg('User interrupt')
|
||||
return False
|
||||
|
||||
def make_tx_data(metadata_fmt, tx_hex, inputs_data, b2m_map, comment):
|
||||
from mmgen.bitcoin import b58encode
|
||||
s = (b58encode(comment.encode('utf8')),) if comment else ()
|
||||
lines = (metadata_fmt, tx_hex, repr(inputs_data), repr(b2m_map)) + s
|
||||
return '\n'.join(lines)+'\n'
|
||||
|
||||
def mmaddr2btcaddr_addrdata(mmaddr,addr_data,source=''):
|
||||
seed_id,idx = mmaddr.split(':')
|
||||
if seed_id in addr_data:
|
||||
|
|
@ -309,3 +172,334 @@ def mmaddr2btcaddr_addrdata(mmaddr,addr_data,source=''):
|
|||
return addr_data[seed_id][idx]
|
||||
|
||||
return '',''
|
||||
|
||||
from mmgen.obj import *
|
||||
|
||||
class MMGenTX(MMGenObject):
|
||||
def __init__(self,filename=None):
|
||||
self.inputs = []
|
||||
self.outputs = {}
|
||||
self.change_addr = ''
|
||||
self.size = 0 # size of raw serialized tx
|
||||
self.fee = Decimal('0')
|
||||
self.send_amt = Decimal('0') # total amt minus change
|
||||
self.hex = '' # raw serialized hex transaction
|
||||
self.comment = ''
|
||||
self.txid = ''
|
||||
self.btc_txid = ''
|
||||
self.timestamp = ''
|
||||
self.chksum = ''
|
||||
self.fmt_data = ''
|
||||
self.blockcount = 0
|
||||
self.isSigned = False
|
||||
|
||||
def add_output(self,btcaddr,amt):
|
||||
self.outputs[btcaddr] = (amt,)
|
||||
|
||||
def del_output(self,btcaddr):
|
||||
del self.outputs[btcaddr]
|
||||
|
||||
def sum_outputs(self):
|
||||
return sum([self.outputs[k][0] for k in self.outputs])
|
||||
|
||||
# returns true if comment added or changed
|
||||
def add_comment(self,infile=None):
|
||||
if infile:
|
||||
s = get_data_from_file(infile,'transaction comment')
|
||||
if is_valid_tx_comment(s):
|
||||
self.comment = s.decode('utf8').strip()
|
||||
return True
|
||||
else:
|
||||
sys.exit(2)
|
||||
else: # get comment from user, or edit existing comment
|
||||
m = ('Add a comment to transaction?','Edit transaction comment?')[bool(self.comment)]
|
||||
if keypress_confirm(m,default_yes=False):
|
||||
while True:
|
||||
s = my_raw_input('Comment: ',insert_txt=self.comment.encode('utf8'))
|
||||
if is_valid_tx_comment(s):
|
||||
csave = self.comment
|
||||
self.comment = s.decode('utf8').strip()
|
||||
return (True,False)[csave == self.comment]
|
||||
else:
|
||||
msg('Invalid comment')
|
||||
return False
|
||||
|
||||
def edit_comment(self):
|
||||
return self.add_comment(self)
|
||||
|
||||
# https://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending
|
||||
def calculate_size_and_fee(self,fee_estimate):
|
||||
self.size = len(self.inputs)*180 + len(self.outputs)*34 + 10
|
||||
if fee_estimate:
|
||||
ftype,fee = 'Calculated','{:.8f}'.format(fee_estimate*opt.tx_fee_adj*self.size / 1024)
|
||||
else:
|
||||
ftype,fee = 'User-selected',opt.tx_fee
|
||||
|
||||
ufee = None
|
||||
if not keypress_confirm('{} TX fee: {} BTC. OK?'.format(ftype,fee),default_yes=True):
|
||||
while True:
|
||||
ufee = my_raw_input('Enter transaction fee: ')
|
||||
if convert_to_btc_amt(ufee,return_on_fail=True):
|
||||
if Decimal(ufee) > g.max_tx_fee:
|
||||
msg('{} BTC: fee too large (maximum fee: {} BTC)'.format(ufee,g.max_tx_fee))
|
||||
else:
|
||||
fee = ufee
|
||||
break
|
||||
self.fee = convert_to_btc_amt(fee)
|
||||
vmsg('Inputs:{} Outputs:{} TX size:{}'.format(
|
||||
len(self.inputs),len(self.outputs),self.size))
|
||||
vmsg('Fee estimate: {} (1024 bytes, {} confs)'.format(fee_estimate,opt.tx_confs))
|
||||
m = ('',' (after %sx adjustment)' % opt.tx_fee_adj)[opt.tx_fee_adj != 1 and not ufee]
|
||||
vmsg('TX fee: {}{}'.format(self.fee,m))
|
||||
|
||||
def copy_inputs(self,source):
|
||||
copy_keys = 'txid','vout','amount','comment','mmid','address',\
|
||||
'confirmations','scriptPubKey'
|
||||
self.inputs = [dict([(k,d[k] if k in d else '') for k in copy_keys]) for d in source]
|
||||
|
||||
def sum_inputs(self):
|
||||
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)
|
||||
self.txid = make_chksum_6(unhexlify(self.hex)).upper()
|
||||
|
||||
# def make_b2m_map(self,ail_w,ail_f):
|
||||
# d = dict([(d['address'], (d['mmid'],d['comment']))
|
||||
# for d in self.inputs if d['mmid']])
|
||||
# d = ail_w.make_reverse_dict(self.outputs.keys())
|
||||
# d.update(ail_f.make_reverse_dict(self.outputs.keys()))
|
||||
# self.b2m_map = d
|
||||
|
||||
def add_mmaddrs_to_outputs(self,ail_w,ail_f):
|
||||
d = ail_w.make_reverse_dict(self.outputs.keys())
|
||||
d.update(ail_f.make_reverse_dict(self.outputs.keys()))
|
||||
for k in self.outputs:
|
||||
if k in d:
|
||||
self.outputs[k] += d[k]
|
||||
|
||||
def add_timestamp(self):
|
||||
self.timestamp = make_timestamp()
|
||||
|
||||
def add_blockcount(self,c):
|
||||
self.blockcount = int(c.getblockcount())
|
||||
|
||||
def format(self):
|
||||
from mmgen.bitcoin import b58encode
|
||||
lines = (
|
||||
'{} {} {} {}'.format(
|
||||
self.txid,
|
||||
self.send_amt,
|
||||
self.timestamp,
|
||||
(self.blockcount or 'None')
|
||||
),
|
||||
self.hex,
|
||||
repr(self.inputs),
|
||||
repr(self.outputs)
|
||||
) + ((b58encode(self.comment.encode('utf8')),) if self.comment else ())
|
||||
self.chksum = make_chksum_6(' '.join(lines))
|
||||
self.fmt_data = '\n'.join((self.chksum,) + lines)+'\n'
|
||||
|
||||
# return true or false, don't exit
|
||||
def sign(self,c,tx_num_str,keys=None):
|
||||
|
||||
if keys:
|
||||
qmsg('Passing %s key%s to bitcoind' % (len(keys),suf(keys,'k')))
|
||||
dmsg('Keys:\n %s' % '\n '.join(keys))
|
||||
|
||||
sig_data = [dict([(k,d[k]) for k in 'txid','vout','scriptPubKey']) for d in self.inputs]
|
||||
dmsg('Sig data:\n%s' % pp_format(sig_data))
|
||||
dmsg('Raw hex:\n%s' % self.hex)
|
||||
|
||||
msg_r('Signing transaction{}...'.format(tx_num_str))
|
||||
sig_tx = c.signrawtransaction(self.hex,sig_data,keys)
|
||||
|
||||
if sig_tx['complete']:
|
||||
msg('OK')
|
||||
self.hex = sig_tx['hex']
|
||||
self.isSigned = True
|
||||
return True
|
||||
else:
|
||||
msg('failed\nBitcoind returned the following errors:')
|
||||
pp_msg(sig_tx['errors'])
|
||||
return False
|
||||
|
||||
def check_signed(self,c):
|
||||
d = c.decoderawtransaction(self.hex)
|
||||
self.isSigned = bool(d['vin'][0]['scriptSig']['hex'])
|
||||
return self.isSigned
|
||||
|
||||
def send(self,c,bogus=False):
|
||||
if bogus:
|
||||
self.btc_txid = 'deadbeef' * 8
|
||||
m = 'BOGUS transaction NOT sent: %s'
|
||||
else:
|
||||
self.btc_txid = c.sendrawtransaction(self.hex) # exits on failure?
|
||||
m = 'Transaction sent: %s'
|
||||
msg(m % self.btc_txid)
|
||||
|
||||
def write_txid_to_file(self,ask_write=False,ask_write_default_yes=True):
|
||||
fn = '%s[%s].%s' % (self.txid,self.send_amt,g.txid_ext)
|
||||
write_data_to_file(fn,self.btc_txid+'\n','transaction ID',
|
||||
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):
|
||||
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',
|
||||
ask_write=ask_write,
|
||||
ask_write_default_yes=ask_write_default_yes)
|
||||
|
||||
def view_with_prompt(self,prompt=''):
|
||||
prompt += ' (y)es, (N)o, pager (v)iew, (t)erse view'
|
||||
reply = prompt_and_get_char(prompt,'YyNnVvTt',enter_ok=True)
|
||||
if reply and reply in 'YyVvTt':
|
||||
self.view(pager=reply in 'Vv',terse=reply in 'Tt')
|
||||
|
||||
def view(self,pager=False,pause=True,terse=False):
|
||||
o = self.format_view(terse=terse).encode('utf8')
|
||||
if pager: do_pager(o)
|
||||
else:
|
||||
sys.stdout.write(o)
|
||||
from mmgen.term import get_char
|
||||
if pause:
|
||||
get_char('Press any key to continue: ')
|
||||
msg('')
|
||||
|
||||
def format_view(self,terse=False):
|
||||
try:
|
||||
blockcount = bitcoin_connection().getblockcount()
|
||||
except:
|
||||
blockcount = None
|
||||
|
||||
fs = (
|
||||
'TRANSACTION DATA\n\nHeader: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n',
|
||||
'Transaction {} - {} BTC - {} UTC\n'
|
||||
)[bool(terse)]
|
||||
|
||||
out = fs.format(self.txid,self.send_amt,self.timestamp)
|
||||
|
||||
enl = ('\n','')[bool(terse)]
|
||||
if self.comment:
|
||||
out += 'Comment: %s\n%s' % (self.comment,enl)
|
||||
out += 'Inputs:\n' + enl
|
||||
|
||||
nonmm_str = 'non-{pnm} address'.format(pnm=g.proj_name)
|
||||
|
||||
for n,i in enumerate(self.inputs):
|
||||
if blockcount:
|
||||
confirmations = i['confirmations'] + blockcount - self.blockcount
|
||||
days = int(confirmations * g.mins_per_block / (60*24))
|
||||
if not i['mmid']:
|
||||
i['mmid'] = nonmm_str
|
||||
mmid_fmt = ' ({:>{l}})'.format(i['mmid'],l=34-len(i['address']))
|
||||
if terse:
|
||||
out += ' %s: %-54s %s BTC' % (n+1,i['address'] + mmid_fmt,
|
||||
normalize_btc_amt(i['amount']))
|
||||
else:
|
||||
for d in (
|
||||
(n+1, 'tx,vout:', '%s,%s' % (i['txid'], i['vout'])),
|
||||
('', 'address:', i['address'] + mmid_fmt),
|
||||
('', 'comment:', i['comment']),
|
||||
('', 'amount:', '%s BTC' % normalize_btc_amt(i['amount'])),
|
||||
('', 'confirmations:', '%s (around %s days)' % (confirmations,days) if blockcount else '')
|
||||
):
|
||||
if d[2]: out += ('%3s %-8s %s\n' % d)
|
||||
out += '\n'
|
||||
|
||||
out += 'Outputs:\n' + enl
|
||||
for n,k in enumerate(self.outputs):
|
||||
btcaddr = k
|
||||
v = self.outputs[k]
|
||||
btc_amt,mmid,comment = (v[0],'Non-MMGen address','') if len(v) == 1 else v
|
||||
mmid_fmt = ' ({:>{l}})'.format(mmid,l=34-len(btcaddr))
|
||||
if terse:
|
||||
out += ' %s: %-54s %s BTC' % (n+1, btcaddr+mmid_fmt, normalize_btc_amt(btc_amt))
|
||||
else:
|
||||
for d in (
|
||||
(n+1, 'address:', btcaddr + mmid_fmt),
|
||||
('', 'comment:', comment),
|
||||
('', 'amount:', '%s BTC' % normalize_btc_amt(btc_amt))
|
||||
):
|
||||
if d[2]: out += ('%3s %-8s %s\n' % d)
|
||||
out += '\n'
|
||||
|
||||
fs = (
|
||||
'Total input: %s BTC\nTotal output: %s BTC\nTX fee: %s BTC\n',
|
||||
'In %s BTC - Out %s BTC - Fee %s BTC\n'
|
||||
)[bool(terse)]
|
||||
|
||||
total_in = self.sum_inputs()
|
||||
total_out = self.sum_outputs()
|
||||
out += fs % (
|
||||
normalize_btc_amt(total_in),
|
||||
normalize_btc_amt(total_out),
|
||||
normalize_btc_amt(total_in-total_out)
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
def parse_tx_file(self,infile,desc):
|
||||
|
||||
self.parse_tx_data(get_lines_from_file(infile,desc))
|
||||
|
||||
def parse_tx_data(self,tx_data):
|
||||
|
||||
err_str,err_fmt = '','Invalid %s in transaction file'
|
||||
|
||||
if len(tx_data) == 6:
|
||||
self.chksum,metadata,self.hex,inputs_data,outputs_data,comment = tx_data
|
||||
elif len(tx_data) == 5:
|
||||
self.chksum,metadata,self.hex,inputs_data,outputs_data = tx_data
|
||||
comment = ''
|
||||
else:
|
||||
err_str = 'number of lines'
|
||||
|
||||
if not err_str:
|
||||
if self.chksum != make_chksum_6(' '.join(tx_data[1:])):
|
||||
err_str = 'checksum'
|
||||
elif len(metadata.split()) != 4:
|
||||
err_str = 'metadata'
|
||||
else:
|
||||
self.txid,send_amt,self.timestamp,blockcount = metadata.split()
|
||||
self.send_amt = Decimal(send_amt)
|
||||
self.blockcount = int(blockcount)
|
||||
try: unhexlify(self.hex)
|
||||
except: err_str = 'hex data'
|
||||
else:
|
||||
try: self.inputs = eval(inputs_data)
|
||||
except: err_str = 'inputs data'
|
||||
else:
|
||||
try: self.outputs = eval(outputs_data)
|
||||
except: err_str = 'btc-to-mmgen address map data'
|
||||
else:
|
||||
if comment:
|
||||
from mmgen.bitcoin import b58decode
|
||||
comment = b58decode(comment)
|
||||
if comment == False:
|
||||
err_str = 'encoded comment (not base58)'
|
||||
else:
|
||||
if is_valid_tx_comment(comment):
|
||||
self.comment = comment.decode('utf8')
|
||||
else:
|
||||
err_str = 'comment'
|
||||
|
||||
if err_str:
|
||||
msg(err_fmt % err_str)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -176,12 +176,25 @@ def split_into_cols(col_wid,s):
|
|||
def capfirst(s):
|
||||
return s if len(s) == 0 else s[0].upper() + s[1:]
|
||||
|
||||
def make_timestamp():
|
||||
tv = time.gmtime(time.time())[:6]
|
||||
def decode_timestamp(s):
|
||||
# with open('/etc/timezone') as f:
|
||||
# tz_save = f.read().rstrip()
|
||||
os.environ['TZ'] = 'UTC'
|
||||
ts = time.strptime(s,'%Y%m%d_%H%M%S')
|
||||
t = time.mktime(ts)
|
||||
# os.environ['TZ'] = tz_save
|
||||
return int(t)
|
||||
|
||||
def make_timestamp(secs=None):
|
||||
t = int(secs) if secs else time.time()
|
||||
tv = time.gmtime(t)[:6]
|
||||
return '{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}'.format(*tv)
|
||||
def make_timestr():
|
||||
tv = time.gmtime(time.time())[:6]
|
||||
|
||||
def make_timestr(secs=None):
|
||||
t = int(secs) if secs else time.time()
|
||||
tv = time.gmtime(t)[:6]
|
||||
return '{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}'.format(*tv)
|
||||
|
||||
def secs_to_hms(secs):
|
||||
return '{:02d}:{:02d}:{:02d}'.format(secs/3600, (secs/60) % 60, secs % 60)
|
||||
|
||||
|
|
@ -791,3 +804,15 @@ 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))
|
||||
|
|
|
|||
116
scripts/tx-old2new.py
Executable file
116
scripts/tx-old2new.py
Executable file
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import sys,os
|
||||
repo_root = os.path.split(os.path.abspath(os.path.dirname(sys.argv[0])))[0]
|
||||
sys.path = [repo_root] + sys.path
|
||||
|
||||
from mmgen.common import *
|
||||
|
||||
from mmgen.tool import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.bitcoin import *
|
||||
from mmgen.seed import *
|
||||
from mmgen.term import do_pager
|
||||
|
||||
help_data = {
|
||||
'desc': "Convert MMGen transaction file from old format to new format",
|
||||
'usage': "<tx file>",
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
"""
|
||||
}
|
||||
|
||||
import mmgen.opts
|
||||
cmd_args = opts.init(help_data)
|
||||
|
||||
if len(cmd_args) != 1: opts.usage()
|
||||
|
||||
def parse_tx_file(infile):
|
||||
|
||||
err_str,err_fmt = '','Invalid %s in transaction file'
|
||||
tx_data = get_lines_from_file(infile)
|
||||
|
||||
if len(tx_data) == 5:
|
||||
metadata,tx_hex,inputs_data,outputs_data,comment = tx_data
|
||||
elif len(tx_data) == 4:
|
||||
metadata,tx_hex,inputs_data,outputs_data = tx_data
|
||||
comment = ''
|
||||
else:
|
||||
err_str = 'number of lines'
|
||||
|
||||
if not err_str:
|
||||
if len(metadata.split()) != 3:
|
||||
err_str = 'metadata'
|
||||
else:
|
||||
try: unhexlify(tx_hex)
|
||||
except: err_str = 'hex data'
|
||||
else:
|
||||
try: inputs_data = eval(inputs_data)
|
||||
except: err_str = 'inputs data'
|
||||
else:
|
||||
try: outputs_data = eval(outputs_data)
|
||||
except: err_str = 'btc-to-mmgen address map data'
|
||||
else:
|
||||
if comment:
|
||||
from mmgen.bitcoin import b58decode
|
||||
comment = b58decode(comment)
|
||||
if comment == False:
|
||||
err_str = 'encoded comment (not base58)'
|
||||
else:
|
||||
if is_valid_tx_comment(comment):
|
||||
comment = comment.decode('utf8')
|
||||
else:
|
||||
err_str = 'comment'
|
||||
|
||||
if err_str:
|
||||
msg(err_fmt % err_str)
|
||||
sys.exit(2)
|
||||
else:
|
||||
return metadata.split(),tx_hex,inputs_data,outputs_data,comment
|
||||
|
||||
def find_block_by_time(c,timestamp):
|
||||
secs = decode_timestamp(timestamp)
|
||||
block_num = c.getblockcount()
|
||||
# print 'secs:',secs, 'last block:',last_block
|
||||
top,bot = block_num,0
|
||||
m = 'Searching for block'
|
||||
msg_r(m)
|
||||
for i in range(40):
|
||||
msg_r('.')
|
||||
bhash = c.getblockhash(block_num)
|
||||
block = c.getblock(bhash)
|
||||
# print 'block_num:',block_num, 'mediantime:',block['mediantime'], 'target:',secs
|
||||
cur_secs = block['mediantime']
|
||||
if cur_secs > secs:
|
||||
top = block_num
|
||||
else:
|
||||
bot = block_num
|
||||
block_num = (top + bot) / 2
|
||||
if top - bot < 2:
|
||||
msg('\nFound: %s ' % block_num)
|
||||
break
|
||||
|
||||
return block_num
|
||||
|
||||
tx = MMGenTX()
|
||||
|
||||
[tx.txid,send_amt,tx.timestamp],tx.hex,inputs,b2m_map,tx.comment = parse_tx_file(cmd_args[0])
|
||||
tx.send_amt = Decimal(send_amt)
|
||||
|
||||
c = bitcoin_connection()
|
||||
|
||||
tx.blockcount = find_block_by_time(c,tx.timestamp)
|
||||
|
||||
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']])
|
||||
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)
|
||||
|
|
@ -103,7 +103,7 @@ cfg = {
|
|||
'tmpdir': 'test/tmp10',
|
||||
'tmpdir_num': 10,
|
||||
'refdir': 'test/ref',
|
||||
'txfile': 'tx_FFB367[1.234].raw',
|
||||
'txfile': 'FFB367[1.234].rawtx',
|
||||
'addrfile': '98831F3A[1,31-33,500-501,1010-1011].addrs',
|
||||
'addrfile_chk': '6FEF 6FB9 7B13 5D91 854A 0BD3',
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue