Basic altcoin framework, full Litecoin support
- LTC: tested on mainnet, full test suite support - simultaneous autosigning for all supported coins (BTC,BCH,LTC)
This commit is contained in:
parent
2435f60b03
commit
35d1091159
64 changed files with 2065 additions and 1105 deletions
|
|
@ -2,6 +2,7 @@ include README.md SIGNING_KEY.pub LICENSE INSTALL
|
||||||
include doc/wiki/using-mmgen/*
|
include doc/wiki/using-mmgen/*
|
||||||
include test/*.py
|
include test/*.py
|
||||||
include test/ref/*
|
include test/ref/*
|
||||||
|
include test/ref/litecoin/*
|
||||||
|
|
||||||
include scripts/bitcoind-walletunlock.py
|
include scripts/bitcoind-walletunlock.py
|
||||||
include scripts/compute-file-chksum.py
|
include scripts/compute-file-chksum.py
|
||||||
|
|
|
||||||
|
|
@ -129,3 +129,4 @@ Donate: 15TLdmi5NYLdqmtCqczUs5pBPkJDXRs83w
|
||||||
[9]: https://cloud.githubusercontent.com/assets/6071028/20677261/6ccab1bc-b58a-11e6-8ab6-094f88befef2.jpg
|
[9]: https://cloud.githubusercontent.com/assets/6071028/20677261/6ccab1bc-b58a-11e6-8ab6-094f88befef2.jpg
|
||||||
[r]: https://github.com/mmgen/mmgen/wiki/Recovering-Your-Keys-Without-the-MMGen-Software
|
[r]: https://github.com/mmgen/mmgen/wiki/Recovering-Your-Keys-Without-the-MMGen-Software
|
||||||
[x]: https://github.com/mmgen/mmgen/wiki/Getting-Started-with-MMGen#a_bch
|
[x]: https://github.com/mmgen/mmgen/wiki/Getting-Started-with-MMGen#a_bch
|
||||||
|
[z]: https://user-images.githubusercontent.com/6071028/31656274-a35a1252-b31a-11e7-93b7-3d666f50f70f.png
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
# this program. If not, see <http://www.gnu.org/licenses/>.
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mmgen-passgen: Generate a series or range of passwords from an MMGen
|
mmgen-passgen: Generate a range or series of passwords from an MMGen
|
||||||
deterministic wallet
|
deterministic wallet
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -23,16 +23,16 @@
|
||||||
# Uncomment to use testnet instead of mainnet:
|
# Uncomment to use testnet instead of mainnet:
|
||||||
# testnet true
|
# testnet true
|
||||||
|
|
||||||
# Set the RPC host (the host bitcoind is running on):
|
# Set the RPC host (the host the coin daemon is running on):
|
||||||
# rpc_host localhost
|
# rpc_host localhost
|
||||||
|
|
||||||
# Set the RPC host's port number
|
# Set the RPC host's port number
|
||||||
# rpc_port 8332
|
# rpc_port 8332
|
||||||
|
|
||||||
# Uncomment to override 'rpcuser' in bitcoin.conf
|
# Uncomment to override 'rpcuser' from coin daemon config file
|
||||||
# rpc_user myusername
|
# rpc_user myusername
|
||||||
|
|
||||||
# Uncomment to override 'rpcpassword' in bitcoin.conf
|
# Uncomment to override 'rpcpassword' from coin daemon config file
|
||||||
# rpc_password mypassword
|
# rpc_password mypassword
|
||||||
|
|
||||||
# Uncomment to set the coin daemon datadir
|
# Uncomment to set the coin daemon datadir
|
||||||
|
|
@ -46,8 +46,14 @@
|
||||||
# A value of 0 disables user entropy, but this is not recommended:
|
# A value of 0 disables user entropy, but this is not recommended:
|
||||||
# usr_randchars 30
|
# usr_randchars 30
|
||||||
|
|
||||||
# Set the maximum transaction fee in BTC:
|
# Set the maximum transaction fee for BTC:
|
||||||
# max_tx_fee 0.01
|
# btc_max_tx_fee 0.01
|
||||||
|
|
||||||
|
# Set the maximum transaction fee for BCH:
|
||||||
|
# bch_max_tx_fee 0.1
|
||||||
|
|
||||||
|
# Set the maximum transaction fee for LTC:
|
||||||
|
# ltc_max_tx_fee 0.3
|
||||||
|
|
||||||
# Set the transaction fee adjustment factor. Auto-calculated fees are
|
# Set the transaction fee adjustment factor. Auto-calculated fees are
|
||||||
# multiplied by this value:
|
# multiplied by this value:
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ class AddrGeneratorP2PKH(AddrGenerator):
|
||||||
def to_addr(self,pubhex):
|
def to_addr(self,pubhex):
|
||||||
from mmgen.protocol import hash160
|
from mmgen.protocol import hash160
|
||||||
assert type(pubhex) == PubKey
|
assert type(pubhex) == PubKey
|
||||||
return CoinAddr(g.proto.hexaddr2addr(hash160(pubhex)))
|
return CoinAddr(g.proto.hexaddr2addr(hash160(pubhex),p2sh=False))
|
||||||
|
|
||||||
def to_segwit_redeem_script(self,pubhex):
|
def to_segwit_redeem_script(self,pubhex):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
@ -160,10 +160,9 @@ class AddrListIDStr(unicode,Hilite):
|
||||||
|
|
||||||
if fmt_str:
|
if fmt_str:
|
||||||
ret = fmt_str.format(s)
|
ret = fmt_str.format(s)
|
||||||
elif addrlist.al_id.mmtype == 'L':
|
|
||||||
ret = '{}[{}]'.format(addrlist.al_id.sid,s)
|
|
||||||
else:
|
else:
|
||||||
ret = '{}-{}[{}]'.format(addrlist.al_id.sid,addrlist.al_id.mmtype,s)
|
bc,mt = g.proto.base_coin,addrlist.al_id.mmtype
|
||||||
|
ret = '{}{}{}[{}]'.format(addrlist.al_id.sid,('-'+bc,'')[bc=='BTC'],('-'+mt,'')[mt=='L'],s)
|
||||||
|
|
||||||
return unicode.__new__(cls,ret)
|
return unicode.__new__(cls,ret)
|
||||||
|
|
||||||
|
|
@ -175,7 +174,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID
|
||||||
# This file is editable.
|
# This file is editable.
|
||||||
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
|
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
|
||||||
# A text label of {n} characters or less may be added to the right of each
|
# A text label of {n} characters or less may be added to the right of each
|
||||||
# address, and it will be appended to the bitcoind wallet label upon import.
|
# address, and it will be appended to the tracking wallet label upon import.
|
||||||
# The label may contain any printable ASCII symbol.
|
# The label may contain any printable ASCII symbol.
|
||||||
""".strip().format(n=TwComment.max_len,pnm=pnm),
|
""".strip().format(n=TwComment.max_len,pnm=pnm),
|
||||||
'record_chksum': """
|
'record_chksum': """
|
||||||
|
|
@ -187,7 +186,7 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
||||||
""".strip().format(pnm=pnm)
|
""".strip().format(pnm=pnm)
|
||||||
}
|
}
|
||||||
entry_type = AddrListEntry
|
entry_type = AddrListEntry
|
||||||
main_key = 'addr'
|
main_attr = 'addr'
|
||||||
data_desc = 'address'
|
data_desc = 'address'
|
||||||
file_desc = 'addresses'
|
file_desc = 'addresses'
|
||||||
gen_desc = 'address'
|
gen_desc = 'address'
|
||||||
|
|
@ -197,7 +196,6 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
||||||
gen_keys = False
|
gen_keys = False
|
||||||
has_keys = False
|
has_keys = False
|
||||||
ext = 'addrs'
|
ext = 'addrs'
|
||||||
dfl_mmtype = MMGenAddrType('L')
|
|
||||||
cook_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible
|
cook_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible
|
||||||
chksum_rec_f = lambda foo,e: (str(e.idx), e.addr)
|
chksum_rec_f = lambda foo,e: (str(e.idx), e.addr)
|
||||||
|
|
||||||
|
|
@ -205,7 +203,7 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
||||||
addrlist='',keylist='',mmtype=None,do_chksum=True,chksum_only=False):
|
addrlist='',keylist='',mmtype=None,do_chksum=True,chksum_only=False):
|
||||||
|
|
||||||
self.update_msgs()
|
self.update_msgs()
|
||||||
mmtype = mmtype or self.dfl_mmtype
|
mmtype = mmtype or MMGenAddrType.dfl_mmtype
|
||||||
assert mmtype in MMGenAddrType.mmtypes
|
assert mmtype in MMGenAddrType.mmtypes
|
||||||
|
|
||||||
if seed and addr_idxs: # data from seed + idxs
|
if seed and addr_idxs: # data from seed + idxs
|
||||||
|
|
@ -296,21 +294,16 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
||||||
self.al_id.hl(),t_addrs,self.gen_desc,suf(t_addrs,self.gen_desc_pl),' '*15))
|
self.al_id.hl(),t_addrs,self.gen_desc,suf(t_addrs,self.gen_desc_pl),' '*15))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
def is_mainnet(self):
|
|
||||||
return self.data[0].addr.is_mainnet()
|
|
||||||
|
|
||||||
def is_for_current_chain(self):
|
|
||||||
return self.data[0].addr.is_for_current_chain()
|
|
||||||
|
|
||||||
def check_format(self,addr): return True # format is checked when added to list entry object
|
def check_format(self,addr): return True # format is checked when added to list entry object
|
||||||
|
|
||||||
def cook_seed(self,seed):
|
def cook_seed(self,seed):
|
||||||
if self.al_id.mmtype == 'L':
|
is_btcfork = g.proto.base_coin == 'BTC'
|
||||||
|
if is_btcfork and self.al_id.mmtype == 'L':
|
||||||
return seed
|
return seed
|
||||||
else:
|
else:
|
||||||
from mmgen.crypto import sha256_rounds
|
from mmgen.crypto import sha256_rounds
|
||||||
import hmac
|
import hmac
|
||||||
key = self.al_id.mmtype.name
|
key = (g.coin.lower()+':','')[is_btcfork] + self.al_id.mmtype.name
|
||||||
cseed = hmac.new(seed,key,sha256).digest()
|
cseed = hmac.new(seed,key,sha256).digest()
|
||||||
dmsg('Seed: {}\nKey: {}\nCseed: {}\nCseed len: {}'.format(hexlify(seed),key,hexlify(cseed),len(cseed)))
|
dmsg('Seed: {}\nKey: {}\nCseed: {}\nCseed len: {}'.format(hexlify(seed),key,hexlify(cseed),len(cseed)))
|
||||||
return sha256_rounds(cseed,self.cook_hash_rounds)
|
return sha256_rounds(cseed,self.cook_hash_rounds)
|
||||||
|
|
@ -406,10 +399,10 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
||||||
if type(self) == PasswordList:
|
if type(self) == PasswordList:
|
||||||
out.append(u'{} {} {}:{} {{'.format(
|
out.append(u'{} {} {}:{} {{'.format(
|
||||||
self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len))
|
self.al_id.sid,self.pw_id_str,self.pw_fmt,self.pw_len))
|
||||||
elif self.al_id.mmtype == 'L':
|
|
||||||
out.append('{} {{'.format(self.al_id.sid))
|
|
||||||
else:
|
else:
|
||||||
out.append('{} {} {{'.format(self.al_id.sid,self.al_id.mmtype.name.upper()))
|
bc,mt = g.proto.base_coin,self.al_id.mmtype
|
||||||
|
lbl = ':'.join(([bc],[])[bc=='BTC']+([mt.name.upper()],[])[mt=='L'])
|
||||||
|
out.append('{} {}{{'.format(self.al_id.sid,('',lbl+' ')[bool(lbl)]))
|
||||||
|
|
||||||
fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
|
fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
|
||||||
for e in self.data:
|
for e in self.data:
|
||||||
|
|
@ -447,16 +440,16 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
||||||
|
|
||||||
if len(d) != 3: d.append('')
|
if len(d) != 3: d.append('')
|
||||||
|
|
||||||
a = le(**{'idx':int(d[0]),self.main_key:d[1],'label':d[2]})
|
a = le(**{'idx':int(d[0]),self.main_attr:d[1],'label':d[2]})
|
||||||
|
|
||||||
if self.has_keys:
|
if self.has_keys:
|
||||||
l = lines.pop(0)
|
l = lines.pop(0)
|
||||||
d = l.split(None,2)
|
d = l.split(None,2)
|
||||||
|
|
||||||
if d[0] != 'wif:':
|
if d[0] != 'wif:':
|
||||||
return "Invalid key line in file: '%s'" % l
|
return "Invalid key line in file: '{}'".format(l)
|
||||||
if not is_wif(d[1]):
|
if not is_wif(d[1]):
|
||||||
return "'%s': invalid Bitcoin key" % d[1]
|
return "'{}': invalid {} key".format(d[1],g.proto.name.capitalize())
|
||||||
|
|
||||||
a.sec = PrivKey(wif=d[1])
|
a.sec = PrivKey(wif=d[1])
|
||||||
|
|
||||||
|
|
@ -498,6 +491,38 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
||||||
if not is_mmgen_seed_id(sid):
|
if not is_mmgen_seed_id(sid):
|
||||||
return do_error("'%s': invalid Seed ID" % ls[0])
|
return do_error("'%s': invalid Seed ID" % ls[0])
|
||||||
|
|
||||||
|
def parse_addrfile_label(lbl): # we must maintain backwards compat, so parse is tricky
|
||||||
|
al_base_coin,al_mmtype = None,None
|
||||||
|
lbl = lbl.split(':',1)
|
||||||
|
if len(lbl) == 2:
|
||||||
|
al_base_coin = lbl[0]
|
||||||
|
al_mmtype = lbl[1].lower()
|
||||||
|
else:
|
||||||
|
if lbl[0].lower() in MMGenAddrType.get_names():
|
||||||
|
al_mmtype = lbl[0].lower()
|
||||||
|
else:
|
||||||
|
al_base_coin = lbl[0]
|
||||||
|
|
||||||
|
# this block fails if al_mmtype is invalid for g.coin
|
||||||
|
if not al_mmtype:
|
||||||
|
mmtype = MMGenAddrType('L')
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
mmtype = MMGenAddrType(al_mmtype)
|
||||||
|
except:
|
||||||
|
return do_error(u"'{}': invalid address type in address file. Must be one of: {}".format(
|
||||||
|
mmtype.upper(),' '.join([i['name'].upper() for i in MMGenAddrType.mmtypes.values()])))
|
||||||
|
|
||||||
|
from mmgen.protocol import CoinProtocol
|
||||||
|
base_coin = CoinProtocol(al_base_coin or 'BTC',testnet=False).base_coin
|
||||||
|
if not base_coin:
|
||||||
|
die(2,"'{}': unknown base coin in address file label!".format(al_base_coin))
|
||||||
|
return base_coin,mmtype
|
||||||
|
|
||||||
|
def check_coin_mismatch(base_coin): # die if addrfile coin doesn't match g.coin
|
||||||
|
if not base_coin == g.proto.base_coin:
|
||||||
|
die(2,'{} address file format, but base coin is {}!'.format(base_coin,g.proto.base_coin))
|
||||||
|
|
||||||
if type(self) == PasswordList and len(ls) == 2:
|
if type(self) == PasswordList and len(ls) == 2:
|
||||||
ss = ls.pop().split(':')
|
ss = ls.pop().split(':')
|
||||||
if len(ss) != 2:
|
if len(ss) != 2:
|
||||||
|
|
@ -507,14 +532,11 @@ Removed %s duplicate WIF key%s from keylist (also in {pnm} key-address file
|
||||||
self.pw_id_str = MMGenPWIDString(ls.pop())
|
self.pw_id_str = MMGenPWIDString(ls.pop())
|
||||||
mmtype = MMGenPasswordType('P')
|
mmtype = MMGenPasswordType('P')
|
||||||
elif len(ls) == 1:
|
elif len(ls) == 1:
|
||||||
mmtype = ls.pop().lower()
|
base_coin,mmtype = parse_addrfile_label(ls[0])
|
||||||
try:
|
check_coin_mismatch(base_coin)
|
||||||
mmtype = MMGenAddrType(mmtype)
|
|
||||||
except:
|
|
||||||
return do_error(u"'{}': invalid address type in address file. Must be one of: {}".format(
|
|
||||||
mmtype.upper(),' '.join([i['name'].upper() for i in MMGenAddrType.mmtypes.values()])))
|
|
||||||
elif len(ls) == 0:
|
elif len(ls) == 0:
|
||||||
mmtype = MMGenAddrType('L')
|
base_coin,mmtype = 'BTC',MMGenAddrType('L')
|
||||||
|
check_coin_mismatch(base_coin)
|
||||||
else:
|
else:
|
||||||
return do_error(u"Invalid first line for {} file: '{}'".format(self.gen_desc,lines[0]))
|
return do_error(u"Invalid first line for {} file: '{}'".format(self.gen_desc,lines[0]))
|
||||||
|
|
||||||
|
|
@ -572,7 +594,7 @@ Record this checksum: it will be used to verify the password file in the future
|
||||||
""".strip()
|
""".strip()
|
||||||
}
|
}
|
||||||
entry_type = PasswordListEntry
|
entry_type = PasswordListEntry
|
||||||
main_key = 'passwd'
|
main_attr = 'passwd'
|
||||||
data_desc = 'password'
|
data_desc = 'password'
|
||||||
file_desc = 'passwords'
|
file_desc = 'passwords'
|
||||||
gen_desc = 'password'
|
gen_desc = 'password'
|
||||||
|
|
@ -666,14 +688,14 @@ Record this checksum: it will be used to verify the password file in the future
|
||||||
|
|
||||||
def cook_seed(self,seed):
|
def cook_seed(self,seed):
|
||||||
from mmgen.crypto import sha256_rounds
|
from mmgen.crypto import sha256_rounds
|
||||||
# Changing either pw_fmt, pw_len or id_str will cause a different, unrelated set of
|
# Changing either pw_fmt, pw_len or cook_str will cause a different,
|
||||||
# passwords to be generated: this is what we want
|
# unrelated set of passwords to be generated: this is what we want.
|
||||||
# NB: In original implementation, pw_id_str was 'baseN', not 'bN'
|
# NB: In original implementation, pw_id_str was 'baseN', not 'bN'
|
||||||
fid_str = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
|
cook_str = '{}:{}:{}'.format(self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
|
||||||
dmsg(u'Full ID string: {}'.format(fid_str.decode('utf8')))
|
dmsg(u'Full ID string: {}'.format(cook_str.decode('utf8')))
|
||||||
# Original implementation was 'cseed = seed + fid_str'; hmac was not used
|
# Original implementation was 'cseed = seed + cook_str'; hmac was not used
|
||||||
import hmac
|
import hmac
|
||||||
cseed = hmac.new(seed,fid_str,sha256).digest()
|
cseed = hmac.new(seed,cook_str,sha256).digest()
|
||||||
dmsg('Seed: {}\nCooked seed: {}\nCooked seed len: {}'.format(hexlify(seed),hexlify(cseed),len(cseed)))
|
dmsg('Seed: {}\nCooked seed: {}\nCooked seed len: {}'.format(hexlify(seed),hexlify(cseed),len(cseed)))
|
||||||
return sha256_rounds(cseed,self.cook_hash_rounds)
|
return sha256_rounds(cseed,self.cook_hash_rounds)
|
||||||
|
|
||||||
|
|
@ -712,10 +734,9 @@ re-import your addresses.
|
||||||
|
|
||||||
def add_tw_data(self):
|
def add_tw_data(self):
|
||||||
vmsg('Getting address data from tracking wallet')
|
vmsg('Getting address data from tracking wallet')
|
||||||
c = rpc_connection()
|
accts = g.rpch.listaccounts(0,True)
|
||||||
accts = c.listaccounts(0,True)
|
|
||||||
data,i = {},0
|
data,i = {},0
|
||||||
alists = c.getaddressesbyaccount([[k] for k in accts],batch=True)
|
alists = g.rpch.getaddressesbyaccount([[k] for k in accts],batch=True)
|
||||||
for acct,addrlist in zip(accts,alists):
|
for acct,addrlist in zip(accts,alists):
|
||||||
l = TwLabel(acct,on_fail='silent')
|
l = TwLabel(acct,on_fail='silent')
|
||||||
if l and l.mmid.type == 'mmgen':
|
if l and l.mmid.type == 'mmgen':
|
||||||
|
|
|
||||||
|
|
@ -26,16 +26,79 @@ import mmgen.opts as opts
|
||||||
from mmgen.opts import opt
|
from mmgen.opts import opt
|
||||||
from mmgen.util import *
|
from mmgen.util import *
|
||||||
|
|
||||||
pw_note = """
|
def help_notes(k):
|
||||||
|
from mmgen.seed import SeedSource
|
||||||
|
return {
|
||||||
|
'passwd': """
|
||||||
For passphrases all combinations of whitespace are equal and leading and
|
For passphrases all combinations of whitespace are equal and leading and
|
||||||
trailing space is ignored. This permits reading passphrase or brainwallet
|
trailing space is ignored. This permits reading passphrase or brainwallet
|
||||||
data from a multi-line file with free spacing and indentation.
|
data from a multi-line file with free spacing and indentation.
|
||||||
""".strip()
|
""".strip(),
|
||||||
|
'brainwallet': """
|
||||||
bw_note = """
|
|
||||||
BRAINWALLET NOTE:
|
BRAINWALLET NOTE:
|
||||||
|
|
||||||
To thwart dictionary attacks, it's recommended to use a strong hash preset
|
To thwart dictionary attacks, it's recommended to use a strong hash preset
|
||||||
with brainwallets. For a brainwallet passphrase to generate the correct
|
with brainwallets. For a brainwallet passphrase to generate the correct
|
||||||
seed, the same seed length and hash preset parameters must always be used.
|
seed, the same seed length and hash preset parameters must always be used.
|
||||||
""".strip()
|
""".strip(),
|
||||||
|
'txcreate': """
|
||||||
|
The transaction's outputs are specified on the command line, while its inputs
|
||||||
|
are chosen from a list of the user's unpent outputs via an interactive menu.
|
||||||
|
|
||||||
|
If the transaction fee is not specified on the command line (see FEE
|
||||||
|
SPECIFICATION below), it will be calculated dynamically using {dn}'s
|
||||||
|
"estimatefee" function for the default (or user-specified) number of
|
||||||
|
confirmations. If "estimatefee" fails, the user will be prompted for a fee.
|
||||||
|
|
||||||
|
Dynamic ("estimatefee") fees will be multiplied by the value of '--tx-fee-adj',
|
||||||
|
if specified.
|
||||||
|
|
||||||
|
Ages of transactions are approximate based on an average block discovery
|
||||||
|
interval of one per {g.proto.secs_per_block} seconds.
|
||||||
|
|
||||||
|
All addresses on the command line can be either {pnu} addresses or {pnm}
|
||||||
|
addresses of the form <seed ID>:<index>.
|
||||||
|
|
||||||
|
To send the value of all inputs (minus TX fee) to a single output, specify
|
||||||
|
one address with no amount on the command line.
|
||||||
|
""".format( g=g,
|
||||||
|
pnm=g.proj_name,
|
||||||
|
dn=g.proto.daemon_name,
|
||||||
|
pnu=g.proto.name.capitalize()),
|
||||||
|
'fee': """
|
||||||
|
FEE SPECIFICATION: Transaction fees, both on the command line and at the
|
||||||
|
interactive prompt, may be specified as either absolute {} amounts, using
|
||||||
|
a plain decimal number, or as satoshis per byte, using an integer followed by
|
||||||
|
the letter 's'.
|
||||||
|
""".format(g.coin),
|
||||||
|
'txsign': """
|
||||||
|
Transactions may contain both {pnm} or non-{pnm} input addresses.
|
||||||
|
|
||||||
|
To sign non-{pnm} inputs, a {dn} wallet dump or flat key list is used
|
||||||
|
as the key source ('--keys-from-file' option).
|
||||||
|
|
||||||
|
To sign {pnm} inputs, key data is generated from a seed as with the
|
||||||
|
{pnl}-addrgen and {pnl}-keygen commands. Alternatively, a key-address file
|
||||||
|
may be used (--mmgen-keys-from-file option).
|
||||||
|
|
||||||
|
Multiple wallets or other seed files can be listed on the command line in
|
||||||
|
any order. If the seeds required to sign the transaction's inputs are not
|
||||||
|
found in these files (or in the default wallet), the user will be prompted
|
||||||
|
for seed data interactively.
|
||||||
|
|
||||||
|
To prevent an attacker from crafting transactions with bogus {pnm}-to-{pnu}
|
||||||
|
address mappings, all outputs to {pnm} addresses are verified with a seed
|
||||||
|
source. Therefore, seed files or a key-address file for all {pnm} outputs
|
||||||
|
must also be supplied on the command line if the data can't be found in the
|
||||||
|
default wallet.
|
||||||
|
|
||||||
|
Seed source files must have the canonical extensions listed in the 'FileExt'
|
||||||
|
column below:
|
||||||
|
|
||||||
|
{n_fmt}
|
||||||
|
""".format( dn=g.proto.daemon_name,
|
||||||
|
n_fmt='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||||
|
pnm=g.proj_name,
|
||||||
|
pnu=g.proto.name.capitalize(),
|
||||||
|
pnl=g.proj_name.lower())
|
||||||
|
}[k]
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,6 @@ globalvars.py: Constants and configuration options for the MMGen suite
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys,os
|
import sys,os
|
||||||
from mmgen.obj import BTCAmt
|
|
||||||
|
|
||||||
# Global vars are set to dfl values in class g.
|
# Global vars are set to dfl values in class g.
|
||||||
# They're overridden in this order:
|
# They're overridden in this order:
|
||||||
|
|
@ -36,7 +35,8 @@ class g(object):
|
||||||
def die(ev=0,s=''):
|
def die(ev=0,s=''):
|
||||||
if s: sys.stderr.write(s+'\n')
|
if s: sys.stderr.write(s+'\n')
|
||||||
sys.exit(ev)
|
sys.exit(ev)
|
||||||
# Variables - these might be altered at runtime:
|
|
||||||
|
# Constants:
|
||||||
|
|
||||||
version = '0.9.499'
|
version = '0.9.499'
|
||||||
release_date = 'October 2017'
|
release_date = 'October 2017'
|
||||||
|
|
@ -47,27 +47,24 @@ class g(object):
|
||||||
author = 'Philemon'
|
author = 'Philemon'
|
||||||
email = '<mmgen@tuta.io>'
|
email = '<mmgen@tuta.io>'
|
||||||
Cdates = '2013-2017'
|
Cdates = '2013-2017'
|
||||||
keywords = 'Bitcoin, cryptocurrency, wallet, cold storage, offline, online, spending, open-source, command-line, Python, Linux, Bitcoin Core, bitcoind, hd, deterministic, hierarchical, secure, anonymous, Electrum, seed, mnemonic, brainwallet, Scrypt, utility, script, scriptable, blockchain, raw, transaction, permissionless, console, terminal, curses, ansi, color, tmux, remote, client, daemon, RPC, json, entropy, xterm, rxvt, PowerShell, MSYS, MinGW, mswin, Armbian, Raspbian, Raspberry Pi, Orange Pi'
|
keywords = 'Bitcoin, BTC, cryptocurrency, wallet, cold storage, offline, online, spending, open-source, command-line, Python, Linux, Bitcoin Core, bitcoind, hd, deterministic, hierarchical, secure, anonymous, Electrum, seed, mnemonic, brainwallet, Scrypt, utility, script, scriptable, blockchain, raw, transaction, permissionless, console, terminal, curses, ansi, color, tmux, remote, client, daemon, RPC, json, entropy, xterm, rxvt, PowerShell, MSYS, MinGW, mswin, Armbian, Raspbian, Raspberry Pi, Orange Pi, BCash, BCH, Litecoin, LTC, altcoin'
|
||||||
|
max_int = 0xffffffff
|
||||||
|
stdin_tty = bool(sys.stdin.isatty() or os.getenv('MMGEN_TEST_SUITE'))
|
||||||
|
http_timeout = 60
|
||||||
|
|
||||||
coin = 'BTC'
|
# Variables - these might be altered at runtime:
|
||||||
coins = 'BTC','BCH'
|
|
||||||
|
|
||||||
user_entropy = ''
|
user_entropy = ''
|
||||||
hash_preset = '3'
|
hash_preset = '3'
|
||||||
usr_randchars = 30
|
usr_randchars = 30
|
||||||
stdin_tty = bool(sys.stdin.isatty() or os.getenv('MMGEN_TEST_SUITE'))
|
|
||||||
|
|
||||||
max_tx_fee = BTCAmt('0.01')
|
|
||||||
tx_fee_adj = 1.0
|
tx_fee_adj = 1.0
|
||||||
tx_confs = 3
|
tx_confs = 3
|
||||||
satoshi = BTCAmt('0.00000001') # One bitcoin equals 100,000,000 satoshis
|
|
||||||
seed_len = 256
|
seed_len = 256
|
||||||
|
|
||||||
http_timeout = 60
|
# Constant vars - some of these might be overriden in opts.py, but they don't change thereafter
|
||||||
max_int = 0xffffffff
|
|
||||||
|
|
||||||
# Constants - some of these might be overriden in opts.py, but they don't change thereafter
|
|
||||||
|
|
||||||
|
coin = 'BTC'
|
||||||
debug = False
|
debug = False
|
||||||
quiet = False
|
quiet = False
|
||||||
no_license = False
|
no_license = False
|
||||||
|
|
@ -76,13 +73,14 @@ class g(object):
|
||||||
force_256_color = False
|
force_256_color = False
|
||||||
testnet = False
|
testnet = False
|
||||||
regtest = False
|
regtest = False
|
||||||
chain = None # set by first call to rpc_connection()
|
chain = None # set by first call to rpc_init()
|
||||||
chains = 'mainnet','testnet','regtest'
|
chains = 'mainnet','testnet','regtest'
|
||||||
bitcoind_version = None # set by first call to rpc_connection()
|
daemon_version = None # set by first call to rpc_init()
|
||||||
rpc_host = ''
|
rpc_host = ''
|
||||||
rpc_port = 0
|
rpc_port = 0
|
||||||
rpc_user = ''
|
rpc_user = ''
|
||||||
rpc_password = ''
|
rpc_password = ''
|
||||||
|
rpch = None # global RPC handle
|
||||||
|
|
||||||
bob = False
|
bob = False
|
||||||
alice = False
|
alice = False
|
||||||
|
|
@ -128,7 +126,8 @@ class g(object):
|
||||||
cfg_file_opts = (
|
cfg_file_opts = (
|
||||||
'color','debug','hash_preset','http_timeout','no_license','rpc_host','rpc_port',
|
'color','debug','hash_preset','http_timeout','no_license','rpc_host','rpc_port',
|
||||||
'quiet','tx_fee_adj','usr_randchars','testnet','rpc_user','rpc_password',
|
'quiet','tx_fee_adj','usr_randchars','testnet','rpc_user','rpc_password',
|
||||||
'daemon_data_dir','force_256_color','max_tx_fee','regtest'
|
'daemon_data_dir','force_256_color','regtest',
|
||||||
|
'btc_max_tx_fee','ltc_max_tx_fee','bch_max_tx_fee'
|
||||||
)
|
)
|
||||||
env_opts = (
|
env_opts = (
|
||||||
'MMGEN_BOGUS_WALLET_DATA',
|
'MMGEN_BOGUS_WALLET_DATA',
|
||||||
|
|
@ -151,7 +150,6 @@ class g(object):
|
||||||
global_sets_opt = ['minconf','seed_len','hash_preset','usr_randchars','debug',
|
global_sets_opt = ['minconf','seed_len','hash_preset','usr_randchars','debug',
|
||||||
'quiet','tx_confs','tx_fee_adj','key_generator']
|
'quiet','tx_confs','tx_fee_adj','key_generator']
|
||||||
|
|
||||||
mins_per_block = 9
|
|
||||||
passwd_max_tries = 5
|
passwd_max_tries = 5
|
||||||
|
|
||||||
max_urandchars = 80
|
max_urandchars = 80
|
||||||
|
|
|
||||||
|
|
@ -98,15 +98,18 @@ ADDRESS TYPES:
|
||||||
|
|
||||||
NOTES FOR ALL GENERATOR COMMANDS
|
NOTES FOR ALL GENERATOR COMMANDS
|
||||||
|
|
||||||
{pwn}
|
{n_pw}
|
||||||
|
|
||||||
{bwn}
|
{n_bw}
|
||||||
|
|
||||||
FMT CODES:
|
FMT CODES:
|
||||||
{f}
|
{n_fmt}
|
||||||
""".format(
|
""".format(
|
||||||
n_secp=note_secp256k1,n_addrkey=note_addrkey,pwn=pw_note,bwn=bw_note,
|
n_secp=note_secp256k1,
|
||||||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
n_addrkey=note_addrkey,
|
||||||
|
n_pw=help_notes('passwd'),
|
||||||
|
n_bw=help_notes('brainwallet'),
|
||||||
|
n_fmt='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||||
n_at='\n '.join(["'{}','{:<12} - {}".format(k,v['name']+"'",v['desc']) for k,v in MAT.mmtypes.items()]),
|
n_at='\n '.join(["'{}','{:<12} - {}".format(k,v['name']+"'",v['desc']) for k,v in MAT.mmtypes.items()]),
|
||||||
o=opts
|
o=opts
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mmgen-addrimport: Import addresses into a MMGen bitcoind tracking wallet
|
mmgen-addrimport: Import addresses into a MMGen coin daemon tracking wallet
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
@ -26,7 +26,25 @@ from mmgen.common import *
|
||||||
from mmgen.addr import AddrList,KeyAddrList
|
from mmgen.addr import AddrList,KeyAddrList
|
||||||
from mmgen.obj import TwLabel
|
from mmgen.obj import TwLabel
|
||||||
|
|
||||||
# In batch mode, bitcoind just rescans each address separately anyway, so make
|
ai_msgs = lambda k: {
|
||||||
|
'rescan': """
|
||||||
|
WARNING: You've chosen the '--rescan' option. Rescanning the blockchain is
|
||||||
|
necessary only if an address you're importing is already on the blockchain,
|
||||||
|
has a balance and is not in your tracking wallet. Note that the rescanning
|
||||||
|
process is very slow (>30 min. for each imported address on a low-powered
|
||||||
|
computer).
|
||||||
|
""".strip() if opt.rescan else """
|
||||||
|
WARNING: If any of the addresses you're importing is already on the blockchain,
|
||||||
|
has a balance and is not in your tracking wallet, you must exit the program now
|
||||||
|
and rerun it using the '--rescan' option.
|
||||||
|
""".strip(),
|
||||||
|
'bad_args': """
|
||||||
|
You must specify an {pnm} address file, a single address with the '--address'
|
||||||
|
option, or a list of non-{pnm} addresses with the '--addrlist' option
|
||||||
|
""".strip().format(pnm=g.proj_name)
|
||||||
|
}[k]
|
||||||
|
|
||||||
|
# In batch mode, daemon just rescans each address separately anyway, so make
|
||||||
# --batch and --rescan incompatible.
|
# --batch and --rescan incompatible.
|
||||||
|
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
|
|
@ -36,14 +54,13 @@ opts_data = lambda: {
|
||||||
'options': """
|
'options': """
|
||||||
-h, --help Print this help message
|
-h, --help Print this help message
|
||||||
--, --longhelp Print help message for long options (common options)
|
--, --longhelp Print help message for long options (common options)
|
||||||
-a, --address=a Import the single Bitcoin address 'a'
|
-a, --address=a Import the single coin address 'a'
|
||||||
-b, --batch Import all addresses in one RPC call.
|
-b, --batch Import all addresses in one RPC call.
|
||||||
-l, --addrlist Address source is a flat list of (non-MMGen) Bitcoin addresses
|
-l, --addrlist Address source is a flat list of non-MMGen coin addresses
|
||||||
-k, --keyaddr-file Address source is a key-address file
|
-k, --keyaddr-file Address source is a key-address file
|
||||||
-q, --quiet Suppress warnings
|
-q, --quiet Suppress warnings
|
||||||
-r, --rescan Rescan the blockchain. Required if address to import is
|
-r, --rescan Rescan the blockchain. Required if address to import is
|
||||||
on the blockchain and has a balance. Rescanning is slow.
|
on the blockchain and has a balance. Rescanning is slow.
|
||||||
-t, --test Simulate operation; don't actually import addresses
|
|
||||||
""",
|
""",
|
||||||
'notes': """\n
|
'notes': """\n
|
||||||
This command can also be used to update the comment fields of addresses already
|
This command can also be used to update the comment fields of addresses already
|
||||||
|
|
@ -63,109 +80,91 @@ def import_mmgen_list(infile):
|
||||||
rdie(2,'Segwit is not active on this chain. Cannot import Segwit addresses')
|
rdie(2,'Segwit is not active on this chain. Cannot import Segwit addresses')
|
||||||
return al
|
return al
|
||||||
|
|
||||||
|
rpc_init()
|
||||||
|
|
||||||
if len(cmd_args) == 1:
|
if len(cmd_args) == 1:
|
||||||
infile = cmd_args[0]
|
infile = cmd_args[0]
|
||||||
check_infile(infile)
|
check_infile(infile)
|
||||||
if opt.addrlist:
|
if opt.addrlist:
|
||||||
lines = get_lines_from_file(
|
al = AddrList(addrlist=get_lines_from_file(
|
||||||
infile,'non-{pnm} addresses'.format(pnm=g.proj_name),trim_comments=True)
|
infile,
|
||||||
al = AddrList(addrlist=lines)
|
'non-{pnm} addresses'.format(pnm=g.proj_name),
|
||||||
|
trim_comments=True))
|
||||||
else:
|
else:
|
||||||
al = import_mmgen_list(infile)
|
al = import_mmgen_list(infile)
|
||||||
elif len(cmd_args) == 0 and opt.address:
|
elif len(cmd_args) == 0 and opt.address:
|
||||||
al = AddrList(addrlist=[opt.address])
|
al = AddrList(addrlist=[opt.address])
|
||||||
infile = 'command line'
|
infile = 'command line'
|
||||||
else:
|
else:
|
||||||
die(1,"""
|
die(1,ai_msgs('bad_args'))
|
||||||
You must specify an {pnm} address file, a single address, or a list of
|
|
||||||
non-{pnm} addresses with the '--addrlist' option)
|
|
||||||
""".strip().format(pnm=g.proj_name))
|
|
||||||
|
|
||||||
m = ' from Seed ID {}'.format(al.al_id.sid) if hasattr(al.al_id,'sid') else ''
|
m = ' from Seed ID {}'.format(al.al_id.sid) if hasattr(al.al_id,'sid') else ''
|
||||||
qmsg('OK. {} addresses{}'.format(al.num_addrs,m))
|
qmsg('OK. {} addresses{}'.format(al.num_addrs,m))
|
||||||
|
|
||||||
if not opt.test:
|
if not opt.quiet: confirm_or_exit(ai_msgs('rescan'),'continue',expect='YES')
|
||||||
c = rpc_connection()
|
|
||||||
|
|
||||||
m = """
|
|
||||||
WARNING: You've chosen the '--rescan' option. Rescanning the blockchain is
|
|
||||||
necessary only if an address you're importing is already on the blockchain,
|
|
||||||
has a balance and is not in your tracking wallet. Note that the rescanning
|
|
||||||
process is very slow (>30 min. for each imported address on a low-powered
|
|
||||||
computer).
|
|
||||||
""".strip() if opt.rescan else """
|
|
||||||
WARNING: If any of the addresses you're importing is already on the blockchain,
|
|
||||||
has a balance and is not in your tracking wallet, you must exit the program now
|
|
||||||
and rerun it using the '--rescan' option.
|
|
||||||
""".strip()
|
|
||||||
|
|
||||||
if not opt.quiet: confirm_or_exit(m, 'continue', expect='YES')
|
|
||||||
|
|
||||||
err_flag = False
|
err_flag = False
|
||||||
|
|
||||||
def import_address(addr,label,rescan):
|
def import_address(addr,label,rescan):
|
||||||
try:
|
try:
|
||||||
if not opt.test:
|
g.rpch.importaddress(addr,label,rescan,timeout=(False,3600)[rescan])
|
||||||
c.importaddress(addr,label,rescan,timeout=(False,3600)[rescan])
|
|
||||||
except:
|
except:
|
||||||
global err_flag
|
global err_flag
|
||||||
err_flag = True
|
err_flag = True
|
||||||
|
|
||||||
w_n_of_m = len(str(al.num_addrs)) * 2 + 2
|
w_n_of_m = len(str(al.num_addrs)) * 2 + 2
|
||||||
w_mmid = '' if opt.addrlist or opt.address else len(str(max(al.idxs()))) + 12
|
w_mmid = 1 if opt.addrlist or opt.address else len(str(max(al.idxs()))) + 13
|
||||||
|
msg_fmt = '{{:{}}} {{:34}} {{:{}}}'.format(w_n_of_m,w_mmid)
|
||||||
|
|
||||||
if opt.rescan:
|
if opt.rescan: import threading
|
||||||
import threading
|
|
||||||
msg_fmt = '\r%s %-{}s %-34s %s'.format(w_n_of_m)
|
|
||||||
else:
|
|
||||||
msg_fmt = '\r%-{}s %-34s %s'.format(w_n_of_m, w_mmid)
|
|
||||||
|
|
||||||
msg("Importing {} address{} from {}{}".format(
|
msg('Importing {} address{} from {}{}'.format(
|
||||||
len(al.data), suf(al.data,'es'), infile,
|
len(al.data),
|
||||||
('',' (batch mode)')[bool(opt.batch)]
|
suf(al.data,'es'),
|
||||||
))
|
infile,
|
||||||
|
('',' (batch mode)')[bool(opt.batch)]))
|
||||||
|
|
||||||
if not al.is_for_current_chain():
|
if not al.data[0].addr.is_for_chain(g.chain):
|
||||||
die(2,"Address{} not compatible with {} chain!".format((' list','')[bool(opt.address)],g.chain))
|
die(2,'Address{} not compatible with {} chain!'.format((' list','')[bool(opt.address)],g.chain))
|
||||||
|
|
||||||
arg_list = []
|
|
||||||
for n,e in enumerate(al.data):
|
for n,e in enumerate(al.data):
|
||||||
if e.idx:
|
if e.idx:
|
||||||
label = '{}:{}'.format(al.al_id,e.idx)
|
label = '{}:{}'.format(al.al_id,e.idx)
|
||||||
if e.label: label += ' ' + e.label
|
if e.label: label += ' ' + e.label
|
||||||
m = label
|
m = label
|
||||||
else:
|
else:
|
||||||
label = 'btc:{}'.format(e.addr)
|
label = '{}:{}'.format(g.proto.base_coin.lower(),e.addr)
|
||||||
m = 'non-'+g.proj_name
|
m = 'non-'+g.proj_name
|
||||||
|
|
||||||
label = TwLabel(label)
|
label = TwLabel(label)
|
||||||
|
|
||||||
if opt.batch:
|
if opt.batch:
|
||||||
|
if n == 0: arg_list = []
|
||||||
arg_list.append((e.addr,label,False))
|
arg_list.append((e.addr,label,False))
|
||||||
elif opt.rescan:
|
continue
|
||||||
|
|
||||||
|
msg_data = ('{}/{}:'.format(n+1,al.num_addrs),e.addr,'({})'.format(m))
|
||||||
|
|
||||||
|
if opt.rescan:
|
||||||
t = threading.Thread(target=import_address,args=[e.addr,label,True])
|
t = threading.Thread(target=import_address,args=[e.addr,label,True])
|
||||||
t.daemon = True
|
t.daemon = True
|
||||||
t.start()
|
t.start()
|
||||||
|
|
||||||
start = int(time.time())
|
start = int(time.time())
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
if t.is_alive():
|
if t.is_alive():
|
||||||
elapsed = int(time.time() - start)
|
elapsed = int(time.time()-start)
|
||||||
count = '%s/%s:' % (n+1, al.num_addrs)
|
msg_r(('\r{} '+msg_fmt).format(secs_to_hms(elapsed),*msg_data))
|
||||||
msg_r(msg_fmt % (secs_to_hms(elapsed),count,e.addr,'(%s)' % m))
|
time.sleep(0.5)
|
||||||
time.sleep(1)
|
|
||||||
else:
|
else:
|
||||||
if err_flag: die(2,'\nImport failed')
|
if err_flag: die(2,'\nImport failed')
|
||||||
msg('\nOK')
|
msg('\nOK')
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
import_address(e.addr,label,False)
|
import_address(e.addr,label,False)
|
||||||
count = '%s/%s:' % (n+1, al.num_addrs)
|
msg_r('\r'+msg_fmt.format(*msg_data))
|
||||||
msg_r(msg_fmt % (count, e.addr, '(%s)' % m))
|
|
||||||
if err_flag: die(2,'\nImport failed')
|
if err_flag: die(2,'\nImport failed')
|
||||||
msg(' - OK')
|
msg(' - OK')
|
||||||
|
|
||||||
if opt.batch:
|
if opt.batch:
|
||||||
ret = c.importaddress(arg_list,batch=True)
|
ret = g.rpch.importaddress(arg_list,batch=True)
|
||||||
msg('OK: %s addresses imported' % len(ret))
|
msg('OK: {} addresses imported'.format(len(ret)))
|
||||||
|
|
|
||||||
|
|
@ -98,18 +98,19 @@ EXAMPLE:
|
||||||
|
|
||||||
NOTES FOR ALL GENERATOR COMMANDS
|
NOTES FOR ALL GENERATOR COMMANDS
|
||||||
|
|
||||||
{pwn}
|
{n_pw}
|
||||||
|
|
||||||
{bwn}
|
{n_bw}
|
||||||
|
|
||||||
FMT CODES:
|
FMT CODES:
|
||||||
{f}
|
{n_fmt}
|
||||||
""".format(
|
""".format(
|
||||||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
|
||||||
o=opts,g=g,d58=dfl_len['b58'],d32=dfl_len['b32'],
|
o=opts,g=g,d58=dfl_len['b58'],d32=dfl_len['b32'],
|
||||||
ml=MMGenPWIDString.max_len,
|
ml=MMGenPWIDString.max_len,
|
||||||
pwn=pw_note,bwn=bw_note,
|
fs="', '".join(MMGenPWIDString.forbidden),
|
||||||
fs="', '".join(MMGenPWIDString.forbidden)
|
n_pw=help_notes('passwd'),
|
||||||
|
n_bw=help_notes('brainwallet'),
|
||||||
|
n_fmt='\n '.join(SeedSource.format_fmt_codes().splitlines())
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,14 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mmgen-regtest: Bitcoind regression test mode setup and operations for the MMGen
|
mmgen-regtest: Coin daemon regression test mode setup and operations for the MMGen
|
||||||
suite
|
suite
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mmgen.common import *
|
from mmgen.common import *
|
||||||
|
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
'desc': 'Bitcoind regression test mode setup and operations for the {} suite'.format(g.proj_name),
|
'desc': 'Coin daemon regression test mode setup and operations for the {} suite'.format(g.proj_name),
|
||||||
'usage': '[opts] <command>',
|
'usage': '[opts] <command>',
|
||||||
'sets': ( ('yes', True, 'quiet', True), ),
|
'sets': ( ('yes', True, 'quiet', True), ),
|
||||||
'options': """
|
'options': """
|
||||||
|
|
@ -40,7 +41,7 @@ opts_data = lambda: {
|
||||||
AVAILABLE COMMANDS
|
AVAILABLE COMMANDS
|
||||||
|
|
||||||
setup - set up system for regtest operation with MMGen
|
setup - set up system for regtest operation with MMGen
|
||||||
stop - stop the regtest bitcoind
|
stop - stop the regtest coin daemon
|
||||||
bob - switch to Bob's wallet, starting daemon if necessary
|
bob - switch to Bob's wallet, starting daemon if necessary
|
||||||
alice - switch to Alice's wallet, starting daemon if necessary
|
alice - switch to Alice's wallet, starting daemon if necessary
|
||||||
user - show current user
|
user - show current user
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mmgen-tool: Perform various MMGen- and Bitcoin-related operations.
|
mmgen-tool: Perform various MMGen- and cryptocoin-related operations.
|
||||||
Part of the MMGen suite
|
Part of the MMGen suite
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
@ -29,24 +29,24 @@ supported commands), use '-' as the first argument.
|
||||||
""".strip()
|
""".strip()
|
||||||
|
|
||||||
cmd_help = """
|
cmd_help = """
|
||||||
Bitcoin address/key operations (compressed public keys supported):
|
Cryptocoin address/key operations (compressed public keys supported):
|
||||||
addr2hexaddr - convert Bitcoin address from base58 to hex format
|
addr2hexaddr - convert coin address from base58 to hex format
|
||||||
hex2wif - convert a private key from hex to WIF format
|
hex2wif - convert a private key from hex to WIF format
|
||||||
hexaddr2addr - convert Bitcoin address from hex to base58 format
|
hexaddr2addr - convert coin address from hex to base58 format
|
||||||
privhex2addr - generate Bitcoin address from private key in hex format
|
privhex2addr - generate coin address from private key in hex format
|
||||||
privhex2pubhex - generate a hex public key from a hex private key
|
privhex2pubhex - generate a hex public key from a hex private key
|
||||||
pubhex2addr - convert a hex pubkey to an address
|
pubhex2addr - convert a hex pubkey to an address
|
||||||
pubhex2redeem_script - convert a hex pubkey to a witness redeem script
|
pubhex2redeem_script - convert a hex pubkey to a witness redeem script
|
||||||
wif2redeem_script - convert a WIF private key to a witness redeem script
|
wif2redeem_script - convert a WIF private key to a witness redeem script
|
||||||
wif2segwit_pair - generate both a Segwit redeem script and address from WIF
|
wif2segwit_pair - generate both a Segwit redeem script and address from WIF
|
||||||
pubkey2addr - convert Bitcoin public key to address
|
pubkey2addr - convert coin public key to address
|
||||||
randpair - generate a random private key/address pair
|
randpair - generate a random private key/address pair
|
||||||
randwif - generate a random private key in WIF format
|
randwif - generate a random private key in WIF format
|
||||||
wif2addr - generate a Bitcoin address from a key in WIF format
|
wif2addr - generate a coin address from a key in WIF format
|
||||||
wif2hex - convert a private key from WIF to hex format
|
wif2hex - convert a private key from WIF to hex format
|
||||||
|
|
||||||
Wallet/TX operations (bitcoind must be running):
|
Wallet/TX operations (coin daemon must be running):
|
||||||
getbalance - like 'bitcoin-cli getbalance' but shows confirmed/unconfirmed,
|
getbalance - like '{pn}-cli getbalance' but shows confirmed/unconfirmed,
|
||||||
spendable/unspendable balances for individual {pnm} wallets
|
spendable/unspendable balances for individual {pnm} wallets
|
||||||
listaddress - list the specified {pnm} address and its balance
|
listaddress - list the specified {pnm} address and its balance
|
||||||
listaddresses - list {pnm} addresses and their balances
|
listaddresses - list {pnm} addresses and their balances
|
||||||
|
|
@ -104,10 +104,10 @@ Mnemonic operations (choose 'electrum' (default), 'tirosh' or 'all'
|
||||||
computed using a different algorithm and are NOT Electrum-compatible!
|
computed using a different algorithm and are NOT Electrum-compatible!
|
||||||
|
|
||||||
{sm}
|
{sm}
|
||||||
""".format(pnm=g.proj_name,sm='\n '.join(stdin_msg.split('\n')))
|
"""
|
||||||
|
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
'desc': 'Perform various {pnm}- and Bitcoin-related operations'.format(pnm=g.proj_name),
|
'desc': 'Perform various {pnm}- and cryptocoin-related operations'.format(pnm=g.proj_name),
|
||||||
'usage': '[opts] <command> <command args>',
|
'usage': '[opts] <command> <command args>',
|
||||||
'options': """
|
'options': """
|
||||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
-d, --outdir= d Specify an alternate directory 'd' for output
|
||||||
|
|
@ -122,9 +122,14 @@ opts_data = lambda: {
|
||||||
'notes': """
|
'notes': """
|
||||||
|
|
||||||
COMMANDS
|
COMMANDS
|
||||||
{}
|
{ch}
|
||||||
Type '{} help <command> for help on a particular command
|
Type '{pn} help <command> for help on a particular command
|
||||||
""".format(cmd_help,g.prog_name)
|
""".format( pn=g.prog_name,
|
||||||
|
ch=cmd_help.format(
|
||||||
|
pn=g.proto.name,
|
||||||
|
pnm=g.proj_name,
|
||||||
|
sm='\n '.join(stdin_msg.split('\n')))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt'])
|
cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt'])
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,8 @@ mmgen-txbump: Increase the fee on a replaceable (replace-by-fee) MMGen
|
||||||
transaction, and optionally sign and send it
|
transaction, and optionally sign and send it
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mmgen.txcreate import *
|
from mmgen.common import *
|
||||||
from mmgen.txsign import *
|
from mmgen.seed import SeedSource
|
||||||
|
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
'desc': 'Increase the fee on a replaceable (RBF) {g.proj_name} transaction, creating a new transaction, and optionally sign and send the new transaction'.format(g=g),
|
'desc': 'Increase the fee on a replaceable (RBF) {g.proj_name} transaction, creating a new transaction, and optionally sign and send the new transaction'.format(g=g),
|
||||||
|
|
@ -58,29 +58,33 @@ opts_data = lambda: {
|
||||||
-O, --old-incog-fmt Specify old-format incognito input
|
-O, --old-incog-fmt Specify old-format incognito input
|
||||||
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
|
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
|
||||||
for password hashing (default: '{g.hash_preset}')
|
for password hashing (default: '{g.hash_preset}')
|
||||||
-P, --passwd-file= f Get {pnm} wallet or bitcoind passphrase from file 'f'
|
-P, --passwd-file= f Get {pnm} wallet or {dn} passphrase from file 'f'
|
||||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||||
-s, --send Sign and send the transaction (the default if seed
|
-s, --send Sign and send the transaction (the default if seed
|
||||||
data is provided)
|
data is provided)
|
||||||
-v, --verbose Produce more verbose output
|
-v, --verbose Produce more verbose output
|
||||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||||
-z, --show-hash-presets Show information on available hash presets
|
-z, --show-hash-presets Show information on available hash presets
|
||||||
""".format(g=g,pnm=pnm,pnl=pnm.lower(),
|
""".format(g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),dn=g.proto.daemon_name,
|
||||||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||||
kg=g.key_generator,
|
kg=g.key_generator,
|
||||||
cu=g.coin
|
cu=g.coin
|
||||||
),
|
),
|
||||||
'notes': '\n' + fee_notes.format(g.coin) + txsign_notes
|
'notes': '\n' + help_notes('fee') + help_notes('txsign')
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_args = opts.init(opts_data)
|
cmd_args = opts.init(opts_data)
|
||||||
|
|
||||||
c = rpc_connection()
|
rpc_init()
|
||||||
|
|
||||||
tx_file = cmd_args.pop(0)
|
tx_file = cmd_args.pop(0)
|
||||||
check_infile(tx_file)
|
check_infile(tx_file)
|
||||||
|
|
||||||
|
from mmgen.txcreate import *
|
||||||
|
from mmgen.txsign import *
|
||||||
|
|
||||||
seed_files = get_seed_files(opt,cmd_args) if (cmd_args or opt.send) else None
|
seed_files = get_seed_files(opt,cmd_args) if (cmd_args or opt.send) else None
|
||||||
|
|
||||||
kal = get_keyaddrlist(opt)
|
kal = get_keyaddrlist(opt)
|
||||||
kl = get_keylist(opt)
|
kl = get_keylist(opt)
|
||||||
|
|
||||||
|
|
@ -112,13 +116,13 @@ fee = tx.get_usr_fee_interactive(tx_fee=opt.tx_fee,desc='User-selected')
|
||||||
tx.update_output_amt(op_idx,tx.sum_inputs()-tx.sum_outputs(exclude=op_idx)-fee)
|
tx.update_output_amt(op_idx,tx.sum_inputs()-tx.sum_outputs(exclude=op_idx)-fee)
|
||||||
|
|
||||||
d = tx.get_fee()
|
d = tx.get_fee()
|
||||||
assert d == fee and d <= g.max_tx_fee
|
assert d == fee and d <= g.proto.max_tx_fee
|
||||||
|
|
||||||
if not opt.yes:
|
if not opt.yes:
|
||||||
tx.add_comment() # edits an existing comment
|
tx.add_comment() # edits an existing comment
|
||||||
tx.create_raw(c) # creates tx.hex, tx.txid
|
tx.create_raw() # creates tx.hex, tx.txid
|
||||||
tx.add_timestamp()
|
tx.add_timestamp()
|
||||||
tx.add_blockcount(c)
|
tx.add_blockcount()
|
||||||
|
|
||||||
qmsg('Fee successfully increased')
|
qmsg('Fee successfully increased')
|
||||||
|
|
||||||
|
|
@ -127,9 +131,9 @@ if not silent:
|
||||||
msg_r(tx.format_view(terse=True))
|
msg_r(tx.format_view(terse=True))
|
||||||
|
|
||||||
if seed_files or kl or kal:
|
if seed_files or kl or kal:
|
||||||
txsign(opt,c,tx,seed_files,kl,kal)
|
txsign(tx,seed_files,kl,kal)
|
||||||
tx.write_to_file(ask_write=False)
|
tx.write_to_file(ask_write=False)
|
||||||
if tx.send(c):
|
if tx.send():
|
||||||
tx.write_to_file(ask_write=False)
|
tx.write_to_file(ask_write=False)
|
||||||
else:
|
else:
|
||||||
tx.write_to_file(ask_write=not opt.yes,ask_write_default_yes=False,ask_overwrite=not opt.yes)
|
tx.write_to_file(ask_write=not opt.yes,ask_write_default_yes=False,ask_overwrite=not opt.yes)
|
||||||
|
|
|
||||||
|
|
@ -17,14 +17,14 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mmgen-txcreate: Create a Bitcoin transaction to and from MMGen- or non-MMGen
|
mmgen-txcreate: Create a cryptocoin transaction with MMGen- and/or non-MMGen
|
||||||
inputs and outputs
|
inputs and outputs
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mmgen.txcreate import *
|
from mmgen.common import *
|
||||||
|
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
'desc': 'Create a transaction with outputs to specified Bitcoin or {g.proj_name} addresses'.format(g=g),
|
'desc': 'Create a transaction with outputs to specified coin or {g.proj_name} addresses'.format(g=g),
|
||||||
'usage': '[opts] <addr,amt> ... [change addr] [addr file] ...',
|
'usage': '[opts] <addr,amt> ... [change addr] [addr file] ...',
|
||||||
'sets': ( ('yes', True, 'quiet', True), ),
|
'sets': ( ('yes', True, 'quiet', True), ),
|
||||||
'options': """
|
'options': """
|
||||||
|
|
@ -37,7 +37,7 @@ opts_data = lambda: {
|
||||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
-d, --outdir= d Specify an alternate directory 'd' for output
|
||||||
-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or in satoshis
|
-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or in satoshis
|
||||||
per byte (an integer followed by 's'). If omitted, fee
|
per byte (an integer followed by 's'). If omitted, fee
|
||||||
will be calculated using bitcoind's 'estimatefee' call
|
will be calculated using {dn}'s 'estimatefee' call
|
||||||
-i, --info Display unspent outputs and exit
|
-i, --info Display unspent outputs and exit
|
||||||
-m, --minconf= n Minimum number of confirmations required to spend
|
-m, --minconf= n Minimum number of confirmations required to spend
|
||||||
outputs (default: 1)
|
outputs (default: 1)
|
||||||
|
|
@ -45,11 +45,12 @@ opts_data = lambda: {
|
||||||
-r, --rbf Make transaction BIP 125 replaceable (replace-by-fee)
|
-r, --rbf Make transaction BIP 125 replaceable (replace-by-fee)
|
||||||
-v, --verbose Produce more verbose output
|
-v, --verbose Produce more verbose output
|
||||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||||
""".format(g=g,cu=g.coin),
|
""".format(g=g,cu=g.coin,dn=g.proto.daemon_name),
|
||||||
'notes': '\n' + txcreate_notes + fee_notes.format(g.coin)
|
'notes': '\n' + help_notes('txcreate') + help_notes('fee')
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_args = opts.init(opts_data)
|
cmd_args = opts.init(opts_data)
|
||||||
do_license_msg()
|
|
||||||
|
from mmgen.txcreate import *
|
||||||
tx = txcreate(cmd_args,do_info=opt.info)
|
tx = txcreate(cmd_args,do_info=opt.info)
|
||||||
tx.write_to_file(ask_write=not opt.yes,ask_overwrite=not opt.yes,ask_write_default_yes=False)
|
tx.write_to_file(ask_write=not opt.yes,ask_overwrite=not opt.yes,ask_write_default_yes=False)
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@
|
||||||
mmgen-txdo: Create, sign and broadcast an online MMGen transaction
|
mmgen-txdo: Create, sign and broadcast an online MMGen transaction
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mmgen.txcreate import *
|
from mmgen.common import *
|
||||||
from mmgen.txsign import *
|
from mmgen.seed import SeedSource
|
||||||
|
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
'desc': 'Create, sign and send an {g.proj_name} transaction'.format(g=g),
|
'desc': 'Create, sign and send an {g.proj_name} transaction'.format(g=g),
|
||||||
|
|
@ -40,7 +40,7 @@ opts_data = lambda: {
|
||||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
-e, --echo-passphrase Print passphrase to screen when typing it
|
||||||
-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or in
|
-f, --tx-fee= f Transaction fee, as a decimal {cu} amount or in
|
||||||
satoshis per byte (an integer followed by 's').
|
satoshis per byte (an integer followed by 's').
|
||||||
If omitted, bitcoind's 'estimatefee' will be used
|
If omitted, {dn}'s 'estimatefee' will be used
|
||||||
to calculate the fee.
|
to calculate the fee.
|
||||||
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
|
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
|
||||||
'f' at offset 'o' (comma-separated)
|
'f' at offset 'o' (comma-separated)
|
||||||
|
|
@ -68,27 +68,29 @@ opts_data = lambda: {
|
||||||
-v, --verbose Produce more verbose output
|
-v, --verbose Produce more verbose output
|
||||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||||
-z, --show-hash-presets Show information on available hash presets
|
-z, --show-hash-presets Show information on available hash presets
|
||||||
""".format(g=g,pnm=pnm,pnl=pnm.lower(),
|
""".format(g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),dn=g.proto.daemon_name,
|
||||||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||||
kg=g.key_generator,
|
kg=g.key_generator,
|
||||||
cu=g.coin
|
cu=g.coin),
|
||||||
),
|
'notes': '\n' + help_notes('txcreate') + help_notes('fee') + help_notes('txsign')
|
||||||
'notes': '\n' + txcreate_notes + fee_notes.format(g.coin) + txsign_notes
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_args = opts.init(opts_data)
|
cmd_args = opts.init(opts_data)
|
||||||
|
|
||||||
|
rpc_init()
|
||||||
|
|
||||||
|
from mmgen.txcreate import *
|
||||||
|
from mmgen.txsign import *
|
||||||
|
|
||||||
seed_files = get_seed_files(opt,cmd_args)
|
seed_files = get_seed_files(opt,cmd_args)
|
||||||
c = rpc_connection()
|
|
||||||
do_license_msg()
|
|
||||||
|
|
||||||
kal = get_keyaddrlist(opt)
|
kal = get_keyaddrlist(opt)
|
||||||
kl = get_keylist(opt)
|
kl = get_keylist(opt)
|
||||||
if kl and kal: kl.remove_dup_keys(kal)
|
if kl and kal: kl.remove_dup_keys(kal)
|
||||||
|
|
||||||
tx = txcreate(cmd_args,caller='txdo')
|
tx = txcreate(cmd_args,caller='txdo')
|
||||||
txsign(opt,c,tx,seed_files,kl,kal)
|
txsign(tx,seed_files,kl,kal)
|
||||||
tx.write_to_file(ask_write=False)
|
tx.write_to_file(ask_write=False)
|
||||||
|
|
||||||
if tx.send(c):
|
if tx.send():
|
||||||
tx.write_to_file(ask_overwrite=False,ask_write=False)
|
tx.write_to_file(ask_overwrite=False,ask_write=False)
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,9 @@ mmgen-txsend: Broadcast a transaction signed by 'mmgen-txsign' to the network
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mmgen.common import *
|
from mmgen.common import *
|
||||||
from mmgen.tx import *
|
|
||||||
|
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
'desc': 'Send a Bitcoin transaction signed by {pnm}-txsign'.format(
|
'desc': 'Send a cryptocoin transaction signed by {pnm}-txsign'.format(pnm=g.proj_name.lower()),
|
||||||
pnm=g.proj_name.lower()),
|
|
||||||
'usage': '[opts] <signed transaction file>',
|
'usage': '[opts] <signed transaction file>',
|
||||||
'sets': ( ('yes', True, 'quiet', True), ),
|
'sets': ( ('yes', True, 'quiet', True), ),
|
||||||
'options': """
|
'options': """
|
||||||
|
|
@ -40,22 +38,25 @@ opts_data = lambda: {
|
||||||
|
|
||||||
cmd_args = opts.init(opts_data)
|
cmd_args = opts.init(opts_data)
|
||||||
|
|
||||||
|
rpc_init()
|
||||||
|
|
||||||
if len(cmd_args) == 1:
|
if len(cmd_args) == 1:
|
||||||
infile = cmd_args[0]; check_infile(infile)
|
infile = cmd_args[0]; check_infile(infile)
|
||||||
else: opts.usage()
|
else: opts.usage()
|
||||||
|
|
||||||
if not opt.status: do_license_msg()
|
if not opt.status: do_license_msg()
|
||||||
|
|
||||||
c = rpc_connection()
|
from mmgen.tx import *
|
||||||
|
|
||||||
tx = MMGenTX(infile) # sig check performed here
|
tx = MMGenTX(infile) # sig check performed here
|
||||||
vmsg("Signed transaction file '%s' is valid" % infile)
|
vmsg("Signed transaction file '%s' is valid" % infile)
|
||||||
|
|
||||||
if not tx.marked_signed(c):
|
if not tx.marked_signed():
|
||||||
die(1,'Transaction is not signed!')
|
die(1,'Transaction is not signed!')
|
||||||
|
|
||||||
if opt.status:
|
if opt.status:
|
||||||
if tx.coin_txid: qmsg('{} txid: {}'.format(g.coin,tx.coin_txid.hl()))
|
if tx.coin_txid: qmsg('{} txid: {}'.format(g.coin,tx.coin_txid.hl()))
|
||||||
tx.get_status(c,status=True)
|
tx.get_status(status=True)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if not opt.yes:
|
if not opt.yes:
|
||||||
|
|
@ -63,5 +64,5 @@ if not opt.yes:
|
||||||
if tx.add_comment(): # edits an existing comment, returns true if changed
|
if tx.add_comment(): # edits an existing comment, returns true if changed
|
||||||
tx.write_to_file(ask_write_default_yes=True)
|
tx.write_to_file(ask_write_default_yes=True)
|
||||||
|
|
||||||
if tx.send(c):
|
if tx.send():
|
||||||
tx.write_to_file(ask_overwrite=False,ask_write=False)
|
tx.write_to_file(ask_overwrite=False,ask_write=False)
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,12 @@
|
||||||
mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
|
mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mmgen.txsign import *
|
from mmgen.common import *
|
||||||
|
from mmgen.seed import SeedSource
|
||||||
|
|
||||||
# -w, --use-wallet-dat (keys from running bitcoind) removed: use bitcoin-cli walletdump instead
|
# -w, --use-wallet-dat (keys from running coin daemon) removed: use walletdump rpc instead
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
'desc': 'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnm.lower()),
|
'desc': 'Sign cryptocoin transactions generated by {pnl}-txcreate'.format(pnl=g.proj_name.lower()),
|
||||||
'usage': '[opts] <transaction file>... [seed source]...',
|
'usage': '[opts] <transaction file>... [seed source]...',
|
||||||
'sets': ( ('yes', True, 'quiet', True), ),
|
'sets': ( ('yes', True, 'quiet', True), ),
|
||||||
'options': """
|
'options': """
|
||||||
|
|
@ -53,19 +54,19 @@ opts_data = lambda: {
|
||||||
online signing without an {pnm} seed source. The
|
online signing without an {pnm} seed source. The
|
||||||
key-address file is also used to verify {pnm}-to-{cu}
|
key-address file is also used to verify {pnm}-to-{cu}
|
||||||
mappings, so the user should record its checksum.
|
mappings, so the user should record its checksum.
|
||||||
-P, --passwd-file= f Get {pnm} wallet or bitcoind passphrase from file 'f'
|
-P, --passwd-file= f Get {pnm} wallet or {dn} passphrase from file 'f'
|
||||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||||
-I, --info Display information about the transaction and exit
|
-I, --info Display information about the transaction and exit
|
||||||
-t, --terse-info Like '--info', but produce more concise output
|
-t, --terse-info Like '--info', but produce more concise output
|
||||||
-v, --verbose Produce more verbose output
|
-v, --verbose Produce more verbose output
|
||||||
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
-y, --yes Answer 'yes' to prompts, suppress non-essential output
|
||||||
""".format(
|
""".format(
|
||||||
g=g,pnm=pnm,pnl=pnm.lower(),
|
g=g,pnm=g.proj_name,pnl=g.proj_name.lower(),dn=g.proto.daemon_name,
|
||||||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||||
kg=g.key_generator,
|
kg=g.key_generator,
|
||||||
cu=g.coin
|
cu=g.coin
|
||||||
),
|
),
|
||||||
'notes': '\n' + txsign_notes
|
'notes': '\n' + help_notes('txsign')
|
||||||
}
|
}
|
||||||
|
|
||||||
infiles = opts.init(opts_data,add_opts=['b16'])
|
infiles = opts.init(opts_data,add_opts=['b16'])
|
||||||
|
|
@ -73,11 +74,13 @@ infiles = opts.init(opts_data,add_opts=['b16'])
|
||||||
if not infiles: opts.usage()
|
if not infiles: opts.usage()
|
||||||
for i in infiles: check_infile(i)
|
for i in infiles: check_infile(i)
|
||||||
|
|
||||||
c = rpc_connection()
|
rpc_init()
|
||||||
|
|
||||||
if not opt.info and not opt.terse_info:
|
if not opt.info and not opt.terse_info:
|
||||||
do_license_msg(immed=True)
|
do_license_msg(immed=True)
|
||||||
|
|
||||||
|
from mmgen.txsign import *
|
||||||
|
|
||||||
tx_files = get_tx_files(opt,infiles)
|
tx_files = get_tx_files(opt,infiles)
|
||||||
seed_files = get_seed_files(opt,infiles)
|
seed_files = get_seed_files(opt,infiles)
|
||||||
|
|
||||||
|
|
@ -105,7 +108,7 @@ for tx_num,tx_file in enumerate(tx_files,1):
|
||||||
if not opt.yes:
|
if not opt.yes:
|
||||||
tx.view_with_prompt('View data for transaction%s?' % tx_num_str)
|
tx.view_with_prompt('View data for transaction%s?' % tx_num_str)
|
||||||
|
|
||||||
txsign(opt,c,tx,seed_files,kl,kal,tx_num_str)
|
txsign(tx,seed_files,kl,kal,tx_num_str)
|
||||||
|
|
||||||
if not opt.yes:
|
if not opt.yes:
|
||||||
tx.add_comment() # edits an existing comment
|
tx.add_comment() # edits an existing comment
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,8 @@ usage = '[opts] [infile]'
|
||||||
nargs = 1
|
nargs = 1
|
||||||
iaction = 'convert'
|
iaction = 'convert'
|
||||||
oaction = 'convert'
|
oaction = 'convert'
|
||||||
|
|
||||||
invoked_as = 'passchg' if g.prog_name == 'mmgen-passchg' else g.prog_name.partition('-wallet')[2]
|
invoked_as = 'passchg' if g.prog_name == 'mmgen-passchg' else g.prog_name.partition('-wallet')[2]
|
||||||
|
bw_note = True
|
||||||
|
|
||||||
# full: defhHiJkKlLmoOpPqrSvz-
|
# full: defhHiJkKlLmoOpPqrSvz-
|
||||||
if invoked_as == 'gen':
|
if invoked_as == 'gen':
|
||||||
|
|
@ -51,7 +51,7 @@ elif invoked_as == 'passchg':
|
||||||
desc = 'Change the passphrase, hash preset or label of an {pnm} wallet'
|
desc = 'Change the passphrase, hash preset or label of an {pnm} wallet'
|
||||||
opt_filter = 'efhdiHkKOlLmpPqrSvz-'
|
opt_filter = 'efhdiHkKOlLmpPqrSvz-'
|
||||||
iaction = 'input'
|
iaction = 'input'
|
||||||
bw_note = ''
|
bw_note = False
|
||||||
else:
|
else:
|
||||||
die(1,"'%s': unrecognized invocation" % g.prog_name)
|
die(1,"'%s': unrecognized invocation" % g.prog_name)
|
||||||
|
|
||||||
|
|
@ -97,14 +97,14 @@ opts_data = lambda: {
|
||||||
),
|
),
|
||||||
'notes': """
|
'notes': """
|
||||||
|
|
||||||
{pwn}{bwn}
|
{n_pw}{n_bw}
|
||||||
|
|
||||||
FMT CODES:
|
FMT CODES:
|
||||||
{f}
|
{f}
|
||||||
""".format(
|
""".format(
|
||||||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||||
pwn=pw_note,
|
n_pw=help_notes('passwd'),
|
||||||
bwn=('','\n\n' + bw_note)[bool(bw_note)]
|
n_bw=('','\n\n' + help_notes('brainwallet'))[bw_note]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
65
mmgen/obj.py
65
mmgen/obj.py
|
|
@ -285,6 +285,8 @@ class BTCAmt(Decimal,Hilite,InitErrors):
|
||||||
color = 'yellow'
|
color = 'yellow'
|
||||||
max_prec = 8
|
max_prec = 8
|
||||||
max_amt = 21000000
|
max_amt = 21000000
|
||||||
|
min_coin_unit = Decimal('0.00000001')
|
||||||
|
|
||||||
def __new__(cls,num,on_fail='die'):
|
def __new__(cls,num,on_fail='die'):
|
||||||
if type(num) == cls: return num
|
if type(num) == cls: return num
|
||||||
cls.arg_chk(cls,on_fail)
|
cls.arg_chk(cls,on_fail)
|
||||||
|
|
@ -297,8 +299,8 @@ class BTCAmt(Decimal,Hilite,InitErrors):
|
||||||
assert me >= 0,'coin amount cannot be negative'
|
assert me >= 0,'coin amount cannot be negative'
|
||||||
return me
|
return me
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
m = "{!r}: value cannot be converted to BTCAmt ({})"
|
m = "{!r}: value cannot be converted to {} ({})"
|
||||||
return cls.init_fail(m.format(num,e[0]),on_fail)
|
return cls.init_fail(m.format(num,cls.__name__,e[0]),on_fail)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fmtc(cls):
|
def fmtc(cls):
|
||||||
|
|
@ -347,24 +349,29 @@ class BTCAmt(Decimal,Hilite,InitErrors):
|
||||||
def __neg__(self,other,context=None):
|
def __neg__(self,other,context=None):
|
||||||
return type(self)(Decimal.__neg__(self,other,context))
|
return type(self)(Decimal.__neg__(self,other,context))
|
||||||
|
|
||||||
|
class BCHAmt(BTCAmt):
|
||||||
|
pass
|
||||||
|
class LTCAmt(BTCAmt):
|
||||||
|
max_amt = 84000000
|
||||||
|
|
||||||
class CoinAddr(str,Hilite,InitErrors,MMGenObject):
|
class CoinAddr(str,Hilite,InitErrors,MMGenObject):
|
||||||
color = 'cyan'
|
color = 'cyan'
|
||||||
width = 35 # max len of testnet p2sh addr
|
width = 35 # max len of testnet p2sh addr
|
||||||
def __new__(cls,s,on_fail='die'):
|
def __new__(cls,s,on_fail='die'):
|
||||||
if type(s) == cls: return s
|
if type(s) == cls: return s
|
||||||
cls.arg_chk(cls,on_fail)
|
cls.arg_chk(cls,on_fail)
|
||||||
|
from mmgen.globalvars import g
|
||||||
try:
|
try:
|
||||||
assert set(s) <= set(ascii_letters+digits),'contains non-ascii characters'
|
assert set(s) <= set(ascii_letters+digits),'contains non-ascii characters'
|
||||||
me = str.__new__(cls,s)
|
me = str.__new__(cls,s)
|
||||||
from mmgen.globalvars import g
|
|
||||||
va = g.proto.verify_addr(s,return_dict=True)
|
va = g.proto.verify_addr(s,return_dict=True)
|
||||||
assert va,'failed verification'
|
assert va,'failed verification'
|
||||||
me.addr_fmt = va['format']
|
me.addr_fmt = va['format']
|
||||||
me.hex = va['hex']
|
me.hex = va['hex']
|
||||||
return me
|
return me
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
m = "{!r}: value cannot be converted to Bitcoin address ({})"
|
m = "{!r}: value cannot be converted to {} address ({})"
|
||||||
return cls.init_fail(m.format(s,e[0]),on_fail)
|
return cls.init_fail(m.format(s,g.proto.__name__,e[0]),on_fail)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fmtc(cls,s,**kwargs):
|
def fmtc(cls,s,**kwargs):
|
||||||
|
|
@ -376,22 +383,17 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
|
||||||
s = s[:kwargs['width']-2] + '..'
|
s = s[:kwargs['width']-2] + '..'
|
||||||
return Hilite.fmtc(s,**kwargs)
|
return Hilite.fmtc(s,**kwargs)
|
||||||
|
|
||||||
def is_for_current_chain(self):
|
def is_for_chain(self,chain):
|
||||||
from mmgen.globalvars import g
|
from mmgen.globalvars import g
|
||||||
assert g.chain,'global chain variable unset'
|
vn = g.proto.get_protocol_by_chain(chain).addr_ver_num
|
||||||
return self[0] in g.proto.get_chain_protocol(g.chain).addr_pfxs
|
if self.addr_fmt == 'p2sh' and 'p2sh2' in vn:
|
||||||
|
return self[0] in vn['p2sh'][1] or self[0] in vn['p2sh2'][1]
|
||||||
def is_mainnet(self):
|
else:
|
||||||
from mmgen.globalvars import g
|
return self[0] in vn[self.addr_fmt][1]
|
||||||
return self[0] in g.proto.get_chain_protocol('mainnet').addr_pfxs
|
|
||||||
|
|
||||||
def is_testnet(self):
|
|
||||||
from mmgen.globalvars import g
|
|
||||||
return self[0] in g.proto.get_chain_protocol('testnet').addr_pfxs
|
|
||||||
|
|
||||||
def is_in_tracking_wallet(self):
|
def is_in_tracking_wallet(self):
|
||||||
from mmgen.rpc import rpc_connection
|
from mmgen.rpc import rpc_init
|
||||||
d = rpc_connection().validateaddress(self)
|
d = rpc_init().validateaddress(self)
|
||||||
return d['iswatchonly'] and 'account' in d
|
return d['iswatchonly'] and 'account' in d
|
||||||
|
|
||||||
class SeedID(str,Hilite,InitErrors):
|
class SeedID(str,Hilite,InitErrors):
|
||||||
|
|
@ -452,7 +454,9 @@ class TwMMGenID(str,Hilite,InitErrors,MMGenObject):
|
||||||
sort_key,idtype = ret.sort_key,'mmgen'
|
sort_key,idtype = ret.sort_key,'mmgen'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
try:
|
try:
|
||||||
assert s[:4] == 'btc:',"not a string beginning with the prefix 'btc:'"
|
from mmgen.globalvars import g
|
||||||
|
assert s.split(':',1)[0] == g.proto.base_coin.lower(),(
|
||||||
|
"not a string beginning with the prefix '{}:'".format(g.proto.base_coin.lower()))
|
||||||
assert set(s[4:]) <= set(ascii_letters+digits),'contains non-ascii characters'
|
assert set(s[4:]) <= set(ascii_letters+digits),'contains non-ascii characters'
|
||||||
assert len(s) > 4,'not more that four characters long'
|
assert len(s) > 4,'not more that four characters long'
|
||||||
ret,sort_key,idtype = str(s),'z_'+s,'non-mmgen'
|
ret,sort_key,idtype = str(s),'z_'+s,'non-mmgen'
|
||||||
|
|
@ -514,7 +518,7 @@ class MMGenTxID(HexStr,Hilite,InitErrors):
|
||||||
m = "{}\n{!r}: value cannot be converted to {}"
|
m = "{}\n{!r}: value cannot be converted to {}"
|
||||||
return cls.init_fail(m.format(e[0],s,cls.__name__),on_fail)
|
return cls.init_fail(m.format(e[0],s,cls.__name__),on_fail)
|
||||||
|
|
||||||
class BitcoinTxID(MMGenTxID):
|
class CoinTxID(MMGenTxID):
|
||||||
color = 'purple'
|
color = 'purple'
|
||||||
width = 64
|
width = 64
|
||||||
hexcase = 'lower'
|
hexcase = 'lower'
|
||||||
|
|
@ -667,32 +671,30 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
|
||||||
'comp':False,
|
'comp':False,
|
||||||
'gen':'p2pkh',
|
'gen':'p2pkh',
|
||||||
'fmt':'p2pkh',
|
'fmt':'p2pkh',
|
||||||
'desc':'Legacy uncompressed Bitcoin address'},
|
'desc':'Legacy uncompressed address'},
|
||||||
'S': { 'name':'segwit',
|
'S': { 'name':'segwit',
|
||||||
'comp':True,
|
'comp':True,
|
||||||
'gen':'segwit',
|
'gen':'segwit',
|
||||||
'fmt':'p2sh',
|
'fmt':'p2sh',
|
||||||
'desc':'Bitcoin Segwit P2SH-P2WPK address' },
|
'desc':'Segwit P2SH-P2WPKH address' },
|
||||||
'C': { 'name':'compressed',
|
'C': { 'name':'compressed',
|
||||||
'comp':True,
|
'comp':True,
|
||||||
'gen':'p2pkh',
|
'gen':'p2pkh',
|
||||||
'fmt':'p2pkh',
|
'fmt':'p2pkh',
|
||||||
'desc':'Compressed Bitcoin P2PKH address'}
|
'desc':'Compressed P2PKH address'}
|
||||||
# 'l': 'litecoin',
|
|
||||||
# 'e': 'ethereum',
|
|
||||||
# 'E': 'ethereum_classic',
|
|
||||||
# 'm': 'monero',
|
|
||||||
# 'z': 'zcash',
|
|
||||||
}
|
}
|
||||||
dfl_mmtype = 'L'
|
dfl_mmtype = 'L'
|
||||||
def __new__(cls,s,on_fail='die',errmsg=None):
|
def __new__(cls,s,on_fail='die',errmsg=None):
|
||||||
if type(s) == cls: return s
|
if type(s) == cls: return s
|
||||||
cls.arg_chk(cls,on_fail)
|
cls.arg_chk(cls,on_fail)
|
||||||
|
from mmgen.globalvars import g
|
||||||
try:
|
try:
|
||||||
for k,v in cls.mmtypes.items():
|
for k,v in cls.mmtypes.items():
|
||||||
if s in (k,v['name']):
|
if s in (k,v['name']):
|
||||||
if s == v['name']: s = k
|
if s == v['name']: s = k
|
||||||
me = str.__new__(cls,s)
|
me = str.__new__(cls,s)
|
||||||
|
assert me in g.proto.mmtypes + ('P',), (
|
||||||
|
"'{}': invalid address type for {}".format(me,g.proto.__name__))
|
||||||
me.name = v['name']
|
me.name = v['name']
|
||||||
me.compressed = v['comp']
|
me.compressed = v['comp']
|
||||||
me.gen_method = v['gen']
|
me.gen_method = v['gen']
|
||||||
|
|
@ -701,9 +703,14 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
|
||||||
return me
|
return me
|
||||||
raise ValueError,'not found'
|
raise ValueError,'not found'
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
m = errmsg or '{!r}: invalid value for {} ({})'.format(s,cls.__name__,e[0])
|
m = '{}{!r}: invalid value for {} ({})'.format(
|
||||||
|
('{!r}\n'.format(errmsg) if errmsg else ''),s,cls.__name__,e[0])
|
||||||
return cls.init_fail(m,on_fail)
|
return cls.init_fail(m,on_fail)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_names(cls):
|
||||||
|
return [v['name'] for v in cls.mmtypes.values()]
|
||||||
|
|
||||||
class MMGenPasswordType(MMGenAddrType):
|
class MMGenPasswordType(MMGenAddrType):
|
||||||
mmtypes = {
|
mmtypes = {
|
||||||
'P': { 'name':'password',
|
'P': { 'name':'password',
|
||||||
|
|
|
||||||
133
mmgen/opts.py
133
mmgen/opts.py
|
|
@ -45,30 +45,6 @@ def _show_hash_presets():
|
||||||
msg(fs.format("'%s'" % i, *g.hash_presets[i]))
|
msg(fs.format("'%s'" % i, *g.hash_presets[i]))
|
||||||
msg('N = memory usage (power of two), p = iterations (rounds)')
|
msg('N = memory usage (power of two), p = iterations (rounds)')
|
||||||
|
|
||||||
# most, but not all, of these set the corresponding global var
|
|
||||||
common_opts_data = """
|
|
||||||
--, --coin=c Choose coin unit. Default: {cu_dfl}. Options: {cu_all}
|
|
||||||
--, --color=0|1 Disable or enable color output
|
|
||||||
--, --force-256-color Force 256-color output when color is enabled
|
|
||||||
--, --bitcoin-data-dir=d Specify Bitcoin data directory location 'd'
|
|
||||||
--, --data-dir=d Specify {pnm} data directory location 'd'
|
|
||||||
--, --no-license Suppress the GPL license prompt
|
|
||||||
--, --rpc-host=h Communicate with bitcoind running on host 'h'
|
|
||||||
--, --rpc-port=p Communicate with bitcoind listening on port 'p'
|
|
||||||
--, --rpc-user=user Override 'rpcuser' in bitcoin.conf
|
|
||||||
--, --rpc-password=pass Override 'rpcpassword' in bitcoin.conf
|
|
||||||
--, --regtest=0|1 Disable or enable regtest mode
|
|
||||||
--, --testnet=0|1 Disable or enable testnet
|
|
||||||
--, --skip-cfg-file Skip reading the configuration file
|
|
||||||
--, --version Print version information and exit
|
|
||||||
--, --bob Switch to user "Bob" in MMGen regtest setup
|
|
||||||
--, --alice Switch to user "Alice" in MMGen regtest setup
|
|
||||||
""".format(
|
|
||||||
pnm=g.proj_name,
|
|
||||||
cu_dfl=g.coin,
|
|
||||||
cu_all=' '.join(g.coins),
|
|
||||||
)
|
|
||||||
|
|
||||||
def opt_preproc_debug(short_opts,long_opts,skipped_opts,uopts,args):
|
def opt_preproc_debug(short_opts,long_opts,skipped_opts,uopts,args):
|
||||||
d = (
|
d = (
|
||||||
('Cmdline', ' '.join(sys.argv)),
|
('Cmdline', ' '.join(sys.argv)),
|
||||||
|
|
@ -118,36 +94,55 @@ def set_data_dir_root():
|
||||||
# mainnet and testnet share cfg file, as with Core
|
# mainnet and testnet share cfg file, as with Core
|
||||||
g.cfg_file = os.path.join(g.data_dir_root,'{}.cfg'.format(g.proj_name.lower()))
|
g.cfg_file = os.path.join(g.data_dir_root,'{}.cfg'.format(g.proj_name.lower()))
|
||||||
|
|
||||||
def get_data_from_config_file():
|
def get_cfg_template_data():
|
||||||
from mmgen.util import msg,die,check_or_create_dir
|
|
||||||
check_or_create_dir(g.data_dir_root) # dies on error
|
|
||||||
|
|
||||||
# https://wiki.debian.org/Python:
|
# https://wiki.debian.org/Python:
|
||||||
# Debian (Ubuntu) sys.prefix is '/usr' rather than '/usr/local, so add 'local'
|
# Debian (Ubuntu) sys.prefix is '/usr' rather than '/usr/local, so add 'local'
|
||||||
# TODO - test for Windows
|
# TODO - test for Windows
|
||||||
# This must match the configuration in setup.py
|
# This must match the configuration in setup.py
|
||||||
data = u''
|
|
||||||
try:
|
|
||||||
with open(g.cfg_file,'rb') as f: data = f.read().decode('utf8')
|
|
||||||
except:
|
|
||||||
cfg_template = os.path.join(*([sys.prefix]
|
cfg_template = os.path.join(*([sys.prefix]
|
||||||
+ (['share'],['local','share'])[g.platform=='linux']
|
+ (['share'],['local','share'])[g.platform=='linux']
|
||||||
+ [g.proj_name.lower(),os.path.basename(g.cfg_file)]))
|
+ [g.proj_name.lower(),os.path.basename(g.cfg_file)]))
|
||||||
try:
|
try:
|
||||||
with open(cfg_template,'rb') as f: template_data = f.read()
|
with open(cfg_template,'rb') as f:
|
||||||
|
return f.read()
|
||||||
except:
|
except:
|
||||||
msg("WARNING: configuration template not found at '{}'".format(cfg_template))
|
msg("WARNING: configuration template not found at '{}'".format(cfg_template))
|
||||||
else:
|
return u''
|
||||||
|
|
||||||
|
def get_data_from_cfg_file():
|
||||||
|
from mmgen.util import msg,die,check_or_create_dir
|
||||||
|
check_or_create_dir(g.data_dir_root) # dies on error
|
||||||
|
template_data = get_cfg_template_data()
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
def copy_template_data(fn):
|
||||||
try:
|
try:
|
||||||
with open(g.cfg_file,'wb') as f: f.write(template_data)
|
with open(fn,'wb') as f: f.write(template_data)
|
||||||
os.chmod(g.cfg_file,0600)
|
os.chmod(fn,0600)
|
||||||
except:
|
except:
|
||||||
die(2,"ERROR: unable to write to datadir '{}'".format(g.data_dir))
|
die(2,"ERROR: unable to write to datadir '{}'".format(g.data_dir))
|
||||||
return data
|
|
||||||
|
for k,suf in (('cfg',''),('sample','.sample')):
|
||||||
|
try:
|
||||||
|
with open(g.cfg_file+suf,'rb') as f:
|
||||||
|
data[k] = f.read().decode('utf8')
|
||||||
|
except:
|
||||||
|
if template_data:
|
||||||
|
copy_template_data(g.cfg_file+suf)
|
||||||
|
data[k] = template_data
|
||||||
|
else:
|
||||||
|
data[k] = u''
|
||||||
|
|
||||||
|
if template_data and data['sample'] != template_data:
|
||||||
|
g.cfg_options_changed = True
|
||||||
|
copy_template_data(g.cfg_file+'.sample')
|
||||||
|
|
||||||
|
return data['cfg']
|
||||||
|
|
||||||
def override_from_cfg_file(cfg_data):
|
def override_from_cfg_file(cfg_data):
|
||||||
from mmgen.util import die,strip_comments,set_for_type
|
from mmgen.util import die,strip_comments,set_for_type
|
||||||
import re
|
import re
|
||||||
|
from mmgen.protocol import CoinProtocol
|
||||||
for n,l in enumerate(cfg_data.splitlines(),1): # DOS-safe
|
for n,l in enumerate(cfg_data.splitlines(),1): # DOS-safe
|
||||||
l = strip_comments(l)
|
l = strip_comments(l)
|
||||||
if l == '': continue
|
if l == '': continue
|
||||||
|
|
@ -155,9 +150,16 @@ def override_from_cfg_file(cfg_data):
|
||||||
if not m: die(2,"Parse error in file '{}', line {}".format(g.cfg_file,n))
|
if not m: die(2,"Parse error in file '{}', line {}".format(g.cfg_file,n))
|
||||||
name,val = m.groups()
|
name,val = m.groups()
|
||||||
if name in g.cfg_file_opts:
|
if name in g.cfg_file_opts:
|
||||||
setattr(g,name,set_for_type(val,getattr(g,name),name,src=g.cfg_file))
|
pfx,cfg_var = name.split('_',1)
|
||||||
|
if pfx in CoinProtocol.coins:
|
||||||
|
cls,attr = CoinProtocol(pfx,False),cfg_var
|
||||||
|
else:
|
||||||
|
cls,attr = g,name
|
||||||
|
setattr(cls,attr,set_for_type(val,getattr(cls,attr),attr,src=g.cfg_file))
|
||||||
|
# pmsg(cls,attr,getattr(cls,attr))
|
||||||
else:
|
else:
|
||||||
die(2,"'{}': unrecognized option in '{}'".format(name,g.cfg_file))
|
die(2,"'{}': unrecognized option in '{}'".format(name,g.cfg_file))
|
||||||
|
# pdie('xxx')
|
||||||
|
|
||||||
def override_from_env():
|
def override_from_env():
|
||||||
from mmgen.util import set_for_type
|
from mmgen.util import set_for_type
|
||||||
|
|
@ -170,12 +172,37 @@ def override_from_env():
|
||||||
|
|
||||||
def init(opts_f,add_opts=[],opt_filter=None):
|
def init(opts_f,add_opts=[],opt_filter=None):
|
||||||
|
|
||||||
|
from mmgen.protocol import CoinProtocol,BitcoinProtocol
|
||||||
|
g.proto = BitcoinProtocol # this must be initialized to something before opts_f is called
|
||||||
|
|
||||||
|
# most, but not all, of these set the corresponding global var
|
||||||
|
common_opts_data = """
|
||||||
|
--, --coin=c Choose coin unit. Default: {cu_dfl}. Options: {cu_all}
|
||||||
|
--, --color=0|1 Disable or enable color output
|
||||||
|
--, --force-256-color Force 256-color output when color is enabled
|
||||||
|
--, --daemon-data-dir=d Specify coin daemon data directory location 'd'
|
||||||
|
--, --data-dir=d Specify {pnm} data directory location 'd'
|
||||||
|
--, --no-license Suppress the GPL license prompt
|
||||||
|
--, --rpc-host=h Communicate with {dn} running on host 'h'
|
||||||
|
--, --rpc-port=p Communicate with {dn} listening on port 'p'
|
||||||
|
--, --rpc-user=user Override 'rpcuser' in {pn}.conf
|
||||||
|
--, --rpc-password=pass Override 'rpcpassword' in {pn}.conf
|
||||||
|
--, --regtest=0|1 Disable or enable regtest mode
|
||||||
|
--, --testnet=0|1 Disable or enable testnet
|
||||||
|
--, --skip-cfg-file Skip reading the configuration file
|
||||||
|
--, --version Print version information and exit
|
||||||
|
--, --bob Switch to user "Bob" in MMGen regtest setup
|
||||||
|
--, --alice Switch to user "Alice" in MMGen regtest setup
|
||||||
|
""".format( pnm=g.proj_name,pn=g.proto.name,dn=g.proto.daemon_name,
|
||||||
|
cu_dfl=g.coin,
|
||||||
|
cu_all=' '.join(CoinProtocol.coins))
|
||||||
|
|
||||||
opts_data = opts_f()
|
opts_data = opts_f()
|
||||||
opts_data['long_options'] = common_opts_data
|
opts_data['long_options'] = common_opts_data
|
||||||
|
|
||||||
version_info = """
|
version_info = """
|
||||||
{pgnm_uc} version {g.version}
|
{pgnm_uc} version {g.version}
|
||||||
Part of the {pnm} suite, a Bitcoin cold-storage solution for the command line.
|
Part of the {pnm} suite, an online/offline cryptocoin wallet for the command line.
|
||||||
Copyright (C) {g.Cdates} {g.author} {g.email}
|
Copyright (C) {g.Cdates} {g.author} {g.email}
|
||||||
""".format(pnm=g.proj_name, g=g, pgnm_uc=g.prog_name.upper()).strip()
|
""".format(pnm=g.proj_name, g=g, pgnm_uc=g.prog_name.upper()).strip()
|
||||||
|
|
||||||
|
|
@ -199,11 +226,10 @@ def init(opts_f,add_opts=[],opt_filter=None):
|
||||||
|
|
||||||
# NB: user opt --data-dir is actually g.data_dir_root
|
# NB: user opt --data-dir is actually g.data_dir_root
|
||||||
# cfg file is in g.data_dir_root, wallet and other data are in g.data_dir
|
# cfg file is in g.data_dir_root, wallet and other data are in g.data_dir
|
||||||
# Must set g.data_dir_root and g.cfg_file from cmdline before processing cfg file
|
# We must set g.data_dir_root and g.cfg_file from cmdline before processing cfg file
|
||||||
set_data_dir_root()
|
set_data_dir_root()
|
||||||
if not opt.skip_cfg_file:
|
if not opt.skip_cfg_file:
|
||||||
cfg_data = get_data_from_config_file()
|
override_from_cfg_file(get_data_from_cfg_file())
|
||||||
override_from_cfg_file(cfg_data)
|
|
||||||
override_from_env()
|
override_from_env()
|
||||||
|
|
||||||
# User opt sets global var - do these here, before opt is set from g.global_sets_opt
|
# User opt sets global var - do these here, before opt is set from g.global_sets_opt
|
||||||
|
|
@ -214,10 +240,10 @@ def init(opts_f,add_opts=[],opt_filter=None):
|
||||||
if g.regtest: g.testnet = True # These are equivalent for now
|
if g.regtest: g.testnet = True # These are equivalent for now
|
||||||
|
|
||||||
# g.testnet is set, so we can set g.proto
|
# g.testnet is set, so we can set g.proto
|
||||||
from mmgen.protocol import get_coin_protocol
|
g.proto = CoinProtocol(g.coin,g.testnet)
|
||||||
g.proto = get_coin_protocol(g.coin,g.testnet)
|
|
||||||
|
|
||||||
if not g.daemon_data_dir: g.daemon_data_dir = g.proto.daemon_data_dir
|
# global sets proto
|
||||||
|
if g.daemon_data_dir: g.proto.daemon_data_dir = g.daemon_data_dir
|
||||||
|
|
||||||
# g.proto is set, so we can set g.data_dir
|
# g.proto is set, so we can set g.data_dir
|
||||||
g.data_dir = os.path.normpath(os.path.join(g.data_dir_root,g.proto.data_subdir))
|
g.data_dir = os.path.normpath(os.path.join(g.data_dir_root,g.proto.data_subdir))
|
||||||
|
|
@ -250,7 +276,7 @@ def init(opts_f,add_opts=[],opt_filter=None):
|
||||||
|
|
||||||
if g.bob or g.alice:
|
if g.bob or g.alice:
|
||||||
g.testnet = True
|
g.testnet = True
|
||||||
g.proto = get_coin_protocol(g.coin,g.testnet)
|
g.proto = CoinProtocol(g.coin,g.testnet)
|
||||||
g.data_dir = os.path.join(g.data_dir_root,'regtest',('alice','bob')[g.bob])
|
g.data_dir = os.path.join(g.data_dir_root,'regtest',('alice','bob')[g.bob])
|
||||||
check_or_create_dir(g.data_dir)
|
check_or_create_dir(g.data_dir)
|
||||||
import regtest as rt
|
import regtest as rt
|
||||||
|
|
@ -263,6 +289,10 @@ def init(opts_f,add_opts=[],opt_filter=None):
|
||||||
if not check_opts(uopts):
|
if not check_opts(uopts):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
if hasattr(g,'cfg_options_changed'):
|
||||||
|
ymsg("Warning: config file options have changed! See '{}' for details".format(g.cfg_file+'.sample'))
|
||||||
|
my_raw_input('Hit ENTER to continue: ')
|
||||||
|
|
||||||
if g.debug: opt_postproc_debug()
|
if g.debug: opt_postproc_debug()
|
||||||
|
|
||||||
# We don't need this data anymore
|
# We don't need this data anymore
|
||||||
|
|
@ -308,8 +338,9 @@ def check_opts(usr_opts): # Returns false if any check fails
|
||||||
if ret == False:
|
if ret == False:
|
||||||
msg("'{}': invalid {} (not a {} amount or satoshis-per-byte specification)".format(
|
msg("'{}': invalid {} (not a {} amount or satoshis-per-byte specification)".format(
|
||||||
val,desc,g.coin.upper()))
|
val,desc,g.coin.upper()))
|
||||||
elif ret != None and ret > g.max_tx_fee:
|
elif ret != None and ret > g.proto.max_tx_fee:
|
||||||
msg("'{}': invalid {} (> max_tx_fee ({} {}))".format(val,desc,g.max_tx_fee,g.coin.upper()))
|
msg("'{}': invalid {} (> max_tx_fee ({} {}))".format(
|
||||||
|
val,desc,g.proto.max_tx_fee,g.coin.upper()))
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
@ -420,7 +451,11 @@ def check_opts(usr_opts): # Returns false if any check fails
|
||||||
if not opt_compares(val,'<=',len(g.key_generators),desc): return False
|
if not opt_compares(val,'<=',len(g.key_generators),desc): return False
|
||||||
if not opt_compares(val,'>',0,desc): return False
|
if not opt_compares(val,'>',0,desc): return False
|
||||||
elif key == 'coin':
|
elif key == 'coin':
|
||||||
if not opt_is_in_list(val.upper(),g.coins,'coin'): return False
|
from mmgen.protocol import CoinProtocol
|
||||||
|
if not opt_is_in_list(val.lower(),CoinProtocol.coins.keys(),'coin'): return False
|
||||||
|
elif key == 'rbf':
|
||||||
|
if not g.proto.cap('rbf'):
|
||||||
|
die(1,'--rbf requested, but {} does not support replace-by-fee transactions'.format(g.coin))
|
||||||
elif key in ('bob','alice'):
|
elif key in ('bob','alice'):
|
||||||
from mmgen.regtest import daemon_dir
|
from mmgen.regtest import daemon_dir
|
||||||
m = "Regtest (Bob and Alice) mode not set up yet. Run '{}-regtest setup' to initialize."
|
m = "Regtest (Bob and Alice) mode not set up yet. Run '{}-regtest setup' to initialize."
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ protocol.py: Coin protocol functions, classes and methods
|
||||||
import os,hashlib
|
import os,hashlib
|
||||||
from binascii import unhexlify
|
from binascii import unhexlify
|
||||||
from mmgen.util import msg,pmsg
|
from mmgen.util import msg,pmsg
|
||||||
|
from mmgen.obj import MMGenObject,BTCAmt,LTCAmt,BCHAmt
|
||||||
|
from mmgen.globalvars import g
|
||||||
|
|
||||||
def hash160(hexnum): # take hex, return hex - OP_HASH160
|
def hash160(hexnum): # take hex, return hex - OP_HASH160
|
||||||
return hashlib.new('ripemd160',hashlib.sha256(unhexlify(hexnum)).digest()).hexdigest()
|
return hashlib.new('ripemd160',hashlib.sha256(unhexlify(hexnum)).digest()).hexdigest()
|
||||||
|
|
@ -51,43 +53,38 @@ def _b58tonum(b58num):
|
||||||
if not i in _b58a: return False
|
if not i in _b58a: return False
|
||||||
return sum(_b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1])))
|
return sum(_b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1])))
|
||||||
|
|
||||||
def get_coin_protocol(coin,testnet):
|
|
||||||
coin = coin.lower()
|
|
||||||
coins = {
|
|
||||||
'btc': (BitcoinProtocol,BitcoinTestnetProtocol),
|
|
||||||
'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol),
|
|
||||||
'ltc': (LitecoinProtocol,LitecoinTestnetProtocol),
|
|
||||||
'eth': (EthereumProtocol,EthereumTestnetProtocol),
|
|
||||||
}
|
|
||||||
assert type(testnet) == bool
|
|
||||||
assert coin in coins
|
|
||||||
return coins[coin][testnet]
|
|
||||||
|
|
||||||
from mmgen.obj import MMGenObject
|
|
||||||
from mmgen.globalvars import g
|
|
||||||
|
|
||||||
class BitcoinProtocol(MMGenObject):
|
class BitcoinProtocol(MMGenObject):
|
||||||
# devdoc/ref_transactions.md:
|
name = 'bitcoin'
|
||||||
addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') }
|
daemon_name = 'bitcoind'
|
||||||
addr_pfxs = '13'
|
addr_ver_num = { 'p2pkh': ('00','1'), 'p2sh': ('05','3') } # chainparams.cpp
|
||||||
uncompressed_wif_pfx = '5'
|
|
||||||
privkey_pfx = '80'
|
privkey_pfx = '80'
|
||||||
mmtypes = ('L','C','S')
|
mmtypes = ('L','C','S')
|
||||||
data_subdir = ''
|
data_subdir = ''
|
||||||
rpc_port = 8332
|
rpc_port = 8332
|
||||||
|
secs_per_block = 600
|
||||||
|
coin_amt = BTCAmt
|
||||||
|
max_tx_fee = BTCAmt('0.01')
|
||||||
daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Bitcoin') if g.platform == 'win' \
|
daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Bitcoin') if g.platform == 'win' \
|
||||||
else os.path.join(g.home_dir,'.bitcoin')
|
else os.path.join(g.home_dir,'.bitcoin')
|
||||||
|
daemon_data_subdir = ''
|
||||||
sighash_type = 'ALL'
|
sighash_type = 'ALL'
|
||||||
block0 = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
|
block0 = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
|
||||||
forks = [
|
forks = [
|
||||||
(478559,'00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148','bch')
|
(478559,'00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148','bch')
|
||||||
]
|
]
|
||||||
|
caps = ('rbf','segwit')
|
||||||
|
base_coin = 'BTC'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_protocol_by_chain(chain):
|
||||||
|
return CoinProtocol(g.coin,{'mainnet':False,'testnet':True,'regtest':True}[chain])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_rpc_coin_amt_type():
|
||||||
|
return (float,str)[g.daemon_version>=120000]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_chain_protocol(cls,chain):
|
def cap(cls,s): return s in cls.caps
|
||||||
chain_protos = { 'mainnet':'', 'testnet':'Testnet', 'regtest':'Testnet' }
|
|
||||||
assert chain in chain_protos
|
|
||||||
return globals()['Bitcoin{}Protocol'.format(chain_protos[chain])]
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def hex2wif(cls,hexpriv,compressed=False):
|
def hex2wif(cls,hexpriv,compressed=False):
|
||||||
|
|
@ -99,9 +96,10 @@ class BitcoinProtocol(MMGenObject):
|
||||||
num = _b58tonum(wif)
|
num = _b58tonum(wif)
|
||||||
if num == False: return False
|
if num == False: return False
|
||||||
key = '{:x}'.format(num)
|
key = '{:x}'.format(num)
|
||||||
compressed = wif[0] != cls.uncompressed_wif_pfx
|
if len(key) not in (74,76): return False
|
||||||
klen = (66,68)[bool(compressed)]
|
compressed = len(key) == 76
|
||||||
if compressed and key[66:68] != '01': return False
|
if compressed and key[66:68] != '01': return False
|
||||||
|
klen = (66,68)[compressed]
|
||||||
if (key[:2] == cls.privkey_pfx and key[klen:] == hash256(key[:klen])[:8]):
|
if (key[:2] == cls.privkey_pfx and key[klen:] == hash256(key[:klen])[:8]):
|
||||||
return { 'hex':key[2:66], 'compressed':compressed }
|
return { 'hex':key[2:66], 'compressed':compressed }
|
||||||
else:
|
else:
|
||||||
|
|
@ -109,7 +107,7 @@ class BitcoinProtocol(MMGenObject):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def verify_addr(cls,addr,verbose=False,return_dict=False):
|
def verify_addr(cls,addr,verbose=False,return_dict=False):
|
||||||
for addr_fmt in ('p2pkh','p2sh'):
|
for addr_fmt in cls.addr_ver_num:
|
||||||
ver_num,ldigit = cls.addr_ver_num[addr_fmt]
|
ver_num,ldigit = cls.addr_ver_num[addr_fmt]
|
||||||
if addr[0] not in ldigit: continue
|
if addr[0] not in ldigit: continue
|
||||||
num = _b58tonum(addr)
|
num = _b58tonum(addr)
|
||||||
|
|
@ -117,7 +115,10 @@ class BitcoinProtocol(MMGenObject):
|
||||||
addr_hex = '{:050x}'.format(num)
|
addr_hex = '{:050x}'.format(num)
|
||||||
if addr_hex[:2] != ver_num: continue
|
if addr_hex[:2] != ver_num: continue
|
||||||
if hash256(addr_hex[:42])[:8] == addr_hex[42:]:
|
if hash256(addr_hex[:42])[:8] == addr_hex[42:]:
|
||||||
return { 'hex':addr_hex[2:42], 'format':addr_fmt } if return_dict else True
|
return {
|
||||||
|
'hex': addr_hex[2:42],
|
||||||
|
'format': {'p2pkh':'p2pkh','p2sh':'p2sh','p2sh2':'p2sh'}[addr_fmt],
|
||||||
|
} if return_dict else True
|
||||||
else:
|
else:
|
||||||
if verbose: Msg("Invalid checksum in address '{}'".format(addr))
|
if verbose: Msg("Invalid checksum in address '{}'".format(addr))
|
||||||
break
|
break
|
||||||
|
|
@ -125,9 +126,9 @@ class BitcoinProtocol(MMGenObject):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def hexaddr2addr(cls,hexaddr,p2sh=False):
|
def hexaddr2addr(cls,hexaddr,p2sh):
|
||||||
s = cls.addr_ver_num[('p2pkh','p2sh')[p2sh]][0] + hexaddr
|
s = cls.addr_ver_num[('p2pkh','p2sh')[p2sh]][0] + hexaddr
|
||||||
lzeroes = (len(s) - len(s.lstrip('0'))) / 2
|
lzeroes = (len(s) - len(s.lstrip('0'))) / 2 # non-zero only for ver num '00' (BTC p2pkh)
|
||||||
return ('1' * lzeroes) + _numtob58(int(s+hash256(s)[:8],16))
|
return ('1' * lzeroes) + _numtob58(int(s+hash256(s)[:8],16))
|
||||||
|
|
||||||
# Segwit:
|
# Segwit:
|
||||||
|
|
@ -144,38 +145,84 @@ class BitcoinProtocol(MMGenObject):
|
||||||
|
|
||||||
class BitcoinTestnetProtocol(BitcoinProtocol):
|
class BitcoinTestnetProtocol(BitcoinProtocol):
|
||||||
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') }
|
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') }
|
||||||
addr_pfxs = 'mn2'
|
|
||||||
uncompressed_wif_pfx = '9'
|
|
||||||
privkey_pfx = 'ef'
|
privkey_pfx = 'ef'
|
||||||
data_subdir = 'testnet3'
|
data_subdir = 'testnet'
|
||||||
|
daemon_data_subdir = 'testnet3'
|
||||||
rpc_port = 18332
|
rpc_port = 18332
|
||||||
|
|
||||||
class BitcoinCashProtocol(BitcoinProtocol):
|
class BitcoinCashProtocol(BitcoinProtocol):
|
||||||
# TODO: assumes MSWin user installs in custom dir 'Bitcoin_ABC'
|
# TODO: assumes MSWin user installs in custom dir 'Bitcoin_ABC'
|
||||||
|
daemon_name = 'bitcoind-abc'
|
||||||
daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Bitcoin_ABC') if g.platform == 'win' \
|
daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Bitcoin_ABC') if g.platform == 'win' \
|
||||||
else os.path.join(g.home_dir,'.bitcoin-abc')
|
else os.path.join(g.home_dir,'.bitcoin-abc')
|
||||||
rpc_port = 8442
|
rpc_port = 8442
|
||||||
mmtypes = ('L','C')
|
mmtypes = ('L','C')
|
||||||
sighash_type = 'ALL|FORKID'
|
sighash_type = 'ALL|FORKID'
|
||||||
block0 = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
|
|
||||||
forks = [
|
forks = [
|
||||||
(478559,'000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec','btc')
|
(478559,'000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec','btc')
|
||||||
]
|
]
|
||||||
|
caps = ()
|
||||||
|
coin_amt = BCHAmt
|
||||||
|
max_tx_fee = BCHAmt('0.1')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def pubhex2redeem_script(cls,pubhex): raise NotImplementedError
|
def pubhex2redeem_script(cls,pubhex): raise NotImplementedError
|
||||||
@classmethod
|
@classmethod
|
||||||
def pubhex2segwitaddr(cls,pubhex): raise NotImplementedError
|
def pubhex2segwitaddr(cls,pubhex): raise NotImplementedError
|
||||||
|
|
||||||
class BitcoinCashTestnetProtocol(BitcoinTestnetProtocol):
|
class BitcoinCashTestnetProtocol(BitcoinCashProtocol):
|
||||||
rpc_port = 18442
|
rpc_port = 18442
|
||||||
@classmethod
|
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('c4','2') }
|
||||||
def pubhex2redeem_script(cls,pubhex): raise NotImplementedError
|
privkey_pfx = 'ef'
|
||||||
@classmethod
|
data_subdir = 'testnet'
|
||||||
def pubhex2segwitaddr(cls,pubhex): raise NotImplementedError
|
daemon_data_subdir = 'testnet3'
|
||||||
|
|
||||||
class LitecoinProtocol(BitcoinProtocol): pass
|
class LitecoinProtocol(BitcoinProtocol):
|
||||||
class LitecoinTestnetProtocol(LitecoinProtocol): pass
|
block0 = '12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2'
|
||||||
|
name = 'litecoin'
|
||||||
|
daemon_name = 'litecoind'
|
||||||
|
daemon_data_dir = os.path.join(os.getenv('APPDATA'),'Litecoin') if g.platform == 'win' \
|
||||||
|
else os.path.join(g.home_dir,'.litecoin')
|
||||||
|
addr_ver_num = { 'p2pkh': ('30','L'), 'p2sh': ('32','M'), 'p2sh2': ('05','3') } # 'p2sh' is new fmt
|
||||||
|
privkey_pfx = 'b0'
|
||||||
|
secs_per_block = 150
|
||||||
|
rpc_port = 9332
|
||||||
|
coin_amt = LTCAmt
|
||||||
|
max_tx_fee = LTCAmt('0.3')
|
||||||
|
base_coin = 'LTC'
|
||||||
|
forks = []
|
||||||
|
|
||||||
|
class LitecoinTestnetProtocol(LitecoinProtocol):
|
||||||
|
# addr ver nums same as Bitcoin testnet, except for 'p2sh'
|
||||||
|
addr_ver_num = { 'p2pkh': ('6f','mn'), 'p2sh': ('3a','Q'), 'p2sh2': ('c4','2') }
|
||||||
|
privkey_pfx = 'ef' # same as Bitcoin testnet
|
||||||
|
data_subdir = 'testnet'
|
||||||
|
daemon_data_subdir = 'testnet4'
|
||||||
|
rpc_port = 19332
|
||||||
|
|
||||||
|
class EthereumProtocol(MMGenObject):
|
||||||
|
base_coin = 'ETH'
|
||||||
|
|
||||||
class EthereumProtocol(MMGenObject): pass
|
|
||||||
class EthereumTestnetProtocol(EthereumProtocol): pass
|
class EthereumTestnetProtocol(EthereumProtocol): pass
|
||||||
|
|
||||||
|
class CoinProtocol(MMGenObject):
|
||||||
|
coins = {
|
||||||
|
'btc': (BitcoinProtocol,BitcoinTestnetProtocol),
|
||||||
|
'bch': (BitcoinCashProtocol,BitcoinCashTestnetProtocol),
|
||||||
|
'ltc': (LitecoinProtocol,LitecoinTestnetProtocol),
|
||||||
|
# 'eth': (EthereumProtocol,EthereumTestnetProtocol),
|
||||||
|
}
|
||||||
|
def __new__(cls,coin,testnet):
|
||||||
|
coin = coin.lower()
|
||||||
|
assert type(testnet) == bool
|
||||||
|
if coin not in cls.coins:
|
||||||
|
from mmgen.util import die
|
||||||
|
die(1,"'{}': not a valid coin. Valid choices are '{}'".format(coin,"','".join(cls.coins)))
|
||||||
|
return cls.coins[coin][testnet]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_base_coin_from_name(cls,name):
|
||||||
|
for proto,foo in cls.coins.values():
|
||||||
|
if name == proto.__name__[:-8].lower():
|
||||||
|
return proto.base_coin
|
||||||
|
return False
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
regtest: Bitcoind regression test mode setup and operations for the MMGen suite
|
regtest: Coin daemon regression test mode setup and operations for the MMGen suite
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os,subprocess,time,shutil
|
import os,subprocess,time,shutil
|
||||||
|
|
@ -42,7 +42,7 @@ common_args = (
|
||||||
|
|
||||||
def start_daemon(user,quiet=False,daemon=True):
|
def start_daemon(user,quiet=False,daemon=True):
|
||||||
cmd = (
|
cmd = (
|
||||||
'bitcoind',
|
g.proto.daemon_name,
|
||||||
'-keypool=1',
|
'-keypool=1',
|
||||||
'-wallet={}'.format(os.path.basename(tr_wallet(user)))
|
'-wallet={}'.format(os.path.basename(tr_wallet(user)))
|
||||||
) + common_args
|
) + common_args
|
||||||
|
|
@ -51,7 +51,7 @@ def start_daemon(user,quiet=False,daemon=True):
|
||||||
p = subprocess.Popen(cmd,stdout=PIPE,stderr=PIPE)
|
p = subprocess.Popen(cmd,stdout=PIPE,stderr=PIPE)
|
||||||
err = process_output(p,silent=False)[1]
|
err = process_output(p,silent=False)[1]
|
||||||
if err:
|
if err:
|
||||||
rdie(1,'Error starting the Bitcoin daemon:\n{}'.format(err))
|
rdie(1,'Error starting the {} daemon:\n{}'.format(g.proto.name.capitalize(),err))
|
||||||
|
|
||||||
def start_daemon_mswin(user,quiet=False):
|
def start_daemon_mswin(user,quiet=False):
|
||||||
import threading
|
import threading
|
||||||
|
|
@ -63,7 +63,7 @@ def start_daemon_mswin(user,quiet=False):
|
||||||
def start_cmd(*args,**kwargs):
|
def start_cmd(*args,**kwargs):
|
||||||
cmd = args
|
cmd = args
|
||||||
if args[0] == 'cli':
|
if args[0] == 'cli':
|
||||||
cmd = ('bitcoin-cli',) + common_args + args[1:]
|
cmd = (g.proto.name+'-cli',) + common_args + args[1:]
|
||||||
if g.debug or not 'quiet' in kwargs:
|
if g.debug or not 'quiet' in kwargs:
|
||||||
vmsg('{}'.format(' '.join(cmd)))
|
vmsg('{}'.format(' '.join(cmd)))
|
||||||
ip = op = ep = (PIPE,None)['no_pipe' in kwargs and kwargs['no_pipe']]
|
ip = op = ep = (PIPE,None)['no_pipe' in kwargs and kwargs['no_pipe']]
|
||||||
|
|
@ -93,19 +93,22 @@ def wait_for_daemon(state,silent=False,nonl=False):
|
||||||
def get_balances():
|
def get_balances():
|
||||||
user1 = get_current_user(quiet=True)
|
user1 = get_current_user(quiet=True)
|
||||||
if user1 == None:
|
if user1 == None:
|
||||||
die(1,'Regtest daemon not running')
|
user('bob')
|
||||||
|
user1 = get_current_user(quiet=True)
|
||||||
|
# die(1,'Regtest daemon not running')
|
||||||
user2 = ('bob','alice')[user1=='bob']
|
user2 = ('bob','alice')[user1=='bob']
|
||||||
tbal = 0
|
tbal = 0
|
||||||
from mmgen.obj import BTCAmt
|
# don't need to save and restore these, as we exit immediately
|
||||||
|
g.rpc_host = 'localhost'
|
||||||
|
g.rpc_port = rpc_port
|
||||||
|
g.rpc_user = rpc_user
|
||||||
|
g.rpc_password = rpc_password
|
||||||
|
g.testnet = True
|
||||||
|
rpc_init()
|
||||||
for u in (user1,user2):
|
for u in (user1,user2):
|
||||||
p = start_cmd('python','mmgen-tool',
|
bal = g.proto.coin_amt(g.rpch.getbalance('*',0,True))
|
||||||
'--{}'.format(u),'--data-dir='+g.data_dir,
|
|
||||||
'getbalance','quiet=1')
|
|
||||||
bal = p.stdout.read().replace(' \b','') # hack
|
|
||||||
if u == user1: user(user2)
|
if u == user1: user(user2)
|
||||||
bal = BTCAmt(bal)
|
msg('{:<16} {:12}'.format(u.capitalize()+"'s balance:",bal))
|
||||||
ustr = "{}'s balance:".format(u.capitalize())
|
|
||||||
msg('{:<16} {:12}'.format(ustr,bal))
|
|
||||||
tbal += bal
|
tbal += bal
|
||||||
msg('{:<16} {:12}'.format('Total balance:',tbal))
|
msg('{:<16} {:12}'.format('Total balance:',tbal))
|
||||||
|
|
||||||
|
|
@ -131,7 +134,7 @@ def process_output(p,silent=False):
|
||||||
return out,err
|
return out,err
|
||||||
|
|
||||||
def start_and_wait(user,silent=False,nonl=False):
|
def start_and_wait(user,silent=False,nonl=False):
|
||||||
vmsg('Starting bitcoin regtest daemon')
|
vmsg('Starting {} regtest daemon'.format(g.proto.name))
|
||||||
(start_daemon_mswin,start_daemon)[g.platform=='linux'](user)
|
(start_daemon_mswin,start_daemon)[g.platform=='linux'](user)
|
||||||
wait_for_daemon('ready',silent=silent,nonl=nonl)
|
wait_for_daemon('ready',silent=silent,nonl=nonl)
|
||||||
|
|
||||||
|
|
@ -141,7 +144,7 @@ def stop_and_wait(silent=False,nonl=False,stop_silent=False,ignore_noconnect_err
|
||||||
|
|
||||||
def send(addr,amt):
|
def send(addr,amt):
|
||||||
user('miner')
|
user('miner')
|
||||||
gmsg('Sending {} BTC to address {}'.format(amt,addr))
|
gmsg('Sending {} {} to address {}'.format(amt,g.coin,addr))
|
||||||
p = start_cmd('cli','sendtoaddress',addr,str(amt))
|
p = start_cmd('cli','sendtoaddress',addr,str(amt))
|
||||||
process_output(p)
|
process_output(p)
|
||||||
p.wait()
|
p.wait()
|
||||||
|
|
@ -189,7 +192,7 @@ def get_current_user_win(quiet=False):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_current_user_unix(quiet=False):
|
def get_current_user_unix(quiet=False):
|
||||||
p = start_cmd('pgrep','-af', 'bitcoind.*-rpcuser={}.*'.format(rpc_user))
|
p = start_cmd('pgrep','-af','{}.*-rpcuser={}.*'.format(g.proto.daemon_name,rpc_user))
|
||||||
cmdline = p.stdout.read()
|
cmdline = p.stdout.read()
|
||||||
if not cmdline: return None
|
if not cmdline: return None
|
||||||
for k in ('miner','bob','alice'):
|
for k in ('miner','bob','alice'):
|
||||||
|
|
@ -215,6 +218,7 @@ def user(user=None,quiet=False):
|
||||||
return True
|
return True
|
||||||
gmsg_r('Switching to user {}'.format(user.capitalize()))
|
gmsg_r('Switching to user {}'.format(user.capitalize()))
|
||||||
stop_and_wait(silent=False,nonl=True,stop_silent=True)
|
stop_and_wait(silent=False,nonl=True,stop_silent=True)
|
||||||
|
time.sleep(0.1) # file lock has race condition - TODO: test for lock file
|
||||||
start_and_wait(user,nonl=True)
|
start_and_wait(user,nonl=True)
|
||||||
else:
|
else:
|
||||||
gmsg_r('Starting regtest daemon with current user {}'.format(user.capitalize()))
|
gmsg_r('Starting regtest daemon with current user {}'.format(user.capitalize()))
|
||||||
|
|
@ -223,12 +227,12 @@ def user(user=None,quiet=False):
|
||||||
|
|
||||||
def stop(silent=False,ignore_noconnect_error=True):
|
def stop(silent=False,ignore_noconnect_error=True):
|
||||||
if test_daemon() != 'stopped' and not silent:
|
if test_daemon() != 'stopped' and not silent:
|
||||||
gmsg('Stopping bitcoin regtest daemon')
|
gmsg('Stopping {} regtest daemon'.format(g.proto.name))
|
||||||
p = start_cmd('cli','stop')
|
p = start_cmd('cli','stop')
|
||||||
err = process_output(p)[1]
|
err = process_output(p)[1]
|
||||||
if err:
|
if err:
|
||||||
if "couldn't connect to server" in err and not ignore_noconnect_error:
|
if "couldn't connect to server" in err and not ignore_noconnect_error:
|
||||||
rdie(1,'Error stopping the Bitcoin daemon:\n{}'.format(err))
|
rdie(1,'Error stopping the {} daemon:\n{}'.format(g.proto.name.capitalize(),err))
|
||||||
msg(err)
|
msg(err)
|
||||||
return p.wait()
|
return p.wait()
|
||||||
|
|
||||||
|
|
|
||||||
44
mmgen/rpc.py
44
mmgen/rpc.py
|
|
@ -17,20 +17,19 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
rpc.py: Bitcoin RPC library for the MMGen suite
|
rpc.py: Cryptocoin RPC library for the MMGen suite
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import httplib,base64,json
|
import httplib,base64,json
|
||||||
|
|
||||||
from mmgen.common import *
|
from mmgen.common import *
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from mmgen.obj import BTCAmt
|
|
||||||
|
|
||||||
class BitcoinRPCConnection(object):
|
class CoinDaemonRPCConnection(object):
|
||||||
|
|
||||||
def __init__(self,host=None,port=None,user=None,passwd=None,auth_cookie=None):
|
def __init__(self,host=None,port=None,user=None,passwd=None,auth_cookie=None):
|
||||||
|
|
||||||
dmsg('=== BitcoinRPCConnection.__init__() debug ===')
|
dmsg('=== CoinDaemonRPCConnection.__init__() debug ===')
|
||||||
dmsg(' host [{}] port [{}] user [{}] passwd [{}] auth_cookie [{}]\n'.format(
|
dmsg(' host [{}] port [{}] user [{}] passwd [{}] auth_cookie [{}]\n'.format(
|
||||||
host,port,user,passwd,auth_cookie))
|
host,port,user,passwd,auth_cookie))
|
||||||
|
|
||||||
|
|
@ -39,19 +38,23 @@ class BitcoinRPCConnection(object):
|
||||||
elif auth_cookie:
|
elif auth_cookie:
|
||||||
self.auth_str = auth_cookie
|
self.auth_str = auth_cookie
|
||||||
else:
|
else:
|
||||||
msg('Error: no Bitcoin RPC authentication method found')
|
msg('Error: no {} RPC authentication method found'.format(g.proto.name.capitalize()))
|
||||||
if passwd: die(1,"'rpcuser' entry not found in bitcoin.conf or mmgen.cfg")
|
if passwd: die(1,"'rpcuser' entry not found in {}.conf or mmgen.cfg".format(g.proto.name))
|
||||||
elif user: die(1,"'rpcpassword' entry not found in bitcoin.conf or mmgen.cfg")
|
elif user: die(1,"'rpcpassword' entry not found in {}.conf or mmgen.cfg".format(g.proto.name))
|
||||||
else:
|
else:
|
||||||
m1 = 'Either provide rpcuser/rpcpassword in bitcoin.conf or mmgen.cfg'
|
m1 = 'Either provide rpcuser/rpcpassword in {pn}.conf or mmgen.cfg\n'
|
||||||
m2 = '(or, alternatively, copy the authentication cookie to Bitcoin data dir'
|
m2 = '(or, alternatively, copy the authentication cookie to the {pnu}\n'
|
||||||
m3 = 'if {} and Bitcoin are running as different users)'.format(g.proj_name)
|
m3 = 'data dir if {pnm} and {dn} are running as different users)'
|
||||||
die(1,'\n'.join((m1,m2,m3)))
|
die(1,(m1+m2+m3).format(
|
||||||
|
pn=g.proto.name,
|
||||||
|
pnu=g.proto.name.capitalize(),
|
||||||
|
dn=g.proto.daemon_name,
|
||||||
|
pnm=g.proj_name))
|
||||||
|
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
|
|
||||||
# Normal mode: call with arg list unrolled, exactly as with 'bitcoin-cli'
|
# Normal mode: call with arg list unrolled, exactly as with cli
|
||||||
# Batch mode: call with list of arg lists as first argument
|
# Batch mode: call with list of arg lists as first argument
|
||||||
# kwargs are for local use and are not passed to server
|
# kwargs are for local use and are not passed to server
|
||||||
|
|
||||||
|
|
@ -84,8 +87,8 @@ class BitcoinRPCConnection(object):
|
||||||
caller = self
|
caller = self
|
||||||
class MyJSONEncoder(json.JSONEncoder):
|
class MyJSONEncoder(json.JSONEncoder):
|
||||||
def default(self, obj):
|
def default(self, obj):
|
||||||
if isinstance(obj, BTCAmt):
|
if isinstance(obj,g.proto.coin_amt):
|
||||||
return (float,str)[g.bitcoind_version>=120000](obj)
|
return g.proto.get_rpc_coin_amt_type()(obj)
|
||||||
return json.JSONEncoder.default(self, obj)
|
return json.JSONEncoder.default(self, obj)
|
||||||
|
|
||||||
# TODO: UTF-8 labels
|
# TODO: UTF-8 labels
|
||||||
|
|
@ -101,20 +104,20 @@ class BitcoinRPCConnection(object):
|
||||||
'Authorization': 'Basic {}'.format(base64.b64encode(self.auth_str))
|
'Authorization': 'Basic {}'.format(base64.b64encode(self.auth_str))
|
||||||
})
|
})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
m = '{}\nUnable to connect to bitcoind at {}:{}'
|
m = '{}\nUnable to connect to {} at {}:{}'
|
||||||
return die_maybe(None,2,m.format(e,self.host,self.port))
|
return die_maybe(None,2,m.format(e,g.proto.daemon_name,self.host,self.port))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = hc.getresponse() # returns HTTPResponse instance
|
r = hc.getresponse() # returns HTTPResponse instance
|
||||||
except Exception:
|
except Exception:
|
||||||
m = 'Unable to connect to bitcoind at {}:{} (but port is bound?)'
|
m = 'Unable to connect to {} at {}:{} (but port is bound?)'
|
||||||
return die_maybe(None,2,m.format(self.host,self.port))
|
return die_maybe(None,2,m.format(g.proto.daemon_name,self.host,self.port))
|
||||||
|
|
||||||
dmsg(' RPC GETRESPONSE data ==> %s\n' % r.__dict__)
|
dmsg(' RPC GETRESPONSE data ==> %s\n' % r.__dict__)
|
||||||
|
|
||||||
if r.status != 200:
|
if r.status != 200:
|
||||||
if cf['on_fail'] != 'silent':
|
if cf['on_fail'] != 'silent':
|
||||||
msg_r(yellow('Bitcoind RPC Error: '))
|
msg_r(yellow('{} RPC Error: '.format(g.proto.daemon_name.capitalize())))
|
||||||
msg(red('{} {}'.format(r.status,r.reason)))
|
msg(red('{} {}'.format(r.status,r.reason)))
|
||||||
e1 = r.read()
|
e1 = r.read()
|
||||||
try:
|
try:
|
||||||
|
|
@ -137,7 +140,8 @@ class BitcoinRPCConnection(object):
|
||||||
|
|
||||||
for resp in r3 if cf['batch'] else [r3]:
|
for resp in r3 if cf['batch'] else [r3]:
|
||||||
if 'error' in resp and resp['error'] != None:
|
if 'error' in resp and resp['error'] != None:
|
||||||
return die_maybe(r,1,'Bitcoind returned an error: %s' % resp['error'])
|
return die_maybe(r,1,'{} returned an error: {}'.format(
|
||||||
|
g.proto.daemon_name.capitalize(),resp['error']))
|
||||||
elif 'result' not in resp:
|
elif 'result' not in resp:
|
||||||
return die_maybe(r,1, 'Missing JSON-RPC result\n' + repr(resps))
|
return die_maybe(r,1, 'Missing JSON-RPC result\n' + repr(resps))
|
||||||
else:
|
else:
|
||||||
|
|
|
||||||
375
mmgen/tool.py
375
mmgen/tool.py
|
|
@ -61,7 +61,7 @@ cmd_data = OrderedDict([
|
||||||
('Wif2hex', ['<wif> [str-]']),
|
('Wif2hex', ['<wif> [str-]']),
|
||||||
('Wif2addr', ['<wif> [str-]','segwit [bool=False]']),
|
('Wif2addr', ['<wif> [str-]','segwit [bool=False]']),
|
||||||
('Wif2segwit_pair',['<wif> [str-]']),
|
('Wif2segwit_pair',['<wif> [str-]']),
|
||||||
('Hexaddr2addr', ['<coin address in hex format> [str-]']),
|
('Hexaddr2addr', ['<coin address in hex format> [str-]','p2sh [bool=False]']),
|
||||||
('Addr2hexaddr', ['<coin address> [str-]']),
|
('Addr2hexaddr', ['<coin address> [str-]']),
|
||||||
('Privhex2addr', ['<private key in hex format> [str-]','compressed [bool=False]','segwit [bool=False]']),
|
('Privhex2addr', ['<private key in hex format> [str-]','compressed [bool=False]','segwit [bool=False]']),
|
||||||
('Privhex2pubhex',['<private key in hex format> [str-]','compressed [bool=False]']),
|
('Privhex2pubhex',['<private key in hex format> [str-]','compressed [bool=False]']),
|
||||||
|
|
@ -78,9 +78,9 @@ cmd_data = OrderedDict([
|
||||||
('Mn_printlist', ["wordlist [str='electrum']"]),
|
('Mn_printlist', ["wordlist [str='electrum']"]),
|
||||||
|
|
||||||
('Listaddress',['<{} address> [str]'.format(pnm),'minconf [int=1]','pager [bool=False]','showempty [bool=True]''showbtcaddr [bool=True]']),
|
('Listaddress',['<{} address> [str]'.format(pnm),'minconf [int=1]','pager [bool=False]','showempty [bool=True]''showbtcaddr [bool=True]']),
|
||||||
('Listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=True]','all_labels [bool=False]']),
|
('Listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=True]','all_labels [bool=False]',"sort [str=''] (options: reverse, age)"]),
|
||||||
('Getbalance', ['minconf [int=1]','quiet [bool=False]']),
|
('Getbalance', ['minconf [int=1]','quiet [bool=False]']),
|
||||||
('Txview', ['<{} TX file(s)> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]',"sort [str='mtime'] (options: 'ctime','atime')",'MARGS']),
|
('Txview', ['<{} TX file(s)> [str]'.format(pnm),'pager [bool=False]','terse [bool=False]',"sort [str='mtime'] (options: ctime, atime)",'MARGS']),
|
||||||
('Twview', ["sort [str='age']",'reverse [bool=False]','show_days [bool=True]','show_mmid [bool=True]','minconf [int=1]','wide [bool=False]','pager [bool=False]']),
|
('Twview', ["sort [str='age']",'reverse [bool=False]','show_days [bool=True]','show_mmid [bool=True]','minconf [int=1]','wide [bool=False]','pager [bool=False]']),
|
||||||
|
|
||||||
('Add_label', ['<{} address> [str]'.format(pnm),'<label> [str]']),
|
('Add_label', ['<{} address> [str]'.format(pnm),'<label> [str]']),
|
||||||
|
|
@ -279,7 +279,7 @@ def Wif2segwit_pair(wif):
|
||||||
rs = ag.to_segwit_redeem_script(pubhex)
|
rs = ag.to_segwit_redeem_script(pubhex)
|
||||||
Msg('{}\n{}'.format(rs,addr))
|
Msg('{}\n{}'.format(rs,addr))
|
||||||
|
|
||||||
def Hexaddr2addr(hexaddr): Msg(g.proto.hexaddr2addr(hexaddr))
|
def Hexaddr2addr(hexaddr,p2sh=False): Msg(g.proto.hexaddr2addr(hexaddr,p2sh=p2sh))
|
||||||
def Addr2hexaddr(addr): Msg(g.proto.verify_addr(addr,return_dict=True)['hex'])
|
def Addr2hexaddr(addr): Msg(g.proto.verify_addr(addr,return_dict=True)['hex'])
|
||||||
def Hash160(pubkeyhex): Msg(hash160(pubkeyhex))
|
def Hash160(pubkeyhex): Msg(hash160(pubkeyhex))
|
||||||
def Pubhex2addr(pubkeyhex,p2sh=False): Msg(g.proto.hexaddr2addr(hash160(pubkeyhex),p2sh=p2sh))
|
def Pubhex2addr(pubkeyhex,p2sh=False): Msg(g.proto.hexaddr2addr(hash160(pubkeyhex),p2sh=p2sh))
|
||||||
|
|
@ -353,178 +353,6 @@ def Id6(infile):
|
||||||
def Str2id6(s): # retain ignoring of space for backwards compat
|
def Str2id6(s): # retain ignoring of space for backwards compat
|
||||||
Msg(make_chksum_6(''.join(s.split())))
|
Msg(make_chksum_6(''.join(s.split())))
|
||||||
|
|
||||||
def Listaddress(addr,minconf=1,pager=False,showempty=True,showbtcaddr=True):
|
|
||||||
return Listaddresses(addrs=addr,minconf=minconf,pager=pager,showempty=showempty,showbtcaddrs=showbtcaddr)
|
|
||||||
|
|
||||||
# List MMGen addresses and their balances. TODO: move this code to AddrList
|
|
||||||
def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=True,all_labels=False):
|
|
||||||
|
|
||||||
c = rpc_connection()
|
|
||||||
|
|
||||||
def check_dup_mmid(acct_labels):
|
|
||||||
mmid_prev,err = None,False
|
|
||||||
for mmid in sorted(a.mmid for a in acct_labels if a):
|
|
||||||
if mmid == mmid_prev:
|
|
||||||
err = True
|
|
||||||
msg('Duplicate MMGen ID ({}) discovered in tracking wallet!\n'.format(mmid))
|
|
||||||
mmid_prev = mmid
|
|
||||||
if err: rdie(3,'Tracking wallet is corrupted!')
|
|
||||||
|
|
||||||
def check_addr_array_lens(acct_pairs):
|
|
||||||
err = False
|
|
||||||
for label,addrs in acct_pairs:
|
|
||||||
if not label: continue
|
|
||||||
if len(addrs) != 1:
|
|
||||||
err = True
|
|
||||||
if len(addrs) == 0:
|
|
||||||
msg("Label '{}': has no associated address!".format(label))
|
|
||||||
else:
|
|
||||||
msg("'{}': more than one {} address in account!".format(addrs,g.coin))
|
|
||||||
if err: rdie(3,'Tracking wallet is corrupted!')
|
|
||||||
|
|
||||||
usr_addr_list = []
|
|
||||||
if addrs:
|
|
||||||
a = addrs.rsplit(':',1)
|
|
||||||
if len(a) != 2:
|
|
||||||
m = "'{}': invalid address list argument (must be in form <seed ID>:[<type>:]<idx list>)"
|
|
||||||
die(1,m.format(addrs))
|
|
||||||
usr_addr_list = [MMGenID('{}:{}'.format(a[0],i)) for i in AddrIdxList(a[1])]
|
|
||||||
|
|
||||||
class TwAddrList(dict,MMGenObject): pass
|
|
||||||
|
|
||||||
addrs = TwAddrList() # reusing name!
|
|
||||||
total = BTCAmt('0')
|
|
||||||
|
|
||||||
for d in c.listunspent(0):
|
|
||||||
if not 'account' in d: continue # skip coinbase outputs with missing account
|
|
||||||
if d['confirmations'] < minconf: continue
|
|
||||||
label = TwLabel(d['account'],on_fail='silent')
|
|
||||||
if label:
|
|
||||||
if usr_addr_list and (label.mmid not in usr_addr_list): continue
|
|
||||||
if label.mmid in addrs:
|
|
||||||
if addrs[label.mmid]['addr'] != d['address']:
|
|
||||||
die(2,'duplicate {} address ({}) for this MMGen address! ({})'.format(
|
|
||||||
g.coin,d['address'],addrs[label.mmid]['addr']))
|
|
||||||
else:
|
|
||||||
addrs[label.mmid] = { 'amt': BTCAmt('0'),
|
|
||||||
'lbl': label,
|
|
||||||
'addr': CoinAddr(d['address'])}
|
|
||||||
addrs[label.mmid]['amt'] += d['amount']
|
|
||||||
total += d['amount']
|
|
||||||
|
|
||||||
# We use listaccounts only for empty addresses, as it shows false positive balances
|
|
||||||
if showempty or all_labels:
|
|
||||||
# for compatibility with old mmids, must use raw RPC rather than native data for matching
|
|
||||||
# args: minconf,watchonly, MUST use keys() so we get list, not dict
|
|
||||||
acct_list = c.listaccounts(0,True).keys() # raw list, no 'L'
|
|
||||||
acct_labels = MMGenList([TwLabel(a,on_fail='silent') for a in acct_list])
|
|
||||||
check_dup_mmid(acct_labels)
|
|
||||||
acct_addrs = c.getaddressesbyaccount([[a] for a in acct_list],batch=True) # use raw list here
|
|
||||||
assert len(acct_list) == len(acct_addrs), 'listaccounts() and getaddressesbyaccount() not equal in length'
|
|
||||||
addr_pairs = zip(acct_labels,acct_addrs)
|
|
||||||
check_addr_array_lens(addr_pairs)
|
|
||||||
for label,addr_arr in addr_pairs:
|
|
||||||
if not label: continue
|
|
||||||
if all_labels and not showempty and not label.comment: continue
|
|
||||||
if usr_addr_list and (label.mmid not in usr_addr_list): continue
|
|
||||||
if label.mmid not in addrs:
|
|
||||||
addrs[label.mmid] = { 'amt':BTCAmt('0'), 'lbl':label, 'addr':'' }
|
|
||||||
if showbtcaddrs:
|
|
||||||
addrs[label.mmid]['addr'] = CoinAddr(addr_arr[0])
|
|
||||||
|
|
||||||
if not addrs:
|
|
||||||
die(0,('No tracked addresses with balances!','No tracked addresses!')[showempty])
|
|
||||||
|
|
||||||
out = ([],[green('Chain: {}'.format(g.chain.upper()))])[g.chain in ('testnet','regtest')]
|
|
||||||
|
|
||||||
fs = ('{mid} {cmt} {amt}','{mid} {addr} {cmt} {amt}')[showbtcaddrs]
|
|
||||||
mmaddrs = [k for k in addrs.keys() if k.type == 'mmgen']
|
|
||||||
max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10
|
|
||||||
max_cmt_len = max(max(len(addrs[k]['lbl'].comment) for k in addrs),7)
|
|
||||||
out += [fs.format(
|
|
||||||
mid=MMGenID.fmtc('MMGenID',width=max_mmid_len),
|
|
||||||
addr=CoinAddr.fmtc('ADDRESS'),
|
|
||||||
cmt=TwComment.fmtc('COMMENT',width=max_cmt_len),
|
|
||||||
amt='BALANCE'
|
|
||||||
)]
|
|
||||||
|
|
||||||
al_id_save = None
|
|
||||||
for mmid in sorted(addrs,key=lambda j: j.sort_key):
|
|
||||||
if mmid.type == 'mmgen':
|
|
||||||
if al_id_save and al_id_save != mmid.obj.al_id:
|
|
||||||
out.append('')
|
|
||||||
al_id_save = mmid.obj.al_id
|
|
||||||
mmid_disp = mmid
|
|
||||||
else:
|
|
||||||
if al_id_save:
|
|
||||||
out.append('')
|
|
||||||
al_id_save = None
|
|
||||||
mmid_disp = 'Non-MMGen'
|
|
||||||
out.append(fs.format(
|
|
||||||
mid = MMGenID.fmtc(mmid_disp,width=max_mmid_len,color=True),
|
|
||||||
addr=(addrs[mmid]['addr'].fmt(color=True) if showbtcaddrs else None),
|
|
||||||
cmt=addrs[mmid]['lbl'].comment.fmt(width=max_cmt_len,color=True,nullrepl='-'),
|
|
||||||
amt=addrs[mmid]['amt'].fmt('3.0',color=True)))
|
|
||||||
|
|
||||||
out.append('\nTOTAL: {} {}'.format(total.hl(color=True),g.coin))
|
|
||||||
o = '\n'.join(out)
|
|
||||||
return do_pager(o) if pager else Msg(o)
|
|
||||||
|
|
||||||
def Getbalance(minconf=1,quiet=False):
|
|
||||||
accts = {}
|
|
||||||
for d in rpc_connection().listunspent(0):
|
|
||||||
ma = split2(d['account'] if 'account' in d else '')[0] # include coinbase outputs if spendable
|
|
||||||
keys = ['TOTAL']
|
|
||||||
if d['spendable']: keys += ['SPENDABLE']
|
|
||||||
if is_mmgen_id(ma): keys += [ma.split(':')[0]]
|
|
||||||
confs = d['confirmations']
|
|
||||||
i = (1,2)[confs >= minconf]
|
|
||||||
|
|
||||||
for key in keys:
|
|
||||||
if key not in accts: accts[key] = [BTCAmt('0')] * 3
|
|
||||||
for j in ([],[0])[confs==0] + [i]:
|
|
||||||
accts[key][j] += d['amount']
|
|
||||||
|
|
||||||
if quiet:
|
|
||||||
Msg('{}'.format(accts['TOTAL'][2] if accts else BTCAmt('0')))
|
|
||||||
else:
|
|
||||||
fs = '{:13} {} {} {}'
|
|
||||||
mc,lbl = str(minconf),'confirms'
|
|
||||||
Msg(fs.format('Wallet',
|
|
||||||
*[s.ljust(16) for s in ' Unconfirmed',' <%s %s'%(mc,lbl),' >=%s %s'%(mc,lbl)]))
|
|
||||||
for key in sorted(accts.keys()):
|
|
||||||
Msg(fs.format(key+':', *[a.fmt(color=True,suf=' '+g.coin) for a in accts[key]]))
|
|
||||||
|
|
||||||
if 'SPENDABLE' in accts:
|
|
||||||
Msg(red('Warning: this wallet contains PRIVATE KEYS for the SPENDABLE balance!'))
|
|
||||||
|
|
||||||
def Txview(*infiles,**kwargs):
|
|
||||||
from mmgen.filename import MMGenFileList
|
|
||||||
pager = 'pager' in kwargs and kwargs['pager']
|
|
||||||
terse = 'terse' in kwargs and kwargs['terse']
|
|
||||||
sort_key = kwargs['sort'] if 'sort' in kwargs else 'mtime'
|
|
||||||
flist = MMGenFileList(infiles,ftype=MMGenTX)
|
|
||||||
flist.sort_by_age(key=sort_key) # in-place sort
|
|
||||||
from mmgen.term import get_terminal_size
|
|
||||||
sep = u'—'*77+'\n'
|
|
||||||
out = sep.join([MMGenTX(fn).format_view(terse=terse) for fn in flist.names()])
|
|
||||||
(Msg,do_pager)[pager](out.rstrip())
|
|
||||||
|
|
||||||
def Twview(pager=False,reverse=False,wide=False,minconf=1,sort='age',show_days=True,show_mmid=True):
|
|
||||||
from mmgen.tw import MMGenTrackingWallet
|
|
||||||
tw = MMGenTrackingWallet(minconf=minconf)
|
|
||||||
tw.do_sort(sort,reverse=reverse)
|
|
||||||
tw.show_days = show_days
|
|
||||||
tw.show_mmid = show_mmid
|
|
||||||
out = tw.format_for_printing(color=True) if wide else tw.format_for_display()
|
|
||||||
(Msg_r,do_pager)[pager](out)
|
|
||||||
|
|
||||||
def Add_label(mmaddr,label):
|
|
||||||
from mmgen.tw import MMGenTrackingWallet
|
|
||||||
MMGenTrackingWallet.add_label(mmaddr,label) # dies on failure
|
|
||||||
|
|
||||||
def Remove_label(mmaddr): Add_label(mmaddr,'')
|
|
||||||
|
|
||||||
def Addrfile_chksum(infile):
|
def Addrfile_chksum(infile):
|
||||||
from mmgen.addr import AddrList
|
from mmgen.addr import AddrList
|
||||||
AddrList(infile,chksum_only=True)
|
AddrList(infile,chksum_only=True)
|
||||||
|
|
@ -663,3 +491,198 @@ def Regtest_setup():
|
||||||
import subprocess as sp
|
import subprocess as sp
|
||||||
sp.check_output()
|
sp.check_output()
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
# ================ RPC commands ================== #
|
||||||
|
|
||||||
|
def Listaddress(addr,minconf=1,pager=False,showempty=True,showbtcaddr=True):
|
||||||
|
return Listaddresses(addrs=addr,minconf=minconf,pager=pager,showempty=showempty,showbtcaddrs=showbtcaddr)
|
||||||
|
|
||||||
|
# List MMGen addresses and their balances. TODO: move this code to AddrList
|
||||||
|
def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=True,all_labels=False,sort=None):
|
||||||
|
|
||||||
|
if sort:
|
||||||
|
sort = set(sort.split(','))
|
||||||
|
sort_params = set(['reverse','age'])
|
||||||
|
if not sort.issubset(sort_params):
|
||||||
|
die(1,"The sort option takes the following parameters: '{}'".format("','".join(sort_params)))
|
||||||
|
|
||||||
|
rpc_init()
|
||||||
|
|
||||||
|
def check_dup_mmid(acct_labels):
|
||||||
|
mmid_prev,err = None,False
|
||||||
|
for mmid in sorted(a.mmid for a in acct_labels if a):
|
||||||
|
if mmid == mmid_prev:
|
||||||
|
err = True
|
||||||
|
msg('Duplicate MMGen ID ({}) discovered in tracking wallet!\n'.format(mmid))
|
||||||
|
mmid_prev = mmid
|
||||||
|
if err: rdie(3,'Tracking wallet is corrupted!')
|
||||||
|
|
||||||
|
def check_addr_array_lens(acct_pairs):
|
||||||
|
err = False
|
||||||
|
for label,addrs in acct_pairs:
|
||||||
|
if not label: continue
|
||||||
|
if len(addrs) != 1:
|
||||||
|
err = True
|
||||||
|
if len(addrs) == 0:
|
||||||
|
msg("Label '{}': has no associated address!".format(label))
|
||||||
|
else:
|
||||||
|
msg("'{}': more than one {} address in account!".format(addrs,g.coin))
|
||||||
|
if err: rdie(3,'Tracking wallet is corrupted!')
|
||||||
|
|
||||||
|
usr_addr_list = []
|
||||||
|
if addrs:
|
||||||
|
a = addrs.rsplit(':',1)
|
||||||
|
if len(a) != 2:
|
||||||
|
m = "'{}': invalid address list argument (must be in form <seed ID>:[<type>:]<idx list>)"
|
||||||
|
die(1,m.format(addrs))
|
||||||
|
usr_addr_list = [MMGenID('{}:{}'.format(a[0],i)) for i in AddrIdxList(a[1])]
|
||||||
|
|
||||||
|
class TwAddrList(dict,MMGenObject): pass
|
||||||
|
|
||||||
|
addrs = TwAddrList() # reusing name!
|
||||||
|
total = g.proto.coin_amt('0')
|
||||||
|
|
||||||
|
for d in g.rpch.listunspent(0):
|
||||||
|
if not 'account' in d: continue # skip coinbase outputs with missing account
|
||||||
|
if d['confirmations'] < minconf: continue
|
||||||
|
label = TwLabel(d['account'],on_fail='silent')
|
||||||
|
if label:
|
||||||
|
if usr_addr_list and (label.mmid not in usr_addr_list): continue
|
||||||
|
if label.mmid in addrs:
|
||||||
|
if addrs[label.mmid]['addr'] != d['address']:
|
||||||
|
die(2,'duplicate {} address ({}) for this MMGen address! ({})'.format(
|
||||||
|
g.coin,d['address'],addrs[label.mmid]['addr']))
|
||||||
|
else:
|
||||||
|
addrs[label.mmid] = {'amt': g.proto.coin_amt('0'),
|
||||||
|
'lbl': label,
|
||||||
|
'addr': CoinAddr(d['address'])}
|
||||||
|
addrs[label.mmid]['lbl'].mmid.confs = d['confirmations']
|
||||||
|
addrs[label.mmid]['amt'] += d['amount']
|
||||||
|
total += d['amount']
|
||||||
|
|
||||||
|
# We use listaccounts only for empty addresses, as it shows false positive balances
|
||||||
|
if showempty or all_labels:
|
||||||
|
# for compatibility with old mmids, must use raw RPC rather than native data for matching
|
||||||
|
# args: minconf,watchonly, MUST use keys() so we get list, not dict
|
||||||
|
acct_list = g.rpch.listaccounts(0,True).keys() # raw list, no 'L'
|
||||||
|
acct_labels = MMGenList([TwLabel(a,on_fail='silent') for a in acct_list])
|
||||||
|
check_dup_mmid(acct_labels)
|
||||||
|
acct_addrs = g.rpch.getaddressesbyaccount([[a] for a in acct_list],batch=True) # use raw list here
|
||||||
|
assert len(acct_list) == len(acct_addrs), 'listaccounts() and getaddressesbyaccount() not equal in length'
|
||||||
|
addr_pairs = zip(acct_labels,acct_addrs)
|
||||||
|
check_addr_array_lens(addr_pairs)
|
||||||
|
for label,addr_arr in addr_pairs:
|
||||||
|
if not label: continue
|
||||||
|
if all_labels and not showempty and not label.comment: continue
|
||||||
|
if usr_addr_list and (label.mmid not in usr_addr_list): continue
|
||||||
|
if label.mmid not in addrs:
|
||||||
|
addrs[label.mmid] = { 'amt':g.proto.coin_amt('0'), 'lbl':label, 'addr':'' }
|
||||||
|
if showbtcaddrs:
|
||||||
|
addrs[label.mmid]['addr'] = CoinAddr(addr_arr[0])
|
||||||
|
|
||||||
|
if not addrs:
|
||||||
|
die(0,('No tracked addresses with balances!','No tracked addresses!')[showempty])
|
||||||
|
|
||||||
|
out = ([],[green('Chain: {}'.format(g.chain.upper()))])[g.chain in ('testnet','regtest')]
|
||||||
|
|
||||||
|
fs = ('{mid} {cmt} {amt}','{mid} {addr} {cmt} {amt}')[showbtcaddrs]
|
||||||
|
mmaddrs = [k for k in addrs.keys() if k.type == 'mmgen']
|
||||||
|
max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10
|
||||||
|
max_cmt_len = max(max(len(addrs[k]['lbl'].comment) for k in addrs),7)
|
||||||
|
out += [fs.format(
|
||||||
|
mid=MMGenID.fmtc('MMGenID',width=max_mmid_len),
|
||||||
|
addr=CoinAddr.fmtc('ADDRESS'),
|
||||||
|
cmt=TwComment.fmtc('COMMENT',width=max_cmt_len),
|
||||||
|
amt='BALANCE'
|
||||||
|
)]
|
||||||
|
|
||||||
|
def sort_algo(j):
|
||||||
|
if sort and 'age' in sort:
|
||||||
|
return '{}_{:>012}_{}'.format(
|
||||||
|
j.obj.rsplit(':',1)[0],
|
||||||
|
(1000000000-j.confs if hasattr(j,'confs') else 0), # Hack, but OK for the foreseeable future
|
||||||
|
j.sort_key)
|
||||||
|
else:
|
||||||
|
return j.sort_key
|
||||||
|
|
||||||
|
al_id_save = None
|
||||||
|
for mmid in sorted(addrs,key=sort_algo,reverse=bool(sort and 'reverse' in sort)):
|
||||||
|
if mmid.type == 'mmgen':
|
||||||
|
if al_id_save and al_id_save != mmid.obj.al_id:
|
||||||
|
out.append('')
|
||||||
|
al_id_save = mmid.obj.al_id
|
||||||
|
mmid_disp = mmid
|
||||||
|
else:
|
||||||
|
if al_id_save:
|
||||||
|
out.append('')
|
||||||
|
al_id_save = None
|
||||||
|
mmid_disp = 'Non-MMGen'
|
||||||
|
out.append(fs.format(
|
||||||
|
mid = MMGenID.fmtc(mmid_disp,width=max_mmid_len,color=True),
|
||||||
|
addr=(addrs[mmid]['addr'].fmt(color=True) if showbtcaddrs else None),
|
||||||
|
cmt=addrs[mmid]['lbl'].comment.fmt(width=max_cmt_len,color=True,nullrepl='-'),
|
||||||
|
amt=addrs[mmid]['amt'].fmt('3.0',color=True)))
|
||||||
|
out.append('\nTOTAL: {} {}'.format(total.hl(color=True),g.coin))
|
||||||
|
o = '\n'.join(out)
|
||||||
|
return do_pager(o) if pager else Msg(o)
|
||||||
|
|
||||||
|
def Getbalance(minconf=1,quiet=False,return_val=False):
|
||||||
|
rpc_init()
|
||||||
|
accts = {}
|
||||||
|
for d in g.rpch.listunspent(0):
|
||||||
|
ma = split2(d['account'] if 'account' in d else '')[0] # include coinbase outputs if spendable
|
||||||
|
keys = ['TOTAL']
|
||||||
|
if d['spendable']: keys += ['SPENDABLE']
|
||||||
|
if is_mmgen_id(ma): keys += [ma.split(':')[0]]
|
||||||
|
confs = d['confirmations']
|
||||||
|
i = (1,2)[confs >= minconf]
|
||||||
|
|
||||||
|
for key in keys:
|
||||||
|
if key not in accts: accts[key] = [g.proto.coin_amt('0')] * 3
|
||||||
|
for j in ([],[0])[confs==0] + [i]:
|
||||||
|
accts[key][j] += d['amount']
|
||||||
|
|
||||||
|
if quiet:
|
||||||
|
o = ['{}'.format(accts['TOTAL'][2] if accts else g.proto.coin_amt('0'))]
|
||||||
|
else:
|
||||||
|
fs = '{:13} {} {} {}'
|
||||||
|
mc,lbl = str(minconf),'confirms'
|
||||||
|
o = [fs.format('Wallet', *[s.ljust(16) for s in ' Unconfirmed',' <%s %s'%(mc,lbl),' >=%s %s'%(mc,lbl)])]
|
||||||
|
for key in sorted(accts.keys()):
|
||||||
|
o += [fs.format(key+':', *[a.fmt(color=True,suf=' '+g.coin) for a in accts[key]])]
|
||||||
|
|
||||||
|
if 'SPENDABLE' in accts:
|
||||||
|
Msg(red('Warning: this wallet contains PRIVATE KEYS for the SPENDABLE balance!'))
|
||||||
|
|
||||||
|
o = '\n'.join(o)
|
||||||
|
if return_val: return o
|
||||||
|
else: Msg(o)
|
||||||
|
|
||||||
|
def Txview(*infiles,**kwargs):
|
||||||
|
from mmgen.filename import MMGenFileList
|
||||||
|
pager = 'pager' in kwargs and kwargs['pager']
|
||||||
|
terse = 'terse' in kwargs and kwargs['terse']
|
||||||
|
sort_key = kwargs['sort'] if 'sort' in kwargs else 'mtime'
|
||||||
|
flist = MMGenFileList(infiles,ftype=MMGenTX)
|
||||||
|
flist.sort_by_age(key=sort_key) # in-place sort
|
||||||
|
from mmgen.term import get_terminal_size
|
||||||
|
sep = u'—'*77+'\n'
|
||||||
|
out = sep.join([MMGenTX(fn).format_view(terse=terse) for fn in flist.names()])
|
||||||
|
(Msg,do_pager)[pager](out.rstrip())
|
||||||
|
|
||||||
|
def Twview(pager=False,reverse=False,wide=False,minconf=1,sort='age',show_days=True,show_mmid=True):
|
||||||
|
rpc_init()
|
||||||
|
from mmgen.tw import MMGenTrackingWallet
|
||||||
|
tw = MMGenTrackingWallet(minconf=minconf)
|
||||||
|
tw.do_sort(sort,reverse=reverse)
|
||||||
|
tw.show_days = show_days
|
||||||
|
tw.show_mmid = show_mmid
|
||||||
|
out = tw.format_for_printing(color=True) if wide else tw.format_for_display()
|
||||||
|
(Msg_r,do_pager)[pager](out)
|
||||||
|
|
||||||
|
def Add_label(mmaddr,label):
|
||||||
|
rpc_init()
|
||||||
|
from mmgen.tw import MMGenTrackingWallet
|
||||||
|
MMGenTrackingWallet.add_label(mmaddr,label) # dies on failure
|
||||||
|
|
||||||
|
def Remove_label(mmaddr): Add_label(mmaddr,'')
|
||||||
|
|
|
||||||
30
mmgen/tw.py
30
mmgen/tw.py
|
|
@ -26,23 +26,15 @@ from mmgen.tx import is_mmgen_id
|
||||||
|
|
||||||
CUR_HOME,ERASE_ALL = '\033[H','\033[0J'
|
CUR_HOME,ERASE_ALL = '\033[H','\033[0J'
|
||||||
|
|
||||||
def parse_tw_acct_label(s):
|
|
||||||
ret = s.split(None,1)
|
|
||||||
a1,a2 = None,None
|
|
||||||
if ret:
|
|
||||||
a1 = ret[0] if is_mmgen_id(ret[0]) else '' if ret[0][:4] == 'btc:' else None
|
|
||||||
a2 = ret[1] if len(ret) == 2 else None
|
|
||||||
return a1,a2
|
|
||||||
|
|
||||||
class MMGenTrackingWallet(MMGenObject):
|
class MMGenTrackingWallet(MMGenObject):
|
||||||
|
|
||||||
class MMGenTwOutputList(list,MMGenObject): pass
|
class MMGenTwOutputList(list,MMGenObject): pass
|
||||||
|
|
||||||
class MMGenTwUnspentOutput(MMGenListItem):
|
class MMGenTwUnspentOutput(MMGenListItem):
|
||||||
# attrs = 'txid','vout','amt','label','twmmid','addr','confs','scriptPubKey','days','skip'
|
# attrs = 'txid','vout','amt','label','twmmid','addr','confs','scriptPubKey','days','skip'
|
||||||
txid = MMGenImmutableAttr('txid','BitcoinTxID')
|
txid = MMGenImmutableAttr('txid','CoinTxID')
|
||||||
vout = MMGenImmutableAttr('vout',int,typeconv=False),
|
vout = MMGenImmutableAttr('vout',int,typeconv=False),
|
||||||
amt = MMGenImmutableAttr('amt','BTCAmt'),
|
amt = MMGenImmutableAttr('amt',g.proto.coin_amt.__name__),
|
||||||
label = MMGenListItemAttr('label','TwComment',reassign_ok=True),
|
label = MMGenListItemAttr('label','TwComment',reassign_ok=True),
|
||||||
twmmid = MMGenImmutableAttr('twmmid','TwMMGenID')
|
twmmid = MMGenImmutableAttr('twmmid','TwMMGenID')
|
||||||
addr = MMGenImmutableAttr('addr','CoinAddr'),
|
addr = MMGenImmutableAttr('addr','CoinAddr'),
|
||||||
|
|
@ -80,12 +72,13 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
||||||
if g.bogus_wallet_data: # for debugging purposes only
|
if g.bogus_wallet_data: # for debugging purposes only
|
||||||
us_rpc = eval(get_data_from_file(g.bogus_wallet_data))
|
us_rpc = eval(get_data_from_file(g.bogus_wallet_data))
|
||||||
else:
|
else:
|
||||||
us_rpc = rpc_connection().listunspent(self.minconf)
|
us_rpc = g.rpch.listunspent(self.minconf)
|
||||||
# write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data')
|
# write_data_to_file('bogus_unspent.json', repr(us), 'bogus unspent data')
|
||||||
# sys.exit(0)
|
# sys.exit(0)
|
||||||
|
|
||||||
if not us_rpc: die(0,self.wmsg['no_spendable_outputs'])
|
if not us_rpc: die(0,self.wmsg['no_spendable_outputs'])
|
||||||
mm_rpc = self.MMGenTwOutputList()
|
mm_rpc = self.MMGenTwOutputList()
|
||||||
|
confs_per_day = 60*60*24 / g.proto.secs_per_block
|
||||||
for o in us_rpc:
|
for o in us_rpc:
|
||||||
if not 'account' in o: continue # coinbase outputs have no account field
|
if not 'account' in o: continue # coinbase outputs have no account field
|
||||||
l = TwLabel(o['account'],on_fail='silent')
|
l = TwLabel(o['account'],on_fail='silent')
|
||||||
|
|
@ -93,8 +86,8 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
||||||
o.update({
|
o.update({
|
||||||
'twmmid': l.mmid,
|
'twmmid': l.mmid,
|
||||||
'label': l.comment,
|
'label': l.comment,
|
||||||
'days': int(o['confirmations'] * g.mins_per_block / (60*24)),
|
'days': int(o['confirmations'] / confs_per_day),
|
||||||
'amt': BTCAmt(o['amount']), # TODO
|
'amt': g.proto.coin_amt(o['amount']), # TODO
|
||||||
'addr': CoinAddr(o['address']), # TODO
|
'addr': CoinAddr(o['address']), # TODO
|
||||||
'confs': o['confirmations']
|
'confs': o['confirmations']
|
||||||
})
|
})
|
||||||
|
|
@ -336,8 +329,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
||||||
msg("Address '{}' not in tracking wallet".format(coinaddr))
|
msg("Address '{}' not in tracking wallet".format(coinaddr))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
c = rpc_connection()
|
if not coinaddr.is_for_chain(g.chain):
|
||||||
if not coinaddr.is_for_current_chain():
|
|
||||||
msg("Address '{}' not valid for chain {}".format(coinaddr,g.chain.upper()))
|
msg("Address '{}' not valid for chain {}".format(coinaddr,g.chain.upper()))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -348,7 +340,7 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
||||||
ad = AddrData(source='tw')
|
ad = AddrData(source='tw')
|
||||||
mmaddr = ad.coinaddr2mmaddr(coinaddr)
|
mmaddr = ad.coinaddr2mmaddr(coinaddr)
|
||||||
|
|
||||||
if not mmaddr: mmaddr = 'btc:'+coinaddr
|
if not mmaddr: mmaddr = '{}:{}'.format(g.proto.base_coin.lower(),coinaddr)
|
||||||
|
|
||||||
mmaddr = TwMMGenID(mmaddr)
|
mmaddr = TwMMGenID(mmaddr)
|
||||||
|
|
||||||
|
|
@ -361,17 +353,17 @@ Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
||||||
# associating the new account with the address.
|
# associating the new account with the address.
|
||||||
# Will be replaced by setlabel() with new RPC label API
|
# Will be replaced by setlabel() with new RPC label API
|
||||||
# RPC args: addr,label,rescan[=true],p2sh[=none]
|
# RPC args: addr,label,rescan[=true],p2sh[=none]
|
||||||
ret = c.importaddress(coinaddr,lbl,False,on_fail='return')
|
ret = g.rpch.importaddress(coinaddr,lbl,False,on_fail='return')
|
||||||
|
|
||||||
from mmgen.rpc import rpc_error,rpc_errmsg
|
from mmgen.rpc import rpc_error,rpc_errmsg
|
||||||
if rpc_error(ret):
|
if rpc_error(ret):
|
||||||
msg('From bitcoind: ' + rpc_errmsg(ret))
|
msg('From {}: {}'.format(g.proto.daemon_name,rpc_errmsg(ret)))
|
||||||
if not silent:
|
if not silent:
|
||||||
msg('Label could not be {}'.format(('removed','added')[bool(label)]))
|
msg('Label could not be {}'.format(('removed','added')[bool(label)]))
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
m = mmaddr.type.replace('mmg','MMG')
|
m = mmaddr.type.replace('mmg','MMG')
|
||||||
a = mmaddr.replace('btc:','')
|
a = mmaddr.replace(g.proto.base_coin.lower()+':','')
|
||||||
s = '{} address {} in tracking wallet'.format(m,a)
|
s = '{} address {} in tracking wallet'.format(m,a)
|
||||||
if label: msg("Added label '{}' to {}".format(label,s))
|
if label: msg("Added label '{}' to {}".format(label,s))
|
||||||
else: msg('Removed label from {}'.format(s))
|
else: msg('Removed label from {}'.format(s))
|
||||||
|
|
|
||||||
302
mmgen/tx.py
302
mmgen/tx.py
|
|
@ -27,7 +27,7 @@ from mmgen.common import *
|
||||||
from mmgen.obj import *
|
from mmgen.obj import *
|
||||||
|
|
||||||
def segwit_is_active(exit_on_error=False):
|
def segwit_is_active(exit_on_error=False):
|
||||||
d = rpc_connection().getblockchaininfo()
|
d = g.rpch.getblockchaininfo()
|
||||||
if d['chain'] == 'regtest':
|
if d['chain'] == 'regtest':
|
||||||
return True
|
return True
|
||||||
if 'segwit' in d['bip9_softforks'] and d['bip9_softforks']['segwit']['status'] == 'active':
|
if 'segwit' in d['bip9_softforks'] and d['bip9_softforks']['segwit']['status'] == 'active':
|
||||||
|
|
@ -45,8 +45,8 @@ def bytes2int(hex_bytes):
|
||||||
die(3,"{}: Negative values not permitted in transaction!".format(hex_bytes))
|
die(3,"{}: Negative values not permitted in transaction!".format(hex_bytes))
|
||||||
return int(r,16)
|
return int(r,16)
|
||||||
|
|
||||||
def bytes2btc(hex_bytes):
|
def bytes2coin_amt(hex_bytes):
|
||||||
return bytes2int(hex_bytes) * g.satoshi
|
return g.proto.coin_amt(bytes2int(hex_bytes) * g.proto.coin_amt.min_coin_unit)
|
||||||
|
|
||||||
def scriptPubKey2addr(s):
|
def scriptPubKey2addr(s):
|
||||||
if len(s) == 50 and s[:6] == '76a914' and s[-4:] == '88ac': addr_hex,p2sh = s[6:-4],False
|
if len(s) == 50 and s[:6] == '76a914' and s[-4:] == '88ac': addr_hex,p2sh = s[6:-4],False
|
||||||
|
|
@ -59,26 +59,30 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
|
||||||
def __init__(self,txhex):
|
def __init__(self,txhex):
|
||||||
tx = list(unhexlify(txhex))
|
tx = list(unhexlify(txhex))
|
||||||
tx_copy = tx[:]
|
tx_copy = tx[:]
|
||||||
|
d = { 'raw_tx':'' }
|
||||||
|
|
||||||
def hshift(l,n,reverse=False):
|
def hshift(l,n,reverse=False,skip=False):
|
||||||
ret = l[:n]
|
ret = l[:n]
|
||||||
|
if not skip: d['raw_tx'] += ''.join(ret)
|
||||||
del l[:n]
|
del l[:n]
|
||||||
return hexlify(''.join(ret[::-1] if reverse else ret))
|
return hexlify(''.join(ret[::-1] if reverse else ret))
|
||||||
|
|
||||||
# https://bitcoin.org/en/developer-reference#compactsize-unsigned-integers
|
# https://bitcoin.org/en/developer-reference#compactsize-unsigned-integers
|
||||||
# For example, the number 515 is encoded as 0xfd0302.
|
# For example, the number 515 is encoded as 0xfd0302.
|
||||||
def readVInt(l):
|
def readVInt(l,skip=False,sub_null=False):
|
||||||
s = int(hexlify(l[0]),16)
|
s = int(hexlify(l[0]),16)
|
||||||
bytes_len = 1 if s < 0xfd else 2 if s == 0xfd else 4 if s == 0xfe else 8
|
bytes_len = 1 if s < 0xfd else 2 if s == 0xfd else 4 if s == 0xfe else 8
|
||||||
if bytes_len != 1: del l[0]
|
if bytes_len != 1: del l[0]
|
||||||
ret = int(hexlify(''.join(l[:bytes_len][::-1])),16)
|
ret = int(hexlify(''.join(l[:bytes_len][::-1])),16)
|
||||||
|
if sub_null: d['raw_tx'] += '\0'
|
||||||
|
elif not skip: d['raw_tx'] += ''.join(l[:bytes_len])
|
||||||
del l[:bytes_len]
|
del l[:bytes_len]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
d = { 'version': bytes2int(hshift(tx,4)) }
|
d['version'] = bytes2int(hshift(tx,4))
|
||||||
has_witness = (False,True)[hexlify(tx[0])=='00']
|
has_witness = (False,True)[hexlify(tx[0])=='00']
|
||||||
if has_witness:
|
if has_witness:
|
||||||
u = hshift(tx,2)[2:]
|
u = hshift(tx,2,skip=True)[2:]
|
||||||
if u != '01':
|
if u != '01':
|
||||||
die(2,"'{}': Illegal value for flag in transaction!".format(u))
|
die(2,"'{}': Illegal value for flag in transaction!".format(u))
|
||||||
del tx_copy[-len(tx)-2:-len(tx)]
|
del tx_copy[-len(tx)-2:-len(tx)]
|
||||||
|
|
@ -87,13 +91,13 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
|
||||||
d['txins'] = MMGenList([OrderedDict((
|
d['txins'] = MMGenList([OrderedDict((
|
||||||
('txid', hshift(tx,32,reverse=True)),
|
('txid', hshift(tx,32,reverse=True)),
|
||||||
('vout', bytes2int(hshift(tx,4))),
|
('vout', bytes2int(hshift(tx,4))),
|
||||||
('scriptSig', hshift(tx,readVInt(tx))),
|
('scriptSig', hshift(tx,readVInt(tx,sub_null=True),skip=True)),
|
||||||
('nSeq', hshift(tx,4,reverse=True))
|
('nSeq', hshift(tx,4,reverse=True))
|
||||||
)) for i in range(d['num_txins'])])
|
)) for i in range(d['num_txins'])])
|
||||||
|
|
||||||
d['num_txouts'] = readVInt(tx)
|
d['num_txouts'] = readVInt(tx)
|
||||||
d['txouts'] = MMGenList([OrderedDict((
|
d['txouts'] = MMGenList([OrderedDict((
|
||||||
('amount', bytes2btc(hshift(tx,8))),
|
('amount', bytes2coin_amt(hshift(tx,8))),
|
||||||
('scriptPubKey', hshift(tx,readVInt(tx)))
|
('scriptPubKey', hshift(tx,readVInt(tx)))
|
||||||
)) for i in range(d['num_txouts'])])
|
)) for i in range(d['num_txouts'])])
|
||||||
|
|
||||||
|
|
@ -110,26 +114,30 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types
|
||||||
d['witness_size'] = len(wd) + 2 # add marker and flag
|
d['witness_size'] = len(wd) + 2 # add marker and flag
|
||||||
for i in range(len(d['txins'])):
|
for i in range(len(d['txins'])):
|
||||||
if hexlify(wd[0]) == '00':
|
if hexlify(wd[0]) == '00':
|
||||||
hshift(wd,1)
|
hshift(wd,1,skip=True)
|
||||||
continue
|
continue
|
||||||
d['txins'][i]['witness'] = [hshift(wd,readVInt(wd)) for item in range(readVInt(wd))]
|
d['txins'][i]['witness'] = [
|
||||||
|
hshift(wd,readVInt(wd,skip=True),skip=True) for item in range(readVInt(wd,skip=True))
|
||||||
|
]
|
||||||
if wd:
|
if wd:
|
||||||
die(3,'More witness data than inputs with witnesses!')
|
die(3,'More witness data than inputs with witnesses!')
|
||||||
|
|
||||||
d['lock_time'] = bytes2int(hshift(tx,4))
|
d['lock_time'] = bytes2int(hshift(tx,4))
|
||||||
d['txid'] = hexlify(sha256(sha256(''.join(tx_copy)).digest()).digest()[::-1])
|
d['txid'] = hexlify(sha256(sha256(''.join(tx_copy)).digest()).digest()[::-1])
|
||||||
|
d['unsigned_hex'] = hexlify(d['raw_tx'])
|
||||||
|
del d['raw_tx']
|
||||||
|
|
||||||
keys = 'txid','version','lock_time','witness_size','num_txins','txins','num_txouts','txouts'
|
keys = 'txid','version','lock_time','witness_size','num_txins','txins','num_txouts','txouts','unsigned_hex'
|
||||||
return OrderedDict.__init__(self, ((k,d[k]) for k in keys))
|
return OrderedDict.__init__(self, ((k,d[k]) for k in keys))
|
||||||
|
|
||||||
txio_attrs = {
|
txio_attrs = {
|
||||||
'vout': MMGenListItemAttr('vout',int,typeconv=False),
|
'vout': MMGenListItemAttr('vout',int,typeconv=False),
|
||||||
'amt': MMGenImmutableAttr('amt','BTCAmt'),
|
'amt': MMGenImmutableAttr('amt',g.proto.coin_amt,typeconv=False), # require amt to be of proper type
|
||||||
'label': MMGenListItemAttr('label','TwComment',reassign_ok=True),
|
'label': MMGenListItemAttr('label','TwComment',reassign_ok=True),
|
||||||
'mmid': MMGenListItemAttr('mmid','MMGenID'),
|
'mmid': MMGenListItemAttr('mmid','MMGenID'),
|
||||||
'addr': MMGenImmutableAttr('addr','CoinAddr'),
|
'addr': MMGenImmutableAttr('addr','CoinAddr'),
|
||||||
'confs': MMGenListItemAttr('confs',int,typeconv=True), # long confs exist in the wild, so convert
|
'confs': MMGenListItemAttr('confs',int,typeconv=True), # long confs exist in the wild, so convert
|
||||||
'txid': MMGenListItemAttr('txid','BitcoinTxID'),
|
'txid': MMGenListItemAttr('txid','CoinTxID'),
|
||||||
'have_wif': MMGenListItemAttr('have_wif',bool,typeconv=False,delete_ok=True)
|
'have_wif': MMGenListItemAttr('have_wif',bool,typeconv=False,delete_ok=True)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -152,10 +160,10 @@ class MMGenTX(MMGenObject):
|
||||||
class MMGenTxInputList(list,MMGenObject): pass
|
class MMGenTxInputList(list,MMGenObject): pass
|
||||||
class MMGenTxOutputList(list,MMGenObject): pass
|
class MMGenTxOutputList(list,MMGenObject): pass
|
||||||
|
|
||||||
def __init__(self,filename=None):
|
def __init__(self,filename=None,md_only=False):
|
||||||
self.inputs = self.MMGenTxInputList()
|
self.inputs = self.MMGenTxInputList()
|
||||||
self.outputs = self.MMGenTxOutputList()
|
self.outputs = self.MMGenTxOutputList()
|
||||||
self.send_amt = BTCAmt('0') # total amt minus change
|
self.send_amt = g.proto.coin_amt('0') # total amt minus change
|
||||||
self.hex = '' # raw serialized hex transaction
|
self.hex = '' # raw serialized hex transaction
|
||||||
self.label = MMGenTXLabel('')
|
self.label = MMGenTXLabel('')
|
||||||
self.txid = ''
|
self.txid = ''
|
||||||
|
|
@ -165,12 +173,14 @@ class MMGenTX(MMGenObject):
|
||||||
self.fmt_data = ''
|
self.fmt_data = ''
|
||||||
self.blockcount = 0
|
self.blockcount = 0
|
||||||
self.chain = None
|
self.chain = None
|
||||||
|
self.coin = None
|
||||||
|
|
||||||
if filename:
|
if filename:
|
||||||
self.parse_tx_file(filename)
|
self.parse_tx_file(filename,md_only=md_only)
|
||||||
|
if md_only: return
|
||||||
self.check_sigs() # marks the tx as signed
|
self.check_sigs() # marks the tx as signed
|
||||||
|
|
||||||
# repeat with sign and send, because bitcoind could be restarted
|
# repeat with sign and send, because coin daemon could be restarted
|
||||||
self.die_if_incorrect_chain()
|
self.die_if_incorrect_chain()
|
||||||
|
|
||||||
def die_if_incorrect_chain(self):
|
def die_if_incorrect_chain(self):
|
||||||
|
|
@ -197,7 +207,7 @@ class MMGenTX(MMGenObject):
|
||||||
def sum_outputs(self,exclude=None):
|
def sum_outputs(self,exclude=None):
|
||||||
olist = self.outputs if exclude == None else \
|
olist = self.outputs if exclude == None else \
|
||||||
self.outputs[:exclude] + self.outputs[exclude+1:]
|
self.outputs[:exclude] + self.outputs[exclude+1:]
|
||||||
return BTCAmt(sum(e.amt for e in olist))
|
return g.proto.coin_amt(sum(e.amt for e in olist))
|
||||||
|
|
||||||
def add_mmaddrs_to_outputs(self,ad_w,ad_f):
|
def add_mmaddrs_to_outputs(self,ad_w,ad_f):
|
||||||
a = [e.addr for e in self.outputs]
|
a = [e.addr for e in self.outputs]
|
||||||
|
|
@ -208,12 +218,22 @@ class MMGenTX(MMGenObject):
|
||||||
e.mmid,f = d[e.addr]
|
e.mmid,f = d[e.addr]
|
||||||
if f: e.label = f
|
if f: e.label = f
|
||||||
|
|
||||||
def create_raw(self,c):
|
def check_dup_addrs(self,io_str):
|
||||||
|
assert io_str in ('inputs','outputs')
|
||||||
|
io = getattr(self,io_str)
|
||||||
|
for k in ('mmid','addr'):
|
||||||
|
old_attr = None
|
||||||
|
for attr in sorted(getattr(e,k) for e in io):
|
||||||
|
if attr != None and attr == old_attr:
|
||||||
|
die(2,'{}: duplicate address in transaction {}'.format(attr,io_str))
|
||||||
|
old_attr = attr
|
||||||
|
|
||||||
|
def create_raw(self):
|
||||||
i = [{'txid':e.txid,'vout':e.vout} for e in self.inputs]
|
i = [{'txid':e.txid,'vout':e.vout} for e in self.inputs]
|
||||||
if self.inputs[0].sequence:
|
if self.inputs[0].sequence:
|
||||||
i[0]['sequence'] = self.inputs[0].sequence
|
i[0]['sequence'] = self.inputs[0].sequence
|
||||||
o = dict([(e.addr,e.amt) for e in self.outputs])
|
o = dict([(e.addr,e.amt) for e in self.outputs])
|
||||||
self.hex = c.createrawtransaction(i,o)
|
self.hex = g.rpch.createrawtransaction(i,o)
|
||||||
self.txid = MMGenTxID(make_chksum_6(unhexlify(self.hex)).upper())
|
self.txid = MMGenTxID(make_chksum_6(unhexlify(self.hex)).upper())
|
||||||
|
|
||||||
# returns true if comment added or changed
|
# returns true if comment added or changed
|
||||||
|
|
@ -312,20 +332,20 @@ class MMGenTX(MMGenObject):
|
||||||
return self.sum_inputs() - self.sum_outputs()
|
return self.sum_inputs() - self.sum_outputs()
|
||||||
|
|
||||||
def btc2spb(self,coin_fee):
|
def btc2spb(self,coin_fee):
|
||||||
return int(coin_fee/g.satoshi/self.estimate_size())
|
return int(coin_fee/g.proto.coin_amt.min_coin_unit/self.estimate_size())
|
||||||
|
|
||||||
def get_relay_fee(self):
|
def get_relay_fee(self):
|
||||||
assert self.estimate_size()
|
assert self.estimate_size()
|
||||||
kb_fee = BTCAmt(rpc_connection().getnetworkinfo()['relayfee'])
|
kb_fee = g.proto.coin_amt(g.rpch.getnetworkinfo()['relayfee'])
|
||||||
vmsg('Relay fee: {} {}/kB'.format(kb_fee,g.coin))
|
vmsg('Relay fee: {} {}/kB'.format(kb_fee,g.coin))
|
||||||
return kb_fee * self.estimate_size() / 1024
|
return kb_fee * self.estimate_size() / 1024
|
||||||
|
|
||||||
def convert_fee_spec(self,tx_fee,tx_size,on_fail='throw'):
|
def convert_fee_spec(self,tx_fee,tx_size,on_fail='throw'):
|
||||||
if BTCAmt(tx_fee,on_fail='silent'):
|
if g.proto.coin_amt(tx_fee,on_fail='silent'):
|
||||||
return BTCAmt(tx_fee)
|
return g.proto.coin_amt(tx_fee)
|
||||||
elif len(tx_fee) >= 2 and tx_fee[-1] == 's' and is_int(tx_fee[:-1]) and int(tx_fee[:-1]) >= 1:
|
elif len(tx_fee) >= 2 and tx_fee[-1] == 's' and is_int(tx_fee[:-1]) and int(tx_fee[:-1]) >= 1:
|
||||||
if tx_size:
|
if tx_size:
|
||||||
return BTCAmt(int(tx_fee[:-1]) * tx_size * g.satoshi)
|
return g.proto.coin_amt(int(tx_fee[:-1]) * tx_size * g.proto.coin_amt.min_coin_unit)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
|
|
@ -344,9 +364,9 @@ class MMGenTX(MMGenObject):
|
||||||
m = "'{}': invalid TX fee (not a {} amount or satoshis-per-byte specification)"
|
m = "'{}': invalid TX fee (not a {} amount or satoshis-per-byte specification)"
|
||||||
msg(m.format(tx_fee,g.coin))
|
msg(m.format(tx_fee,g.coin))
|
||||||
return False
|
return False
|
||||||
elif coin_fee > g.max_tx_fee:
|
elif coin_fee > g.proto.max_tx_fee:
|
||||||
m = '{} {c}: {} fee too large (maximum fee: {} {c})'
|
m = '{} {c}: {} fee too large (maximum fee: {} {c})'
|
||||||
msg(m.format(coin_fee,desc,g.max_tx_fee,c=g.coin))
|
msg(m.format(coin_fee,desc,g.proto.max_tx_fee,c=g.coin))
|
||||||
return False
|
return False
|
||||||
elif coin_fee < self.get_relay_fee():
|
elif coin_fee < self.get_relay_fee():
|
||||||
m = '{} {c}: {} fee too small (below relay fee of {} {c})'
|
m = '{} {c}: {} fee too small (below relay fee of {} {c})'
|
||||||
|
|
@ -411,12 +431,13 @@ class MMGenTX(MMGenObject):
|
||||||
def add_timestamp(self):
|
def add_timestamp(self):
|
||||||
self.timestamp = make_timestamp()
|
self.timestamp = make_timestamp()
|
||||||
|
|
||||||
def add_blockcount(self,c):
|
def add_blockcount(self):
|
||||||
self.blockcount = int(c.getblockcount())
|
self.blockcount = int(g.rpch.getblockcount())
|
||||||
|
|
||||||
def format(self):
|
def format(self):
|
||||||
lines = [
|
lines = [
|
||||||
'{} {} {} {} {}'.format(
|
'{}{} {} {} {} {}'.format(
|
||||||
|
(g.coin+' ','')[g.coin=='BTC'],
|
||||||
self.chain.upper() if self.chain else 'Unknown',
|
self.chain.upper() if self.chain else 'Unknown',
|
||||||
self.txid,
|
self.txid,
|
||||||
self.send_amt,
|
self.send_amt,
|
||||||
|
|
@ -439,14 +460,17 @@ class MMGenTX(MMGenObject):
|
||||||
return list(set(i.addr for i in getattr(self,desc) if not i.mmid))
|
return list(set(i.addr for i in getattr(self,desc) if not i.mmid))
|
||||||
|
|
||||||
# return true or false; don't exit
|
# return true or false; don't exit
|
||||||
def sign(self,c,tx_num_str,keys):
|
def sign(self,tx_num_str,keys):
|
||||||
|
|
||||||
|
if self.marked_signed():
|
||||||
|
die(1,'Transaction is already signed!')
|
||||||
|
|
||||||
self.die_if_incorrect_chain()
|
self.die_if_incorrect_chain()
|
||||||
|
|
||||||
if g.coin == 'BCH' and (self.has_segwit_inputs() or self.has_segwit_outputs()):
|
if (self.has_segwit_inputs() or self.has_segwit_outputs()) and not g.proto.cap('segwit'):
|
||||||
die(2,yellow("Segwit inputs cannot be spent or spent to on the BCH chain!"))
|
die(2,yellow("TX has Segwit inputs or outputs, but {} doesn't support Segwit!".format(g.coin)))
|
||||||
|
|
||||||
qmsg('Passing {} key{} to bitcoind'.format(len(keys),suf(keys,'s')))
|
qmsg('Passing {} key{} to {}'.format(len(keys),suf(keys,'s'),g.proto.daemon_name))
|
||||||
|
|
||||||
if self.has_segwit_inputs():
|
if self.has_segwit_inputs():
|
||||||
from mmgen.addr import KeyGenerator,AddrGenerator
|
from mmgen.addr import KeyGenerator,AddrGenerator
|
||||||
|
|
@ -467,7 +491,7 @@ class MMGenTX(MMGenObject):
|
||||||
wifs = [d.sec.wif for d in keys]
|
wifs = [d.sec.wif for d in keys]
|
||||||
# keys.pmsg()
|
# keys.pmsg()
|
||||||
# pmsg(wifs)
|
# pmsg(wifs)
|
||||||
ret = c.signrawtransaction(self.hex,sig_data,wifs,g.proto.sighash_type,on_fail='return')
|
ret = g.rpch.signrawtransaction(self.hex,sig_data,wifs,g.proto.sighash_type,on_fail='return')
|
||||||
|
|
||||||
from mmgen.rpc import rpc_error,rpc_errmsg
|
from mmgen.rpc import rpc_error,rpc_errmsg
|
||||||
if rpc_error(ret):
|
if rpc_error(ret):
|
||||||
|
|
@ -481,18 +505,20 @@ class MMGenTX(MMGenObject):
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
if ret['complete']:
|
if ret['complete']:
|
||||||
|
# Msg(pretty_hexdump(unhexlify(self.hex),cols=16)) # DEBUG
|
||||||
|
# pmsg(make_chksum_6(unhexlify(self.hex)).upper())
|
||||||
self.hex = ret['hex']
|
self.hex = ret['hex']
|
||||||
vmsg('Signed transaction size: {}'.format(len(self.hex)/2))
|
vmsg('Signed transaction size: {}'.format(len(self.hex)/2))
|
||||||
dt = DeserializedTX(self.hex)
|
dt = DeserializedTX(self.hex)
|
||||||
self.check_hex_tx_matches_mmgen_tx(dt)
|
self.check_hex_tx_matches_mmgen_tx(dt)
|
||||||
txid = dt['txid']
|
self.coin_txid = CoinTxID(dt['txid'],on_fail='return')
|
||||||
self.check_sigs(dt)
|
self.check_sigs(dt)
|
||||||
assert txid == c.decoderawtransaction(self.hex)['txid'], 'txid mismatch (after signing)'
|
assert self.coin_txid == g.rpch.decoderawtransaction(self.hex)['txid'],(
|
||||||
self.coin_txid = BitcoinTxID(txid,on_fail='return')
|
'txid mismatch (after signing)')
|
||||||
msg('OK')
|
msg('OK')
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
msg('failed\nBitcoind returned the following errors:')
|
msg('failed\n{} returned the following errors:'.format(g.proto.daemon_name.capitalize()))
|
||||||
msg(repr(ret['errors']))
|
msg(repr(ret['errors']))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
@ -508,7 +534,7 @@ class MMGenTX(MMGenObject):
|
||||||
ret = self.desc == 'signed transaction'
|
ret = self.desc == 'signed transaction'
|
||||||
return (red,green)[ret](str(ret)) if color else ret
|
return (red,green)[ret](str(ret)) if color else ret
|
||||||
|
|
||||||
# protect against an attack where a malicious, compromised or malfunctioning daemon could switch
|
# protect against an attack where a malicious, compromised or malfunctioning coin daemon could switch
|
||||||
# hex transaction data.
|
# hex transaction data.
|
||||||
def check_hex_tx_matches_mmgen_tx(self,deserial_tx):
|
def check_hex_tx_matches_mmgen_tx(self,deserial_tx):
|
||||||
m = 'Fatal error: a malicious or malfunctioning coin daemon or other program has altered your data!'
|
m = 'Fatal error: a malicious or malfunctioning coin daemon or other program has altered your data!'
|
||||||
|
|
@ -516,21 +542,24 @@ class MMGenTX(MMGenObject):
|
||||||
if deserial_tx['lock_time'] != 0:
|
if deserial_tx['lock_time'] != 0:
|
||||||
rdie(3,'\nLock time is not zero!\n' + m)
|
rdie(3,'\nLock time is not zero!\n' + m)
|
||||||
|
|
||||||
def do_io_err(desc,mmio,hexio):
|
def check_equal(desc,mmio,hexio):
|
||||||
|
if mmio != hexio:
|
||||||
msg('\nMMGen {}:\n{}'.format(desc,pformat(mmio)))
|
msg('\nMMGen {}:\n{}'.format(desc,pformat(mmio)))
|
||||||
msg('Hex {}:\n{}'.format(desc,pformat(hexio)))
|
msg('Hex {}:\n{}'.format(desc,pformat(hexio)))
|
||||||
m2 = '{} in hex transaction data from coin daemon do not match those in MMGen transaction!\n' + m
|
m2 = '{} in hex transaction data from coin daemon do not match those in MMGen transaction!\n' + m
|
||||||
rdie(3,m2.format(desc.capitalize()))
|
rdie(3,m2.format(desc.capitalize()))
|
||||||
|
|
||||||
i_hex = sorted((i['txid'],i['vout']) for i in deserial_tx['txins'])
|
d_hex = sorted((i['txid'],i['vout']) for i in deserial_tx['txins'])
|
||||||
i_mmgen = sorted((i.txid,i.vout) for i in self.inputs)
|
d_mmgen = sorted((i.txid,i.vout) for i in self.inputs)
|
||||||
if i_hex != i_mmgen:
|
check_equal('inputs',d_hex,d_mmgen)
|
||||||
do_io_err('inputs',i_hex,i_mmgen)
|
|
||||||
|
|
||||||
o_hex = sorted((o['address'],BTCAmt(o['amount'])) for o in deserial_tx['txouts'])
|
d_hex = sorted((o['address'],g.proto.coin_amt(o['amount'])) for o in deserial_tx['txouts'])
|
||||||
o_mmgen = sorted((o.addr,o.amt) for o in self.outputs)
|
d_mmgen = sorted((o.addr,o.amt) for o in self.outputs)
|
||||||
if o_hex != o_mmgen:
|
check_equal('outputs',d_hex,d_mmgen)
|
||||||
do_io_err('outputs',o_hex,o_mmgen)
|
|
||||||
|
uh = deserial_tx['unsigned_hex']
|
||||||
|
if str(self.txid) != make_chksum_6(unhexlify(uh)).upper():
|
||||||
|
die(3,'MMGen TxID ({}) does not match hex transaction data!'.format(self.txid))
|
||||||
|
|
||||||
def check_sigs(self,deserial_tx=None): # return False if no sigs, die on error
|
def check_sigs(self,deserial_tx=None): # return False if no sigs, die on error
|
||||||
txins = (deserial_tx or DeserializedTX(self.hex))['txins']
|
txins = (deserial_tx or DeserializedTX(self.hex))['txins']
|
||||||
|
|
@ -555,39 +584,42 @@ class MMGenTX(MMGenObject):
|
||||||
def has_segwit_outputs(self):
|
def has_segwit_outputs(self):
|
||||||
return any(o.mmid and o.mmid.mmtype == 'S' for o in self.outputs)
|
return any(o.mmid and o.mmid.mmtype == 'S' for o in self.outputs)
|
||||||
|
|
||||||
def is_in_mempool(self,c):
|
def is_in_mempool(self):
|
||||||
return 'size' in c.getmempoolentry(self.coin_txid,on_fail='silent')
|
return 'size' in g.rpch.getmempoolentry(self.coin_txid,on_fail='silent')
|
||||||
|
|
||||||
def is_in_wallet(self,c):
|
def is_in_wallet(self):
|
||||||
ret = c.gettransaction(self.coin_txid,on_fail='silent')
|
ret = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
|
||||||
if 'confirmations' in ret and ret['confirmations'] > 0:
|
if 'confirmations' in ret and ret['confirmations'] > 0:
|
||||||
return ret['confirmations']
|
return ret['confirmations']
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_replaced(self,c):
|
def is_replaced(self):
|
||||||
if self.is_in_mempool(c): return False
|
if self.is_in_mempool(): return False
|
||||||
ret = c.gettransaction(self.coin_txid,on_fail='silent')
|
ret = g.rpch.gettransaction(self.coin_txid,on_fail='silent')
|
||||||
if not 'bip125-replaceable' in ret or not 'confirmations' in ret or ret['confirmations'] > 0:
|
if not 'bip125-replaceable' in ret or not 'confirmations' in ret or ret['confirmations'] > 0:
|
||||||
return False
|
return False
|
||||||
return -ret['confirmations'] + 1 # 1: replacement in mempool, 2: replacement confirmed
|
return -ret['confirmations'] + 1 # 1: replacement in mempool, 2: replacement confirmed
|
||||||
|
|
||||||
def is_in_utxos(self,c):
|
def is_in_utxos(self):
|
||||||
return 'txid' in c.getrawtransaction(self.coin_txid,True,on_fail='silent')
|
return 'txid' in g.rpch.getrawtransaction(self.coin_txid,True,on_fail='silent')
|
||||||
|
|
||||||
def get_status(self,c,status=False):
|
def get_status(self,status=False):
|
||||||
if self.is_in_mempool(c):
|
if self.is_in_mempool():
|
||||||
msg(('Warning: transaction is in mempool!','Transaction is in mempool')[status])
|
msg(('Warning: transaction is in mempool!','Transaction is in mempool')[status])
|
||||||
elif self.is_in_wallet(c):
|
elif self.is_in_wallet():
|
||||||
confs = self.is_in_wallet(c)
|
confs = self.is_in_wallet()
|
||||||
die(0,'Transaction has {} confirmation{}'.format(confs,suf(confs,'s')))
|
die(0,'Transaction has {} confirmation{}'.format(confs,suf(confs,'s')))
|
||||||
elif self.is_in_utxos(c):
|
elif self.is_in_utxos():
|
||||||
die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!'))
|
die(2,red('ERROR: transaction is in the blockchain (but not in the tracking wallet)!'))
|
||||||
ret = self.is_replaced(c) # 1: replacement in mempool, 2: replacement confirmed
|
ret = self.is_replaced() # 1: replacement in mempool, 2: replacement confirmed
|
||||||
if ret:
|
if ret:
|
||||||
die(1,'Transaction has been replaced'+('',', and the replacement TX is confirmed')[ret==2]+'!')
|
die(1,'Transaction has been replaced'+('',', and the replacement TX is confirmed')[ret==2]+'!')
|
||||||
|
|
||||||
def send(self,c,prompt_user=True):
|
def send(self,prompt_user=True):
|
||||||
|
|
||||||
|
if not self.marked_signed():
|
||||||
|
die(1,'Transaction is not signed!')
|
||||||
|
|
||||||
self.die_if_incorrect_chain()
|
self.die_if_incorrect_chain()
|
||||||
|
|
||||||
|
|
@ -599,10 +631,11 @@ class MMGenTX(MMGenObject):
|
||||||
m = 'Transaction has MMGen Segwit outputs, but this blockchain does not support Segwit'
|
m = 'Transaction has MMGen Segwit outputs, but this blockchain does not support Segwit'
|
||||||
die(2,m+' at the current height')
|
die(2,m+' at the current height')
|
||||||
|
|
||||||
if self.get_fee() > g.max_tx_fee:
|
if self.get_fee() > g.proto.max_tx_fee:
|
||||||
die(2,'Transaction fee ({}) greater than max_tx_fee ({})!'.format(self.get_fee(),g.max_tx_fee))
|
die(2,'Transaction fee ({}) greater than {} max_tx_fee ({} {})!'.format(
|
||||||
|
self.get_fee(),g.proto.name.capitalize(),g.proto.max_tx_fee,g.coin.upper()))
|
||||||
|
|
||||||
self.get_status(c)
|
self.get_status()
|
||||||
|
|
||||||
if prompt_user:
|
if prompt_user:
|
||||||
m1 = ("Once this transaction is sent, there's no taking it back!",'')[bool(opt.quiet)]
|
m1 = ("Once this transaction is sent, there's no taking it back!",'')[bool(opt.quiet)]
|
||||||
|
|
@ -611,7 +644,7 @@ class MMGenTX(MMGenObject):
|
||||||
confirm_or_exit(m1,m2,m3)
|
confirm_or_exit(m1,m2,m3)
|
||||||
|
|
||||||
msg('Sending transaction')
|
msg('Sending transaction')
|
||||||
ret = None if bogus_send else c.sendrawtransaction(self.hex,on_fail='return')
|
ret = None if bogus_send else g.rpch.sendrawtransaction(self.hex,on_fail='return')
|
||||||
|
|
||||||
from mmgen.rpc import rpc_error,rpc_errmsg
|
from mmgen.rpc import rpc_error,rpc_errmsg
|
||||||
if rpc_error(ret):
|
if rpc_error(ret):
|
||||||
|
|
@ -636,7 +669,7 @@ class MMGenTX(MMGenObject):
|
||||||
self.desc = 'sent transaction'
|
self.desc = 'sent transaction'
|
||||||
msg(m.format(self.coin_txid.hl()))
|
msg(m.format(self.coin_txid.hl()))
|
||||||
self.add_timestamp()
|
self.add_timestamp()
|
||||||
self.add_blockcount(c)
|
self.add_blockcount()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def write_txid_to_file(self,ask_write=False,ask_write_default_yes=True):
|
def write_txid_to_file(self,ask_write=False,ask_write_default_yes=True):
|
||||||
|
|
@ -649,8 +682,12 @@ class MMGenTX(MMGenObject):
|
||||||
if ask_write == False:
|
if ask_write == False:
|
||||||
ask_write_default_yes=True
|
ask_write_default_yes=True
|
||||||
self.format()
|
self.format()
|
||||||
spbs = ('',',{}'.format(self.btc2spb(self.get_fee())))[self.is_rbf()]
|
fn = '{}{}[{}{}].{}'.format(
|
||||||
fn = '{}[{}{}].{}'.format(self.txid,self.send_amt,spbs,self.ext)
|
self.txid,
|
||||||
|
('-'+g.coin,'')[g.coin=='BTC'],
|
||||||
|
self.send_amt,
|
||||||
|
('',',{}'.format(self.btc2spb(self.get_fee())))[self.is_rbf()],
|
||||||
|
self.ext)
|
||||||
write_data_to_file(fn,self.fmt_data,self.desc+add_desc,
|
write_data_to_file(fn,self.fmt_data,self.desc+add_desc,
|
||||||
ask_overwrite=ask_overwrite,
|
ask_overwrite=ask_overwrite,
|
||||||
ask_write=ask_write,
|
ask_write=ask_write,
|
||||||
|
|
@ -664,7 +701,7 @@ class MMGenTX(MMGenObject):
|
||||||
self.view(pager=reply in 'Vv',terse=reply in 'Tt')
|
self.view(pager=reply in 'Vv',terse=reply in 'Tt')
|
||||||
|
|
||||||
def view(self,pager=False,pause=True,terse=False):
|
def view(self,pager=False,pause=True,terse=False):
|
||||||
o = self.format_view(terse=terse).encode('utf8')
|
o = self.format_view(terse=terse)
|
||||||
if pager: do_pager(o)
|
if pager: do_pager(o)
|
||||||
else:
|
else:
|
||||||
msg_r(o)
|
msg_r(o)
|
||||||
|
|
@ -673,24 +710,20 @@ class MMGenTX(MMGenObject):
|
||||||
get_char('Press any key to continue: ')
|
get_char('Press any key to continue: ')
|
||||||
msg('')
|
msg('')
|
||||||
|
|
||||||
# def is_rbf_fromhex(self,color=False):
|
# def is_rbf_from_rpc(self):
|
||||||
# try:
|
# dec_tx = g.rpch.decoderawtransaction(self.hex)
|
||||||
# dec_tx = rpc_connection().decoderawtransaction(self.hex)
|
# return None < dec_tx['vin'][0]['sequence'] <= g.max_int - 2
|
||||||
# except:
|
|
||||||
# return yellow('Unknown') if color else None
|
|
||||||
# rbf = bool(dec_tx['vin'][0]['sequence'] == g.max_int - 2)
|
|
||||||
# return (red,green)[rbf](str(rbf)) if color else rbf
|
|
||||||
|
|
||||||
def is_rbf(self,color=False):
|
def is_rbf(self):
|
||||||
ret = None < self.inputs[0].sequence <= g.max_int - 2
|
return self.inputs[0].sequence == g.max_int - 2
|
||||||
return (red,green)[ret](str(ret)) if color else ret
|
|
||||||
|
|
||||||
def signal_for_rbf(self):
|
def signal_for_rbf(self):
|
||||||
self.inputs[0].sequence = g.max_int - 2
|
self.inputs[0].sequence = g.max_int - 2
|
||||||
|
|
||||||
def format_view(self,terse=False):
|
def format_view(self,terse=False):
|
||||||
try:
|
try:
|
||||||
blockcount = rpc_connection().getblockcount()
|
rpc_init()
|
||||||
|
blockcount = g.rpch.getblockcount()
|
||||||
except:
|
except:
|
||||||
blockcount = None
|
blockcount = None
|
||||||
|
|
||||||
|
|
@ -712,10 +745,11 @@ class MMGenTX(MMGenObject):
|
||||||
def format_io(io):
|
def format_io(io):
|
||||||
ip = io == self.inputs
|
ip = io == self.inputs
|
||||||
io_out = ''
|
io_out = ''
|
||||||
|
confs_per_day = 60*60*24 / g.proto.secs_per_block
|
||||||
for n,e in enumerate(sorted(io,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)):
|
for n,e in enumerate(sorted(io,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)):
|
||||||
if ip and blockcount:
|
if ip and blockcount != None:
|
||||||
confs = e.confs + blockcount - self.blockcount
|
confs = e.confs + blockcount - self.blockcount
|
||||||
days = int(confs * g.mins_per_block / (60*24))
|
days = int(confs / confs_per_day)
|
||||||
if e.mmid:
|
if e.mmid:
|
||||||
app=('',' (chg)')[bool(not ip and e.is_chg and terse)]
|
app=('',' (chg)')[bool(not ip and e.is_chg and terse)]
|
||||||
mmid_fmt = e.mmid.fmt(width=max_mmwid,encl='()',color=True,app=app,appcolor='green')
|
mmid_fmt = e.mmid.fmt(width=max_mmwid,encl='()',color=True,app=app,appcolor='green')
|
||||||
|
|
@ -725,18 +759,18 @@ class MMGenTX(MMGenObject):
|
||||||
io_out += '{:3} {} {} {} {}\n'.format(n+1,e.addr.fmt(color=True),mmid_fmt,e.amt.hl(),g.coin)
|
io_out += '{:3} {} {} {} {}\n'.format(n+1,e.addr.fmt(color=True),mmid_fmt,e.amt.hl(),g.coin)
|
||||||
else:
|
else:
|
||||||
icommon = [
|
icommon = [
|
||||||
((n+1,'')[ip], 'address:', e.addr.fmt(color=True) + ' ' + mmid_fmt),
|
((n+1,'')[ip],'address:',e.addr.fmt(color=True) + ' '+mmid_fmt),
|
||||||
('', 'comment:', e.label.hl() if e.label else ''),
|
('','comment:',e.label.hl() if e.label else ''),
|
||||||
('', 'amount:', '{} {}'.format(e.amt.hl(),g.coin))]
|
('','amount:','{} {}'.format(e.amt.hl(),g.coin))]
|
||||||
items = [(n+1, 'tx,vout:', '%s,%s' % (e.txid, e.vout))] + icommon + [
|
items = [(n+1, 'tx,vout:','{},{}'.format(e.txid,e.vout))] + icommon + [
|
||||||
('', 'confirmations:', '%s (around %s days)' % (confs,days) if blockcount else '')
|
('','confirmations:','{} (around {} days)'.format(confs,days) if blockcount!=None else '')
|
||||||
] if ip else icommon + [
|
] if ip else icommon + [
|
||||||
('', 'change:', green('True') if e.is_chg else '')]
|
('','change:',green('True') if e.is_chg else '')]
|
||||||
io_out += '\n'.join([('%3s %-8s %s' % d) for d in items if d[2]]) + '\n\n'
|
io_out += '\n'.join([('{:>3} {:<8} {}'.format(*d)) for d in items if d[2]]) + '\n\n'
|
||||||
return io_out
|
return io_out
|
||||||
|
|
||||||
out = hdr_fs.format(self.txid.hl(),self.send_amt.hl(),g.coin,self.timestamp,
|
out = hdr_fs.format(self.txid.hl(),self.send_amt.hl(),g.coin,self.timestamp,
|
||||||
self.is_rbf(color=True),self.marked_signed(color=True))
|
(red('False'),green('True'))[self.is_rbf()],self.marked_signed(color=True))
|
||||||
if self.chain in ('testnet','regtest'):
|
if self.chain in ('testnet','regtest'):
|
||||||
out += green('Chain: {}\n'.format(self.chain.upper()))
|
out += green('Chain: {}\n'.format(self.chain.upper()))
|
||||||
if self.coin_txid:
|
if self.coin_txid:
|
||||||
|
|
@ -767,67 +801,63 @@ class MMGenTX(MMGenObject):
|
||||||
|
|
||||||
return out # TX label might contain non-ascii chars
|
return out # TX label might contain non-ascii chars
|
||||||
|
|
||||||
def parse_tx_file(self,infile):
|
def parse_tx_file(self,infile,md_only=False):
|
||||||
|
|
||||||
self.parse_tx_data(get_lines_from_file(infile,self.desc+' data'))
|
tx_data = get_lines_from_file(infile,self.desc+' data')
|
||||||
|
|
||||||
def parse_tx_data(self,tx_data):
|
try:
|
||||||
|
desc = 'data'
|
||||||
def do_err(s): die(2,'Invalid %s in transaction file' % s)
|
assert len(tx_data) >= 5,'number of lines less than 5'
|
||||||
|
self.chksum = HexStr(tx_data.pop(0),on_fail='raise')
|
||||||
if len(tx_data) < 5: do_err('number of lines')
|
assert self.chksum == make_chksum_6(' '.join(tx_data)),'file data does not match checksum'
|
||||||
|
|
||||||
self.chksum = HexStr(tx_data.pop(0))
|
|
||||||
if self.chksum != make_chksum_6(' '.join(tx_data)):
|
|
||||||
do_err('checksum')
|
|
||||||
|
|
||||||
if len(tx_data) == 6:
|
if len(tx_data) == 6:
|
||||||
self.coin_txid = BitcoinTxID(tx_data.pop(-1),on_fail='return')
|
desc = '{} TxID'.format(g.proto.name.capitalize())
|
||||||
if not self.coin_txid:
|
self.coin_txid = CoinTxID(tx_data.pop(-1),on_fail='raise')
|
||||||
do_err('Bitcoin TxID')
|
|
||||||
|
|
||||||
if len(tx_data) == 5:
|
if len(tx_data) == 5:
|
||||||
c = tx_data.pop(-1)
|
c = tx_data.pop(-1)
|
||||||
if c != '-':
|
if c != '-':
|
||||||
|
desc = 'encoded comment (not base58)'
|
||||||
comment = baseconv.b58decode(c).decode('utf8')
|
comment = baseconv.b58decode(c).decode('utf8')
|
||||||
if comment == False:
|
assert comment != False,'invalid comment'
|
||||||
do_err('encoded comment (not base58)')
|
desc = 'comment'
|
||||||
else:
|
self.label = MMGenTXLabel(comment,on_fail='raise')
|
||||||
self.label = MMGenTXLabel(comment,on_fail='return')
|
|
||||||
if not self.label:
|
|
||||||
do_err('comment')
|
|
||||||
else:
|
|
||||||
comment = u''
|
|
||||||
|
|
||||||
if len(tx_data) == 4:
|
desc = 'number of lines' # four required lines
|
||||||
metadata,self.hex,inputs_data,outputs_data = tx_data
|
metadata,self.hex,inputs_data,outputs_data = tx_data
|
||||||
else:
|
|
||||||
do_err('number of lines')
|
|
||||||
|
|
||||||
metadata = metadata.split()
|
metadata = metadata.split()
|
||||||
if len(metadata) not in (4,5): do_err('metadata')
|
|
||||||
|
self.coin = metadata.pop(0) if len(metadata) == 6 else 'BTC'
|
||||||
|
|
||||||
if len(metadata) == 5:
|
if len(metadata) == 5:
|
||||||
t = metadata.pop(0)
|
t = metadata.pop(0)
|
||||||
self.chain = (t.lower(),None)[t=='Unknown']
|
self.chain = (t.lower(),None)[t=='Unknown']
|
||||||
|
|
||||||
|
desc = 'metadata (4 items minimum required)'
|
||||||
self.txid,send_amt,self.timestamp,blockcount = metadata
|
self.txid,send_amt,self.timestamp,blockcount = metadata
|
||||||
self.txid = MMGenTxID(self.txid)
|
desc = 'metadata'
|
||||||
self.send_amt = BTCAmt(send_amt)
|
self.txid = MMGenTxID(self.txid,on_fail='raise')
|
||||||
|
self.send_amt = g.proto.coin_amt(send_amt,on_fail='raise')
|
||||||
|
desc = 'block count in metadata'
|
||||||
self.blockcount = int(blockcount)
|
self.blockcount = int(blockcount)
|
||||||
self.hex = HexStr(self.hex)
|
desc = 'transaction hex data'
|
||||||
|
self.hex = HexStr(self.hex,on_fail='raise')
|
||||||
|
if md_only: return # the following ops will all fail if g.coin doesn't match tx.coin
|
||||||
|
desc = 'coin type in metadata'
|
||||||
|
assert self.coin == g.coin,'invalid coin type'
|
||||||
|
desc = 'inputs data'
|
||||||
|
self.inputs = self.decode_io('inputs',eval(inputs_data))
|
||||||
|
assert len(self.inputs),'no inputs!'
|
||||||
|
desc = '{}-to-MMGen address map data'.format(g.coin)
|
||||||
|
self.outputs = self.decode_io('outputs',eval(outputs_data))
|
||||||
|
assert len(self.outputs),'no outputs!'
|
||||||
|
except Exception as e:
|
||||||
|
die(2,'Invalid {} in transaction file: {}'.format(desc,e[0]))
|
||||||
|
|
||||||
try: unhexlify(self.hex)
|
if not self.chain and not self.inputs[0].addr.is_for_chain('testnet'):
|
||||||
except: do_err('hex data')
|
|
||||||
|
|
||||||
try: self.inputs = self.decode_io('inputs',eval(inputs_data))
|
|
||||||
except: do_err('inputs data')
|
|
||||||
|
|
||||||
if not self.chain and not self.inputs[0].addr.is_testnet():
|
|
||||||
self.chain = 'mainnet'
|
self.chain = 'mainnet'
|
||||||
|
|
||||||
try: self.outputs = self.decode_io('outputs',eval(outputs_data))
|
|
||||||
except: do_err('btc-to-mmgen address map data')
|
|
||||||
|
|
||||||
class MMGenBumpTX(MMGenTX):
|
class MMGenBumpTX(MMGenTX):
|
||||||
|
|
||||||
min_fee = None
|
min_fee = None
|
||||||
|
|
@ -886,7 +916,7 @@ class MMGenBumpTX(MMGenTX):
|
||||||
ret = super(type(self),self).get_usr_fee(tx_fee,desc)
|
ret = super(type(self),self).get_usr_fee(tx_fee,desc)
|
||||||
if ret < self.min_fee:
|
if ret < self.min_fee:
|
||||||
msg('{} {c}: {} fee too small. Minimum fee: {} {c} ({} satoshis per byte)'.format(
|
msg('{} {c}: {} fee too small. Minimum fee: {} {c} ({} satoshis per byte)'.format(
|
||||||
ret,desc,self.min_fee,self.btc2spb(self.min_fee,c=g.coin)))
|
ret,desc,self.min_fee,self.btc2spb(self.min_fee),c=g.coin))
|
||||||
return False
|
return False
|
||||||
output_amt = self.outputs[self.bump_output_idx].amt
|
output_amt = self.outputs[self.bump_output_idx].amt
|
||||||
if ret >= output_amt:
|
if ret >= output_amt:
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
txcreate: Create a Bitcoin transaction to and from MMGen- or non-MMGen inputs
|
txcreate: Create a cryptocoin transaction with MMGen- and/or non-MMGen inputs and outputs
|
||||||
and outputs
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from mmgen.common import *
|
from mmgen.common import *
|
||||||
|
|
@ -27,35 +26,6 @@ from mmgen.tw import *
|
||||||
|
|
||||||
pnm = g.proj_name
|
pnm = g.proj_name
|
||||||
|
|
||||||
txcreate_notes = """
|
|
||||||
The transaction's outputs are specified on the command line, while its inputs
|
|
||||||
are chosen from a list of the user's unpent outputs via an interactive menu.
|
|
||||||
|
|
||||||
If the transaction fee is not specified on the command line (see FEE
|
|
||||||
SPECIFICATION below), it will be calculated dynamically using bitcoind's
|
|
||||||
"estimatefee" function for the default (or user-specified) number of
|
|
||||||
confirmations. If "estimatefee" fails, the user will be prompted for a fee.
|
|
||||||
|
|
||||||
Dynamic ("estimatefee") fees will be multiplied by the value of '--tx-fee-adj',
|
|
||||||
if specified.
|
|
||||||
|
|
||||||
Ages of transactions are approximate based on an average block discovery
|
|
||||||
interval of {g.mins_per_block} minutes.
|
|
||||||
|
|
||||||
All addresses on the command line can be either Bitcoin addresses or {pnm}
|
|
||||||
addresses of the form <seed ID>:<index>.
|
|
||||||
|
|
||||||
To send the value of all inputs (minus TX fee) to a single output, specify
|
|
||||||
one address with no amount on the command line.
|
|
||||||
""".format(g=g,pnm=pnm)
|
|
||||||
|
|
||||||
fee_notes = """
|
|
||||||
FEE SPECIFICATION: Transaction fees, both on the command line and at the
|
|
||||||
interactive prompt, may be specified as either absolute {} amounts, using
|
|
||||||
a plain decimal number, or as satoshis per byte, using an integer followed by
|
|
||||||
the letter 's'.
|
|
||||||
"""
|
|
||||||
|
|
||||||
wmsg = {
|
wmsg = {
|
||||||
'addr_in_addrfile_only': """
|
'addr_in_addrfile_only': """
|
||||||
Warning: output address {mmgenaddr} is not in the tracking wallet, which means
|
Warning: output address {mmgenaddr} is not in the tracking wallet, which means
|
||||||
|
|
@ -98,7 +68,7 @@ def select_unspent(unspent,prompt):
|
||||||
return selected
|
return selected
|
||||||
msg('Unspent output number must be <= %s' % len(unspent))
|
msg('Unspent output number must be <= %s' % len(unspent))
|
||||||
|
|
||||||
def mmaddr2coinaddr(c,mmaddr,ad_w,ad_f):
|
def mmaddr2coinaddr(mmaddr,ad_w,ad_f):
|
||||||
|
|
||||||
# assume mmaddr has already been checked
|
# assume mmaddr has already been checked
|
||||||
coin_addr = ad_w.mmaddr2coinaddr(mmaddr)
|
coin_addr = ad_w.mmaddr2coinaddr(mmaddr)
|
||||||
|
|
@ -119,21 +89,19 @@ def mmaddr2coinaddr(c,mmaddr,ad_w,ad_f):
|
||||||
|
|
||||||
def get_fee_from_estimate_or_user(tx,estimate_fail_msg_shown=[]):
|
def get_fee_from_estimate_or_user(tx,estimate_fail_msg_shown=[]):
|
||||||
|
|
||||||
c = rpc_connection()
|
|
||||||
|
|
||||||
if opt.tx_fee:
|
if opt.tx_fee:
|
||||||
desc = 'User-selected'
|
desc = 'User-selected'
|
||||||
start_fee = opt.tx_fee
|
start_fee = opt.tx_fee
|
||||||
else:
|
else:
|
||||||
desc = 'Network-estimated'
|
desc = 'Network-estimated'
|
||||||
ret = c.estimatefee(opt.tx_confs)
|
ret = g.rpch.estimatefee(opt.tx_confs)
|
||||||
if ret == -1:
|
if ret == -1:
|
||||||
if not estimate_fail_msg_shown:
|
if not estimate_fail_msg_shown:
|
||||||
msg('Network fee estimation for {} confirmations failed'.format(opt.tx_confs))
|
msg('Network fee estimation for {} confirmations failed'.format(opt.tx_confs))
|
||||||
estimate_fail_msg_shown.append(True)
|
estimate_fail_msg_shown.append(True)
|
||||||
start_fee = None
|
start_fee = None
|
||||||
else:
|
else:
|
||||||
start_fee = BTCAmt(ret) * opt.tx_fee_adj * tx.estimate_size() / 1024
|
start_fee = g.proto.coin_amt(ret) * opt.tx_fee_adj * tx.estimate_size() / 1024
|
||||||
if opt.verbose:
|
if opt.verbose:
|
||||||
msg('{} fee ({} confs): {} {}/kB'.format(desc,opt.tx_confs,ret,g.coin))
|
msg('{} fee ({} confs): {} {}/kB'.format(desc,opt.tx_confs,ret,g.coin))
|
||||||
msg('TX size (estimated): {}'.format(tx.estimate_size()))
|
msg('TX size (estimated): {}'.format(tx.estimate_size()))
|
||||||
|
|
@ -156,17 +124,17 @@ def get_outputs_from_cmdline(cmd_args,tx):
|
||||||
if ',' in a:
|
if ',' in a:
|
||||||
a1,a2 = a.split(',',1)
|
a1,a2 = a.split(',',1)
|
||||||
if is_mmgen_id(a1) or is_coin_addr(a1):
|
if is_mmgen_id(a1) or is_coin_addr(a1):
|
||||||
coin_addr = mmaddr2coinaddr(c,a1,ad_w,ad_f) if is_mmgen_id(a1) else CoinAddr(a1)
|
coin_addr = mmaddr2coinaddr(a1,ad_w,ad_f) if is_mmgen_id(a1) else CoinAddr(a1)
|
||||||
tx.add_output(coin_addr,BTCAmt(a2))
|
tx.add_output(coin_addr,g.proto.coin_amt(a2))
|
||||||
else:
|
else:
|
||||||
die(2,"%s: unrecognized subargument in argument '%s'" % (a1,a))
|
die(2,"%s: invalid subargument in command-line argument '%s'" % (a1,a))
|
||||||
elif is_mmgen_id(a) or is_coin_addr(a):
|
elif is_mmgen_id(a) or is_coin_addr(a):
|
||||||
if tx.get_chg_output_idx() != None:
|
if tx.get_chg_output_idx() != None:
|
||||||
die(2,'ERROR: More than one change address listed on command line')
|
die(2,'ERROR: More than one change address listed on command line')
|
||||||
coin_addr = mmaddr2coinaddr(c,a,ad_w,ad_f) if is_mmgen_id(a) else CoinAddr(a)
|
coin_addr = mmaddr2coinaddr(a,ad_w,ad_f) if is_mmgen_id(a) else CoinAddr(a)
|
||||||
tx.add_output(coin_addr,BTCAmt('0'),is_chg=True)
|
tx.add_output(coin_addr,g.proto.coin_amt('0'),is_chg=True)
|
||||||
else:
|
else:
|
||||||
die(2,'%s: unrecognized argument' % a)
|
die(2,'{}: invalid command-line argument'.format(a))
|
||||||
|
|
||||||
if not tx.outputs:
|
if not tx.outputs:
|
||||||
die(2,'At least one output must be specified on the command line')
|
die(2,'At least one output must be specified on the command line')
|
||||||
|
|
@ -175,6 +143,7 @@ def get_outputs_from_cmdline(cmd_args,tx):
|
||||||
die(2,('ERROR: No change output specified',wmsg['no_change_output'])[len(tx.outputs) == 1])
|
die(2,('ERROR: No change output specified',wmsg['no_change_output'])[len(tx.outputs) == 1])
|
||||||
|
|
||||||
tx.add_mmaddrs_to_outputs(ad_w,ad_f)
|
tx.add_mmaddrs_to_outputs(ad_w,ad_f)
|
||||||
|
tx.check_dup_addrs('outputs')
|
||||||
|
|
||||||
if not segwit_is_active() and tx.has_segwit_outputs():
|
if not segwit_is_active() and tx.has_segwit_outputs():
|
||||||
fs = '{} Segwit address requested on the command line, but Segwit is not active on this chain'
|
fs = '{} Segwit address requested on the command line, but Segwit is not active on this chain'
|
||||||
|
|
@ -214,14 +183,16 @@ def get_inputs_from_user(tw,tx,caller):
|
||||||
|
|
||||||
def txcreate(cmd_args,do_info=False,caller='txcreate'):
|
def txcreate(cmd_args,do_info=False,caller='txcreate'):
|
||||||
|
|
||||||
|
rpc_init()
|
||||||
|
|
||||||
tx = MMGenTX()
|
tx = MMGenTX()
|
||||||
|
|
||||||
if opt.comment_file: tx.add_comment(opt.comment_file)
|
if opt.comment_file: tx.add_comment(opt.comment_file)
|
||||||
|
|
||||||
c = rpc_connection()
|
|
||||||
|
|
||||||
if not do_info: get_outputs_from_cmdline(cmd_args,tx)
|
if not do_info: get_outputs_from_cmdline(cmd_args,tx)
|
||||||
|
|
||||||
|
do_license_msg()
|
||||||
|
|
||||||
tw = MMGenTrackingWallet(minconf=opt.minconf)
|
tw = MMGenTrackingWallet(minconf=opt.minconf)
|
||||||
tw.view_and_sort(tx)
|
tw.view_and_sort(tx)
|
||||||
tw.display_total()
|
tw.display_total()
|
||||||
|
|
@ -244,7 +215,7 @@ def txcreate(cmd_args,do_info=False,caller='txcreate'):
|
||||||
msg('Warning: Change address will be deleted as transaction produces no change')
|
msg('Warning: Change address will be deleted as transaction produces no change')
|
||||||
tx.del_output(chg_idx)
|
tx.del_output(chg_idx)
|
||||||
else:
|
else:
|
||||||
tx.update_output_amt(chg_idx,BTCAmt(change_amt))
|
tx.update_output_amt(chg_idx,g.proto.coin_amt(change_amt))
|
||||||
|
|
||||||
if not tx.send_amt:
|
if not tx.send_amt:
|
||||||
tx.send_amt = change_amt
|
tx.send_amt = change_amt
|
||||||
|
|
@ -253,13 +224,13 @@ def txcreate(cmd_args,do_info=False,caller='txcreate'):
|
||||||
|
|
||||||
if not opt.yes:
|
if not opt.yes:
|
||||||
tx.add_comment() # edits an existing comment
|
tx.add_comment() # edits an existing comment
|
||||||
tx.create_raw(c) # creates tx.hex, tx.txid
|
tx.create_raw() # creates tx.hex, tx.txid
|
||||||
|
|
||||||
tx.add_timestamp()
|
tx.add_timestamp()
|
||||||
tx.add_blockcount(c)
|
tx.add_blockcount()
|
||||||
tx.chain = g.chain
|
tx.chain = g.chain
|
||||||
|
|
||||||
assert tx.sum_inputs() - tx.sum_outputs() <= g.max_tx_fee
|
assert tx.sum_inputs() - tx.sum_outputs() <= g.proto.max_tx_fee
|
||||||
|
|
||||||
qmsg('Transaction successfully created')
|
qmsg('Transaction successfully created')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,34 +27,6 @@ from mmgen.addr import *
|
||||||
|
|
||||||
pnm = g.proj_name
|
pnm = g.proj_name
|
||||||
|
|
||||||
txsign_notes = """
|
|
||||||
Transactions may contain both {pnm} or non-{pnm} input addresses.
|
|
||||||
|
|
||||||
To sign non-{pnm} inputs, a bitcoind wallet dump or flat key list is used
|
|
||||||
as the key source ('--keys-from-file' option).
|
|
||||||
|
|
||||||
To sign {pnm} inputs, key data is generated from a seed as with the
|
|
||||||
{pnl}-addrgen and {pnl}-keygen commands. Alternatively, a key-address file
|
|
||||||
may be used (--mmgen-keys-from-file option).
|
|
||||||
|
|
||||||
Multiple wallets or other seed files can be listed on the command line in
|
|
||||||
any order. If the seeds required to sign the transaction's inputs are not
|
|
||||||
found in these files (or in the default wallet), the user will be prompted
|
|
||||||
for seed data interactively.
|
|
||||||
|
|
||||||
To prevent an attacker from crafting transactions with bogus {pnm}-to-Bitcoin
|
|
||||||
address mappings, all outputs to {pnm} addresses are verified with a seed
|
|
||||||
source. Therefore, seed files or a key-address file for all {pnm} outputs
|
|
||||||
must also be supplied on the command line if the data can't be found in the
|
|
||||||
default wallet.
|
|
||||||
|
|
||||||
Seed source files must have the canonical extensions listed in the 'FileExt'
|
|
||||||
column below:
|
|
||||||
|
|
||||||
{f}
|
|
||||||
""".format(f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
|
||||||
pnm=pnm,pnl=pnm.lower())
|
|
||||||
|
|
||||||
wmsg = {
|
wmsg = {
|
||||||
'mapping_error': """
|
'mapping_error': """
|
||||||
{pnm} -> {c} address mappings differ!
|
{pnm} -> {c} address mappings differ!
|
||||||
|
|
@ -154,17 +126,19 @@ def get_keyaddrlist(opt):
|
||||||
def get_keylist(opt):
|
def get_keylist(opt):
|
||||||
if opt.keys_from_file:
|
if opt.keys_from_file:
|
||||||
l = get_lines_from_file(opt.keys_from_file,'key-address data',trim_comments=True)
|
l = get_lines_from_file(opt.keys_from_file,'key-address data',trim_comments=True)
|
||||||
kal = KeyAddrList(keylist=[m.split()[0] for m in l]) # accept bitcoind wallet dumps
|
kal = KeyAddrList(keylist=[m.split()[0] for m in l]) # accept coin daemon wallet dumps
|
||||||
kal.generate_addrs_from_keys()
|
kal.generate_addrs_from_keys()
|
||||||
return kal
|
return kal
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def txsign(opt,c,tx,seed_files,kl,kal,tx_num_str=''):
|
def txsign(tx,seed_files,kl,kal,tx_num_str=''):
|
||||||
|
|
||||||
keys = MMGenList() # list of AddrListEntry objects
|
keys = MMGenList() # list of AddrListEntry objects
|
||||||
non_mm_addrs = tx.get_non_mmaddrs('inputs')
|
non_mm_addrs = tx.get_non_mmaddrs('inputs')
|
||||||
|
|
||||||
if non_mm_addrs:
|
if non_mm_addrs:
|
||||||
|
if not kl:
|
||||||
|
die(2,'Transaction has non-{} inputs, but no flat key list is present'.format(g.proj_name))
|
||||||
tmp = KeyAddrList(addrlist=non_mm_addrs,do_chksum=False)
|
tmp = KeyAddrList(addrlist=non_mm_addrs,do_chksum=False)
|
||||||
tmp.add_wifs(kl)
|
tmp.add_wifs(kl)
|
||||||
m = tmp.list_missing('sec')
|
m = tmp.list_missing('sec')
|
||||||
|
|
@ -186,7 +160,7 @@ def txsign(opt,c,tx,seed_files,kl,kal,tx_num_str=''):
|
||||||
if extra_sids:
|
if extra_sids:
|
||||||
msg('Unused Seed ID{}: {}'.format(suf(extra_sids,'s'),' '.join(extra_sids)))
|
msg('Unused Seed ID{}: {}'.format(suf(extra_sids,'s'),' '.join(extra_sids)))
|
||||||
|
|
||||||
if tx.sign(c,tx_num_str,keys):
|
if tx.sign(tx_num_str,keys):
|
||||||
return tx
|
return tx
|
||||||
else:
|
else:
|
||||||
die(3,red('Transaction {}could not be signed.'.format(tx_num_str)))
|
die(3,red('Transaction {}could not be signed.'.format(tx_num_str)))
|
||||||
|
|
|
||||||
|
|
@ -421,12 +421,12 @@ def compare_or_die(val1, desc1, val2, desc2, e='Error'):
|
||||||
dmsg('%s OK (%s)' % (capfirst(desc2),val2))
|
dmsg('%s OK (%s)' % (capfirst(desc2),val2))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def open_file_or_exit(filename,mode):
|
def open_file_or_exit(filename,mode,silent=False):
|
||||||
try:
|
try:
|
||||||
f = open(filename, mode)
|
f = open(filename, mode)
|
||||||
except:
|
except:
|
||||||
op = ('writing','reading')['r' in mode]
|
op = ('writing','reading')['r' in mode]
|
||||||
die(2,"Unable to open file '%s' for %s" % (filename,op))
|
die(2,("Unable to open file '{}' for {}".format(filename,op),'')[silent])
|
||||||
return f
|
return f
|
||||||
|
|
||||||
def check_file_type_and_access(fname,ftype,blkdev_ok=False):
|
def check_file_type_and_access(fname,ftype,blkdev_ok=False):
|
||||||
|
|
@ -468,12 +468,15 @@ def make_full_path(outdir,outfile):
|
||||||
def get_seed_file(cmd_args,nargs,invoked_as=None):
|
def get_seed_file(cmd_args,nargs,invoked_as=None):
|
||||||
from mmgen.filename import find_file_in_dir
|
from mmgen.filename import find_file_in_dir
|
||||||
from mmgen.seed import Wallet
|
from mmgen.seed import Wallet
|
||||||
|
|
||||||
wf = find_file_in_dir(Wallet,g.data_dir)
|
wf = find_file_in_dir(Wallet,g.data_dir)
|
||||||
|
|
||||||
wd_from_opt = bool(opt.hidden_incog_input_params or opt.in_fmt) # have wallet data from opt?
|
wd_from_opt = bool(opt.hidden_incog_input_params or opt.in_fmt) # have wallet data from opt?
|
||||||
|
|
||||||
import mmgen.opts as opts
|
import mmgen.opts as opts
|
||||||
if len(cmd_args) + (wd_from_opt or bool(wf)) < nargs:
|
if len(cmd_args) + (wd_from_opt or bool(wf)) < nargs:
|
||||||
|
if not wf:
|
||||||
|
msg('No default wallet found, and no other seed source was specified')
|
||||||
opts.usage()
|
opts.usage()
|
||||||
elif len(cmd_args) > nargs:
|
elif len(cmd_args) > nargs:
|
||||||
opts.usage()
|
opts.usage()
|
||||||
|
|
@ -628,8 +631,8 @@ def get_words(infile,desc,prompt):
|
||||||
else:
|
else:
|
||||||
return get_words_from_user(prompt)
|
return get_words_from_user(prompt)
|
||||||
|
|
||||||
def mmgen_decrypt_file_maybe(fn,desc=''):
|
def mmgen_decrypt_file_maybe(fn,desc='',silent=False):
|
||||||
d = get_data_from_file(fn,desc,binary=True)
|
d = get_data_from_file(fn,desc,binary=True,silent=silent)
|
||||||
have_enc_ext = get_extension(fn) == g.mmenc_ext
|
have_enc_ext = get_extension(fn) == g.mmenc_ext
|
||||||
if have_enc_ext or not is_utf8(d):
|
if have_enc_ext or not is_utf8(d):
|
||||||
m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
|
m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
|
||||||
|
|
@ -638,11 +641,11 @@ def mmgen_decrypt_file_maybe(fn,desc=''):
|
||||||
d = mmgen_decrypt_retry(d,desc)
|
d = mmgen_decrypt_retry(d,desc)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def get_lines_from_file(fn,desc='',trim_comments=False):
|
def get_lines_from_file(fn,desc='',trim_comments=False,silent=False):
|
||||||
dec = mmgen_decrypt_file_maybe(fn,desc)
|
dec = mmgen_decrypt_file_maybe(fn,desc,silent=silent)
|
||||||
ret = dec.decode('utf8').splitlines() # DOS-safe
|
ret = dec.decode('utf8').splitlines() # DOS-safe
|
||||||
if trim_comments: ret = remove_comments(ret)
|
if trim_comments: ret = remove_comments(ret)
|
||||||
vmsg(u"Got {} lines from file '{}'".format(len(ret),fn))
|
dmsg(u"Got {} lines from file '{}'".format(len(ret),fn))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def get_data_from_user(desc='data',silent=False):
|
def get_data_from_user(desc='data',silent=False):
|
||||||
|
|
@ -653,9 +656,9 @@ def get_data_from_user(desc='data',silent=False):
|
||||||
|
|
||||||
def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False):
|
def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False):
|
||||||
if dash and infile == '-': return sys.stdin.read()
|
if dash and infile == '-': return sys.stdin.read()
|
||||||
if not silent and desc:
|
if not opt.quiet and not silent and desc:
|
||||||
qmsg("Getting %s from file '%s'" % (desc,infile))
|
qmsg("Getting %s from file '%s'" % (desc,infile))
|
||||||
f = open_file_or_exit(infile,('r','rb')[bool(binary)])
|
f = open_file_or_exit(infile,('r','rb')[bool(binary)],silent=silent)
|
||||||
data = f.read()
|
data = f.read()
|
||||||
f.close()
|
f.close()
|
||||||
return data
|
return data
|
||||||
|
|
@ -777,32 +780,35 @@ def do_license_msg(immed=False):
|
||||||
msg_r('\r')
|
msg_r('\r')
|
||||||
msg('')
|
msg('')
|
||||||
|
|
||||||
def get_bitcoind_cfg_options(cfg_keys):
|
def get_daemon_cfg_options(cfg_keys):
|
||||||
|
cfg_file = os.path.join(g.proto.daemon_data_dir,g.proto.name+'.conf')
|
||||||
cfg_file = os.path.join(g.daemon_data_dir,'bitcoin.conf')
|
try:
|
||||||
|
cfg = dict([(k,v) for k,v in [
|
||||||
cfg = dict([(k,v) for k,v in [split2(str(line).translate(None,'\t '),'=')
|
split2(str(line).translate(None,'\t '),'=')
|
||||||
for line in get_lines_from_file(cfg_file,'')] if k in cfg_keys]) \
|
for line in get_lines_from_file(cfg_file,'',silent=bool(opt.quiet))
|
||||||
if file_is_readable(cfg_file) else {}
|
] if k in cfg_keys])
|
||||||
|
except:
|
||||||
|
vmsg("Warning: '{}' does not exist or is unreadable".format(cfg_file))
|
||||||
|
cfg = {}
|
||||||
for k in set(cfg_keys) - set(cfg.keys()): cfg[k] = ''
|
for k in set(cfg_keys) - set(cfg.keys()): cfg[k] = ''
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
def get_bitcoind_auth_cookie():
|
def get_coin_daemon_auth_cookie():
|
||||||
f = os.path.join(g.daemon_data_dir,g.proto.data_subdir,'.cookie')
|
f = os.path.join(g.proto.daemon_data_dir,g.proto.daemon_data_subdir,'.cookie')
|
||||||
return get_lines_from_file(f,'')[0] if file_is_readable(f) else ''
|
return get_lines_from_file(f,'')[0] if file_is_readable(f) else ''
|
||||||
|
|
||||||
def rpc_connection():
|
def rpc_init(reinit=False):
|
||||||
|
|
||||||
def check_chainfork_mismatch(c):
|
if g.rpch != None and not reinit: return g.rpch
|
||||||
block0 = c.getblockhash(0)
|
|
||||||
latest = c.getblockcount()
|
def check_chainfork_mismatch(conn):
|
||||||
|
block0 = conn.getblockhash(0)
|
||||||
|
latest = conn.getblockcount()
|
||||||
try:
|
try:
|
||||||
assert block0 == g.proto.block0,'Incorrect Genesis block for {}'.format(g.proto.__name__)
|
assert block0 == g.proto.block0,'Incorrect Genesis block for {}'.format(g.proto.__name__)
|
||||||
for fork in g.proto.forks:
|
for fork in g.proto.forks:
|
||||||
if latest < fork[0]: break
|
if latest < fork[0]: break
|
||||||
bhash = c.getblockhash(fork[0])
|
bhash = conn.getblockhash(fork[0])
|
||||||
assert bhash == fork[1], (
|
assert bhash == fork[1], (
|
||||||
'Bad block hash at fork block {}. Is this the {} chain?'.format(fork[0],fork[2].upper()))
|
'Bad block hash at fork block {}. Is this the {} chain?'.format(fork[0],fork[2].upper()))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -816,24 +822,26 @@ def rpc_connection():
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
die(1,'{}\nChain is {}!'.format(e,g.chain))
|
die(1,'{}\nChain is {}!'.format(e,g.chain))
|
||||||
|
|
||||||
cfg = get_bitcoind_cfg_options(('rpcuser','rpcpassword'))
|
cfg = get_daemon_cfg_options(('rpcuser','rpcpassword'))
|
||||||
import mmgen.rpc
|
import mmgen.rpc
|
||||||
c = mmgen.rpc.BitcoinRPCConnection(
|
conn = mmgen.rpc.CoinDaemonRPCConnection(
|
||||||
g.rpc_host or 'localhost',
|
g.rpc_host or 'localhost',
|
||||||
g.rpc_port or g.proto.rpc_port,
|
g.rpc_port or g.proto.rpc_port,
|
||||||
g.rpc_user or cfg['rpcuser'], # MMGen's rpcuser,rpcpassword override bitcoind's
|
g.rpc_user or cfg['rpcuser'], # MMGen's rpcuser,rpcpassword override coin daemon's
|
||||||
g.rpc_password or cfg['rpcpassword'],
|
g.rpc_password or cfg['rpcpassword'],
|
||||||
auth_cookie=get_bitcoind_auth_cookie())
|
auth_cookie=get_coin_daemon_auth_cookie())
|
||||||
|
|
||||||
if not g.bitcoind_version: # First call
|
if not g.daemon_version: # First call
|
||||||
if g.bob or g.alice:
|
if g.bob or g.alice:
|
||||||
import regtest as rt
|
import regtest as rt
|
||||||
rt.user(('alice','bob')[g.bob],quiet=True)
|
rt.user(('alice','bob')[g.bob],quiet=True)
|
||||||
g.bitcoind_version = int(c.getnetworkinfo()['version'])
|
g.daemon_version = int(conn.getnetworkinfo()['version'])
|
||||||
g.chain = c.getblockchaininfo()['chain']
|
g.chain = conn.getblockchaininfo()['chain']
|
||||||
if g.chain != 'regtest': g.chain += 'net'
|
if g.chain != 'regtest': g.chain += 'net'
|
||||||
assert g.chain in g.chains
|
assert g.chain in g.chains
|
||||||
check_chaintype_mismatch()
|
check_chaintype_mismatch()
|
||||||
if g.chain == 'mainnet':
|
if g.chain == 'mainnet': # skip this for testnet, as Genesis block may change
|
||||||
check_chainfork_mismatch(c)
|
check_chainfork_mismatch(conn)
|
||||||
return c
|
|
||||||
|
g.rpch = conn
|
||||||
|
return conn
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,12 @@ mmgen-autosign: Auto-sign MMGen transactions
|
||||||
import sys,os,subprocess,time,signal
|
import sys,os,subprocess,time,signal
|
||||||
from stat import *
|
from stat import *
|
||||||
|
|
||||||
|
mountpoint = '/mnt/tx'
|
||||||
|
tx_dir = os.path.join(mountpoint,'tx')
|
||||||
|
part_label = 'MMGEN_TX'
|
||||||
|
shm_dir = '/dev/shm'
|
||||||
|
secret_fn = 'txsign-secret'
|
||||||
|
|
||||||
from mmgen.common import *
|
from mmgen.common import *
|
||||||
prog_name = os.path.basename(sys.argv[0])
|
prog_name = os.path.basename(sys.argv[0])
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
|
|
@ -30,10 +36,11 @@ opts_data = lambda: {
|
||||||
'usage':'[opts] [command]',
|
'usage':'[opts] [command]',
|
||||||
'options': """
|
'options': """
|
||||||
-h, --help Print this help message
|
-h, --help Print this help message
|
||||||
--, --longhelp Print help message for long options (common options)
|
-c, --coins=c Coins to sign for (comma-separated list)
|
||||||
-l, --led Use status LED to signal standby, busy and error
|
-l, --led Use status LED to signal standby, busy and error
|
||||||
-s, --stealth-led Stealth LED mode - signal busy and error only, and only
|
-s, --stealth-led Stealth LED mode - signal busy and error only, and only
|
||||||
after successful authorization.
|
after successful authorization.
|
||||||
|
-q, --quiet Produce quieter output
|
||||||
-v, --verbose Produce more verbose output
|
-v, --verbose Produce more verbose output
|
||||||
""",
|
""",
|
||||||
'notes': """
|
'notes': """
|
||||||
|
|
@ -50,13 +57,13 @@ wait - start in loop mode: wait - mount - sign - unmount - wait
|
||||||
If invoked with no command, the program mounts the USB stick, signs any
|
If invoked with no command, the program mounts the USB stick, signs any
|
||||||
unsigned transactions, unmounts the USB stick and exits.
|
unsigned transactions, unmounts the USB stick and exits.
|
||||||
|
|
||||||
If invoked with 'wait', the program waits in a loop, mounting, signing
|
If invoked with 'wait', the program waits in a loop, mounting, signing and
|
||||||
and unmounting every time the USB stick is inserted. The status LED
|
unmounting every time the USB stick is inserted. On supported platforms,
|
||||||
indicates whether the program is busy or in standby mode, i.e. ready for
|
the status LED indicates whether the program is busy or in standby mode,
|
||||||
USB stick insertion or removal.
|
i.e. ready for USB stick insertion or removal.
|
||||||
|
|
||||||
The USB stick must have a partition with the label MMGEN_TX and a user-
|
The USB stick must have a partition labeled MMGEN_TX and a user-writable
|
||||||
writable directory '/tx', where unsigned MMGen transactions are placed.
|
directory '/tx', where unsigned MMGen transactions are placed.
|
||||||
|
|
||||||
On the signing machine the directory /mnt/tx must exist and /etc/fstab must
|
On the signing machine the directory /mnt/tx must exist and /etc/fstab must
|
||||||
contain the following entry:
|
contain the following entry:
|
||||||
|
|
@ -83,22 +90,32 @@ in the scripts directory of the MMGen repository root where it resides.
|
||||||
""".format(prog_name)
|
""".format(prog_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_args = opts.init(opts_data)
|
cmd_args = opts.init(opts_data,add_opts=['mmgen_keys_from_file','in_fmt'])
|
||||||
|
|
||||||
|
import mmgen.tx
|
||||||
|
from mmgen.txsign import txsign
|
||||||
|
from mmgen.protocol import CoinProtocol
|
||||||
|
|
||||||
if opt.stealth_led: opt.led = True
|
if opt.stealth_led: opt.led = True
|
||||||
|
opt.outdir = tx_dir
|
||||||
|
|
||||||
mountpoint = '/mnt/tx'
|
def check_daemons_running():
|
||||||
tx_dir = os.path.join(mountpoint,'tx')
|
if opt.coin:
|
||||||
part_label = 'MMGEN_TX'
|
die(1,'--coin option not supported with this command. Use --coins instead')
|
||||||
shm_dir = '/dev/shm'
|
if opt.coins:
|
||||||
secret_fn = 'txsign-secret'
|
coins = opt.coins.split(',')
|
||||||
tn_arg = ['--testnet={}'.format(opt.testnet or '0')]
|
else:
|
||||||
coin_arg = ['--coin={}'.format(opt.coin or 'btc')]
|
ymsg('Warning: no coins specified, so defaulting to BTC only')
|
||||||
|
coins = ['btc']
|
||||||
|
|
||||||
def check_daemon_running():
|
for coin in coins:
|
||||||
cmd = ['mmgen-tool'] + tn_arg + coin_arg + ['getbalance']
|
cmd = [ 'mmgen-tool',
|
||||||
|
'--coin={}'.format(coin),
|
||||||
|
'--testnet={}'.format(opt.testnet or 0)
|
||||||
|
] + ([],['--quiet'])[bool(opt.quiet)] + ['getbalance']
|
||||||
vmsg('Executing: {}'.format(' '.join(cmd)))
|
vmsg('Executing: {}'.format(' '.join(cmd)))
|
||||||
try: subprocess.check_output(cmd)
|
try: subprocess.check_output(cmd)
|
||||||
except: die(1,'Daemon not running')
|
except: die(1,'{} daemon not running'.format(coin.upper()))
|
||||||
|
|
||||||
def get_wallet_files():
|
def get_wallet_files():
|
||||||
wfs = [f for f in os.listdir(shm_dir) if f[-8:] == '.mmwords']
|
wfs = [f for f in os.listdir(shm_dir) if f[-8:] == '.mmwords']
|
||||||
|
|
@ -134,6 +151,19 @@ def do_umount():
|
||||||
msg('Unmounting '+mountpoint)
|
msg('Unmounting '+mountpoint)
|
||||||
subprocess.call(['umount',mountpoint])
|
subprocess.call(['umount',mountpoint])
|
||||||
|
|
||||||
|
def sign_tx_file(txfile):
|
||||||
|
try:
|
||||||
|
g.coin = mmgen.tx.MMGenTX(txfile,md_only=True).coin
|
||||||
|
g.proto = CoinProtocol(g.coin,g.testnet)
|
||||||
|
reload(sys.modules['mmgen.tx'])
|
||||||
|
tx = mmgen.tx.MMGenTX(txfile)
|
||||||
|
rpc_init(reinit=True)
|
||||||
|
txsign(tx,wfs,None,None)
|
||||||
|
tx.write_to_file(ask_write=False)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
def sign():
|
def sign():
|
||||||
dirlist = os.listdir(tx_dir)
|
dirlist = os.listdir(tx_dir)
|
||||||
raw = [f for f in dirlist if f[-6:] == '.rawtx']
|
raw = [f for f in dirlist if f[-6:] == '.rawtx']
|
||||||
|
|
@ -141,16 +171,34 @@ def sign():
|
||||||
unsigned = [os.path.join(tx_dir,f) for f in raw if f[:-6] not in signed]
|
unsigned = [os.path.join(tx_dir,f) for f in raw if f[:-6] not in signed]
|
||||||
|
|
||||||
if unsigned:
|
if unsigned:
|
||||||
cmd = ['mmgen-txsign','--yes','--outdir='+tx_dir] + tn_arg + coin_arg + unsigned + wfs
|
fails = 0
|
||||||
vmsg('Executing: {}'.format(' '.join(cmd)))
|
for txfile in unsigned:
|
||||||
ret = subprocess.call(cmd)
|
ret = sign_tx_file(txfile)
|
||||||
msg('')
|
if not ret:
|
||||||
|
fails += 1
|
||||||
|
qmsg('')
|
||||||
|
if fails: ymsg('{} failed signs'.format(fails))
|
||||||
time.sleep(0.3)
|
time.sleep(0.3)
|
||||||
return (1,0)[ret==0]
|
return False if fails else True
|
||||||
else:
|
else:
|
||||||
msg('No unsigned transactions')
|
msg('No unsigned transactions')
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
return 0
|
return True
|
||||||
|
|
||||||
|
def do_sign():
|
||||||
|
if not opt.stealth_led: set_led('busy')
|
||||||
|
do_mount()
|
||||||
|
ret = get_secret_in_dir(tx_dir,on_fail='return')
|
||||||
|
if ret == secret:
|
||||||
|
if opt.stealth_led: set_led('busy')
|
||||||
|
ret = sign()
|
||||||
|
do_umount()
|
||||||
|
set_led(('standby','off','error')[(not ret)*2 or bool(opt.stealth_led)])
|
||||||
|
else:
|
||||||
|
if ret != None:
|
||||||
|
msg('Secret is incorrect!')
|
||||||
|
do_umount()
|
||||||
|
if not opt.stealth_led: set_led('error')
|
||||||
|
|
||||||
def wipe_existing_secret_files():
|
def wipe_existing_secret_files():
|
||||||
for d in (tx_dir,shm_dir):
|
for d in (tx_dir,shm_dir):
|
||||||
|
|
@ -203,33 +251,18 @@ def do_led(on,off):
|
||||||
|
|
||||||
def set_led(cmd):
|
def set_led(cmd):
|
||||||
if not opt.led: return
|
if not opt.led: return
|
||||||
|
vmsg("Setting LED state to '{}'".format(cmd))
|
||||||
timings = {
|
timings = {
|
||||||
'off': ( 0, 0 ),
|
'off': ( 0, 0 ),
|
||||||
'standby': ( 2.2, 0.2 ),
|
'standby': ( 2.2, 0.2 ),
|
||||||
'busy': ( 0.06, 0.06 ),
|
'busy': ( 0.06, 0.06 ),
|
||||||
'error': ( 0.5, 0.5 )}[cmd]
|
'error': ( 0.5, 0.5 )}[cmd]
|
||||||
vmsg("Executing command '{}'".format(cmd))
|
|
||||||
global led_thread
|
global led_thread
|
||||||
if led_thread:
|
if led_thread:
|
||||||
ev.set(); led_thread.join(); ev.clear()
|
ev.set(); led_thread.join(); ev.clear()
|
||||||
led_thread = threading.Thread(target=do_led,name='LED loop',args=timings)
|
led_thread = threading.Thread(target=do_led,name='LED loop',args=timings)
|
||||||
led_thread.start()
|
led_thread.start()
|
||||||
|
|
||||||
def do_sign():
|
|
||||||
if not opt.stealth_led: set_led('busy')
|
|
||||||
do_mount()
|
|
||||||
ret = get_secret_in_dir(tx_dir,on_fail='return')
|
|
||||||
if ret == secret:
|
|
||||||
if opt.stealth_led: set_led('busy')
|
|
||||||
exit_val = sign()
|
|
||||||
do_umount()
|
|
||||||
set_led(('standby','off','error')[bool(exit_val)*2 or bool(opt.stealth_led)])
|
|
||||||
else:
|
|
||||||
if ret != None:
|
|
||||||
msg('Secret is incorrect!')
|
|
||||||
do_umount()
|
|
||||||
if not opt.stealth_led: set_led('error')
|
|
||||||
|
|
||||||
def get_insert_status():
|
def get_insert_status():
|
||||||
try: os.stat(os.path.join('/dev/disk/by-label/',part_label))
|
try: os.stat(os.path.join('/dev/disk/by-label/',part_label))
|
||||||
except: return False
|
except: return False
|
||||||
|
|
@ -317,8 +350,11 @@ if opt.led:
|
||||||
|
|
||||||
check_wipe_present()
|
check_wipe_present()
|
||||||
wfs = get_wallet_files()
|
wfs = get_wallet_files()
|
||||||
|
|
||||||
secret = get_secret_in_dir(shm_dir,on_fail='die')
|
secret = get_secret_in_dir(shm_dir,on_fail='die')
|
||||||
check_daemon_running()
|
|
||||||
|
check_daemons_running()
|
||||||
|
#sign(); sys.exit()
|
||||||
|
|
||||||
signal.signal(signal.SIGTERM,handler)
|
signal.signal(signal.SIGTERM,handler)
|
||||||
signal.signal(signal.SIGINT,handler)
|
signal.signal(signal.SIGINT,handler)
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Tested on Linux, MinGW-64
|
# Tested on Linux, MinGW-64
|
||||||
|
# MinGW's bash 3.1.17 doesn't do ${var^^}
|
||||||
|
|
||||||
|
dfl_tests='obj btc btc_tn btc_rt bch bch_rt ltc ltc_tn ltc_rt tool gen'
|
||||||
PROGNAME=$(basename $0)
|
PROGNAME=$(basename $0)
|
||||||
while getopts hint OPT
|
while getopts hinPt OPT
|
||||||
do
|
do
|
||||||
case "$OPT" in
|
case "$OPT" in
|
||||||
h) printf " %-16s Test MMGen release\n" "${PROGNAME^^}:"
|
h) printf " %-16s Test MMGen release\n" "${PROGNAME}:"
|
||||||
echo " USAGE: $PROGNAME [options] branch [tests]"
|
echo " USAGE: $PROGNAME [options] branch [tests]"
|
||||||
echo " OPTIONS: '-h' Print this help message"
|
echo " OPTIONS: '-h' Print this help message"
|
||||||
echo " '-i' Install only; don't run tests"
|
echo " '-i' Install only; don't run tests"
|
||||||
echo " '-n' Don't install; test in place"
|
echo " '-n' Don't install; test in place"
|
||||||
|
echo " '-P' Don't pause between tests"
|
||||||
echo " '-t' Print the tests without running them"
|
echo " '-t' Print the tests without running them"
|
||||||
echo " AVAILABLE TESTS:"
|
echo " AVAILABLE TESTS:"
|
||||||
echo " 1 - main"
|
echo " obj - data objects"
|
||||||
echo " 2 - tooltest"
|
echo " btc - bitcoin"
|
||||||
echo " 3 - gentest"
|
echo " btc_tn - bitcoin testnet"
|
||||||
echo " 4 - regtest"
|
echo " btc_rt - bitcoin regtest"
|
||||||
|
echo " bch - bitcoin cash (BCH)"
|
||||||
|
echo " bch_rt - bitcoin cash (BCH) regtest"
|
||||||
|
echo " ltc - litecoin"
|
||||||
|
echo " ltc_tn - litecoin testnet"
|
||||||
|
echo " ltc_rt - litecoin regtest"
|
||||||
|
echo " tool - tooltest (all supported coins)"
|
||||||
|
echo " gen - gentest (all supported coins)"
|
||||||
echo " By default, all tests are run"
|
echo " By default, all tests are run"
|
||||||
exit ;;
|
exit ;;
|
||||||
i) INSTALL_ONLY=1 ;;
|
i) INSTALL_ONLY=1 ;;
|
||||||
n) NO_INSTALL=1 ;;
|
n) NO_INSTALL=1 ;;
|
||||||
|
P) NO_PAUSE=1 ;;
|
||||||
t) TESTING=1 ;;
|
t) TESTING=1 ;;
|
||||||
*) exit ;;
|
*) exit ;;
|
||||||
esac
|
esac
|
||||||
|
|
@ -27,9 +38,15 @@ done
|
||||||
|
|
||||||
shift $((OPTIND-1))
|
shift $((OPTIND-1))
|
||||||
|
|
||||||
set -e
|
RED="\e[31;1m" GREEN="\e[32;1m" YELLOW="\e[33;1m" RESET="\e[0m"
|
||||||
GREEN="\e[32;1m" YELLOW="\e[33;1m" RESET="\e[0m"
|
|
||||||
BRANCH=$1; shift
|
BRANCH=$1; shift
|
||||||
|
BRANCHES=$(git branch)
|
||||||
|
FOUND_BRANCH=$(for b in ${BRANCHES/\*}; do [ "$b" == "$BRANCH" ] && echo ok; done)
|
||||||
|
[ "$FOUND_BRANCH" ] || { echo "Branch '$BRANCH' not found!"; exit; }
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
REFDIR=test/ref
|
REFDIR=test/ref
|
||||||
if uname -a | grep -qi mingw; then SUDO='' MINGW=1; else SUDO='sudo' MINGW=''; fi
|
if uname -a | grep -qi mingw; then SUDO='' MINGW=1; else SUDO='sudo' MINGW=''; fi
|
||||||
|
|
||||||
|
|
@ -56,27 +73,112 @@ function install {
|
||||||
[ "$MINGW" ] && ./setup.py build --compiler=mingw32
|
[ "$MINGW" ] && ./setup.py build --compiler=mingw32
|
||||||
eval "$SUDO ./setup.py install"
|
eval "$SUDO ./setup.py install"
|
||||||
}
|
}
|
||||||
[ -z "$TESTING" ] && LS='\n'
|
|
||||||
function do_test {
|
function do_test {
|
||||||
set +x
|
set +x
|
||||||
for i in "$@"; do
|
for i in "$@"; do
|
||||||
|
LS='\n'
|
||||||
|
[ "$TESTING" ] && LS=''
|
||||||
|
echo $i | grep -q 'gentest' && LS=''
|
||||||
echo -e "$LS${GREEN}Running:$RESET $YELLOW$i$RESET"
|
echo -e "$LS${GREEN}Running:$RESET $YELLOW$i$RESET"
|
||||||
[ "$TESTING" ] || eval "$i"
|
[ "$TESTING" ] || eval "$i" || { echo -e $RED'Test failed!'$RESET; exit; }
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
i_obj='Data objects'
|
||||||
|
s_obj='Testing data objects'
|
||||||
|
t_obj=(
|
||||||
|
'test/objtest.py --coin=btc -S'
|
||||||
|
'test/objtest.py --coin=btc --testnet=1 -S'
|
||||||
|
'test/objtest.py --coin=ltc -S'
|
||||||
|
'test/objtest.py --coin=ltc --testnet=1 -S')
|
||||||
|
f_obj='Data object test complete'
|
||||||
|
|
||||||
T1=('test/test.py -On'
|
i_btc='Bitcoin mainnet'
|
||||||
|
s_btc='The bitcoin (mainnet) daemon must both be running for the following tests'
|
||||||
|
t_btc=(
|
||||||
|
'test/test.py -On'
|
||||||
'test/test.py -On --segwit dfl_wallet main ref ref_other'
|
'test/test.py -On --segwit dfl_wallet main ref ref_other'
|
||||||
'test/test.py -On --coin=bch dfl_wallet main ref ref_other'
|
'test/test.py -On --segwit-random dfl_wallet main'
|
||||||
'test/test.py -On --segwit-random dfl_wallet main')
|
'test/tooltest.py rpc'
|
||||||
T2=('test/tooltest.py' 'test/tooltest.py --testnet=1') # tooltest tests both segwit and non-segwit
|
"scripts/compute-file-chksum.py $REFDIR/*testnet.rawtx >/dev/null 2>&1")
|
||||||
T3=("test/gentest.py -q 2 $REFDIR/btcwallet.dump"
|
f_btc='You may stop the bitcoin (mainnet) daemon if you wish'
|
||||||
"test/gentest.py -q --testnet=1 2 $REFDIR/btcwallet-testnet.dump"
|
|
||||||
|
i_btc_tn='Bitcoin testnet'
|
||||||
|
s_btc_tn='The bitcoin testnet daemon must both be running for the following tests'
|
||||||
|
t_btc_tn=(
|
||||||
|
'test/test.py -On --testnet=1'
|
||||||
|
'test/test.py -On --testnet=1 --segwit dfl_wallet main ref ref_other'
|
||||||
|
'test/test.py -On --testnet=1 --segwit-random dfl_wallet main'
|
||||||
|
'test/tooltest.py --testnet=1 rpc')
|
||||||
|
f_btc_tn='You may stop the bitcoin testnet daemon if you wish'
|
||||||
|
|
||||||
|
i_btc_rt='Bitcoin regtest'
|
||||||
|
s_btc_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
|
||||||
|
t_btc_rt=('test/test.py -On regtest')
|
||||||
|
f_btc_rt="Regtest (Bob and Alice) mode tests for BTC completed"
|
||||||
|
|
||||||
|
i_bch='Bitcoin cash (BCH)'
|
||||||
|
s_bch='The bitcoin cash daemon (Bitcoin ABC) must both be running for the following tests'
|
||||||
|
t_bch=('test/test.py -On --coin=bch dfl_wallet main ref ref_other')
|
||||||
|
f_bch='You may stop the Bitcoin ABC daemon if you wish'
|
||||||
|
|
||||||
|
i_bch_rt='Bitcoin cash (BCH) regtest'
|
||||||
|
s_bch_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
|
||||||
|
t_bch_rt=('test/test.py --coin=bch -On regtest')
|
||||||
|
f_bch_rt="Regtest (Bob and Alice) mode tests for BCH completed"
|
||||||
|
|
||||||
|
i_ltc='Litecoin'
|
||||||
|
s_ltc='The litecoin daemon must both be running for the following tests'
|
||||||
|
t_ltc=(
|
||||||
|
'test/test.py --coin=ltc -On dfl_wallet main'
|
||||||
|
'test/test.py --coin=ltc --segwit -On dfl_wallet main'
|
||||||
|
'test/test.py --coin=ltc --segwit-random -On dfl_wallet main'
|
||||||
|
'test/tooltest.py --coin=ltc rpc'
|
||||||
|
)
|
||||||
|
f_ltc='You may stop the litecoin daemon if you wish'
|
||||||
|
|
||||||
|
i_ltc_tn='Litecoin testnet'
|
||||||
|
s_ltc_tn='The litecoin testnet daemon must both be running for the following tests'
|
||||||
|
t_ltc_tn=(
|
||||||
|
'test/test.py --coin=ltc -On --testnet=1'
|
||||||
|
'test/test.py --coin=ltc -On --testnet=1 --segwit dfl_wallet main ref ref_other'
|
||||||
|
'test/test.py --coin=ltc -On --testnet=1 --segwit-random dfl_wallet main'
|
||||||
|
'test/tooltest.py --coin=ltc --testnet=1 rpc')
|
||||||
|
f_ltc_tn='You may stop the litecoin testnet daemon if you wish'
|
||||||
|
|
||||||
|
i_ltc_rt='Litecoin regtest'
|
||||||
|
s_ltc_rt="The following tests will test MMGen's regtest (Bob and Alice) mode"
|
||||||
|
t_ltc_rt=('test/test.py --coin=ltc -On regtest')
|
||||||
|
f_ltc_rt="Regtest (Bob and Alice) mode tests for LTC completed"
|
||||||
|
|
||||||
|
i_tool='Tooltest'
|
||||||
|
s_tool='The following tests will run test/tooltest.py for all supported coins'
|
||||||
|
t_tool=(
|
||||||
|
'test/tooltest.py --coin=btc util'
|
||||||
|
'test/tooltest.py --coin=btc cryptocoin'
|
||||||
|
'test/tooltest.py --coin=btc mnemonic'
|
||||||
|
'test/tooltest.py --coin=ltc util'
|
||||||
|
'test/tooltest.py --coin=ltc cryptocoin'
|
||||||
|
'test/tooltest.py --coin=ltc mnemonic'
|
||||||
|
)
|
||||||
|
f_tool="tooltest tests completed"
|
||||||
|
|
||||||
|
i_gen='Gentest'
|
||||||
|
s_gen='The following tests will run test/gentest.py on mainnet and testnet for all supported coins'
|
||||||
|
t_gen=(
|
||||||
|
"test/gentest.py -q 2 $REFDIR/btcwallet.dump"
|
||||||
'test/gentest.py -q 1:2 10'
|
'test/gentest.py -q 1:2 10'
|
||||||
'test/gentest.py -q --segwit 1:2 10'
|
'test/gentest.py -q --segwit 1:2 10'
|
||||||
# "scripts/tx-old2new.py -S $REFDIR/tx_*raw >/dev/null 2>&1"
|
"test/gentest.py -q --testnet=1 2 $REFDIR/btcwallet-testnet.dump"
|
||||||
"scripts/compute-file-chksum.py $REFDIR/*testnet.rawtx >/dev/null 2>&1")
|
'test/gentest.py -q --testnet=1 1:2 10'
|
||||||
T4=('test/test.py -On regtest')
|
'test/gentest.py -q --testnet=1 --segwit 1:2 10'
|
||||||
|
"test/gentest.py -q --coin=ltc 2 $REFDIR/litecoin/ltcwallet.dump"
|
||||||
|
'test/gentest.py -q --coin=ltc 1:2 10'
|
||||||
|
'test/gentest.py -q --coin=ltc --segwit 1:2 10'
|
||||||
|
"test/gentest.py -q --coin=ltc --testnet=1 2 $REFDIR/litecoin/ltcwallet-testnet.dump"
|
||||||
|
'test/gentest.py -q --coin=ltc --testnet=1 1:2 10'
|
||||||
|
'test/gentest.py -q --coin=ltc --testnet=1 --segwit 1:2 10'
|
||||||
|
)
|
||||||
|
f_gen="gentest tests completed"
|
||||||
|
|
||||||
[ -d .git -a -z "$NO_INSTALL" -a -z "$TESTING" ] && {
|
[ -d .git -a -z "$NO_INSTALL" -a -z "$TESTING" ] && {
|
||||||
check
|
check
|
||||||
|
|
@ -85,26 +187,24 @@ T4=('test/test.py -On regtest')
|
||||||
}
|
}
|
||||||
[ "$INSTALL_ONLY" ] && exit
|
[ "$INSTALL_ONLY" ] && exit
|
||||||
|
|
||||||
|
function skip_maybe {
|
||||||
|
echo -n "Enter 's' to skip, or ENTER to continue: "; read
|
||||||
|
[ "$REPLY" == 's' ] && return 0
|
||||||
|
return 1
|
||||||
|
}
|
||||||
function run_tests {
|
function run_tests {
|
||||||
for t in $1; do
|
for t in $1; do
|
||||||
[ $t == 4 ] && LS=''
|
eval echo -e \${GREEN}'###' Running $(echo \$i_$t) tests\$RESET
|
||||||
eval "do_test \"\${T$t[@]}\""
|
[ "$PAUSE" ] && { eval echo $(echo \$s_$t); skip_maybe && continue; }
|
||||||
|
# echo RUNNING
|
||||||
|
eval "do_test \"\${t_$t[@]}\""
|
||||||
|
eval echo -e \$GREEN$(echo \$f_$t)\$RESET
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ "$*" ]; then
|
tests=$dfl_tests
|
||||||
run_tests "$*"
|
[ "$*" ] && tests="$*"
|
||||||
else
|
[ "$NO_PAUSE" ] || PAUSE=1
|
||||||
echo 'Bitcoin and Bitcoin ABC must both be running for the following tests'
|
run_tests "$tests"
|
||||||
echo 'The bitcoin-abc daemon must be listening on RPC port 8442 (-rpcport 8442)'
|
|
||||||
echo -n 'Hit ENTER to continue: '; read
|
|
||||||
run_tests '1'
|
|
||||||
echo 'The bitcoin (mainnet) and testnet daemons must both be running for the following tests'
|
|
||||||
echo -n 'Hit ENTER to continue: '; read
|
|
||||||
run_tests '2 3'
|
|
||||||
echo 'You may stop the mainnet and testnet daemons now'
|
|
||||||
echo -n 'Hit ENTER to continue: '; read
|
|
||||||
run_tests '4'
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "$LS${GREEN}All OK$RESET"
|
echo -e "${GREEN}All OK$RESET"
|
||||||
|
|
|
||||||
|
|
@ -19,4 +19,4 @@ except:
|
||||||
def yellow(s): return '{e}[33;1m{}{e}[0m'.format(s,e='\033')
|
def yellow(s): return '{e}[33;1m{}{e}[0m'.format(s,e='\033')
|
||||||
sys.stdout.write('{}{}'.format(yellow(''.join(l)),red(exc)))
|
sys.stdout.write('{}{}'.format(yellow(''.join(l)),red(exc)))
|
||||||
traceback.print_exc(file=f)
|
traceback.print_exc(file=f)
|
||||||
|
sys.exit(1)
|
||||||
|
|
|
||||||
32
setup.py
32
setup.py
|
|
@ -97,7 +97,7 @@ setup(
|
||||||
author_email = g.email,
|
author_email = g.email,
|
||||||
url = g.proj_url,
|
url = g.proj_url,
|
||||||
license = 'GNU GPL v3',
|
license = 'GNU GPL v3',
|
||||||
platforms = 'Linux, MS Windows, Raspberry Pi',
|
platforms = 'Linux, MS Windows, Raspberry Pi/Raspbian, Orange Pi/Armbian',
|
||||||
keywords = g.keywords,
|
keywords = g.keywords,
|
||||||
cmdclass = { 'build_ext': my_build_ext, 'install_data': my_install_data },
|
cmdclass = { 'build_ext': my_build_ext, 'install_data': my_install_data },
|
||||||
ext_modules = [module1],
|
ext_modules = [module1],
|
||||||
|
|
@ -149,20 +149,20 @@ setup(
|
||||||
'mmgen.share.Opts',
|
'mmgen.share.Opts',
|
||||||
],
|
],
|
||||||
scripts=[
|
scripts=[
|
||||||
'mmgen-addrgen',
|
'cmds/mmgen-addrgen',
|
||||||
'mmgen-keygen',
|
'cmds/mmgen-keygen',
|
||||||
'mmgen-passgen',
|
'cmds/mmgen-passgen',
|
||||||
'mmgen-addrimport',
|
'cmds/mmgen-addrimport',
|
||||||
'mmgen-passchg',
|
'cmds/mmgen-passchg',
|
||||||
'mmgen-regtest',
|
'cmds/mmgen-regtest',
|
||||||
'mmgen-walletchk',
|
'cmds/mmgen-walletchk',
|
||||||
'mmgen-walletconv',
|
'cmds/mmgen-walletconv',
|
||||||
'mmgen-walletgen',
|
'cmds/mmgen-walletgen',
|
||||||
'mmgen-txcreate',
|
'cmds/mmgen-txcreate',
|
||||||
'mmgen-txbump',
|
'cmds/mmgen-txbump',
|
||||||
'mmgen-txsign',
|
'cmds/mmgen-txsign',
|
||||||
'mmgen-txsend',
|
'cmds/mmgen-txsend',
|
||||||
'mmgen-txdo',
|
'cmds/mmgen-txdo',
|
||||||
'mmgen-tool'
|
'cmds/mmgen-tool'
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
test/gentest.py: Bitcoin key/address generation tests for the MMGen suite
|
test/gentest.py: Cryptocoin key/address generation tests for the MMGen suite
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys,os
|
import sys,os
|
||||||
|
|
@ -56,8 +56,8 @@ EXAMPLES:
|
||||||
{prog} 2 1000
|
{prog} 2 1000
|
||||||
(test speed of secp256k1 library address generation, 1000 rounds)
|
(test speed of secp256k1 library address generation, 1000 rounds)
|
||||||
{prog} 2 my.dump
|
{prog} 2 my.dump
|
||||||
(compare addrs generated with secp256k1 library to bitcoind wallet dump)
|
(compare addrs generated with secp256k1 library to {dn} wallet dump)
|
||||||
""".format(prog='gentest.py',pnm=g.proj_name,snum=rounds)
|
""".format(prog='gentest.py',pnm=g.proj_name,snum=rounds,dn=g.proto.daemon_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
|
sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
|
||||||
|
|
|
||||||
|
|
@ -92,36 +92,39 @@ class MMGenPexpect(object):
|
||||||
atexit.register(lambda: os.system('stty sane'))
|
atexit.register(lambda: os.system('stty sane'))
|
||||||
NL = '\n'
|
NL = '\n'
|
||||||
|
|
||||||
def __init__(self,name,mmgen_cmd,cmd_args,desc,no_output=False,passthru_args=[]):
|
def __init__(self,name,mmgen_cmd,cmd_args,desc,no_output=False,passthru_args=[],msg_only=False):
|
||||||
cmd_args = ['--{}{}'.format(k.replace('_','-'),
|
cmd_args = ['--{}{}'.format(k.replace('_','-'),
|
||||||
'='+getattr(opt,k) if getattr(opt,k) != True else ''
|
'='+getattr(opt,k) if getattr(opt,k) != True else ''
|
||||||
) for k in passthru_args if getattr(opt,k)] \
|
) for k in passthru_args if getattr(opt,k)] \
|
||||||
+ ['--data-dir='+os.path.join('test','data_dir')] + cmd_args
|
+ ['--data-dir='+os.path.join('test','data_dir')] + cmd_args
|
||||||
cmd = (('./','')[bool(opt.system)]+mmgen_cmd,'python')[g.platform=='win']
|
|
||||||
args = (cmd_args,[mmgen_cmd]+cmd_args)[g.platform=='win']
|
if g.platform == 'win': cmd,args = 'python',[mmgen_cmd]+cmd_args
|
||||||
|
else: cmd,args = mmgen_cmd,cmd_args
|
||||||
|
|
||||||
for i in args:
|
for i in args:
|
||||||
if type(i) not in (str,unicode):
|
if type(i) not in (str,unicode):
|
||||||
m1 = 'Error: missing input files in cmd line?:'
|
m1 = 'Error: missing input files in cmd line?:'
|
||||||
m2 = '\nName: {}\nCmd: {}\nCmd args: {}'
|
m2 = '\nName: {}\nCmd: {}\nCmd args: {}'
|
||||||
die(2,(m1+m2).format(name,cmd,args))
|
die(2,(m1+m2).format(name,cmd,args))
|
||||||
|
|
||||||
if opt.popen_spawn:
|
if opt.popen_spawn:
|
||||||
args = [("'"+a+"'" if ' ' in a else a) for a in args]
|
args = [(a,"'{}'".format(a))[' ' in a] for a in args]
|
||||||
cmd_str = '{} {}'.format(cmd,' '.join(args))
|
|
||||||
if opt.popen_spawn:
|
cmd_str = '{} {}'.format(cmd,' '.join(args)).replace('\\','/')
|
||||||
cmd_str = cmd_str.replace('\\','/')
|
|
||||||
|
|
||||||
if opt.log:
|
if opt.log:
|
||||||
log_fd.write(cmd_str+'\n')
|
log_fd.write(cmd_str+'\n')
|
||||||
|
|
||||||
if opt.verbose or opt.print_cmdline or opt.exact_output:
|
if opt.verbose or opt.print_cmdline or opt.exact_output:
|
||||||
clr1,clr2,eol = ((green,cyan,'\n'),(nocolor,nocolor,' '))[bool(opt.print_cmdline)]
|
clr1,clr2,eol = ((green,cyan,'\n'),(nocolor,nocolor,' '))[bool(opt.print_cmdline)]
|
||||||
sys.stderr.write(green('Testing: {}\n'.format(desc)))
|
sys.stderr.write(green('Testing: {}\n'.format(desc)))
|
||||||
|
if not msg_only:
|
||||||
sys.stderr.write(clr1('Executing {}{}'.format(clr2(cmd_str),eol)))
|
sys.stderr.write(clr1('Executing {}{}'.format(clr2(cmd_str),eol)))
|
||||||
else:
|
else:
|
||||||
m = 'Testing %s: ' % desc
|
m = 'Testing %s: ' % desc
|
||||||
msg_r(m)
|
msg_r(m)
|
||||||
|
|
||||||
if mmgen_cmd == '': return
|
if msg_only: return
|
||||||
|
|
||||||
if opt.direct_exec:
|
if opt.direct_exec:
|
||||||
msg('')
|
msg('')
|
||||||
|
|
@ -135,6 +138,7 @@ class MMGenPexpect(object):
|
||||||
if opt.traceback:
|
if opt.traceback:
|
||||||
cmd,args = g.traceback_cmd,[cmd]+args
|
cmd,args = g.traceback_cmd,[cmd]+args
|
||||||
cmd_str = g.traceback_cmd + ' ' + cmd_str
|
cmd_str = g.traceback_cmd + ' ' + cmd_str
|
||||||
|
# Msg('\ncmd_str: {}'.format(cmd_str))
|
||||||
if opt.popen_spawn:
|
if opt.popen_spawn:
|
||||||
self.p = PopenSpawn(cmd_str)
|
self.p = PopenSpawn(cmd_str)
|
||||||
else:
|
else:
|
||||||
|
|
@ -143,6 +147,7 @@ class MMGenPexpect(object):
|
||||||
|
|
||||||
def ok(self,exit_val=0):
|
def ok(self,exit_val=0):
|
||||||
ret = self.p.wait()
|
ret = self.p.wait()
|
||||||
|
# Msg('expect: {} got: {}'.format(exit_val,ret))
|
||||||
if ret != exit_val:
|
if ret != exit_val:
|
||||||
die(1,red('test.py: spawned program exited with value {}'.format(ret)))
|
die(1,red('test.py: spawned program exited with value {}'.format(ret)))
|
||||||
if opt.profile: return
|
if opt.profile: return
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,7 @@ def run_test(test,arg,input_data):
|
||||||
die(2,red('{}'.format(e[0])))
|
die(2,red('{}'.format(e[0])))
|
||||||
|
|
||||||
r32,r24,r16,r17,r18 = os.urandom(32),os.urandom(24),os.urandom(16),os.urandom(17),os.urandom(18)
|
r32,r24,r16,r17,r18 = os.urandom(32),os.urandom(24),os.urandom(16),os.urandom(17),os.urandom(18)
|
||||||
|
tw_pfx = g.proto.base_coin.lower()+':'
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
tests = OrderedDict([
|
tests = OrderedDict([
|
||||||
|
|
@ -114,14 +115,15 @@ tests = OrderedDict([
|
||||||
'bad': ('-3.2','0.123456789',123L,'123L',22000000,20999999.12345678),
|
'bad': ('-3.2','0.123456789',123L,'123L',22000000,20999999.12345678),
|
||||||
'good': (('20999999.12345678',Decimal('20999999.12345678')),)
|
'good': (('20999999.12345678',Decimal('20999999.12345678')),)
|
||||||
}),
|
}),
|
||||||
('BTCAddr', {
|
('CoinAddr', {
|
||||||
'bad': (1,'x','я'),
|
'bad': (1,'x','я'),
|
||||||
'good': (
|
'good': {
|
||||||
'1MjjELEy6EJwk8fSNfpS8b5teFRo4X5fZr',
|
'btc': (('1MjjELEy6EJwk8fSNfpS8b5teFRo4X5fZr','32GiSWo9zJQgkCmjAaLRrbPwXhKry2jHhj'),
|
||||||
'32GiSWo9zJQgkCmjAaLRrbPwXhKry2jHhj',
|
('n2FgXPKwuFkCXF946EnoxWJDWF2VwQ6q8J','2MspvWFjBbkv2wzQGqhxJUYPCk3Y2jMaxLN')),
|
||||||
'n2FgXPKwuFkCXF946EnoxWJDWF2VwQ6q8J',
|
'ltc': (('LXYx4j8PDGE8GEwDFnEQhcLyHFGsRxSJwt','MEnuCzUGHaQx9fK5WYvLwR1NK4SAo8HmSr'),
|
||||||
'2MspvWFjBbkv2wzQGqhxJUYPCk3Y2jMaxLN'
|
('n2D3joAy3yE5fqxUeCp38X6uPUcVn7EFw9','QN59YbnHsPQcbKWSq9PmTpjrhBnHGQqRmf'))
|
||||||
)}),
|
}[g.coin.lower()][bool(g.testnet)]
|
||||||
|
}),
|
||||||
('SeedID', {
|
('SeedID', {
|
||||||
'bad': (
|
'bad': (
|
||||||
{'sid':'я'},
|
{'sid':'я'},
|
||||||
|
|
@ -138,8 +140,8 @@ tests = OrderedDict([
|
||||||
'good': (('F00BAA12:99','F00BAA12:L:99'),'F00BAA12:L:99','F00BAA12:S:99')
|
'good': (('F00BAA12:99','F00BAA12:L:99'),'F00BAA12:L:99','F00BAA12:S:99')
|
||||||
}),
|
}),
|
||||||
('TwMMGenID', {
|
('TwMMGenID', {
|
||||||
'bad': ('x','я','я:я',1,'f00f00f','a:b','x:L:3','F00BAA12:0','F00BAA12:Z:99','btc:','btc:я'),
|
'bad': ('x','я','я:я',1,'f00f00f','a:b','x:L:3','F00BAA12:0','F00BAA12:Z:99',tw_pfx,tw_pfx+'я'),
|
||||||
'good': (('F00BAA12:99','F00BAA12:L:99'),'F00BAA12:L:99','F00BAA12:S:9999999','btc:x')
|
'good': (('F00BAA12:99','F00BAA12:L:99'),'F00BAA12:L:99','F00BAA12:S:9999999',tw_pfx+'x')
|
||||||
}),
|
}),
|
||||||
('TwComment', {
|
('TwComment', {
|
||||||
'bad': ('я',"comment too long for tracking wallet",),
|
'bad': ('я',"comment too long for tracking wallet",),
|
||||||
|
|
@ -147,12 +149,12 @@ tests = OrderedDict([
|
||||||
}),
|
}),
|
||||||
('TwLabel', {
|
('TwLabel', {
|
||||||
'bad': ('x x','x я','я:я',1,'f00f00f','a:b','x:L:3','F00BAA12:0 x',
|
'bad': ('x x','x я','я:я',1,'f00f00f','a:b','x:L:3','F00BAA12:0 x',
|
||||||
'F00BAA12:Z:99','F00BAA12:L:99 я','btc: x','btc:я x'),
|
'F00BAA12:Z:99','F00BAA12:L:99 я',tw_pfx+' x',tw_pfx+'я x'),
|
||||||
'good': (
|
'good': (
|
||||||
('F00BAA12:99 a comment','F00BAA12:L:99 a comment'),
|
('F00BAA12:99 a comment','F00BAA12:L:99 a comment'),
|
||||||
'F00BAA12:L:99 comment',
|
'F00BAA12:L:99 comment',
|
||||||
'F00BAA12:S:9999999 comment',
|
'F00BAA12:S:9999999 comment',
|
||||||
'btc:x comment')
|
tw_pfx+'x comment')
|
||||||
}),
|
}),
|
||||||
('HexStr', {
|
('HexStr', {
|
||||||
'bad': (1,[],'\0','\1','я','g','gg','FF','f00'),
|
'bad': (1,[],'\0','\1','я','g','gg','FF','f00'),
|
||||||
|
|
@ -162,18 +164,22 @@ tests = OrderedDict([
|
||||||
'bad': (1,[],'\0','\1','я','g','gg','FF','f00','F00F0012'),
|
'bad': (1,[],'\0','\1','я','g','gg','FF','f00','F00F0012'),
|
||||||
'good': ('DEADBE','F00BAA')
|
'good': ('DEADBE','F00BAA')
|
||||||
}),
|
}),
|
||||||
('BitcoinTxID',{
|
('CoinTxID',{
|
||||||
'bad': (1,[],'\0','\1','я','g','gg','FF','f00','F00F0012',hexlify(r16),hexlify(r32)+'ee'),
|
'bad': (1,[],'\0','\1','я','g','gg','FF','f00','F00F0012',hexlify(r16),hexlify(r32)+'ee'),
|
||||||
'good': (hexlify(r32),)
|
'good': (hexlify(r32),)
|
||||||
}),
|
}),
|
||||||
('WifKey', {
|
('WifKey', {
|
||||||
'bad': (1,[],'\0','\1','я','g','gg','FF','f00',hexlify(r16),'2MspvWFjBbkv2wzQGqhxJUYPCk3Y2jMaxLN'),
|
'bad': (1,[],'\0','\1','я','g','gg','FF','f00',hexlify(r16),'2MspvWFjBbkv2wzQGqhxJUYPCk3Y2jMaxLN'),
|
||||||
'good': (
|
'good': {
|
||||||
'5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb',
|
'btc': (('5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb',
|
||||||
'KwWr9rDh8KK5TtDa3HLChEvQXNYcUXpwhRFUPc5uSNnMtqNKLFhk',
|
'KwWr9rDh8KK5TtDa3HLChEvQXNYcUXpwhRFUPc5uSNnMtqNKLFhk'),
|
||||||
{'arg':'93HsQEpH75ibaUJYi3QwwiQxnkW4dUuYFPXZxcbcKds7XrqHkY6','testnet':True},
|
('93HsQEpH75ibaUJYi3QwwiQxnkW4dUuYFPXZxcbcKds7XrqHkY6',
|
||||||
{'arg':'cMsqcmDYZP1LdKgqRh9L4ZRU9br28yvdmTPwW2YQwVSN9aQiMAoR','testnet':True}
|
'cMsqcmDYZP1LdKgqRh9L4ZRU9br28yvdmTPwW2YQwVSN9aQiMAoR')),
|
||||||
)
|
'ltc': (('6udBAGS6B9RfGyvEQDkVDsWy3Kqv9eTULqtEfVkJtTJyHdLvojw',
|
||||||
|
'T7kCSp5E71jzV2zEJW4q5qU1SMB5CSz8D9VByxMBkamv1uM3Jjca'),
|
||||||
|
('936Fd4qs3Zy2ZiYHH7vZ3UpT23KtCAiGiG2xBTkjHo7jE9aWA2f',
|
||||||
|
'cQY3EumdaSNuttvDSUuPdiMYLyw8aVmYfFqxo9kdPuWbJBN4Ny66'))
|
||||||
|
}[g.coin.lower()][bool(g.testnet)]
|
||||||
}),
|
}),
|
||||||
('PubKey', {
|
('PubKey', {
|
||||||
'bad': ({'arg':1,'compressed':False},{'arg':'F00BAA12','compressed':False},),
|
'bad': ({'arg':1,'compressed':False},{'arg':'F00BAA12','compressed':False},),
|
||||||
|
|
@ -181,15 +187,24 @@ tests = OrderedDict([
|
||||||
}),
|
}),
|
||||||
('PrivKey', {
|
('PrivKey', {
|
||||||
'bad': ({'wif':1},),
|
'bad': ({'wif':1},),
|
||||||
'good': (
|
'good': ({
|
||||||
{'wif':'5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb',
|
'btc': (({'wif':'5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb',
|
||||||
'ret':'e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c'},
|
'ret':'e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c'},
|
||||||
{'wif':'KwWr9rDh8KK5TtDa3HLChEvQXNYcUXpwhRFUPc5uSNnMtqNKLFhk',
|
{'wif':'KwWr9rDh8KK5TtDa3HLChEvQXNYcUXpwhRFUPc5uSNnMtqNKLFhk',
|
||||||
'ret':'08d0ed83b64b68d56fa064be48e2385060ed205be2b1e63cd56d218038c3a05f'},
|
'ret':'08d0ed83b64b68d56fa064be48e2385060ed205be2b1e63cd56d218038c3a05f'}),
|
||||||
{'wif':'93HsQEpH75ibaUJYi3QwwiQxnkW4dUuYFPXZxcbcKds7XrqHkY6','testnet':True,
|
({'wif':'93HsQEpH75ibaUJYi3QwwiQxnkW4dUuYFPXZxcbcKds7XrqHkY6',
|
||||||
'ret':'e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c'},
|
'ret':'e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c'},
|
||||||
{'wif':'cMsqcmDYZP1LdKgqRh9L4ZRU9br28yvdmTPwW2YQwVSN9aQiMAoR','testnet':True,
|
{'wif':'cMsqcmDYZP1LdKgqRh9L4ZRU9br28yvdmTPwW2YQwVSN9aQiMAoR',
|
||||||
'ret':'08d0ed83b64b68d56fa064be48e2385060ed205be2b1e63cd56d218038c3a05f'},
|
'ret':'08d0ed83b64b68d56fa064be48e2385060ed205be2b1e63cd56d218038c3a05f'})),
|
||||||
|
'ltc': (({'wif':'6ufJhtQQiRYA3w2QvDuXNXuLgPFp15i3HR1Wp8An2mx1JnhhJAh',
|
||||||
|
'ret':'470a974ffca9fca1299b706b09142077bea3acbab6d6480b87dbba79d5fd279b'},
|
||||||
|
{'wif':'T41Fm7J3mtZLKYPMCLVSFARz4QF8nvSDhLAfW97Ds56Zm9hRJgn8',
|
||||||
|
'ret':'1c6feab55a4c3b4ad1823d4ecacd1565c64228c01828cf44fb4db1e2d82c3d56'}),
|
||||||
|
({'wif':'92iqzh6NqiKawyB1ronw66YtEHrU4rxRJ5T4aHniZqvuSVZS21f',
|
||||||
|
'ret':'95b2aa7912550eacdd3844dcc14bee08ce7bc2434ad4858beb136021e945afeb'},
|
||||||
|
{'wif':'cSaJAXBAm9ooHpVJgoxqjDG3AcareFy29Cz8mhnNTRijjv2HLgta',
|
||||||
|
'ret':'94fa8b90c11fea8fb907c9376b919534b0a75b9a9621edf71a78753544b4101c'})),
|
||||||
|
}[g.coin.lower()][bool(g.testnet)],
|
||||||
{'s':r32,'compressed':False,'ret':hexlify(r32)},
|
{'s':r32,'compressed':False,'ret':hexlify(r32)},
|
||||||
{'s':r32,'compressed':True,'ret':hexlify(r32)}
|
{'s':r32,'compressed':True,'ret':hexlify(r32)}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
6
test/ref/99BE60-BCH[106.6789].rawtx
Normal file
6
test/ref/99BE60-BCH[106.6789].rawtx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
8332a9
|
||||||
|
BCH MAINNET 99BE60 106.6789 20171025_141647 434
|
||||||
|
02000000030a30fe391cc62f8a94a43a51b581cf176a8567431fcb01318cd1005757d0168d0000000000ffffffff0a30fe391cc62f8a94a43a51b581cf176a8567431fcb01318cd1005757d0168d0100000000ffffffff0a30fe391cc62f8a94a43a51b581cf176a8567431fcb01318cd1005757d0168d0200000000ffffffff0400e40b54020000001976a9149316cedacbfcc41e63bdb903b43ae5157654044188ac30051d21000000001976a914b57ca511b3e5b54a7ec936b0fc6b44f595a7cffe88ac48126028090000001976a9142794da6d30fc6adced41313ac3bfab900096b44488ac202cb206000000001976a914cd88336614e1d3dadf2b1e9c5b598e28c8ffcf1288ac00000000
|
||||||
|
[{'confs': 1, 'scriptPubKey': '76a9149b1a59d1411e2678a707b371e8e805bbacdde32288ac', 'addr': '1F97Jd89wwmu4ELadesAdGDzg3d8Y6j5iP', 'vout': 0, 'txid': '8d16d0575700d18c3101cb1f4367856a17cf81b5513aa4948a2fc61c39fe300a', 'mmid': '98831F3A:C:1', 'amt': BCHAmt('100'), 'label': u''}, {'confs': 1, 'scriptPubKey': '76a914abe58e1e45f6176910a4c1ac1ee62328d5cc4fd588ac', 'addr': '1GfuYaKHrhdiVybXMGCcjadSgfjvpdt2x9', 'vout': 1, 'txid': '8d16d0575700d18c3101cb1f4367856a17cf81b5513aa4948a2fc61c39fe300a', 'mmid': '98831F3A:L:2', 'amt': BCHAmt('200'), 'label': u''}, {'confs': 1, 'scriptPubKey': '76a914bba3993079ccdf40c9bbbe495473f0b3d2dc5eec88ac', 'addr': '1J79LtWctedRLnMfFNRgzzSFsozQqDeoKD', 'vout': 2, 'txid': '8d16d0575700d18c3101cb1f4367856a17cf81b5513aa4948a2fc61c39fe300a', 'mmid': '98831F3A:L:3', 'amt': BCHAmt('199.9999416'), 'label': u''}]
|
||||||
|
[{'is_chg': True, 'addr': '14cHfz8dixc3GL3HFZEbicjinguTkaL1BJ', 'amt': BCHAmt('393.3209044'), 'mmid': '98831F3A:L:4'}, {'mmid': '98831F3A:C:5', 'amt': BCHAmt('5.5555'), 'addr': '1HYcdCFPmWakX2g8mP6ksxDDokDyRbeaAb'}, {'mmid': '98831F3A:C:3', 'amt': BCHAmt('1.1234'), 'addr': '1KjkgipjAyTR8Lc6Xrr8RoeSeiYXaExKHo'}, {'addr': '1EQjevrVa3XWzSnRq7cyc1D7wnNXsHx73x', 'amt': BCHAmt('100')}]
|
||||||
|
TvwWgaAnrkQFpAxxjBa4PHvJ8NsJDsurtiv2HuzdnXWjQmY7LHyt6PZn5J7BNtB5VzHtBG7bUosCAMFon8yxUe2mYTZoH9e6dpoAz9E6JDZtUNYz9YnF1Z3jFND1X89RuKAk6YVBrfWseeyHR8vZDdaFzBPK5SPos
|
||||||
6
test/ref/99BE60-BCH[106.6789].testnet.rawtx
Normal file
6
test/ref/99BE60-BCH[106.6789].testnet.rawtx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
a27973
|
||||||
|
BCH TESTNET 99BE60 106.6789 20171025_141647 434
|
||||||
|
02000000030a30fe391cc62f8a94a43a51b581cf176a8567431fcb01318cd1005757d0168d0000000000ffffffff0a30fe391cc62f8a94a43a51b581cf176a8567431fcb01318cd1005757d0168d0100000000ffffffff0a30fe391cc62f8a94a43a51b581cf176a8567431fcb01318cd1005757d0168d0200000000ffffffff0400e40b54020000001976a9149316cedacbfcc41e63bdb903b43ae5157654044188ac30051d21000000001976a914b57ca511b3e5b54a7ec936b0fc6b44f595a7cffe88ac48126028090000001976a9142794da6d30fc6adced41313ac3bfab900096b44488ac202cb206000000001976a914cd88336614e1d3dadf2b1e9c5b598e28c8ffcf1288ac00000000
|
||||||
|
[{'confs': 1, 'addr': 'muf4bgD8kyD9qLpCMDqYTBSKY3DqTWZR92', 'vout': 0, 'label': u'', 'mmid': '98831F3A:C:1', 'txid': '8d16d0575700d18c3101cb1f4367856a17cf81b5513aa4948a2fc61c39fe300a', 'amt': BCHAmt('100'), 'scriptPubKey': '76a9149b1a59d1411e2678a707b371e8e805bbacdde32288ac'}, {'confs': 1, 'addr': 'mwBrqdQGfj4yH6594qAzZVqmYfLdmB1C7W', 'vout': 1, 'label': u'', 'mmid': '98831F3A:L:2', 'txid': '8d16d0575700d18c3101cb1f4367856a17cf81b5513aa4948a2fc61c39fe300a', 'amt': BCHAmt('200'), 'scriptPubKey': '76a914abe58e1e45f6176910a4c1ac1ee62328d5cc4fd588ac'}, {'confs': 1, 'addr': 'mxd6dwbbhg4g7tqGxwQ4pueajob7kjWHBG', 'vout': 2, 'label': u'', 'mmid': '98831F3A:L:3', 'txid': '8d16d0575700d18c3101cb1f4367856a17cf81b5513aa4948a2fc61c39fe300a', 'amt': BCHAmt('199.9999416'), 'scriptPubKey': '76a914bba3993079ccdf40c9bbbe495473f0b3d2dc5eec88ac'}]
|
||||||
|
[{'addr': 'mj8Ey3DcXz3J3SWty8CyYXx3egWAapMVMF', 'mmid': '98831F3A:L:4', 'amt': BCHAmt('393.3209044'), 'is_chg': True}, {'mmid': '98831F3A:C:5', 'addr': 'mx4ZvFLNaY21J99kUx58hsRYfjpgPYwpHn', 'amt': BCHAmt('5.5555')}, {'mmid': '98831F3A:C:3', 'addr': 'mzFhymuhyztfuT5iFRpWFirmWi9EWMrybs', 'amt': BCHAmt('1.1234')}, {'addr': 'mtvgwywUP4xmmZG3YgbMRvRSomyEh3ShVb', 'amt': BCHAmt('100')}]
|
||||||
|
TvwWgaAnrkQFpAxxjBa4PHvJ8NsJDsurtiv2HuzdnXWjQmY7LHyt6PZn5J7BNtB5VzHtBG7bUosCAMFon8yxUe2mYTZoH9e6dpoAz9E6JDZtUNYz9YnF1Z3jFND1X89RuKAk6YVBrfWseeyHR8vZDdaFzBPK5SPos
|
||||||
6
test/ref/litecoin/75F455-LTC[106.6789].rawtx
Normal file
6
test/ref/litecoin/75F455-LTC[106.6789].rawtx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
2622b7
|
||||||
|
LTC MAINNET 75F455 106.6789 20171018_182153 434
|
||||||
|
0200000003040e4755ff327ef5edb98aa488a9de7dcd266f4f7faba383aadb502743eb8fc50200000000ffffffff040e4755ff327ef5edb98aa488a9de7dcd266f4f7faba383aadb502743eb8fc50100000000ffffffff040e4755ff327ef5edb98aa488a9de7dcd266f4f7faba383aadb502743eb8fc50000000000ffffffff0400e40b54020000001976a9141e43586462a093ef77a140f7fc33bc9b62b8ea4688ac30051d21000000001976a914f9c129a968a4242f6c29da9cc77dd2fc9408481488ac202cb2060000000017a914f17c92d1a9ca42be1b2bd5293f1e638d457b05848768bda6927d0000001976a914b2da98c692ed20d7d155ca8c13284bf55f7988a988ac00000000
|
||||||
|
[{'confs': 1, 'scriptPubKey': '76a914cb4067ff77f8133dc20790c702a994006a2f956c88ac', 'addr': 'LdkebBKVXSs6NNoPJWGM8KciDnL8LhXXjb', 'vout': 2, 'txid': 'c58feb432750dbaa83a3ab7f4f6f26cd7ddea988a48ab9edf57e32ff55470e04', 'mmid': '98831F3A:C:1', 'amt': LTCAmt('100'), 'label': u''}, {'confs': 1, 'scriptPubKey': '76a9142fad2fae2cee09645766da65a5566e9e49dc305688ac', 'addr': 'LPa3VRMf4aYfd85bq2LyN69KfzV6KBJ9gy', 'vout': 1, 'txid': 'c58feb432750dbaa83a3ab7f4f6f26cd7ddea988a48ab9edf57e32ff55470e04', 'mmid': '98831F3A:L:2', 'amt': LTCAmt('200'), 'label': u''}, {'confs': 1, 'scriptPubKey': 'a9143d6021a6448977d93cbff97f884b10506ff625d987', 'addr': 'MDVgcTj9JGFE8xbSwprg7D9zgSRCBzr3CC', 'vout': 0, 'txid': 'c58feb432750dbaa83a3ab7f4f6f26cd7ddea988a48ab9edf57e32ff55470e04', 'mmid': '98831F3A:S:2', 'amt': LTCAmt('5199.99799'), 'label': u''}]
|
||||||
|
[{'is_chg': True, 'addr': 'LbXeQjFrukhFJr1nmiUx8UftEQ3DszF3Ud', 'amt': LTCAmt('5393.31313'), 'mmid': '98831F3A:L:4'}, {'mmid': '98831F3A:C:5', 'amt': LTCAmt('5.5555'), 'addr': 'LhzXvAJzRhmn1s8nGSxsajF9wdS6c8AXhA'}, {'mmid': '98831F3A:S:3', 'amt': LTCAmt('1.1234'), 'addr': 'MVv2KBRyRT4Kpf6AokKwZAWmrRvM1DP5K9'}, {'addr': 'LMyyDCZWLnThGrCYcUM4SZdeTBEgV95odE', 'amt': LTCAmt('100')}]
|
||||||
|
TvwWgaAnrkQFpAxxjBa4PHvJ8NsJDsurtiv2HuzdnXWjQmY7LHyt6PZn5J7BNtB5VzHtBG7bUosCAMFon8yxUe2mYTZoH9e6dpoAz9E6JDZtUNYz9YnF1Z3jFND1X89RuKAk6YVBrfWseeyHR8vZDdaFzBPK5SPos
|
||||||
6
test/ref/litecoin/75F455-LTC[106.6789].testnet.rawtx
Normal file
6
test/ref/litecoin/75F455-LTC[106.6789].testnet.rawtx
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
68c9ce
|
||||||
|
LTC TESTNET 75F455 106.6789 20171018_182153 434
|
||||||
|
0200000003040e4755ff327ef5edb98aa488a9de7dcd266f4f7faba383aadb502743eb8fc50200000000ffffffff040e4755ff327ef5edb98aa488a9de7dcd266f4f7faba383aadb502743eb8fc50100000000ffffffff040e4755ff327ef5edb98aa488a9de7dcd266f4f7faba383aadb502743eb8fc50000000000ffffffff0400e40b54020000001976a9141e43586462a093ef77a140f7fc33bc9b62b8ea4688ac30051d21000000001976a914f9c129a968a4242f6c29da9cc77dd2fc9408481488ac202cb2060000000017a914f17c92d1a9ca42be1b2bd5293f1e638d457b05848768bda6927d0000001976a914b2da98c692ed20d7d155ca8c13284bf55f7988a988ac00000000
|
||||||
|
[{'confs': 1, 'addr': 'mz3ed26eFp4HtgaqqwFRgDmGsZZZAHbryP', 'vout': 2, 'label': u'', 'mmid': '98831F3A:C:1', 'txid': 'c58feb432750dbaa83a3ab7f4f6f26cd7ddea988a48ab9edf57e32ff55470e04', 'amt': LTCAmt('100'), 'scriptPubKey': '76a914cb4067ff77f8133dc20790c702a994006a2f956c88ac'}, {'confs': 1, 'addr': 'mjs3XG8onwjs9Rs4NTL3uzHtKmiX5bCH6Y', 'vout': 1, 'label': u'', 'mmid': '98831F3A:L:2', 'txid': 'c58feb432750dbaa83a3ab7f4f6f26cd7ddea988a48ab9edf57e32ff55470e04', 'amt': LTCAmt('200'), 'scriptPubKey': '76a9142fad2fae2cee09645766da65a5566e9e49dc305688ac'}, {'confs': 1, 'addr': 'QSCWVL7SyhxEgRi99BXDzDLHiUUjn5cGSJ', 'vout': 0, 'label': u'', 'mmid': '98831F3A:S:2', 'txid': 'c58feb432750dbaa83a3ab7f4f6f26cd7ddea988a48ab9edf57e32ff55470e04', 'amt': LTCAmt('5199.99799'), 'scriptPubKey': 'a9143d6021a6448977d93cbff97f884b10506ff625d987'}]
|
||||||
|
[{'addr': 'mwpeSa31e7tSq9oFK9U2gNpStBGeeKfNC4', 'mmid': '98831F3A:L:4', 'amt': LTCAmt('5393.31313'), 'is_chg': True}, {'mmid': '98831F3A:C:5', 'addr': 'n4HXx169A4xyYAvEoswx8dPibQfXPpjAMT', 'amt': LTCAmt('5.5555')}, {'mmid': '98831F3A:S:3', 'addr': 'QicrC3pH6tmLN8Cs16zVSAh4tTytgRSCTZ', 'amt': LTCAmt('1.1234')}, {'addr': 'miGyF3Lf59eto9z19uL8zTnD6xU7Kbiumg', 'amt': LTCAmt('100')}]
|
||||||
|
TvwWgaAnrkQFpAxxjBa4PHvJ8NsJDsurtiv2HuzdnXWjQmY7LHyt6PZn5J7BNtB5VzHtBG7bUosCAMFon8yxUe2mYTZoH9e6dpoAz9E6JDZtUNYz9YnF1Z3jFND1X89RuKAk6YVBrfWseeyHR8vZDdaFzBPK5SPos
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# MMGen address file
|
||||||
|
#
|
||||||
|
# This file is editable.
|
||||||
|
# Everything following a hash symbol '#' is a comment and ignored by MMGen.
|
||||||
|
# A text label of 32 characters or less may be added to the right of each
|
||||||
|
# address, and it will be appended to the bitcoind wallet label upon import.
|
||||||
|
# The label may contain any printable ASCII symbol.
|
||||||
|
# Address data checksum for 98831F3A-LTC-S[1,31-33,500-501,1010-1011]: 63DF E42A 0827 21C3
|
||||||
|
# Record this value to a secure location.
|
||||||
|
98831F3A LTC:SEGWIT {
|
||||||
|
1 MQrY3vEbqKMBgegXrSaR93R2HoTDE5bKrY
|
||||||
|
31 MAEgtjR8MLZpLMqPaBjjas1HM3Rri119Uq
|
||||||
|
32 MDdv4ofTTdTQXZLFkBkgt2aotrdytaChyK
|
||||||
|
33 MAbECS5tZdVMQ5Fc3U3zW1ZLLvY7bY3mJU
|
||||||
|
500 MH7eBQ6tSzMWL41m9T2h96H3RaVpXYRLDF
|
||||||
|
501 MG6NdJs9iQkV9TLWTy4wahNXK8pMSkpFve
|
||||||
|
1010 MEzfeSh1kBmSDKAKQZoEm6293RCdFunhP3
|
||||||
|
1011 MBVCP3kQEZr2NwBQWHY6dP72sYkh9rRmWt
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# MMGen address file
|
||||||
|
#
|
||||||
|
# This file is editable.
|
||||||
|
# Everything following a hash symbol '#' is a comment and ignored by MMGen.
|
||||||
|
# A text label of 32 characters or less may be added to the right of each
|
||||||
|
# address, and it will be appended to the bitcoind wallet label upon import.
|
||||||
|
# The label may contain any printable ASCII symbol.
|
||||||
|
# Address data checksum for 98831F3A-LTC-S[1,31-33,500-501,1010-1011]: 1A3F 3016 2E2B F33A
|
||||||
|
# Record this value to a secure location.
|
||||||
|
98831F3A LTC:SEGWIT {
|
||||||
|
1 QdZMvncuWm4CE7oE3oEy23bKKqWktQ4ySr
|
||||||
|
31 QNwWmboS2nGpspx5mYQHTsBaP5VQPqGVP5
|
||||||
|
32 QSLjwg3m95AR52SwwYREm2m6vthXbvMewN
|
||||||
|
33 QPJ45JUCF5CMwYNJEpiYP1jdNxbfFNXQ2Z
|
||||||
|
500 QVpU4GVC8S4WsX8TLohF26TLTcZNB5qxLw
|
||||||
|
501 QUoCWBFTPrTVgvTCfKjVThYpMAsuDqcQ4m
|
||||||
|
1010 QThVXK5KRdUSknH1bvTne6CS5TGAqpTbR2
|
||||||
|
1011 QQC2Fv8hv1Z2vQJ6heCeWPHKuapErrdaQs
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
# MMGen address file
|
||||||
|
#
|
||||||
|
# This file is editable.
|
||||||
|
# Everything following a hash symbol '#' is a comment and ignored by MMGen.
|
||||||
|
# A text label of 32 characters or less may be added to the right of each
|
||||||
|
# address, and it will be appended to the bitcoind wallet label upon import.
|
||||||
|
# The label may contain any printable ASCII symbol.
|
||||||
|
# Address data checksum for 98831F3A-LTC[1,31-33,500-501,1010-1011]: AD52 C3FE 8924 AAF0
|
||||||
|
# Record this value to a secure location.
|
||||||
|
98831F3A LTC {
|
||||||
|
1 LMxB474SVfxeYdqxNrM1WZDZMnifteSMv1
|
||||||
|
31 LhpYfXVizZFun9RRrqTdmiAie2Q5YqaRRm
|
||||||
|
32 LYvg2DDKxwZ6FBb6XcpsSK14swAMFLzH1h
|
||||||
|
33 LSkSht1wZs38gichnoznmXaMw4apJUDPMG
|
||||||
|
500 LWNJGQVj3MBWwVFJV6tGZUhZpPTUUxJJJi
|
||||||
|
501 LN6qgkswJRzbJzc4xz5EKNUHVzhFiXR8B4
|
||||||
|
1010 LKQhgeRuhz8ULXSnh5ctkroJocGDrXkQau
|
||||||
|
1011 LfCFQM5qHS324enT5eYXvPrv8UKKV4yT1A
|
||||||
|
}
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,19 @@
|
||||||
|
# MMGen address file
|
||||||
|
#
|
||||||
|
# This file is editable.
|
||||||
|
# Everything following a hash symbol '#' is a comment and ignored by MMGen.
|
||||||
|
# A text label of 32 characters or less may be added to the right of each
|
||||||
|
# address, and it will be appended to the bitcoind wallet label upon import.
|
||||||
|
# The label may contain any printable ASCII symbol.
|
||||||
|
# Address data checksum for 98831F3A-LTC[1,31-33,500-501,1010-1011]: 5738 5C4F 167C F9AE
|
||||||
|
# Record this value to a secure location.
|
||||||
|
98831F3A LTC {
|
||||||
|
1 miFB5wqbE39r4wdQvHL64TN81Zx6kRnocA
|
||||||
|
31 n47YhNGsivT7JTCtQGSiKcKHHodWFSFtnK
|
||||||
|
32 muDg43zUhJkHmVNZ53owzD9dXiPn2FEJN9
|
||||||
|
33 mo3Sjio6JEELD2QALEysKRivaqpF53jWfu
|
||||||
|
500 mrfJJFGsmiNiTo2m2XsM7Nr8UAguLqz6Rk
|
||||||
|
501 miPqibf62oBnqJPXWR4JsGcr9mvgSoF8hc
|
||||||
|
1010 mfhhiVD4SMKfrqEFEWbyJkwsTPVegXra11
|
||||||
|
1011 n1VFSBrz1oEDaxZud5XcUJ1UnFYkMScbVj
|
||||||
|
}
|
||||||
Binary file not shown.
109
test/ref/litecoin/ltcwallet-testnet.dump
Normal file
109
test/ref/litecoin/ltcwallet-testnet.dump
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
# Wallet dump created by Litecoin v0.15.0.1-ba8ed3a
|
||||||
|
# * Created on 2017-10-09T12:59:12Z
|
||||||
|
# * Best block at time of backup was 0 (4966625a4b2851d9fdee139e56211a0d88575f59ed816ff5e6a63deb4e3e29a0),
|
||||||
|
# mined on 2017-02-13T01:29:26Z
|
||||||
|
|
||||||
|
# extended private masterkey: tprv8ZgxMBicQKsPf1UGSRxdjZvEHMWSWDDeg6b5amVfK6fh4ZPh42poHYokt7MWrkXNK6Wi9EMti4qxaCa5e9Sxy7qRnTM6dxqhk2KBpJ9ENmy
|
||||||
|
|
||||||
|
cTfSojVNo6NkKNenUxGt5JH2DRawPE76Y38kmHZmoeQ5vN18aZZF 2017-10-06T18:38:08Z reserve=1 # addr=mfX1U3pcHtpHtuj34be5YZe56DPsXBLdur hdkeypath=m/0'/0'/222'
|
||||||
|
cRnMYZCrfvuE68kHhsB4x2pPd4E6vUaZd1HMWPjioCXb5ZhV22ue 2017-10-06T18:38:08Z reserve=1 # addr=mfYWSEdAJcPd7n2R8y2HPzDT1XADTTNGht hdkeypath=m/0'/0'/257'
|
||||||
|
cMsvBz2sARkgEDwTRhGZx9io1zKkFtRyYrurWPzbyMQxyqFkSYYs 2017-10-06T18:38:08Z reserve=1 # addr=mfZqvi6LYB4QcVConShKuj8NAPsnd2ES6B hdkeypath=m/0'/0'/696'
|
||||||
|
cVweKrF9XGW4gVWrpZeZzL33NvuWnczMiHgAxcA717VXfe4HdxRd 2017-10-06T18:38:08Z reserve=1 # addr=mfbP1YxYbwnX3NkzspMy5U6YahjLhjWhaa hdkeypath=m/0'/0'/396'
|
||||||
|
cMyeJtrVqyJXoU8GJnwuuSGFacUC5Gn3EKaA5Wc9wdrowvgiWpxS 2017-10-06T18:38:08Z reserve=1 # addr=mfeErXpgjYZJ4PTKpgiAYRCCQU1sDT66Rg hdkeypath=m/0'/0'/895'
|
||||||
|
cTfU4XoRLthDPKb6ZXSXVPyWqwA3MVRt8RpUdF4pQF7oogivq3hp 2017-10-06T18:38:08Z reserve=1 # addr=mfm3cgPQ6tWwWWvube8efobFD1xupv6zZq hdkeypath=m/0'/0'/526'
|
||||||
|
cPf63HwQ9BUPP8JXGAQvr7y7VfjuSQWRKGPuXPZB2Qc9GmAvkkLT 2017-10-06T18:38:08Z reserve=1 # addr=mfm9T6jghZjE3hvbujA6ARWYg4fFaRPTiT hdkeypath=m/0'/0'/675'
|
||||||
|
cUJVqBZB9GGCkfJP33VmJvNCf6e17RNLxajFjdkwZnHEfkm6NZbt 2017-10-06T18:38:08Z reserve=1 # addr=mfmvf6PtusLPsi7j3rLVrLcEnSEtbXHdNf hdkeypath=m/0'/0'/424'
|
||||||
|
cVLXQhVMGATwSuapySzdmNVriHqFk3vMt2aQo58Xyh7cUDvtR2nr 2017-10-06T18:38:08Z reserve=1 # addr=mfnBqHA4N7YZ7UqhJPu8uioGvMuYqkNGkv hdkeypath=m/0'/0'/89'
|
||||||
|
cQvLdkqaxwAjtLtiw473HaMtk5zBrMPMevxs2YiZTB6LAPGdFnZB 2017-10-06T18:38:08Z reserve=1 # addr=mfnKMmMvScJyiotN15GZvVxjpEzv22fVLR hdkeypath=m/0'/0'/62'
|
||||||
|
cVkEz2t3uT7f3vjcCvouwyVfqigKuP75oZjskaQFNXCK5pzsyQqs 2017-10-06T18:38:08Z reserve=1 # addr=mfo9qxjcoyTCjiFxQQozj9vdwksRLA9YgG hdkeypath=m/0'/0'/244'
|
||||||
|
cT9mkrNuWNYuqkR9D4crVRidCv4NRw6WXtRwi8i39fZ1fYeuDPvC 2017-10-06T18:38:08Z reserve=1 # addr=mfoZyg1fJhgfRafjHYXgsYxJ7WUXPTER5E hdkeypath=m/0'/0'/24'
|
||||||
|
cT2Kky9Cz8d2pLG9GNQskyMWckLWhEeFe2tqsDxs5wD7rUSddp6N 2017-10-06T18:38:08Z reserve=1 # addr=mfpw3EYUF1ru8iUoTkdx5J7qoAsyvqRawA hdkeypath=m/0'/0'/554'
|
||||||
|
cNiR4SJ12Jxu4nucsQEvkrwFLb5K1Fbe5vKTBEHXfJChc2hwwxHH 2017-10-06T18:38:08Z reserve=1 # addr=mg139CQzWLQyffUrY7hLKxfaaXGhZdqmAG hdkeypath=m/0'/0'/68'
|
||||||
|
cNetpXFz3EUU3BoGGnY4Kh6Ue6ZfMYWmZ8D5c7gzustozqSupa2L 2017-10-06T18:38:08Z reserve=1 # addr=mg5EFMwUNwAZr1ndMBcyR5eJfetmVpBdJe hdkeypath=m/0'/0'/51'
|
||||||
|
cT4ckbsTWrLr175yKKTwHxJZPvHEBbdcGXckLBcwS3N1fGoLoM4v 2017-10-06T18:38:08Z reserve=1 # addr=mg5yt9VSeeXC7iQUibKrmxtoYmLh12GSjZ hdkeypath=m/0'/0'/629'
|
||||||
|
cUb6YbBcqLJ3ddd2duuEV7bX3VmwoFTsELLe7xCyXTeDMYEkBYj1 2017-10-06T18:38:08Z reserve=1 # addr=mg6fGXUDetRAWqGg2L1ZatRTcMJ9WSotRq hdkeypath=m/0'/0'/378'
|
||||||
|
cS28GCbKgyJcWqj5Buh5nkVHZnD38Bqzi7NZHDVJuPTH9nhdFMjx 2017-10-06T18:38:08Z reserve=1 # addr=mg6z4QrUdoBWAmy8UzNeHvEBnaRhF8g6p6 hdkeypath=m/0'/0'/226'
|
||||||
|
cNdZNpbWsSa7BavGfNHidcpMrzTouuDw2Wtg9VnFSrXHvjNHKipw 2017-10-06T18:38:08Z reserve=1 # addr=mg7MFPaR9nsi3BUP4doURSYeN5TcxgsXRN hdkeypath=m/0'/0'/274'
|
||||||
|
cNRzekgY99TzDimQf2W5T3AMSLRg4nyLeU3GFah25wUk191yc9Gk 2017-10-06T18:38:08Z reserve=1 # addr=mg7aup5gctnZRPyeihXWRzXWyPycHQ4b5Y hdkeypath=m/0'/0'/216'
|
||||||
|
cRMiqxj56cnzxKDYt4knEtPqQdzU6NjFrTWeZsBYT8sHyhMvRSvB 2017-10-06T18:38:08Z reserve=1 # addr=mg7bG1LV4mBf6x1RqFwUSb6qvgt1NP8LbL hdkeypath=m/0'/0'/504'
|
||||||
|
cQBNBhFRct6SZdjp7PoncfxEFhQthfJ9sYEG6u7squXNJ1t4h9SY 2017-10-06T18:38:08Z reserve=1 # addr=mgAMBGtbgFCkfa2dW7mxHGun4oR1BvPnDF hdkeypath=m/0'/0'/3'
|
||||||
|
cMssEYDdr42pyjqgeb6uKkQFJ6LaKH9W2LiCPwUCRx3AztkdgVuk 2017-10-06T18:38:08Z reserve=1 # addr=mgAoszngNFWxVQHL79RKtPzEuDuRVrFRnu hdkeypath=m/0'/0'/176'
|
||||||
|
cQUvFSm37naPrTVPJ1LJCVgQ1Eyh1WMktCZDaQSsAnkZ1D6gMJL1 2017-10-06T18:38:08Z reserve=1 # addr=mgDTVN4XfzYLFVfDua5BLLNzuSL73RYQkz hdkeypath=m/0'/0'/259'
|
||||||
|
cNS2cyU8bsaB7jnj48GJEepVtdSTEWKCirwCpCLYhL7mFCUGqjan 2017-10-06T18:38:08Z reserve=1 # addr=mgEik2XWL8xUudKnReZwXBka8CpHeLw6qm hdkeypath=m/0'/0'/307'
|
||||||
|
cSkjShf25EqD3cokhXLrnkgtshahs7EciMrei7UbYS7U15BepMRr 2017-10-06T18:38:08Z reserve=1 # addr=mgFL1XW5Qw3CTZoveqPafDUePJC6crVwmU hdkeypath=m/0'/0'/497'
|
||||||
|
cNuPKwwF3zH4SfTkfzdg7AJ7wkmMYqNjPyX239dFCkWvBkGU9gmH 2017-10-06T18:38:08Z reserve=1 # addr=mgLKcw7yCR3A7jNtFMY4po9pwur24N5BhQ hdkeypath=m/0'/0'/255'
|
||||||
|
cRpdQrXDm1XzznY6TiN7SByjnMua5YpzNfvHakcP85bU9MTBVayF 2017-10-06T18:38:08Z reserve=1 # addr=mgLvpJqR3VqwoGSA3GPDZs781PVWUNtCcX hdkeypath=m/0'/0'/109'
|
||||||
|
cUML63DH1xCELDRkVrhozLjbj53QpJQfaiPU5U6ixmq8mWMJLgBP 2017-10-06T18:38:08Z reserve=1 # addr=mgLzzWz5U9FKNZkTiSDCf4yDpF5kpVJuBy hdkeypath=m/0'/0'/552'
|
||||||
|
cSNToDVRd327zvzX95kxMUUdcBtAhJqVD7RqNTzNfpXZsFUkzmUf 2017-10-06T18:38:08Z reserve=1 # addr=mgN357wUpraQcwt6WueoMCb6tgmfiWEB5o hdkeypath=m/0'/0'/791'
|
||||||
|
cVkcpMaEgCAKhdfuFuVHVEcqFT8cfaX2XNt8SyfPpCg13mrS5R5c 2017-10-06T18:38:08Z reserve=1 # addr=mgPPDAmEkEYfCkWbKEFMbTjpLqRwAtwMgP hdkeypath=m/0'/0'/32'
|
||||||
|
cW6P7nFmP3J2Eznjx58SoDWMHRh1kAgPYdyaue9f9fbGVbrhwwty 2017-10-06T18:38:08Z reserve=1 # addr=mgQHtQ56kN6gebZdPDMMxqUFRxUwthb2fE hdkeypath=m/0'/0'/581'
|
||||||
|
cUn8WeGVJK9NQzGbUZtYcNpyxYx9W1uCFwCqtFtY4bY3UisPMPE7 2017-10-06T18:38:08Z reserve=1 # addr=mgRasyyxCWi1EncuL9gcrQstGd4W3EbS1u hdkeypath=m/0'/0'/144'
|
||||||
|
cNZQsndeUzGnDEnu8HpLViBiXhsbNFDyoTszgLhF1YmUnKEXdZ8g 2017-10-06T18:38:08Z reserve=1 # addr=mgRoMKgApf8EasvZ6JYyihqw8d3jcxFvuU hdkeypath=m/0'/0'/444'
|
||||||
|
cMw5zMQiJN3pvLVEJjm2z72juXRA6jmkNEnnbroZ95FJyi1QX1Xh 2017-10-06T18:38:08Z reserve=1 # addr=mgSiW4vzbdFuiFDL4rQUcDwQoLByMfgNGT hdkeypath=m/0'/0'/301'
|
||||||
|
cTKXALJSvLCCqmzWzmHpzPz6EJRynPbEu63BBJiVxZBLWFZs5Pxi 2017-10-06T18:38:08Z reserve=1 # addr=mgTPcsJ83L1htMUbQfBSXfSw8Vx5ZrP6TG hdkeypath=m/0'/0'/94'
|
||||||
|
cVkFPnZeCVo4zBuZnpCDqug5YQkBN3bEmof9PxDLY4fXcj2M8H2G 2017-10-06T18:38:08Z reserve=1 # addr=mgTR6hV2CSQbTzAvrqUym9j7zNQkiLrHpj hdkeypath=m/0'/0'/230'
|
||||||
|
cUZBhYFc93Y7CicHYBsmdMR451CVoE8t8KeYCavQugVzdkgZsw7V 2017-10-06T18:38:08Z reserve=1 # addr=mgTks6eVsZU4zfy3TW2i7sVM78dozmrrLC hdkeypath=m/0'/0'/100'
|
||||||
|
cV6CH598xFZNL5vkmKcTXQHkMXCSHU69TdFafjsXJCTrs8Jj27s5 2017-10-06T18:38:08Z reserve=1 # addr=mgTof2bXaavwDMQjcenpa8ofzYRijT8rw9 hdkeypath=m/0'/0'/398'
|
||||||
|
cVBetPPSNFjfG32rJt9SPfm5cuZ9fphrxMZW9NBKqt8RbECypTN1 2017-10-06T18:38:08Z reserve=1 # addr=mgUNCLtSsF622XiSw791NtH8ujKkMxJaEd hdkeypath=m/0'/0'/795'
|
||||||
|
cT16XS9Pz982p5xUsogbSTp2x6wVcXBVPeRm5HDHk7oyurRu2zER 2017-10-06T18:38:08Z reserve=1 # addr=mgXeNtXr1iJTycXZMoePT76C1Q35fWMZ7k hdkeypath=m/0'/0'/618'
|
||||||
|
cPLNphAvsS8HGgr5XA3wnRiQWP4DCbsGs5wrS6g8nTiNJoi6Yamk 2017-10-06T18:38:08Z reserve=1 # addr=mgXmaePXbdEY21MXP6ftmsqt1Sz3tP2aM7 hdkeypath=m/0'/0'/73'
|
||||||
|
cVEv6LGsWcQn3JV7KMJhSK855JuT76jxqzn76R7HUZNwxtnBuZjk 2017-10-06T18:38:08Z reserve=1 # addr=mgZyswAnDxVEHn6Jo344cDGEDYUEU58QTg hdkeypath=m/0'/0'/468'
|
||||||
|
cSmqnQAHd8kMjG89e2LyNyfa2ro7ofrEYfHoC9i3oPS2z8wXWAFo 2017-10-06T18:38:08Z reserve=1 # addr=mgb1Uxy9LXmjM4rCtpN4uMDxeZ5gEauWw8 hdkeypath=m/0'/0'/651'
|
||||||
|
cQbG6taFvNatqKDeHB8e1MkG9Rq2iGR2yLC5ZnkMbXEmacbj6AJf 2017-10-06T18:38:08Z reserve=1 # addr=mghNiS5QnBh739KMdWB1cC2VoAN7RXh7Hw hdkeypath=m/0'/0'/239'
|
||||||
|
cQ5jRv7wPFPwzzKSb4SNRSrMetQPpWBkuZ52At7xUuThfJuWoexC 2017-10-06T18:38:08Z reserve=1 # addr=mgpjL9oV3vRTNudLEjAArxVMVNW5oXY5tQ hdkeypath=m/0'/0'/796'
|
||||||
|
cRPaKxxu6LfNg6p45c7BosyxTLN2PZiNY2rSEocrPAJ4jty8yMxW 2017-10-06T18:38:08Z reserve=1 # addr=mgqSg9PqoKD476N89jFGxxjNj9vPUN5hqC hdkeypath=m/0'/0'/628'
|
||||||
|
cNbja8Pbp5exTeNrr37zC5RVLB5UK4XyY842sj6jHRuZiqeRBMLx 2017-10-06T18:38:08Z reserve=1 # addr=mgqeREKjbw3fyAioCcAtuh6GVGnGtVtAiW hdkeypath=m/0'/0'/102'
|
||||||
|
cPV7eRmWs6VSUXLEX92xTCQxn8hJVCTgxa1CJcwF35E6GPrzkMY5 2017-10-06T18:38:08Z reserve=1 # addr=mgrfRRfV2dQ2miVrHGRokzEdbsxfCCoM34 hdkeypath=m/0'/0'/291'
|
||||||
|
cRtAm5Qk7iqK78UPfLcndSCf338H89MCu4KAMVjyKLwSzf6TGxeu 2017-10-06T18:38:08Z reserve=1 # addr=mgrgHvL4DHYxaQvuptFr4bxSAUBaY9kAJw hdkeypath=m/0'/0'/260'
|
||||||
|
cVbVLSiixfo8xz4UMUmX1G1Hrrb8USj5RAVRoiPrk9GNf4yV1utB 2017-10-06T18:38:08Z reserve=1 # addr=mgrzZEa5nMioBknjxYryUthz4rMmNZ4MWn hdkeypath=m/0'/0'/192'
|
||||||
|
cQYc2XGRUYQikJay6k4pxGEsQ929TGdgNYNepaMK7NgK2os1vg7Z 2017-10-06T18:38:08Z reserve=1 # addr=mgvtmtwvyQTn4T8xoYPZL2TPTbGsWLeqjb hdkeypath=m/0'/0'/833'
|
||||||
|
cS36V4Tp94TWmg7B7bf9Znqyw2HebE2HEo98CR4nfpvGvFo3sUqn 2017-10-06T18:38:08Z reserve=1 # addr=mgxzJhL5ugWTyGnW5PAJJfT2TNtCK1PLpd hdkeypath=m/0'/0'/406'
|
||||||
|
cNGDfMkijnFLRQigTmtBvu2fGReQTHopNr2589BouR5gamDhFZjt 2017-10-06T18:38:08Z reserve=1 # addr=mgz8LY4EMFL1hVJaw86Fi17LZVcsfQetLq hdkeypath=m/0'/0'/543'
|
||||||
|
cRrToL4Z2s8VRJtnuHgxWePWA1usRqFpTzjKPbBy85q9n4kGC1wt 2017-10-06T18:38:08Z reserve=1 # addr=mgzv9sEw8ySEJRnSRBhad6JkjqrKdqbVEj hdkeypath=m/0'/0'/502'
|
||||||
|
cSZbkJGiskATiYif3g8n953ZTdnhPMYxE8NXBykEvVKfu76oV9PW 2017-10-06T18:38:08Z reserve=1 # addr=mh4RFcdrVMDd1crNHXPmQYG3VveonRoUcq hdkeypath=m/0'/0'/806'
|
||||||
|
cVqnXBMF762WgzQjJd6CHeaL2GsTi5kpDPiRSzSJ2NkPMGXJZ9Rc 2017-10-06T18:38:08Z reserve=1 # addr=mh5DjZxLMmcpGjDym64LMsGdrAMn6NQ5MP hdkeypath=m/0'/0'/886'
|
||||||
|
cVHXSMkEFQghd69Zi4zkzfvnC82G5mT4kGHrVb97UD3s2EjE6E4J 2017-10-06T18:38:08Z reserve=1 # addr=mh5npc3d29qnYcrkA7iNUvTmptSZ6YcHGU hdkeypath=m/0'/0'/268'
|
||||||
|
cSq5mnLWPWYYzbtSXYf5tU2aBkhpiBnHYARyxsG3rbBL4dfy1BzE 2017-10-06T18:38:08Z reserve=1 # addr=mh65LQWgKi86kFgjezT779DbPjhZpvHoBM hdkeypath=m/0'/0'/344'
|
||||||
|
cPfMmxPGbheNSJXz6DaExD6Htdtsd6Asw2wXS4bW2rrrTz3kJjwb 2017-10-06T18:38:08Z reserve=1 # addr=mh6wJ8cVhYFjx5xZ63UKPcreBRQFrFS9Ti hdkeypath=m/0'/0'/859'
|
||||||
|
cV8ZUQERgfoCg82Uawhx2V1EWQ6gEbcvqinRSb9G8tLDdyQXzZEp 2017-10-06T18:38:08Z reserve=1 # addr=mh9F16b4aK4m3QB3P42osopoBxoT1MZpoU hdkeypath=m/0'/0'/107'
|
||||||
|
cThVcaScHownY9Rp1uePnMpcVYpNsng4ohWGtsuHv3puR6MbMqPA 2017-10-06T18:38:08Z reserve=1 # addr=mhBhm6wR3JcCtaNwswjzCfBkuokiyjiXKv hdkeypath=m/0'/0'/262'
|
||||||
|
cN5Krz8kVvz5WpKoZPCX2wZwYq6NvathWEKP8jGLf4gkg5cHmwHV 2017-10-06T18:38:08Z reserve=1 # addr=mhCLaBXb7ga5NDoo4dj4oSBS5wgFQ5uRtH hdkeypath=m/0'/0'/16'
|
||||||
|
cUSXt2i38XpXSGoeuvvXPK6LqDyCo2Yh6sCPPZpzpyxVwK2dWgb2 2017-10-06T18:38:08Z reserve=1 # addr=mhCZPJhPjrsyw7JfomRfHaVX43bqAcnLxw hdkeypath=m/0'/0'/225'
|
||||||
|
cUV4az3NxVWKxyfowZh2BSRymZMSBrAqNgHuJK7kLN6EKJUNYmkh 2017-10-06T18:38:08Z reserve=1 # addr=mhDabQN6XUNtvrvUBubLqhaAwxu56eZSFo hdkeypath=m/0'/0'/678'
|
||||||
|
cRVbaPoEBq6NW5v4kHPX89N3249gDbWE4C4qFcszDrpSca5Lp1Hp 2017-10-06T18:38:08Z reserve=1 # addr=mhEmCCXdGbZk1htCuWA4gHK3DRYeNL3aZZ hdkeypath=m/0'/0'/735'
|
||||||
|
cSqU9gdqdHVveyG29aYU8EYactfzkw4KyJDn6BRomnyXHzLGb7VE 2017-10-06T18:38:08Z reserve=1 # addr=mhF2kyuEDfPoPpo4JCbnuCfz3bkSPjS2yh hdkeypath=m/0'/0'/152'
|
||||||
|
cTBKBgJa3MwchdNdBEXw6enFcPtgcSssqHxgaurL2cz1UbmhkVUb 2017-10-06T18:38:08Z reserve=1 # addr=mhGzk78bgBdTkEU7DqctoaqkR52a593tgo hdkeypath=m/0'/0'/60'
|
||||||
|
cT3AzXdCyioPULgK4J95FLNdN9zFEtCw9ZucukUXFbcDHrvWrfv7 2017-10-06T18:38:08Z reserve=1 # addr=mhHERGnyumYMrfEFEb75H1fYbeQmbKvS4Y hdkeypath=m/0'/0'/637'
|
||||||
|
cVra8SwAPA6qA7QuSBDCC1SL4fjpmgsomBMAG9uHVbzLTgHJKeiP 2017-10-06T18:38:08Z reserve=1 # addr=mhJDpv4hYqorcrcc4NTXPv9oyMgTU2KbZz hdkeypath=m/0'/0'/715'
|
||||||
|
cSN6aRqxSWyDxDasH8Jzb5nd3UbKG4SLyEv5yENaz3ozNiFYbNxG 2017-10-06T18:38:08Z reserve=1 # addr=mhKdKUezYQQsDguahbe35KN5KPKMAExQAQ hdkeypath=m/0'/0'/300'
|
||||||
|
cToYTpje3AQt3Z4k2LcUZo5cuREXhvH3strSNdKrjvqT7P6DqjN4 2017-10-06T18:38:08Z reserve=1 # addr=mhMjLs3vJWFvaQHktyGaMQEaGj6vo8kjkn hdkeypath=m/0'/0'/822'
|
||||||
|
cPq6pTEGXmUXJ6gC2ioFnheVgYj5KrztN1J2ysnxGa2aCciCU3Ju 2017-10-06T18:38:08Z reserve=1 # addr=mhNLzmDKqkCpj6rBXk4yrBPw9jnXCSQicw hdkeypath=m/0'/0'/149'
|
||||||
|
cNWTdjiujRKX48SNQAfvM66vcmifXtKjiD8ZdzKDCz1Nxkj3tCDA 2017-10-06T18:38:08Z reserve=1 # addr=mhPcRatBAatGxRs2eahHtNujMzKZKtmRWW hdkeypath=m/0'/0'/507'
|
||||||
|
cUYZc66fy718SkyUpaADt4xUnT7qkv4WEoC7idQ8xx9SdFvx8sir 2017-10-06T18:38:08Z reserve=1 # addr=mhQoDtUq5fLd3GnEgv9im7QaJUwQ7rHUx3 hdkeypath=m/0'/0'/303'
|
||||||
|
cRPstjfALMgyNJNPiC1APxuiEG22e5SwsVATp72sd3xqEtyqgrYT 2017-10-06T18:38:08Z reserve=1 # addr=mhWnFMD2MF4zqpdJA8L6pAfEu42kGc76Fe hdkeypath=m/0'/0'/39'
|
||||||
|
cVz8YtybDe28bZtdmify2HJ6AYabZasPUVsyQ7XYXkCmjReCEBzn 2017-10-06T18:38:08Z reserve=1 # addr=mhX4puoMkZDpu5SH1sLBPqvhNNHeUausG1 hdkeypath=m/0'/0'/508'
|
||||||
|
cQjzB7kepSdGJ9qjS6QJB6NL2zhqJQ7KeWH59ymqWWcmwz81DAEM 2017-10-06T18:38:08Z reserve=1 # addr=mhaPZQVYJR4QQsvBThZgucCiscrcMGfdZZ hdkeypath=m/0'/0'/288'
|
||||||
|
cPy6grSXRUAQtuT8PHU6TjegwEPjFcKMejiFWTwJNdXMeQT2b7LX 2017-10-06T18:38:08Z reserve=1 # addr=mhbHymfYiJfw8PRckLzyG9D9wbYpbkbadM hdkeypath=m/0'/0'/888'
|
||||||
|
cQRDMtRJEDZp3T2cecrvp3PczTC2MiwhJysYixAtmFBm9BhobZs6 2017-10-06T18:38:08Z reserve=1 # addr=mhbyBgjjcsLzqr18aLnm4SJLB3RJPkqLxP hdkeypath=m/0'/0'/34'
|
||||||
|
cQRCUYtuxgySsj4rrzK1Cbv5jARsUktopuiGwQZP3cANTWWhRY4q 2017-10-06T18:38:08Z reserve=1 # addr=mhc7mwmKcFEmFutEL6KVvKfAY85H8ab9Rc hdkeypath=m/0'/0'/131'
|
||||||
|
cTu1b1ArNMmRN6N1oP3iuSw8xBSseJ7VwL7yXny1kQ8T4HbfD7KY 2017-10-06T18:38:08Z reserve=1 # addr=mhdLJ9Y3R4FRfEXhLsjsHwU8NYC28JegYD hdkeypath=m/0'/0'/208'
|
||||||
|
cMcWpk8kzzo3p89DfHLdnJbbU5K3ngsHhLPKmCDP28nsaHZ7rQJ9 2017-10-06T18:38:08Z reserve=1 # addr=mhdr9ExNJ1vSXHLxn46S7uoGPU3RB2D68G hdkeypath=m/0'/0'/429'
|
||||||
|
cUhpH1YLBhbcfKmPTJ1mVq32MPiDQFa62b3rCYVGK8VaJzrD6hi6 2017-10-06T18:38:08Z reserve=1 # addr=mhgjf6iAtzEMd7mGEzPa3TWdBxGY8bEbG7 hdkeypath=m/0'/0'/707'
|
||||||
|
cMsEWFzfteWraya6Da3RruDXeEdEZZkdoFhmxH4TpSNtVa9fuH9J 2017-10-06T18:38:08Z reserve=1 # addr=mhhr6KADc9oj8m4FgqJbaAe728meyX1BBz hdkeypath=m/0'/0'/431'
|
||||||
|
cT1E4UEpASEpCrrRAZeuLZog7vjd9TyMxmau6SnWM6thTKwiTsYG 2017-10-06T18:38:08Z reserve=1 # addr=mhk2TwttzeZEtLZhAn54NkvdoEuZX9AKGW hdkeypath=m/0'/0'/813'
|
||||||
|
cNkfBu9SobpreBjurtYb4fbbMxJmW6MmBB5H5KETio7AmFtHEzNQ 2017-10-06T18:38:08Z reserve=1 # addr=mhkRZiANGD4EqQVh97YAPUmd8AqGaJ5GPM hdkeypath=m/0'/0'/708'
|
||||||
|
cTF7WKSXPR6WwNVGJRJyWadZyMVyT2spJejkYxk52HEsfsvjg9Sc 2017-10-06T18:38:08Z reserve=1 # addr=mhnjzKBkV42cfAWUFAjjcPnPHymTTBsR3g hdkeypath=m/0'/0'/283'
|
||||||
|
cT43AQFZFdk9BFAw5aNc4DkoKREeiX2Adj4ji3NcicFJvHNfAPkB 2017-10-06T18:38:08Z reserve=1 # addr=mhp2mWKm6g4K1b86mS5uBfcnHmnm85dBn8 hdkeypath=m/0'/0'/769'
|
||||||
|
cPR8QL82wnnbW5g7myRyoqxK9vorVNVh8xNmrYaBjFssUVWPErQo 2017-10-06T18:38:08Z reserve=1 # addr=mhq2wZqY3xpTAcsuzGbyTR2scqCfJQ26Wa hdkeypath=m/0'/0'/538'
|
||||||
|
cSCf7a6e5uB9139xuEsEm9gqy31yx21z6XYoQaRRmocUDNaHU8Rh 2017-10-06T18:38:08Z reserve=1 # addr=mhtqcYx5s499XB6HNkPCQxtpHid1SixEUD hdkeypath=m/0'/0'/133'
|
||||||
|
cRNUNFn4YEZL68nWrheHzu5tjYVgRLtJCYYb6M8vfq6SEqvtkixd 2017-10-06T18:38:08Z reserve=1 # addr=mhvR3xfh97yAsVYVEpqVTVkNwWLpTJvPsi hdkeypath=m/0'/0'/824'
|
||||||
|
cS7fG5HSkrdmmEnxakNpqBHh474315cqTq5S9niLNQksV9fVPceQ 2017-10-06T18:38:08Z reserve=1 # addr=mhwSjr4NQBwYA1USCa6bjgjx4tAYafQUrM hdkeypath=m/0'/0'/302'
|
||||||
|
cNzBs84ZsLXjadLaXQhWogYXAD4aR4GoEwmtEoCt4fvhGLg48JBe 2017-10-06T18:38:08Z reserve=1 # addr=mhxASedzwYXLu92G3cxrRTeMibWFYTjMt4 hdkeypath=m/0'/0'/442'
|
||||||
|
cVqDxQzEKngKSdhrMXN2jdwVdXHVfmRrEPUaMVoHpSsHHtAu9hDq 2017-10-06T18:38:08Z reserve=1 # addr=mhxRUM62F1fGHbf6jNfq7EDF6u5Wh2nTGt hdkeypath=m/0'/0'/511'
|
||||||
|
cV5KziDW15nPKNjrVvKcL5TBhBvUmfMAwxdLDwc3Nsoxb13aakBD 2017-10-06T18:38:08Z reserve=1 # addr=mi31DZm2CcRVaWDZvcfj5KCWiv1RNJfx4g hdkeypath=m/0'/0'/47'
|
||||||
|
cQMvLZHkWVa6pKJ1RNf1N32gEg4LAMcz7cQ3GGeFXfcXvmUUYDna 2017-10-06T18:38:08Z reserve=1 # addr=mi3ZmmmRYDDvsTqFqqB3Yyqd3uCKb7BoBe hdkeypath=m/0'/0'/191'
|
||||||
|
cVQUJrZa3W8XxAuYJ9F5ogRNMqX4Npp4CZ9oN819uEQe1dTPn64B 2017-10-06T18:38:08Z reserve=1 # addr=mi444dExNAN4ept7QLiepe48kBvfEpCw8r hdkeypath=m/0'/0'/551'
|
||||||
|
cRZu1UY6QYhwUJXEvqXRCLf82cZ2mPMEbwpAdyarpFEm7y7bqERu 2017-10-06T18:38:08Z reserve=1 # addr=mi6zJtwDmAaKTibtxjxrucTTVppT6DSM5B hdkeypath=m/0'/0'/584'
|
||||||
|
cPZN61KZaycb34VoQdfhbdMt8XkEDw7asTNruDUfJXk5TkC8vUdA 2017-10-06T18:38:08Z reserve=1 # addr=mi7MmAZMdvT9vLWnELBXxDBP2tqHxeqWJV hdkeypath=m/0'/0'/53'
|
||||||
|
|
||||||
|
# End of dump
|
||||||
109
test/ref/litecoin/ltcwallet.dump
Normal file
109
test/ref/litecoin/ltcwallet.dump
Normal file
|
|
@ -0,0 +1,109 @@
|
||||||
|
# Wallet dump created by Litecoin v0.15.0.1-ba8ed3a
|
||||||
|
# * Created on 2017-10-05T11:37:22Z
|
||||||
|
# * Best block at time of backup was 0 (12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2),
|
||||||
|
# mined on 2011-10-07T07:31:05Z
|
||||||
|
|
||||||
|
# extended private masterkey: xprv9s21ZrQH143K4HP5mfvPzp6QEGnH9GGAHam3sgePqEHMToeNqY7Mw9uEM3xCXJ2cx4wVEViaah6KAxVTBL7fY1DP6NbK9Pa3AobJykaf2zN
|
||||||
|
|
||||||
|
T3mRqHyhXtPaT7RXXoMDJrcFme16Ntyx6w5A137GKMWx7tNi38Ck 2017-10-04T14:48:08Z reserve=1 # addr=LKJhRSuJLsdoGKpEPbRrTYQGo87VgGQPvM hdkeypath=m/0'/0'/43'
|
||||||
|
T3Zsw6cXMEgrFShSqapn8ayRCJ1R1oCuvjZ6DuPz9TG8XF5UbTHj 2017-10-04T14:48:08Z reserve=1 # addr=LKLsbGqMKPdzh7pMvVNWuGJX5gPofppAbD hdkeypath=m/0'/0'/99'
|
||||||
|
T9URDhvjdAJfrMUXHgpxnoJYpdiR5iyMqwr6cEUZBoQpYqKqn5g4 2017-10-04T14:48:08Z reserve=1 # addr=LKNL1SpaTgi9p8mwLvxoebiVr3ohwpWJSX hdkeypath=m/0'/0'/39'
|
||||||
|
T7WJkRwxbxd4oL2qKYU1XUgDx3zEYv8k9BjWbZYdgZjAFhc1YFdi 2017-10-04T14:48:08Z reserve=1 # addr=LKZGfkQVjAejbKjHofKXfKA9SGBsa7wAEm hdkeypath=m/0'/0'/53'
|
||||||
|
T7AQVL71gr89SwnVDVn5yNU4fjygFVNcLXiwU5pzMSF6FsRAMxGK 2017-10-04T14:48:08Z reserve=1 # addr=LKf1KstrhKZ3RAAySvvbD6nEuAm6kwLR7N hdkeypath=m/0'/0'/69'
|
||||||
|
T56m5PFbjcRjvP9vgHnoEDJ5C1HhdtkGB5pnhNpMg5iUM8gfHyJC 2017-10-04T14:48:08Z reserve=1 # addr=LKwDjyntj2LiR4AAQBzfTi7yCsM9W3nWve hdkeypath=m/0'/0'/84'
|
||||||
|
T6yGBGAChKaw3ZDfgpqvJ96MxuhhVNA9vFsJo5JWDAmfoNrS4xGA 2017-10-04T14:48:08Z reserve=1 # addr=LL4j1hdNRgAx9SiyunD6hrEdUujYhesL6C hdkeypath=m/0'/0'/11'
|
||||||
|
T3KY6godaNL9Z1m6r9gzG1hNjzbBFbWbzQe7rhvW5hGACvxrW23K 2017-10-04T14:48:08Z reserve=1 # addr=LL8euS6Rc84mweN34mGdQJzVE2UqoQoeTM hdkeypath=m/0'/0'/123'
|
||||||
|
T8v3zxDnMqbLS37PGVYdskRrSC9J15HVSgGDzDZsfQMNnnfRAtRE 2017-10-04T14:48:08Z reserve=1 # addr=LL99t8n8Yg4HASJPUY9nhbUe6CsG1iHYc8 hdkeypath=m/0'/0'/5'
|
||||||
|
T3S2BNHng5E3xY93HtxJ6daHoru6MFJJrpKx8UZuC1kpEuyDysS4 2017-10-04T14:48:08Z reserve=1 # addr=LLL6q7ePmVkqZkJuvSnWLi9kNdH2AnGrPt hdkeypath=m/0'/0'/57'
|
||||||
|
T5aDFhYA18k3qMm5g3RtehRUcXS4knhRiii6mWDSeXwTfcMhRUNc 2017-10-04T14:48:08Z reserve=1 # addr=LLXpFAhNxtcDo2Sq6j8kFk3LdjGWheLCJh hdkeypath=m/0'/0'/20'
|
||||||
|
TA49m62urbcagKSsw3pNGkTykZYcwAC9we3X4kt3ytq7EqcgWh4X 2017-10-04T14:48:08Z reserve=1 # addr=LLbH361vNKhaVQHBkziwJF8rndsBpH1qYb hdkeypath=m/0'/0'/81'
|
||||||
|
T3im3y8SydGGNA2awtGvExhqif4Tzf4RZ2yqV6ir4ZtW7WbAz9AS 2017-10-04T14:48:08Z reserve=1 # addr=LLeGTXr1XfyRiVPBkzLXHXLKgo7uH5pqV3 hdkeypath=m/0'/0'/25'
|
||||||
|
TBGCREVwnB4v9H1bBJurDb48XDBqKt9RiSEykHN47PYcCr36NktV 2017-10-04T14:48:08Z reserve=1 # addr=LLmwq3ZDkjXgM666h9afpbq8KRUsP56172 hdkeypath=m/0'/0'/63'
|
||||||
|
T8TCk9XQMQdahnyDkNY219KReRbyfWF9WENxAUkFTzVSs3f5sWh8 2017-10-04T14:48:08Z reserve=1 # addr=LLpF4bkgF5dosaq77BhUkBySLHsv9qDo8q hdkeypath=m/0'/0'/113'
|
||||||
|
T7waGxxHek4U3hXPmidC3roZULxZb589hFRUhMTzaGb3CaBaeuad 2017-10-04T14:48:08Z reserve=1 # addr=LLwZW5cYjzEbz9YpGBDrG6SXTYtqazr5b8 hdkeypath=m/0'/0'/86'
|
||||||
|
T3ifYRMcXk8fZqwf7Ty2EoGpnzaRRfLGgi7HoKNztD2q9hkKMSpD 2017-10-04T14:48:08Z reserve=1 # addr=LM2nGfEGb51sehZu8gAFpBv81utuM6T8jc hdkeypath=m/0'/0'/68'
|
||||||
|
TAVsWRX5xJTDH3JhzRC38cNTmsqHHRDMcqgAJs19Kp3NShZwYnjR 2017-10-04T14:48:08Z reserve=1 # addr=LM9vyAVjR6ekRZMK8BbQUCmcoEN6ezTbAN hdkeypath=m/0'/0'/89'
|
||||||
|
T7FaiQJUjgkwRgnwHa2EnsFKs2jFNykkbcMzCzXTZSofw9tPyqwq 2017-10-04T14:48:08Z reserve=1 # addr=LMBdBR6t9WW9C5YjAV8bRkybNt5gYXxGLr hdkeypath=m/0'/0'/46'
|
||||||
|
T4SEJoanP7ogBLZ2ERBU1hqs21E9m6mTrc7qp7j9yJRixsSw2miJ 2017-10-04T14:48:08Z reserve=1 # addr=LMCZL9MpLjVGFjgyKn3BQiLKk23ptSVjaR hdkeypath=m/0'/0'/26'
|
||||||
|
T9gEdgebLLf4uT8e6kJDYvfJionHBEdbKoRUBfRBhr8o2Lqmph6m 2017-10-04T14:48:08Z reserve=1 # addr=LMGrgVHuaiToUvF4u5Pr2sfKerjnToNFKz hdkeypath=m/0'/0'/72'
|
||||||
|
T4CSAf5FcvHkjmXecXaSsYQXmjKKEtH7auw1EvnYWszG5R2BoiwF 2017-10-04T14:48:08Z reserve=1 # addr=LMLb5TTmGdwdYRw4HP7E9Rp6ukMiJDBou1 hdkeypath=m/0'/0'/3'
|
||||||
|
T5vKzHJWZS6TtQGtzm8oj86GX8tmDzgpNyYiZJDfBzFebqcXHze2 2017-10-04T14:48:08Z reserve=1 # addr=LMNy5QsrfXykYqwP5hHhJLRRF9XnMRMekK hdkeypath=m/0'/0'/70'
|
||||||
|
TBKGs27NR9oH7zU5fkJh5jSA7RZoEAbibvYTVLkNiR8ypteiLNK2 2017-10-04T14:48:08Z reserve=1 # addr=LMPznMFzgUNMs3LN1nu5m19QKDdm8XTZRR hdkeypath=m/0'/0'/109'
|
||||||
|
T5tEyU2pDbtDQrujf6RSAdEx2M3z9mdVtiwoMF4cxSGxD7ow8Hw5 2017-10-04T14:48:08Z reserve=1 # addr=LMbvsr3Cn3rFvP1vYMpaV1APYjGLnCesSe hdkeypath=m/0'/0'/47'
|
||||||
|
T7h9Hq5fRGW8wcVD3XVxDBQvQf9ox7WmW2NoSvPtRJAnusxVphET 2017-10-04T14:48:08Z reserve=1 # addr=LMch1stEinfMcVSoq3Ccf6TavAELL3CWVy hdkeypath=m/0'/0'/114'
|
||||||
|
T4XJ9EFG1rtXpqH46b7sd2L2VJgv7ijsHSg8e9NxmUePJEUWazqY 2017-10-04T14:48:08Z reserve=1 # addr=LMhw816b4tieZ24RSwsb8mv9SRJNDkDcRv hdkeypath=m/0'/0'/18'
|
||||||
|
TB9L7RRc1saYrMn3XWHra1ddiju99P46JoZFKm4X6gxXbgE1Ej7G 2017-10-04T14:48:08Z reserve=1 # addr=LMyUZsQD9Jda2hv7jokgrYxdGp7pCQNAeK hdkeypath=m/0'/0'/71'
|
||||||
|
T6fwrz1QyJed2Yhar3xFKLkBLvHvJTBGtL15b6D2evEiGCTVxo5q 2017-10-04T14:48:08Z reserve=1 # addr=LN1RrbuXy48m2GBdwbHnHyx5VLGCothT2F hdkeypath=m/0'/0'/121'
|
||||||
|
T8NC1dhgmnHZoALaBWU4EPVyxFP3nRAKWwupVBYVcKXdkvSaEx3W 2017-10-04T14:48:08Z reserve=1 # addr=LNLTYi9j47NWDvZZd8KbBj8r6o33y3i3QW hdkeypath=m/0'/0'/119'
|
||||||
|
T5fKubJtf9oZow3XK2E4pU89Gj14PvKMoHvFgEWzmVykpSN6MrNn 2017-10-04T14:48:08Z reserve=1 # addr=LNkfJwnDqo6WM9sVZTcaehP6L3zGSwjuLr hdkeypath=m/0'/0'/15'
|
||||||
|
T7y6DSPu6KpA35CYrd5UUfJMz9t9nJgHqTbkg7xQrDTgi1gFR4rr 2017-10-04T14:48:08Z reserve=1 # addr=LNm8Sa8bXGwaERLEZgHbAhY5iLT7hkFcyj hdkeypath=m/0'/0'/62'
|
||||||
|
T4XvMvrpfAL2gbeDetTmKoqNCNsNkCNrMpbEmknTBieanN5PxHVK 2017-10-04T14:48:08Z reserve=1 # addr=LNpLNK3tWWzXKgnxGgKtwy1DwLfNAwqH1j hdkeypath=m/0'/0'/131'
|
||||||
|
T9saJz47kbHCNkNPi4ESTyVLH6E8FWVa3sJtUY8T76uJZw6WTxPv 2017-10-04T14:48:08Z reserve=1 # addr=LNqeZzAdkP9r4XhrefiQ3YsqiyBEMheKj8 hdkeypath=m/0'/0'/97'
|
||||||
|
TAwFJWpsUPubBBMcmikaPBFDZBb7CmBPiGYbizFMA8m73i1CeoVk 2017-10-04T14:48:08Z reserve=1 # addr=LPb7xnrs7ktMmpcmNzj19skDGcoRWuHDHG hdkeypath=m/0'/0'/133'
|
||||||
|
T7oY3Npei8pZUoohD2PBZvRgzfWq2Cp5S5dog4B9c5AAfCVH8b23 2017-10-04T14:48:08Z reserve=1 # addr=LPf6u8uAN27rC9jkxLBRwqnHeaPHZUk2ZR hdkeypath=m/0'/0'/37'
|
||||||
|
T7Q1ZG7t5B52K9cbj3crFAt6QqAs8b3wRCsSWetMw9kTYj9WD6JK 2017-10-04T14:48:08Z reserve=1 # addr=LPm2hPDqRcucUuv3BvAgr8zriH9vbsjjuS hdkeypath=m/0'/0'/122'
|
||||||
|
TAL2Ka73nnTcZ43H9ZaiCBDASjVxEC3pDxhJkKpsVz69ZWB91s1H 2017-10-04T14:48:08Z reserve=1 # addr=LPn7x9LN3XKMaz366cYxgoutxTBMucivx9 hdkeypath=m/0'/0'/41'
|
||||||
|
T3ViSsRvGziviXXwXVMS98VmRHJFdsG4FpF6mpxqieXEj3bvCm6t 2017-10-04T14:48:08Z reserve=1 # addr=LQUPqfwn6R6HGW7kPDzEKMFVZQsiSfoMsv hdkeypath=m/0'/0'/61'
|
||||||
|
T8mAqqHziNFqw2n6Nizr3fxQFzTQhRRDZg2M5qXTcHokBUHHzXeL 2017-10-04T14:48:08Z reserve=1 # addr=LR5VYqWU2yRuTafPWySotV18zDdLaRwUyg hdkeypath=m/0'/0'/127'
|
||||||
|
T6Z1pLvXvUb3azbA6tg2gGoBswMmFEcpvr3HyfSML5PzTZjGq4ud 2017-10-04T14:48:08Z reserve=1 # addr=LR6cWeyKQ1F3zWBZiecPPKr9pcYAbhwhJ6 hdkeypath=m/0'/0'/104'
|
||||||
|
T4VPC6ohn7zQTsHc773ejRHDAS3gBbxjempw1DFqnQ2L5JoxX8Fn 2017-10-04T14:48:08Z reserve=1 # addr=LRAoVvvCJy6XPD9e9si6wZutkZT3a3pt8Y hdkeypath=m/0'/0'/8'
|
||||||
|
T5UMAR6QxvsZPyxQdt6p84SYw31rxMq6NXRWQiiC32CowqMbJ6io 2017-10-04T14:48:08Z reserve=1 # addr=LRT6XNpCY84GRaNx56UsLhgXPZp3Q5bq72 hdkeypath=m/0'/0'/55'
|
||||||
|
T9wwtntVALxqy9kqNJEntceYD9e2Yty8HJ56YQxa8mQMcCy3XsBJ 2017-10-04T14:48:08Z reserve=1 # addr=LRYhng4cTxiKJjMzUL5mQjRTFjWS8c7B8M hdkeypath=m/0'/0'/10'
|
||||||
|
T5357iXwSm96M8iPQBdciNiNNNuSyi4Z3JBPUv54juLv6EffYVZC 2017-10-04T14:48:08Z reserve=1 # addr=LRjGRmJfDDyXoQBbB1ahTANb3SV8Q4xNzZ hdkeypath=m/0'/0'/45'
|
||||||
|
T9QAYjgpcSdLsR7QjkqQ9FxVvpBRMaMSeLQCZctagkw6ho4X8451 2017-10-04T14:48:08Z reserve=1 # addr=LS5GmJqkm8wpqgg6aFixiUCuhu6SuUviF7 hdkeypath=m/0'/0'/19'
|
||||||
|
T9Jpv1NhHj3y15tYySsWuCTh1X7jdhv5iTVaGD1LHu5LBgq6Z2Kk 2017-10-04T14:48:08Z reserve=1 # addr=LSqLEhLRqynv6JQrsV6To9m2qndNHVjeD5 hdkeypath=m/0'/0'/13'
|
||||||
|
T85Fsin4NL8c1Xcx4XRL586H1vc1HBsR1Gbuh1v89AwbsdLC3icX 2017-10-04T14:48:08Z reserve=1 # addr=LSqWTEfJJDSPW8SWpxMBjtK3KmdHhWbuB3 hdkeypath=m/0'/0'/124'
|
||||||
|
TAhJ3ZtJqdJ3akYhJ1anduLoNZcfcJcMRRRimkyFZgDSW1mcpGyU 2017-10-04T14:48:08Z hdmaster=1 # addr=LSs8CuV6p14VgrNrnEAge8e66nhFGjJpzF hdkeypath=m
|
||||||
|
T9vUHqY7o9D13Enqzsv8bxz6FiGYzqC2q3KzFKPvf3em9CaAVdFr 2017-10-04T14:48:08Z reserve=1 # addr=LT3AZKLbHrNoxqy9FHUBacJQ6M5yUirRmK hdkeypath=m/0'/0'/1'
|
||||||
|
T3RpVdSDvgxKUtcAjDnc74XgFVr2PE1jGpAgLryDNSAcuNTgs7gj 2017-10-04T14:48:08Z reserve=1 # addr=LT3Sgvkxt8SZjx1QJoQvmHWb44yHDhGc5C hdkeypath=m/0'/0'/91'
|
||||||
|
T652xJjwRS8i1kyzEbzY4g2rqrDznrW9bKsAEw3RmAVQ5K2Zpx18 2017-10-04T14:48:08Z reserve=1 # addr=LT6UjMJPvPapdw7mdm13ev2MABQLfRJtry hdkeypath=m/0'/0'/95'
|
||||||
|
TBLABZwhc8mVAJCj9uYvYGBHyaQDX2FFR7saK6A6PtwhxzwqWHu7 2017-10-04T14:48:08Z reserve=1 # addr=LT7Lj9HiWHUUxmrfWU4xWcyhfC2eyXWQZt hdkeypath=m/0'/0'/40'
|
||||||
|
T3ttpVMBUMmnaZzpWqLr6NCunw4jzscKa3SJcHjd4Dw3FnW7N8dR 2017-10-04T14:48:08Z reserve=1 # addr=LTJ79HyN3NB5kHarLUt7731ixtuHUdUVKn hdkeypath=m/0'/0'/30'
|
||||||
|
T3uQAJdV635ca3hkqduT9BRQd5EjxrYBM4VqZxhrmyXpaMokvo4s 2017-10-04T14:48:08Z label= # addr=LTUacuBvFqGqqZrAdjkNahVt457XJx1RhX hdkeypath=m/0'/0'/0'
|
||||||
|
T66S2jTnPEAurJcSpW6W6PvTDmy9StCVZ4ButYyLSpM7vcjHmgSN 2017-10-04T14:48:08Z reserve=1 # addr=LTVAiWSvCfBV44Q9smZsHYPQCsNpdMsBd3 hdkeypath=m/0'/0'/35'
|
||||||
|
T3tjMTqQZctdhgRySYHgH47rPnkub4h7jUq13cEntHmM2iHmXzzh 2017-10-04T14:48:08Z reserve=1 # addr=LTgW8DuC3rU2Wao3EGHoGwYuvJmpFned3B hdkeypath=m/0'/0'/24'
|
||||||
|
T5t9je7mJkRiUwJVj8hzshZzxZR89N27SzkvWxnrPr21tmQYSS4x 2017-10-04T14:48:08Z reserve=1 # addr=LTge5NsL7FKYXAZQRx1L3vTQpzRyN9feGp hdkeypath=m/0'/0'/105'
|
||||||
|
T8qzvk7JW9HF4aGgRAfQgP478zRzN2AsHf9FQdw759mMgPLkFwXQ 2017-10-04T14:48:08Z reserve=1 # addr=LU1foBG61ABDmoJpK429tzahKGY6ovexuq hdkeypath=m/0'/0'/78'
|
||||||
|
TBFYbnK8myB5rxAw2WQtQEbic2B697Vqu7TmaDRizBFdc1H4U7Rh 2017-10-04T14:48:08Z reserve=1 # addr=LU3mk2HCmAY9EQZZ5eb6FKZT1QwDFU2F7U hdkeypath=m/0'/0'/103'
|
||||||
|
T6e2vDPkK94tPEgZBhJKcJpL1d5QDASNi8yNra2orofhTw7Jajn6 2017-10-04T14:48:08Z reserve=1 # addr=LUDV4wUaBzpr5qYHFzkCJvioGnXNA5tv48 hdkeypath=m/0'/0'/65'
|
||||||
|
TAjz7NCZys4MGZnzZKgdESYd8uuquBwU4Dtj84KtCxbGphNCwX3B 2017-10-04T14:48:08Z reserve=1 # addr=LULNzTTHcAfkDs3k8A8i1FrB4yTZd4kbBm hdkeypath=m/0'/0'/58'
|
||||||
|
T4GLMgzgMRvBDN6kveq5tr42mqW34qbU9Gfqho4Q4zD74qrNeHk5 2017-10-04T14:48:08Z reserve=1 # addr=LUQoqMF79dwyi1r8QcxDN6XQjh9sKs2FVg hdkeypath=m/0'/0'/64'
|
||||||
|
T5UpQNsQF4EqPQjDRqMDsHYNSYJxTr6FZ6Ut3BaMDiqgD7TPWvey 2017-10-04T14:48:08Z reserve=1 # addr=LUbaBs7ukwgQSkFW4EiCX6N5ThDtrVKeta hdkeypath=m/0'/0'/34'
|
||||||
|
T3A6pr6tVUQeNFsASEssbUBhiuZjgkuwM2XYGbPz5CJgtt6WN9YX 2017-10-04T14:48:08Z reserve=1 # addr=LV2SZ4s3WBqoErBgT3tmLiSCJkKvZZFfwe hdkeypath=m/0'/0'/111'
|
||||||
|
T5isXosFpoQiritzQMHxCBuzdjdTSkDt14vtQrJnGThqnwd25vGD 2017-10-04T14:48:08Z reserve=1 # addr=LV5WAzx7s78HrbSnh4TBjiHoNJtQQxJWKi hdkeypath=m/0'/0'/50'
|
||||||
|
T3VfqdExhA9ahWMKqfHKTCdStp4bhSjMtxwKq9AtS6G7GimfK7Zs 2017-10-04T14:48:08Z reserve=1 # addr=LV5swfHkzzTob6F9fAXbsho4KxPdfJNLzd hdkeypath=m/0'/0'/27'
|
||||||
|
TBJvhNwEg64DscQhGmbrpvbzwTHn3Dai4GYDpqZdpvFinW1W9N68 2017-10-04T14:48:08Z reserve=1 # addr=LVdrkgRihyEGxp9qakYx2LmtgcQboWvbU4 hdkeypath=m/0'/0'/117'
|
||||||
|
TBNxbEcpBwDwLs8FUafheUDysbAmgbjxVzPwio9cGTrkpvRqZx2U 2017-10-04T14:48:08Z reserve=1 # addr=LVyf8AZ13XZz1zeTFafbnkUj2CCqcoW5rb hdkeypath=m/0'/0'/116'
|
||||||
|
T5nK1i7ZmHhSv5z4Na7zowqvCSWxAnEr9FmdiMYRSsGJd9PQU238 2017-10-04T14:48:08Z reserve=1 # addr=LW1bD6A3oGNmfhm4sBZQBZAF7XKvydDKkz hdkeypath=m/0'/0'/129'
|
||||||
|
T5WSerYuKKK6goT4g1rqN6N73Uswm9aTn7URG1Fe3n9PUgGwWxZe 2017-10-04T14:48:08Z reserve=1 # addr=LWAQx33RkWmDVdDoqCAP5JSQjuxbUhB885 hdkeypath=m/0'/0'/4'
|
||||||
|
T48QfivUTozdCoFE4i3ch7EC82s4wE2f6hCtCNzpMmBbiPif1jQr 2017-10-04T14:48:08Z reserve=1 # addr=LWNjrkSajzPpAMwGZE1rfaqzSGQFcrE3pL hdkeypath=m/0'/0'/16'
|
||||||
|
TBBnyTsQf8bqwY47k3ha21tPQZ5XVTj6thp3qty9d68cKwP7dxSt 2017-10-04T14:48:08Z reserve=1 # addr=LWa4gRn2ao35hMGRTKZxVhf6UHaqM9CWgP hdkeypath=m/0'/0'/85'
|
||||||
|
T6ff3LL9UgjJVsExazw61k95aSTNDNjafYY8YHRwGfFUr6YKP8LA 2017-10-04T14:48:08Z reserve=1 # addr=LWqUrYetNChKyAi7oMAp4kYKZVWzmPotGw hdkeypath=m/0'/0'/120'
|
||||||
|
T5AEoLWuBZPuyFxWs9uPYiAQpEXkReS5hbMJFG24C9g8f4bQBDV4 2017-10-04T14:48:08Z reserve=1 # addr=LXHc584XU4ntq8jAVpTvXEZKpLNXgkZN2N hdkeypath=m/0'/0'/87'
|
||||||
|
T8sk7EZRBMYZkwzVvueZpoBuae1mEUdWACVJjpBgXynqMNQFMPfh 2017-10-04T14:48:08Z reserve=1 # addr=LXJBz1wcD4AC7LDYNLcuQoFafdeJLn7quA hdkeypath=m/0'/0'/67'
|
||||||
|
T9FdBbGkSnXVR4tPYDXFvzzJuRtQLzMYyMrBkVJRbZJjfNFC31KU 2017-10-04T14:48:08Z reserve=1 # addr=LXKNcKa2iDmCbujVVoYU32HHM8ZeRVZWfv hdkeypath=m/0'/0'/75'
|
||||||
|
T4ShKJhNgkkMc1XbaRKJvdfnX6TPVdrQzjmKHZ62iFEuo6B1C9gV 2017-10-04T14:48:08Z reserve=1 # addr=LXRWCdWmvTxt8f4ohtQFYjqE2G2ibxrTZL hdkeypath=m/0'/0'/93'
|
||||||
|
T7wPrzTing6LShZNcpxDYWETZMSrns8eaM5gQobGSqpdxrtd9wh4 2017-10-04T14:48:08Z reserve=1 # addr=LXnf9uEdEJbEVeKDsJ7YeMeXAgaJFmTS8R hdkeypath=m/0'/0'/48'
|
||||||
|
T3S1zdoA6XhjiRgfNdSFRzNQJVxZwBihqY1MSzWQ9eT1beEbrNPT 2017-10-04T14:48:08Z reserve=1 # addr=LYJSvV9zdRwuUVoqLN7dRmA9JU1rJtJhgb hdkeypath=m/0'/0'/28'
|
||||||
|
T8wLmVTfBzTTMk9bx34rvkmeK4n2aPPicG7fXvbACyrv16Du4iN2 2017-10-04T14:48:08Z reserve=1 # addr=LYqNSgjc3Pp4aYnivHaaxJP6jKzCYhWY7J hdkeypath=m/0'/0'/132'
|
||||||
|
T8h6FjvM4XaSm2jS22mktnC6W2NNupg9kiDPWMbk5HjQ5g2KGbGE 2017-10-04T14:48:08Z reserve=1 # addr=LZ5vE8TQMpoDAb58ayPH37yAY83cHiKhwA hdkeypath=m/0'/0'/88'
|
||||||
|
T3ADW5C25CHF75kTdopcuFR75DupoWT2MiZpCTHxEPnSo9F32ZwF 2017-10-04T14:48:08Z reserve=1 # addr=LZFJYKx28dvjjfwdusUQtCisaTnSD9pF4Z hdkeypath=m/0'/0'/83'
|
||||||
|
T3JjTDeBmLKz1YqxsRP6Eu3QrMdGh8wHtWb4Xpo5YtGsw5ovVPw8 2017-10-04T14:48:08Z reserve=1 # addr=LZXgvvxULDC8atG16FwDG2GR6svemhszMs hdkeypath=m/0'/0'/101'
|
||||||
|
T6cWTMAHW5NhHJuvCSyqKPzxCGPKBSWtYwY1LXtGtBh64z7UFJgt 2017-10-04T14:48:08Z reserve=1 # addr=LZtoZXSZ3BFHKdmwVsGN3bYxayxcTxBaPr hdkeypath=m/0'/0'/51'
|
||||||
|
TA6FuX5497DENJxcq6iBYJvY3Z9TYzmkGhVo7BJvz9t9dJoWX984 2017-10-04T14:48:08Z reserve=1 # addr=LaSMqKBaQGFCvyLEvb8w4b13csJLLTjcJm hdkeypath=m/0'/0'/42'
|
||||||
|
T7SJaGyhLzwpPwsXgKZa9N8p9ZTvzHbS3Ej9KBGkpexQ2NFNLxZF 2017-10-04T14:48:08Z reserve=1 # addr=LaVEJXqbKksyTedriitBuiCLZUWFxCEVQE hdkeypath=m/0'/0'/125'
|
||||||
|
T65jdsUF5XtzLP62uoB7VP9FAwD61eRrFv7VY7o6xagCoucvqDzX 2017-10-04T14:48:08Z reserve=1 # addr=LaiFAeRp7A9Qu3grwMDkebDv5fNGYzAvrY hdkeypath=m/0'/0'/112'
|
||||||
|
TAJCqFg3JJBgYxqzuNVQG5hCfqghFJYgExMpYX6DrH8HFDyWVDCD 2017-10-04T14:48:08Z reserve=1 # addr=Lb1DPn8dCAWSohEqDVFALSW4foWF3xFVcw hdkeypath=m/0'/0'/107'
|
||||||
|
T4nUsXxjxRF3q75mQt35Ht7yh9pQ6PWGeDPWdsFXaj4zJMRANYQv 2017-10-04T14:48:08Z reserve=1 # addr=Lb1F9TF4Dhy8diHHp1z2yRDoqvF3NdcWCT hdkeypath=m/0'/0'/77'
|
||||||
|
T4GtbkhiNCLABbi6LPtetQk14AMS26GsecZgHN9ihFt3FXgbUYev 2017-10-04T14:48:08Z reserve=1 # addr=Lb7DAvX9nVUX5gLr7Y4hwME3CEyE56kPTR hdkeypath=m/0'/0'/44'
|
||||||
|
TAvus3rhumh6xeQ4PWNrY9B1vsp9fyi4zLDjUXy2et2aqE1JdWpn 2017-10-04T14:48:08Z reserve=1 # addr=Lb8qYPrMUobbKG1KBwWXYQk6J4uCkAKkHQ hdkeypath=m/0'/0'/31'
|
||||||
|
T4XnN2MaQvEy3f41bVnThyMdbBuTKsUQGZZKAH6nGNB6vgb16tti 2017-10-04T14:48:08Z reserve=1 # addr=LbNj9VPCSphjnuKnAPnu4uDDYiaS9mHaSc hdkeypath=m/0'/0'/22'
|
||||||
|
T5P7qwR7eGBcAAJKg6N9f2zvfmXQEt2hHuE7fLASZ8y7GdSnSGsG 2017-10-04T14:48:08Z reserve=1 # addr=LbUPH7FWE7e3Ad3mXyftp5EVLRmnhJ9atp hdkeypath=m/0'/0'/38'
|
||||||
|
T95uAMy842cfLYac9KWNVJxV4TqdQ8vvQLNCYgGCmcp9CRh1Uy5r 2017-10-04T14:48:08Z reserve=1 # addr=LbaaD1GoBUjnac4VWxDdShxZBrELFzkzJ3 hdkeypath=m/0'/0'/33'
|
||||||
|
T7m2JkiNq27TZhp7YjMioKXJXe1NNGBnUcJNBe9VKoE3r48FWXap 2017-10-04T14:48:08Z reserve=1 # addr=Lc1LcydegW1YWvvuN1BYZibu7oJ78SMSWe hdkeypath=m/0'/0'/130'
|
||||||
|
T7P2aaSimnXrgn646UmTVDvvVUf63EytQ29bsbkTTokfrzaJbGqg 2017-10-04T14:48:08Z reserve=1 # addr=LcGuePpkfsUiCc72yDAM792fiZnNSzivjm hdkeypath=m/0'/0'/56'
|
||||||
|
T3psoe7VBETwXJKRhqEWDpX4kUbhyb4TUnzCWRtQmjbtzM9jiBTs 2017-10-04T14:48:08Z reserve=1 # addr=LcPzzyjZWu5fWSvsYnABajH7KZGWVeSoew hdkeypath=m/0'/0'/128'
|
||||||
|
T5B3xQpZWz2PwV48rn8NK6SJ8zy91durzGESURLu5Q9NKkzzpejZ 2017-10-04T14:48:08Z reserve=1 # addr=LcVjwHr3mSwgDZczcKTKVGRrNz1YAnQoc3 hdkeypath=m/0'/0'/52'
|
||||||
|
T86aRnWbJje7XkH9MgSVG8HdTeP52qt7Z1Rdmkt7ewizw6937ZmT 2017-10-04T14:48:08Z reserve=1 # addr=Lcp11fbnZBZo5bJqdKUGmx5xMK29goa5c7 hdkeypath=m/0'/0'/100'
|
||||||
|
|
||||||
|
# End of dump
|
||||||
491
test/test.py
491
test/test.py
|
|
@ -22,14 +22,14 @@ test/test.py: Test suite for the MMGen suite
|
||||||
|
|
||||||
import sys,os,subprocess,shutil,time,re
|
import sys,os,subprocess,shutil,time,re
|
||||||
|
|
||||||
pn = os.path.dirname(sys.argv[0])
|
repo_root = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.pardir)))
|
||||||
os.chdir(os.path.join(pn,os.pardir))
|
os.chdir(repo_root)
|
||||||
sys.path.__setitem__(0,os.path.abspath(os.curdir))
|
sys.path.__setitem__(0,repo_root)
|
||||||
|
|
||||||
# Import these _after_ local path's been added to sys.path
|
# Import these _after_ local path's been added to sys.path
|
||||||
from mmgen.common import *
|
from mmgen.common import *
|
||||||
from mmgen.test import *
|
from mmgen.test import *
|
||||||
from mmgen.protocol import get_coin_protocol
|
from mmgen.protocol import CoinProtocol
|
||||||
|
|
||||||
g.quiet = False # if 'quiet' was set in config file, disable here
|
g.quiet = False # if 'quiet' was set in config file, disable here
|
||||||
os.environ['MMGEN_QUIET'] = '0' # and for the spawned scripts
|
os.environ['MMGEN_QUIET'] = '0' # and for the spawned scripts
|
||||||
|
|
@ -51,13 +51,12 @@ ref_wallet_brainpass = 'abc'
|
||||||
ref_wallet_hash_preset = '1'
|
ref_wallet_hash_preset = '1'
|
||||||
ref_wallet_incog_offset = 123
|
ref_wallet_incog_offset = 123
|
||||||
|
|
||||||
from mmgen.obj import MMGenTXLabel,PrivKey,BTCAmt
|
from mmgen.obj import MMGenTXLabel,PrivKey
|
||||||
from mmgen.addr import AddrGenerator,KeyGenerator,AddrList,AddrData,AddrIdxList
|
from mmgen.addr import AddrGenerator,KeyGenerator,AddrList,AddrData,AddrIdxList
|
||||||
ref_tx_label = ''.join([unichr(i) for i in range(65,91) +
|
ref_tx_label = ''.join([unichr(i) for i in range(65,91) +
|
||||||
range(1040,1072) + # cyrillic
|
range(1040,1072) + # cyrillic
|
||||||
range(913,939) + # greek
|
range(913,939) + # greek
|
||||||
range(97,123)])[:MMGenTXLabel.max_len]
|
range(97,123)])[:MMGenTXLabel.max_len]
|
||||||
tx_fee = '0.0001'
|
|
||||||
ref_bw_hash_preset = '1'
|
ref_bw_hash_preset = '1'
|
||||||
ref_bw_file = 'wallet.mmbrain'
|
ref_bw_file = 'wallet.mmbrain'
|
||||||
ref_bw_file_spc = 'wallet-spaced.mmbrain'
|
ref_bw_file_spc = 'wallet-spaced.mmbrain'
|
||||||
|
|
@ -83,7 +82,7 @@ if not any(e in ('--skip-deps','--resume','-S','-r') for e in sys.argv+shortopts
|
||||||
else:
|
else:
|
||||||
try: shutil.rmtree(data_dir)
|
try: shutil.rmtree(data_dir)
|
||||||
except: # we couldn't remove data dir - perhaps regtest daemon is running
|
except: # we couldn't remove data dir - perhaps regtest daemon is running
|
||||||
try: subprocess.call(['python','mmgen-regtest','stop'])
|
try: subprocess.call(['python',os.path.join('cmds','mmgen-regtest'),'stop'])
|
||||||
except: rdie(1,'Unable to remove data dir!')
|
except: rdie(1,'Unable to remove data dir!')
|
||||||
else:
|
else:
|
||||||
time.sleep(2)
|
time.sleep(2)
|
||||||
|
|
@ -148,11 +147,30 @@ sys.argv = [sys.argv[0]] + ['--data-dir',data_dir] + sys.argv[1:]
|
||||||
cmd_args = opts.init(opts_data)
|
cmd_args = opts.init(opts_data)
|
||||||
opt.popen_spawn = True # popen has issues, so use popen_spawn always
|
opt.popen_spawn = True # popen has issues, so use popen_spawn always
|
||||||
|
|
||||||
|
ref_subdir = '' if g.proto.base_coin == 'BTC' else g.proto.name
|
||||||
|
altcoin_pfx = '' if g.proto.base_coin == 'BTC' else '-'+g.proto.base_coin
|
||||||
|
tn_ext = ('','.testnet')[g.testnet]
|
||||||
|
|
||||||
|
fork = {'bch':'btc','btc':'btc','ltc':'ltc'}[g.coin.lower()]
|
||||||
|
tx_fee = {'btc':'0.0001','bch':'0.001','ltc':'0.01'}[g.coin.lower()]
|
||||||
|
txbump_fee = {'btc':'123s','bch':'567s','ltc':'12345s'}[g.coin.lower()]
|
||||||
|
|
||||||
|
rtFundAmt = {'btc':'500','bch':'500','ltc':'5500'}[g.coin.lower()]
|
||||||
|
rtFee = {
|
||||||
|
'btc': ('20s','10s','60s','0.0001','10s','20s'),
|
||||||
|
'bch': ('20s','10s','60s','0.0001','10s','20s'),
|
||||||
|
'ltc': ('1000s','500s','1500s','0.05','400s','1000s')
|
||||||
|
}[g.coin.lower()]
|
||||||
|
rtBals = {
|
||||||
|
'btc': ('499.999942','399.9998214','399.9998079','399.9996799','13.00000000','986.99957990','999.99957990'),
|
||||||
|
'bch': ('499.9999416','399.9999124','399.99989','399.9997616','276.22339397','723.77626763','999.99966160'),
|
||||||
|
'ltc': ('5499.9971','5399.994085','5399.993545','5399.987145','13.00000000','10986.93714500','10999.93714500'),
|
||||||
|
}[g.coin.lower()]
|
||||||
|
rtBobOp3 = {'btc':'S:2','bch':'L:3','ltc':'S:2'}[g.coin.lower()]
|
||||||
|
|
||||||
if opt.segwit and 'S' not in g.proto.mmtypes:
|
if opt.segwit and 'S' not in g.proto.mmtypes:
|
||||||
die(1,'--segwit option incompatible with {}'.format(g.proto.__name__))
|
die(1,'--segwit option incompatible with {}'.format(g.proto.__name__))
|
||||||
|
|
||||||
tn_desc = ('','.testnet')[g.testnet]
|
|
||||||
|
|
||||||
def randbool():
|
def randbool():
|
||||||
return hexlify(os.urandom(1))[1] in '12345678'
|
return hexlify(os.urandom(1))[1] in '12345678'
|
||||||
def get_segwit_val():
|
def get_segwit_val():
|
||||||
|
|
@ -280,12 +298,30 @@ cfgs = {
|
||||||
'seed_len': 128,
|
'seed_len': 128,
|
||||||
'seed_id': 'FE3C6545',
|
'seed_id': 'FE3C6545',
|
||||||
'ref_bw_seed_id': '33F10310',
|
'ref_bw_seed_id': '33F10310',
|
||||||
'addrfile_chk': ('B230 7526 638F 38CB','B64D 7327 EF2A 60FE'),
|
'addrfile_chk': {
|
||||||
'addrfile_segwit_chk': ('9914 6D10 2307 F348','7DBF 441F E188 8B37'),
|
'btc': ('B230 7526 638F 38CB','B64D 7327 EF2A 60FE'),
|
||||||
'addrfile_compressed_chk': ('95EB 8CC0 7B3B 7856','629D FDE4 CDC0 F276'),
|
'ltc': ('2B23 5E97 848A B961','928D 3CB6 78FF 9829'),
|
||||||
'keyaddrfile_chk': ('CF83 32FB 8A8B 08E2','FEBF 7878 97BB CC35'),
|
},
|
||||||
'keyaddrfile_segwit_chk': ('C13B F717 D4E8 CF59','4DB5 BAF0 45B7 6E81'),
|
'addrfile_segwit_chk': {
|
||||||
'keyaddrfile_compressed_chk': ('E43A FA46 5751 720A','B995 A6CF D1CD FAD0'),
|
'btc': ('9914 6D10 2307 F348','7DBF 441F E188 8B37'),
|
||||||
|
'ltc': ('CC09 A190 B7DF B7CD','3676 4C49 14F8 1AD0'),
|
||||||
|
},
|
||||||
|
'addrfile_compressed_chk': {
|
||||||
|
'btc': ('95EB 8CC0 7B3B 7856','629D FDE4 CDC0 F276'),
|
||||||
|
'ltc': ('35D5 8ECA 9A42 46C3','37E9 A36E 94A2 010F'),
|
||||||
|
},
|
||||||
|
'keyaddrfile_chk': {
|
||||||
|
'btc': ('CF83 32FB 8A8B 08E2','FEBF 7878 97BB CC35'),
|
||||||
|
'ltc': ('1896 A26C 7F14 2D01','B41D BA63 0605 DD66'),
|
||||||
|
},
|
||||||
|
'keyaddrfile_segwit_chk': {
|
||||||
|
'btc': ('C13B F717 D4E8 CF59','4DB5 BAF0 45B7 6E81'),
|
||||||
|
'ltc': ('054B 9794 55B4 5D82','C373 0074 DEE6 B70A'),
|
||||||
|
},
|
||||||
|
'keyaddrfile_compressed_chk': {
|
||||||
|
'btc': ('E43A FA46 5751 720A','B995 A6CF D1CD FAD0'),
|
||||||
|
'ltc': ('7603 2FE3 2145 FFAD','3248 356A C707 4A41'),
|
||||||
|
},
|
||||||
'passfile_chk': 'EB29 DC4F 924B 289F',
|
'passfile_chk': 'EB29 DC4F 924B 289F',
|
||||||
'passfile32_chk': '37B6 C218 2ABC 7508',
|
'passfile32_chk': '37B6 C218 2ABC 7508',
|
||||||
'wpasswd': 'reference password',
|
'wpasswd': 'reference password',
|
||||||
|
|
@ -313,12 +349,30 @@ cfgs = {
|
||||||
'seed_len': 192,
|
'seed_len': 192,
|
||||||
'seed_id': '1378FC64',
|
'seed_id': '1378FC64',
|
||||||
'ref_bw_seed_id': 'CE918388',
|
'ref_bw_seed_id': 'CE918388',
|
||||||
'addrfile_chk': ('8C17 A5FA 0470 6E89','0A59 C8CD 9439 8B81'),
|
'addrfile_chk': {
|
||||||
'addrfile_segwit_chk': ('91C4 0414 89E4 2089','3BA6 7494 8E2B 858D'),
|
'btc': ('8C17 A5FA 0470 6E89','0A59 C8CD 9439 8B81'),
|
||||||
'addrfile_compressed_chk': ('2615 8401 2E98 7ECA','DF38 22AB AAB0 124E'),
|
'ltc': ('2B77 A009 D5D0 22AD','FCEC 0032 9EF9 B201'),
|
||||||
'keyaddrfile_chk': ('9648 5132 B98E 3AD9','2F72 C83F 44C5 0FAC'),
|
},
|
||||||
'keyaddrfile_segwit_chk': ('C98B DF08 A3D5 204B','25F2 AEB6 AAAC 8BBE'),
|
'addrfile_segwit_chk': {
|
||||||
'keyaddrfile_compressed_chk': ('6D6D 3D35 04FD B9C3','B345 9CD8 9EAE 5489'),
|
'btc': ('91C4 0414 89E4 2089','3BA6 7494 8E2B 858D'),
|
||||||
|
'ltc': ('8F12 FA7B 9F12 594C','E79E F55B 1536 56F2'),
|
||||||
|
},
|
||||||
|
'addrfile_compressed_chk': {
|
||||||
|
'btc': ('2615 8401 2E98 7ECA','DF38 22AB AAB0 124E'),
|
||||||
|
'ltc': ('197C C48C 3C37 AB0F','5072 15DA 1A90 5E99'),
|
||||||
|
},
|
||||||
|
'keyaddrfile_chk': {
|
||||||
|
'btc': ('9648 5132 B98E 3AD9','2F72 C83F 44C5 0FAC'),
|
||||||
|
'ltc': ('DBD4 FAB6 7E46 CD07','1DA9 C245 F669 670C'),
|
||||||
|
},
|
||||||
|
'keyaddrfile_segwit_chk': {
|
||||||
|
'btc': ('C98B DF08 A3D5 204B','25F2 AEB6 AAAC 8BBE'),
|
||||||
|
'ltc': ('1829 7FE7 2567 CB91','1305 9007 E515 B66A'),
|
||||||
|
},
|
||||||
|
'keyaddrfile_compressed_chk': {
|
||||||
|
'btc': ('6D6D 3D35 04FD B9C3','B345 9CD8 9EAE 5489'),
|
||||||
|
'ltc': ('F5DA 9D60 6798 C4E9','F928 113B C9D7 9DF5'),
|
||||||
|
},
|
||||||
'passfile_chk': 'ADEA 0083 094D 489A',
|
'passfile_chk': 'ADEA 0083 094D 489A',
|
||||||
'passfile32_chk': '2A28 C5C7 36EC 217A',
|
'passfile32_chk': '2A28 C5C7 36EC 217A',
|
||||||
'wpasswd': 'reference password',
|
'wpasswd': 'reference password',
|
||||||
|
|
@ -346,26 +400,57 @@ cfgs = {
|
||||||
'seed_len': 256,
|
'seed_len': 256,
|
||||||
'seed_id': '98831F3A',
|
'seed_id': '98831F3A',
|
||||||
'ref_bw_seed_id': 'B48CD7FC',
|
'ref_bw_seed_id': 'B48CD7FC',
|
||||||
'addrfile_chk': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'),
|
'addrfile_chk': {
|
||||||
'addrfile_segwit_chk': ('06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14'),
|
'btc': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'),
|
||||||
'addrfile_compressed_chk': ('A33C 4FDE F515 F5BC','5186 02C2 535E B7D5'),
|
'ltc': ('AD52 C3FE 8924 AAF0','5738 5C4F 167C F9AE'),
|
||||||
'keyaddrfile_chk': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'),
|
},
|
||||||
'keyaddrfile_segwit_chk': ('A447 12C2 DD14 5A9B','0690 460D A600 D315'),
|
'addrfile_segwit_chk': {
|
||||||
'keyaddrfile_compressed_chk': ('420A 8EB5 A9E2 7814','3243 DD92 809E FE8D'),
|
'btc': ('06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14'),
|
||||||
|
'ltc': ('63DF E42A 0827 21C3','1A3F 3016 2E2B F33A'),
|
||||||
|
},
|
||||||
|
'addrfile_compressed_chk': {
|
||||||
|
'btc': ('A33C 4FDE F515 F5BC','5186 02C2 535E B7D5'),
|
||||||
|
'ltc': ('3FC0 8F03 C2D6 BD19','535E 5CDC 1CA7 08D5'),
|
||||||
|
},
|
||||||
|
'keyaddrfile_chk': {
|
||||||
|
'btc': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'),
|
||||||
|
'ltc': ('B804 978A 8796 3ED4','93A6 844C 8ECC BEF4'),
|
||||||
|
},
|
||||||
|
'keyaddrfile_segwit_chk': {
|
||||||
|
'btc': ('A447 12C2 DD14 5A9B','0690 460D A600 D315'),
|
||||||
|
'ltc': ('E8A3 9F6E E164 A521','70ED 8557 5882 08A5'),
|
||||||
|
},
|
||||||
|
'keyaddrfile_compressed_chk': {
|
||||||
|
'btc': ('420A 8EB5 A9E2 7814','3243 DD92 809E FE8D'),
|
||||||
|
'ltc': ('8D1C 781F EB7F 44BC','678E 8EF9 1396 B140'),
|
||||||
|
},
|
||||||
'passfile_chk': '2D6D 8FBA 422E 1315',
|
'passfile_chk': '2D6D 8FBA 422E 1315',
|
||||||
'passfile32_chk': 'F6C1 CDFB 97D9 FCAE',
|
'passfile32_chk': 'F6C1 CDFB 97D9 FCAE',
|
||||||
'wpasswd': 'reference password',
|
'wpasswd': 'reference password',
|
||||||
'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]),
|
'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]),
|
||||||
'ref_addrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.addrs'.format(tn_desc),
|
'ref_addrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs'.format(altcoin_pfx,tn_ext),
|
||||||
'ref_segwitaddrfile':'98831F3A-S[1,31-33,500-501,1010-1011]{}.addrs'.format(tn_desc),
|
'ref_segwitaddrfile':'98831F3A{}-S[1,31-33,500-501,1010-1011]{}.addrs'.format(altcoin_pfx,tn_ext),
|
||||||
'ref_keyaddrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.akeys.mmenc'.format(tn_desc),
|
'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc'.format(altcoin_pfx,tn_ext),
|
||||||
'ref_passwdfile': '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
|
'ref_passwdfile': '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
|
||||||
'ref_addrfile_chksum': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E')[g.testnet],
|
'ref_addrfile_chksum': {
|
||||||
'ref_segwitaddrfile_chksum':('06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14')[g.testnet],
|
'btc': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'),
|
||||||
'ref_keyaddrfile_chksum': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2')[g.testnet],
|
'ltc': ('AD52 C3FE 8924 AAF0','5738 5C4F 167C F9AE'),
|
||||||
|
},
|
||||||
|
'ref_segwitaddrfile_chksum': {
|
||||||
|
'btc': ('06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14'),
|
||||||
|
'ltc': ('63DF E42A 0827 21C3','1A3F 3016 2E2B F33A'),
|
||||||
|
},
|
||||||
|
'ref_keyaddrfile_chksum': {
|
||||||
|
'btc': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'),
|
||||||
|
'ltc': ('B804 978A 8796 3ED4','93A6 844C 8ECC BEF4'),
|
||||||
|
},
|
||||||
'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB',
|
'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB',
|
||||||
# 'ref_fake_unspent_data':'98831F3A_unspent.json',
|
# 'ref_fake_unspent_data':'98831F3A_unspent.json',
|
||||||
'ref_tx_file': 'FFB367[1.234]{}.rawtx'.format(tn_desc),
|
'ref_tx_file': {
|
||||||
|
'btc': 'FFB367[1.234]{}.rawtx',
|
||||||
|
'bch': '99BE60-BCH[106.6789]{}.rawtx',
|
||||||
|
'ltc': '75F455-LTC[106.6789]{}.rawtx',
|
||||||
|
},
|
||||||
'ic_wallet': '98831F3A-5482381C-18460FB1[256,1].mmincog',
|
'ic_wallet': '98831F3A-5482381C-18460FB1[256,1].mmincog',
|
||||||
'ic_wallet_hex': '98831F3A-1630A9F2-870376A9[256,1].mmincox',
|
'ic_wallet_hex': '98831F3A-1630A9F2-870376A9[256,1].mmincox',
|
||||||
|
|
||||||
|
|
@ -583,6 +668,28 @@ cmd_group['regtest'] = (
|
||||||
('regtest_stop', 'stopping regtest daemon'),
|
('regtest_stop', 'stopping regtest daemon'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# undocumented admin cmds
|
||||||
|
cmd_group_admin = OrderedDict()
|
||||||
|
cmd_group_admin['create_ref_tx'] = (
|
||||||
|
('ref_tx_setup', 'regtest (Bob and Alice) mode setup'),
|
||||||
|
('ref_tx_addrgen_bob_ref_wallet', 'address generation (Bob - reference wallet)'),
|
||||||
|
('ref_tx_addrimport_bob_ref_wallet', "importing Bob's addresses (reference wallet)"),
|
||||||
|
('ref_tx_fund_bob', "funding Bob's wallet (reference wallet)"),
|
||||||
|
('ref_tx_bob_split', "splitting Bob's funds (reference wallet)"),
|
||||||
|
('ref_tx_generate', 'mining a block'),
|
||||||
|
('ref_tx_bob_create_tx', "creating reference transaction"),
|
||||||
|
('ref_tx_bob_modify_tx', "modifying reference transaction (testnet+mainnet)"),
|
||||||
|
)
|
||||||
|
cmd_list_admin = OrderedDict()
|
||||||
|
cmd_data_admin = OrderedDict()
|
||||||
|
for k in cmd_group_admin: cmd_list_admin[k] = []
|
||||||
|
|
||||||
|
cmd_data_admin['info_create_ref_tx'] = 'create reference tx',[8]
|
||||||
|
for a,b in cmd_group_admin['create_ref_tx']:
|
||||||
|
cmd_list_admin['create_ref_tx'].append(a)
|
||||||
|
cmd_data_admin[a] = (8,b,[[[],8]])
|
||||||
|
# end undocumented admin commands
|
||||||
|
|
||||||
cmd_list = OrderedDict()
|
cmd_list = OrderedDict()
|
||||||
for k in cmd_group: cmd_list[k] = []
|
for k in cmd_group: cmd_list[k] = []
|
||||||
|
|
||||||
|
|
@ -636,12 +743,6 @@ utils = {
|
||||||
|
|
||||||
addrs_per_wallet = 8
|
addrs_per_wallet = 8
|
||||||
|
|
||||||
# total of two outputs must be < 10 BTC
|
|
||||||
for k in cfgs:
|
|
||||||
cfgs[k]['amts'] = [0,0]
|
|
||||||
for idx,mod in ((0,6),(1,4)):
|
|
||||||
cfgs[k]['amts'][idx] = '%s.%s' % ((getrandnum(2) % mod), str(getrandnum(4))[:5])
|
|
||||||
|
|
||||||
meta_cmds = OrderedDict([
|
meta_cmds = OrderedDict([
|
||||||
['ref1', ('refwalletgen1','refaddrgen1','refkeyaddrgen1')],
|
['ref1', ('refwalletgen1','refaddrgen1','refkeyaddrgen1')],
|
||||||
['ref2', ('refwalletgen2','refaddrgen2','refkeyaddrgen2')],
|
['ref2', ('refwalletgen2','refaddrgen2','refkeyaddrgen2')],
|
||||||
|
|
@ -685,8 +786,6 @@ usr_rand_chars = (5,30)[bool(opt.usr_random)]
|
||||||
usr_rand_arg = '-r%s' % usr_rand_chars
|
usr_rand_arg = '-r%s' % usr_rand_chars
|
||||||
cmd_total = 0
|
cmd_total = 0
|
||||||
|
|
||||||
if opt.system: sys.path.pop(0)
|
|
||||||
|
|
||||||
# Disable color in spawned scripts so we can parse their output
|
# Disable color in spawned scripts so we can parse their output
|
||||||
os.environ['MMGEN_DISABLE_COLOR'] = '1'
|
os.environ['MMGEN_DISABLE_COLOR'] = '1'
|
||||||
os.environ['MMGEN_NO_LICENSE'] = '1'
|
os.environ['MMGEN_NO_LICENSE'] = '1'
|
||||||
|
|
@ -798,20 +897,39 @@ def verify_checksum_or_exit(checksum,chk):
|
||||||
|
|
||||||
from test.mmgen_pexpect import MMGenPexpect
|
from test.mmgen_pexpect import MMGenPexpect
|
||||||
class MMGenExpect(MMGenPexpect):
|
class MMGenExpect(MMGenPexpect):
|
||||||
def __init__(self,name,mmgen_cmd,cmd_args=[],extra_desc='',no_output=False):
|
|
||||||
|
def __init__(self,name,mmgen_cmd,cmd_args=[],extra_desc='',no_output=False,msg_only=False):
|
||||||
|
|
||||||
desc = (cmd_data[name][1],name)[bool(opt.names)] + (' ' + extra_desc).strip()
|
desc = (cmd_data[name][1],name)[bool(opt.names)] + (' ' + extra_desc).strip()
|
||||||
pa = ['testnet','rpc_host','rpc_port','regtest','coin']
|
passthru_args = ['testnet','rpc_host','rpc_port','regtest','coin']
|
||||||
return MMGenPexpect.__init__(self,name,mmgen_cmd,cmd_args,desc,no_output=no_output,passthru_args=pa)
|
|
||||||
|
if not opt.system:
|
||||||
|
os.environ['PYTHONPATH'] = repo_root
|
||||||
|
mmgen_cmd = os.path.relpath(os.path.join(repo_root,'cmds',mmgen_cmd))
|
||||||
|
elif g.platform == 'win':
|
||||||
|
mmgen_cmd = os.path.join('/mingw64','opt','bin',mmgen_cmd)
|
||||||
|
|
||||||
|
return MMGenPexpect.__init__(
|
||||||
|
self,
|
||||||
|
name,
|
||||||
|
mmgen_cmd,
|
||||||
|
cmd_args,
|
||||||
|
desc,
|
||||||
|
no_output=no_output,
|
||||||
|
passthru_args=passthru_args,
|
||||||
|
msg_only=msg_only)
|
||||||
|
|
||||||
def create_fake_unspent_entry(coinaddr,al_id=None,idx=None,lbl=None,non_mmgen=False,segwit=False):
|
def create_fake_unspent_entry(coinaddr,al_id=None,idx=None,lbl=None,non_mmgen=False,segwit=False):
|
||||||
if 'S' not in g.proto.mmtypes: segwit = False
|
if 'S' not in g.proto.mmtypes: segwit = False
|
||||||
if lbl: lbl = ' ' + lbl
|
if lbl: lbl = ' ' + lbl
|
||||||
spk1,spk2 = (('76a914','88ac'),('a914','87'))[segwit and coinaddr.addr_fmt=='p2sh']
|
spk1,spk2 = (('76a914','88ac'),('a914','87'))[segwit and coinaddr.addr_fmt=='p2sh']
|
||||||
|
amt1,amt2 = {'btc':(10,40),'bch':(10,40),'ltc':(1000,4000)}[g.coin.lower()]
|
||||||
return {
|
return {
|
||||||
'account': 'btc:{}'.format(coinaddr) if non_mmgen else (u'{}:{}{}'.format(al_id,idx,lbl.decode('utf8'))),
|
'account': '{}:{}'.format(g.proto.base_coin.lower(),coinaddr) if non_mmgen \
|
||||||
|
else (u'{}:{}{}'.format(al_id,idx,lbl.decode('utf8'))),
|
||||||
'vout': int(getrandnum(4) % 8),
|
'vout': int(getrandnum(4) % 8),
|
||||||
'txid': hexlify(os.urandom(32)).decode('utf8'),
|
'txid': hexlify(os.urandom(32)).decode('utf8'),
|
||||||
'amount': BTCAmt('%s.%s' % (10+(getrandnum(4) % 40), getrandnum(4) % 100000000)),
|
'amount': g.proto.coin_amt('%s.%s' % (amt1+(getrandnum(4) % amt2), getrandnum(4) % 100000000)),
|
||||||
'address': coinaddr,
|
'address': coinaddr,
|
||||||
'spendable': False,
|
'spendable': False,
|
||||||
'scriptPubKey': '{}{}{}'.format(spk1,coinaddr.hex,spk2),
|
'scriptPubKey': '{}{}{}'.format(spk1,coinaddr.hex,spk2),
|
||||||
|
|
@ -860,7 +978,7 @@ def create_fake_unspent_data(adata,tx_data,non_mmgen_input=''):
|
||||||
privkey = PrivKey(os.urandom(32),compressed=True)
|
privkey = PrivKey(os.urandom(32),compressed=True)
|
||||||
coinaddr = AddrGenerator('p2pkh').to_addr(KeyGenerator().to_pubhex(privkey))
|
coinaddr = AddrGenerator('p2pkh').to_addr(KeyGenerator().to_pubhex(privkey))
|
||||||
of = os.path.join(cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn)
|
of = os.path.join(cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn)
|
||||||
write_data_to_file(of,privkey.wif+'\n','compressed bitcoin key',silent=True)
|
write_data_to_file(of,privkey.wif+'\n','compressed {} key'.format(g.proto.name),silent=True)
|
||||||
out.append(create_fake_unspent_entry(coinaddr,non_mmgen=True,segwit=False))
|
out.append(create_fake_unspent_entry(coinaddr,non_mmgen=True,segwit=False))
|
||||||
|
|
||||||
# msg('\n'.join([repr(o) for o in out])); sys.exit(0)
|
# msg('\n'.join([repr(o) for o in out])); sys.exit(0)
|
||||||
|
|
@ -901,6 +1019,14 @@ def make_txcreate_cmdline(tx_data):
|
||||||
t = ('p2pkh','segwit')['S' in g.proto.mmtypes]
|
t = ('p2pkh','segwit')['S' in g.proto.mmtypes]
|
||||||
coinaddr = AddrGenerator(t).to_addr(KeyGenerator().to_pubhex(privkey))
|
coinaddr = AddrGenerator(t).to_addr(KeyGenerator().to_pubhex(privkey))
|
||||||
|
|
||||||
|
# total of two outputs must be < 10 BTC (<1000 LTC)
|
||||||
|
mods = {'btc':(6,4),'bch':(6,4),'ltc':(600,400)}[g.coin.lower()]
|
||||||
|
for k in cfgs:
|
||||||
|
cfgs[k]['amts'] = [None,None]
|
||||||
|
for idx,mod in enumerate(mods):
|
||||||
|
cfgs[k]['amts'][idx] = '%s.%s' % ((getrandnum(4) % mod), str(getrandnum(4))[:5])
|
||||||
|
|
||||||
|
|
||||||
cmd_args = ['-d',cfg['tmpdir']]
|
cmd_args = ['-d',cfg['tmpdir']]
|
||||||
for num in tx_data:
|
for num in tx_data:
|
||||||
s = tx_data[num]
|
s = tx_data[num]
|
||||||
|
|
@ -1104,10 +1230,11 @@ class MMGenTestSuite(object):
|
||||||
def helpscreens(self,name,arg='--help'):
|
def helpscreens(self,name,arg='--help'):
|
||||||
scripts = (
|
scripts = (
|
||||||
'walletgen','walletconv','walletchk','txcreate','txsign','txsend','txdo','txbump',
|
'walletgen','walletconv','walletchk','txcreate','txsign','txsend','txdo','txbump',
|
||||||
'addrgen','addrimport','keygen','passchg','tool','passgen')
|
'addrgen','addrimport','keygen','passchg','tool','passgen','regtest')
|
||||||
for s in scripts:
|
for s in scripts:
|
||||||
t = MMGenExpect(name,('mmgen-'+s),[arg],extra_desc='(mmgen-%s)'%s,no_output=True)
|
t = MMGenExpect(name,('mmgen-'+s),[arg],extra_desc='(mmgen-%s)'%s,no_output=True)
|
||||||
t.read(); t.ok()
|
t.read()
|
||||||
|
t.ok()
|
||||||
|
|
||||||
def longhelpscreens(self,name): self.helpscreens(name,arg='--longhelp')
|
def longhelpscreens(self,name): self.helpscreens(name,arg='--longhelp')
|
||||||
|
|
||||||
|
|
@ -1208,7 +1335,7 @@ class MMGenTestSuite(object):
|
||||||
if opt.no_dw_delete: return True
|
if opt.no_dw_delete: return True
|
||||||
for wf in [f for f in os.listdir(g.data_dir) if f[-6:]=='.mmdat']:
|
for wf in [f for f in os.listdir(g.data_dir) if f[-6:]=='.mmdat']:
|
||||||
os.unlink(os.path.join(g.data_dir,wf))
|
os.unlink(os.path.join(g.data_dir,wf))
|
||||||
MMGenExpect(name,'')
|
MMGenExpect(name,'',msg_only=True)
|
||||||
global have_dfl_wallet
|
global have_dfl_wallet
|
||||||
have_dfl_wallet = False
|
have_dfl_wallet = False
|
||||||
ok()
|
ok()
|
||||||
|
|
@ -1222,7 +1349,7 @@ class MMGenTestSuite(object):
|
||||||
([],['--type='+str(mmtype)])[bool(mmtype)] +
|
([],['--type='+str(mmtype)])[bool(mmtype)] +
|
||||||
([],[wf])[bool(wf)] +
|
([],[wf])[bool(wf)] +
|
||||||
([],[id_str])[bool(id_str)] +
|
([],[id_str])[bool(id_str)] +
|
||||||
[cfg['{}_idx_list'.format(cmd_pfx)]])
|
[cfg['{}_idx_list'.format(cmd_pfx)]],extra_desc=('','(segwit)')[mmtype=='segwit'])
|
||||||
t.license()
|
t.license()
|
||||||
t.passphrase('MMGen wallet',cfg['wpasswd'])
|
t.passphrase('MMGen wallet',cfg['wpasswd'])
|
||||||
t.expect('Passphrase is OK')
|
t.expect('Passphrase is OK')
|
||||||
|
|
@ -1232,7 +1359,7 @@ class MMGenTestSuite(object):
|
||||||
k = 'passfile32_chk' if ftype == 'pass32' \
|
k = 'passfile32_chk' if ftype == 'pass32' \
|
||||||
else 'passfile_chk' if ftype == 'pass' \
|
else 'passfile_chk' if ftype == 'pass' \
|
||||||
else '{}file{}_chk'.format(ftype,'_'+mmtype if mmtype else '')
|
else '{}file{}_chk'.format(ftype,'_'+mmtype if mmtype else '')
|
||||||
chk_ref = cfg[k] if ftype[:4] == 'pass' else cfg[k][g.testnet]
|
chk_ref = cfg[k] if ftype[:4] == 'pass' else cfg[k][fork][g.testnet]
|
||||||
refcheck('address data checksum',chk,chk_ref)
|
refcheck('address data checksum',chk,chk_ref)
|
||||||
return
|
return
|
||||||
t.written_to_file('Addresses',oo=True)
|
t.written_to_file('Addresses',oo=True)
|
||||||
|
|
@ -1245,6 +1372,8 @@ class MMGenTestSuite(object):
|
||||||
self.addrgen(name,wf,pf=pf,check_ref=True)
|
self.addrgen(name,wf,pf=pf,check_ref=True)
|
||||||
|
|
||||||
def refaddrgen_compressed(self,name,wf,pf):
|
def refaddrgen_compressed(self,name,wf,pf):
|
||||||
|
if opt.segwit:
|
||||||
|
msg('Skipping non-Segwit address generation'); return True
|
||||||
self.addrgen(name,wf,pf=pf,check_ref=True,mmtype='compressed')
|
self.addrgen(name,wf,pf=pf,check_ref=True,mmtype='compressed')
|
||||||
|
|
||||||
def addrimport(self,name,addrfile):
|
def addrimport(self,name,addrfile):
|
||||||
|
|
@ -1269,7 +1398,10 @@ class MMGenTestSuite(object):
|
||||||
|
|
||||||
if opt.verbose or opt.exact_output: sys.stderr.write('\n')
|
if opt.verbose or opt.exact_output: sys.stderr.write('\n')
|
||||||
|
|
||||||
t = MMGenExpect(name,'mmgen-'+('txcreate','txdo')[bool(txdo_args)],['--rbf','-f',tx_fee] + add_args + cmd_args + txdo_args)
|
t = MMGenExpect(name,
|
||||||
|
'mmgen-'+('txcreate','txdo')[bool(txdo_args)],
|
||||||
|
([],['--rbf'])[g.proto.cap('rbf')] +
|
||||||
|
['-f',tx_fee] + add_args + cmd_args + txdo_args)
|
||||||
t.license()
|
t.license()
|
||||||
|
|
||||||
if txdo_args and add_args: # txdo4
|
if txdo_args and add_args: # txdo4
|
||||||
|
|
@ -1284,10 +1416,10 @@ class MMGenTestSuite(object):
|
||||||
|
|
||||||
# not in tracking wallet warning, (1 + num sources) times
|
# not in tracking wallet warning, (1 + num sources) times
|
||||||
if t.expect(['Continue anyway? (y/N): ',
|
if t.expect(['Continue anyway? (y/N): ',
|
||||||
'Unable to connect to bitcoind']) == 0:
|
'Unable to connect to {}'.format(g.proto.daemon_name)]) == 0:
|
||||||
t.send('y')
|
t.send('y')
|
||||||
else:
|
else:
|
||||||
errmsg(red('Error: unable to connect to bitcoind. Exiting'))
|
errmsg(red('Error: unable to connect to {}. Exiting'.format(g.proto.daemon_name)))
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
for num in tx_data:
|
for num in tx_data:
|
||||||
|
|
@ -1315,6 +1447,8 @@ class MMGenTestSuite(object):
|
||||||
self.txcreate_common(name,sources=['1'])
|
self.txcreate_common(name,sources=['1'])
|
||||||
|
|
||||||
def txbump(self,name,txfile,prepend_args=[],seed_args=[]):
|
def txbump(self,name,txfile,prepend_args=[],seed_args=[]):
|
||||||
|
if not g.proto.cap('rbf'):
|
||||||
|
msg('Skipping RBF'); return True
|
||||||
args = prepend_args + ['-q','-d',cfg['tmpdir'],txfile] + seed_args
|
args = prepend_args + ['-q','-d',cfg['tmpdir'],txfile] + seed_args
|
||||||
t = MMGenExpect(name,'mmgen-txbump',args)
|
t = MMGenExpect(name,'mmgen-txbump',args)
|
||||||
if seed_args:
|
if seed_args:
|
||||||
|
|
@ -1324,7 +1458,7 @@ class MMGenTestSuite(object):
|
||||||
t.expect('deduct the fee from (Hit ENTER for the change output): ','1\n')
|
t.expect('deduct the fee from (Hit ENTER for the change output): ','1\n')
|
||||||
# Fee must be > tx_fee + network relay fee (currently 0.00001)
|
# Fee must be > tx_fee + network relay fee (currently 0.00001)
|
||||||
t.expect('OK? (Y/n): ','\n')
|
t.expect('OK? (Y/n): ','\n')
|
||||||
t.expect('Enter transaction fee: ','124s\n')
|
t.expect('Enter transaction fee: ',txbump_fee+'\n')
|
||||||
t.expect('OK? (Y/n): ','\n')
|
t.expect('OK? (Y/n): ','\n')
|
||||||
if seed_args: # sign and send
|
if seed_args: # sign and send
|
||||||
t.expect('Edit transaction comment? (y/N): ','\n')
|
t.expect('Edit transaction comment? (y/N): ','\n')
|
||||||
|
|
@ -1497,13 +1631,13 @@ class MMGenTestSuite(object):
|
||||||
if cfg['segwit'] and not mmtype: mmtype = 'segwit'
|
if cfg['segwit'] and not mmtype: mmtype = 'segwit'
|
||||||
args = ['-d',cfg['tmpdir'],usr_rand_arg,wf,cfg['addr_idx_list']]
|
args = ['-d',cfg['tmpdir'],usr_rand_arg,wf,cfg['addr_idx_list']]
|
||||||
t = MMGenExpect(name,'mmgen-keygen',
|
t = MMGenExpect(name,'mmgen-keygen',
|
||||||
([],['--type='+str(mmtype)])[bool(mmtype)] + args)
|
([],['--type='+str(mmtype)])[bool(mmtype)] + args,extra_desc=('','(segwit)')[mmtype=='segwit'])
|
||||||
t.license()
|
t.license()
|
||||||
t.passphrase('MMGen wallet',cfg['wpasswd'])
|
t.passphrase('MMGen wallet',cfg['wpasswd'])
|
||||||
chk = t.expect_getend(r'Checksum for key-address data .*?: ',regex=True)
|
chk = t.expect_getend(r'Checksum for key-address data .*?: ',regex=True)
|
||||||
if check_ref:
|
if check_ref:
|
||||||
k = 'keyaddrfile{}_chk'.format('_'+mmtype if mmtype else '')
|
k = 'keyaddrfile{}_chk'.format('_'+mmtype if mmtype else '')
|
||||||
refcheck('key-address data checksum',chk,cfg[k][g.testnet])
|
refcheck('key-address data checksum',chk,cfg[k][fork][g.testnet])
|
||||||
return
|
return
|
||||||
t.expect('Encrypt key list? (y/N): ','y')
|
t.expect('Encrypt key list? (y/N): ','y')
|
||||||
t.usr_rand(usr_rand_chars)
|
t.usr_rand(usr_rand_chars)
|
||||||
|
|
@ -1517,6 +1651,8 @@ class MMGenTestSuite(object):
|
||||||
self.keyaddrgen(name,wf,pf,check_ref=True)
|
self.keyaddrgen(name,wf,pf,check_ref=True)
|
||||||
|
|
||||||
def refkeyaddrgen_compressed(self,name,wf,pf):
|
def refkeyaddrgen_compressed(self,name,wf,pf):
|
||||||
|
if opt.segwit:
|
||||||
|
msg('Skipping non-Segwit key-address generation'); return True
|
||||||
self.keyaddrgen(name,wf,pf,check_ref=True,mmtype='compressed')
|
self.keyaddrgen(name,wf,pf,check_ref=True,mmtype='compressed')
|
||||||
|
|
||||||
def refpasswdgen(self,name,wf,pf):
|
def refpasswdgen(self,name,wf,pf):
|
||||||
|
|
@ -1784,15 +1920,18 @@ class MMGenTestSuite(object):
|
||||||
cmp_or_die(cfg['seed_id'],chk)
|
cmp_or_die(cfg['seed_id'],chk)
|
||||||
|
|
||||||
def ref_addrfile_chk(self,name,ftype='addr'):
|
def ref_addrfile_chk(self,name,ftype='addr'):
|
||||||
wf = os.path.join(ref_dir,cfg['ref_'+ftype+'file'])
|
af_key = 'ref_{}file'.format(ftype)
|
||||||
t = MMGenExpect(name,'mmgen-tool',[ftype.replace('segwit','')+'file_chksum',wf])
|
af = os.path.join(ref_dir,(ref_subdir,'')[ftype=='passwd'],cfg[af_key])
|
||||||
|
t = MMGenExpect(name,'mmgen-tool',[ftype.replace('segwit','')+'file_chksum',af])
|
||||||
if ftype == 'keyaddr':
|
if ftype == 'keyaddr':
|
||||||
w = 'key-address data'
|
w = 'key-address data'
|
||||||
t.hash_preset(w,ref_kafile_hash_preset)
|
t.hash_preset(w,ref_kafile_hash_preset)
|
||||||
t.passphrase(w,ref_kafile_pass)
|
t.passphrase(w,ref_kafile_pass)
|
||||||
t.expect('Check key-to-address validity? (y/N): ','y')
|
t.expect('Check key-to-address validity? (y/N): ','y')
|
||||||
o = t.read().strip().split('\n')[-1]
|
o = t.read().strip().split('\n')[-1]
|
||||||
cmp_or_die(cfg['ref_'+ftype+'file_chksum'],o)
|
rc = cfg['ref_'+ftype+'file_chksum']
|
||||||
|
ref_chksum = rc if ftype == 'passwd' else rc[g.proto.base_coin.lower()][g.testnet]
|
||||||
|
cmp_or_die(ref_chksum,o)
|
||||||
|
|
||||||
def ref_keyaddrfile_chk(self,name):
|
def ref_keyaddrfile_chk(self,name):
|
||||||
self.ref_addrfile_chk(name,ftype='keyaddr')
|
self.ref_addrfile_chk(name,ftype='keyaddr')
|
||||||
|
|
@ -1801,13 +1940,17 @@ class MMGenTestSuite(object):
|
||||||
self.ref_addrfile_chk(name,ftype='passwd')
|
self.ref_addrfile_chk(name,ftype='passwd')
|
||||||
|
|
||||||
def ref_segwitaddrfile_chk(self,name):
|
def ref_segwitaddrfile_chk(self,name):
|
||||||
|
if not 'S' in g.proto.mmtypes:
|
||||||
|
msg_r('Skipping {} (not supported)'.format(name))
|
||||||
|
ok()
|
||||||
|
else:
|
||||||
self.ref_addrfile_chk(name,ftype='segwitaddr')
|
self.ref_addrfile_chk(name,ftype='segwitaddr')
|
||||||
|
|
||||||
# def txcreate8(self,name,addrfile):
|
# def txcreate8(self,name,addrfile):
|
||||||
# self.txcreate_common(name,sources=['8'])
|
# self.txcreate_common(name,sources=['8'])
|
||||||
|
|
||||||
def ref_tx_chk(self,name):
|
def ref_tx_chk(self,name):
|
||||||
tf = os.path.join(ref_dir,cfg['ref_tx_file'])
|
tf = os.path.join(ref_dir,ref_subdir,cfg['ref_tx_file'][g.coin.lower()].format(tn_ext))
|
||||||
wf = os.path.join(ref_dir,cfg['ref_wallet'])
|
wf = os.path.join(ref_dir,cfg['ref_wallet'])
|
||||||
write_to_tmpfile(cfg,pwfile,cfg['wpasswd'])
|
write_to_tmpfile(cfg,pwfile,cfg['wpasswd'])
|
||||||
pf = get_tmpfile_fn(cfg,pwfile)
|
pf = get_tmpfile_fn(cfg,pwfile)
|
||||||
|
|
@ -1885,6 +2028,8 @@ class MMGenTestSuite(object):
|
||||||
extra_desc='(check)')
|
extra_desc='(check)')
|
||||||
|
|
||||||
def regtest_setup(self,name):
|
def regtest_setup(self,name):
|
||||||
|
if g.testnet:
|
||||||
|
die(2,'--testnet option incompatible with regtest test suite')
|
||||||
try: shutil.rmtree(os.path.join(data_dir,'regtest'))
|
try: shutil.rmtree(os.path.join(data_dir,'regtest'))
|
||||||
except: pass
|
except: pass
|
||||||
os.environ['MMGEN_TEST_SUITE'] = '' # mnemonic is piped to stdin, so stop being a terminal
|
os.environ['MMGEN_TEST_SUITE'] = '' # mnemonic is piped to stdin, so stop being a terminal
|
||||||
|
|
@ -1912,79 +2057,86 @@ class MMGenTestSuite(object):
|
||||||
def regtest_user_sid(self,user):
|
def regtest_user_sid(self,user):
|
||||||
return os.path.basename(get_file_with_ext('mmdat',self.regtest_user_dir(user)))[:8]
|
return os.path.basename(get_file_with_ext('mmdat',self.regtest_user_dir(user)))[:8]
|
||||||
|
|
||||||
def regtest_addrgen(self,name,user):
|
def regtest_addrgen(self,name,user,wf=None,passwd='abc',addr_range='1-5'):
|
||||||
for mmtype in ('legacy','compressed','segwit'):
|
from mmgen.addr import MMGenAddrType
|
||||||
|
for mmtype in g.proto.mmtypes:
|
||||||
t = MMGenExpect(name,'mmgen-addrgen',
|
t = MMGenExpect(name,'mmgen-addrgen',
|
||||||
['--quiet','--'+user,'--type='+mmtype,
|
['--quiet','--'+user,'--type='+mmtype,'--outdir={}'.format(self.regtest_user_dir(user))] +
|
||||||
'--outdir={}'.format(self.regtest_user_dir(user)),
|
([],[wf])[bool(wf)] + [addr_range],
|
||||||
'1-5'],extra_desc='({})'.format(mmtype))
|
extra_desc='({})'.format(MMGenAddrType.mmtypes[mmtype]['name']))
|
||||||
t.passphrase('MMGen wallet','abc')
|
t.passphrase('MMGen wallet',passwd)
|
||||||
t.written_to_file('Addresses')
|
t.written_to_file('Addresses')
|
||||||
t.ok()
|
t.ok()
|
||||||
|
|
||||||
def regtest_addrgen_bob(self,name): self.regtest_addrgen(name,'bob')
|
def regtest_addrgen_bob(self,name): self.regtest_addrgen(name,'bob')
|
||||||
def regtest_addrgen_alice(self,name): self.regtest_addrgen(name,'alice')
|
def regtest_addrgen_alice(self,name): self.regtest_addrgen(name,'alice')
|
||||||
|
|
||||||
def regtest_addrimport(self,name,user):
|
def regtest_addrimport(self,name,user,sid=None,addr_range='1-5',num_addrs=5):
|
||||||
id_strs = { 'legacy':'', 'compressed':'-C', 'segwit':'-S' }
|
id_strs = { 'legacy':'', 'compressed':'-C', 'segwit':'-S' }
|
||||||
sid = self.regtest_user_sid(user)
|
if not sid: sid = self.regtest_user_sid(user)
|
||||||
for desc in ('legacy','compressed','segwit'):
|
from mmgen.addr import MMGenAddrType
|
||||||
fn = os.path.join(self.regtest_user_dir(user),'{}{}[1-5].addrs'.format(sid,id_strs[desc]))
|
for mmtype in g.proto.mmtypes:
|
||||||
|
desc = MMGenAddrType.mmtypes[mmtype]['name']
|
||||||
|
fn = os.path.join(self.regtest_user_dir(user),
|
||||||
|
'{}{}{}[{}].addrs'.format(sid,altcoin_pfx,id_strs[desc],addr_range))
|
||||||
t = MMGenExpect(name,'mmgen-addrimport', ['--quiet','--'+user,'--batch',fn],extra_desc='('+desc+')')
|
t = MMGenExpect(name,'mmgen-addrimport', ['--quiet','--'+user,'--batch',fn],extra_desc='('+desc+')')
|
||||||
t.expect('Importing')
|
t.expect('Importing')
|
||||||
t.expect('5 addresses imported')
|
t.expect('{} addresses imported'.format(num_addrs))
|
||||||
t.ok()
|
t.ok()
|
||||||
|
|
||||||
def regtest_addrimport_bob(self,name): self.regtest_addrimport(name,'bob')
|
def regtest_addrimport_bob(self,name): self.regtest_addrimport(name,'bob')
|
||||||
def regtest_addrimport_alice(self,name): self.regtest_addrimport(name,'alice')
|
def regtest_addrimport_alice(self,name): self.regtest_addrimport(name,'alice')
|
||||||
|
|
||||||
def regtest_fund_wallet(self,name,user,mmtype,amt):
|
def regtest_fund_wallet(self,name,user,mmtype,amt,sid=None,addr_range='1-5'):
|
||||||
sid = self.regtest_user_sid(user)
|
if not sid: sid = self.regtest_user_sid(user)
|
||||||
addr = self.get_addr_from_regtest_addrlist(user,sid,mmtype,0)
|
addr = self.get_addr_from_regtest_addrlist(user,sid,mmtype,0,addr_range=addr_range)
|
||||||
t = MMGenExpect(name,'mmgen-regtest', ['send',str(addr),str(amt)])
|
t = MMGenExpect(name,'mmgen-regtest', ['send',str(addr),str(amt)])
|
||||||
t.expect('Sending {} BTC'.format(amt))
|
t.expect('Sending {} {}'.format(amt,g.coin))
|
||||||
t.expect('Mined 1 block')
|
t.expect('Mined 1 block')
|
||||||
t.ok()
|
t.ok()
|
||||||
|
|
||||||
def regtest_fund_bob(self,name): self.regtest_fund_wallet(name,'bob','C',500)
|
def regtest_fund_bob(self,name): self.regtest_fund_wallet(name,'bob','C',rtFundAmt)
|
||||||
def regtest_fund_alice(self,name): self.regtest_fund_wallet(name,'alice','S',500)
|
def regtest_fund_alice(self,name): self.regtest_fund_wallet(name,'alice',('L','S')[g.proto.cap('segwit')],rtFundAmt)
|
||||||
|
|
||||||
def regtest_user_bal(self,name,user,bal):
|
def regtest_user_bal(self,name,user,bal):
|
||||||
t = MMGenExpect(name,'mmgen-tool',['--'+user,'listaddresses','showempty=1'])
|
t = MMGenExpect(name,'mmgen-tool',['--'+user,'listaddresses','showempty=1'])
|
||||||
total = t.expect_getend('TOTAL: ')
|
total = t.expect_getend('TOTAL: ')
|
||||||
cmp_or_die(total,'{} BTC'.format(bal))
|
cmp_or_die(total,'{} {}'.format(bal,g.coin))
|
||||||
|
|
||||||
def regtest_alice_bal1(self,name):
|
def regtest_alice_bal1(self,name):
|
||||||
return self.regtest_user_bal(name,'alice','500')
|
return self.regtest_user_bal(name,'alice',rtFundAmt)
|
||||||
|
|
||||||
def regtest_bob_bal1(self,name):
|
def regtest_bob_bal1(self,name):
|
||||||
return self.regtest_user_bal(name,'bob','500')
|
return self.regtest_user_bal(name,'bob',rtFundAmt)
|
||||||
|
|
||||||
def regtest_bob_bal2(self,name):
|
def regtest_bob_bal2(self,name):
|
||||||
return self.regtest_user_bal(name,'bob','499.999942')
|
return self.regtest_user_bal(name,'bob',rtBals[0])
|
||||||
|
|
||||||
def regtest_bob_bal3(self,name):
|
def regtest_bob_bal3(self,name):
|
||||||
return self.regtest_user_bal(name,'bob','399.9998214')
|
return self.regtest_user_bal(name,'bob',rtBals[1])
|
||||||
|
|
||||||
def regtest_bob_bal4(self,name):
|
def regtest_bob_bal4(self,name):
|
||||||
return self.regtest_user_bal(name,'bob','399.9998079')
|
return self.regtest_user_bal(name,'bob',rtBals[2])
|
||||||
|
|
||||||
def regtest_bob_bal5(self,name):
|
def regtest_bob_bal5(self,name):
|
||||||
return self.regtest_user_bal(name,'bob','399.9996799')
|
return self.regtest_user_bal(name,'bob',rtBals[3])
|
||||||
|
|
||||||
def regtest_bob_alice_bal(self,name):
|
def regtest_bob_alice_bal(self,name):
|
||||||
t = MMGenExpect(name,'mmgen-regtest',['get_balances'])
|
t = MMGenExpect(name,'mmgen-regtest',['get_balances'])
|
||||||
t.expect('Switching')
|
t.expect('Switching')
|
||||||
ret = t.expect_getend("Bob's balance:").strip()
|
ret = t.expect_getend("Bob's balance:").strip()
|
||||||
cmp_or_die(ret,'13.00000000',skip_ok=True)
|
cmp_or_die(ret,rtBals[4],skip_ok=True)
|
||||||
ret = t.expect_getend("Alice's balance:").strip()
|
ret = t.expect_getend("Alice's balance:").strip()
|
||||||
cmp_or_die(ret,'986.99957990',skip_ok=True)
|
cmp_or_die(ret,rtBals[5],skip_ok=True)
|
||||||
|
ret = t.expect_getend("Total balance:").strip()
|
||||||
|
cmp_or_die(ret,rtBals[6],skip_ok=True)
|
||||||
t.ok()
|
t.ok()
|
||||||
|
|
||||||
def regtest_user_txdo(self,name,user,fee,outputs_cl,outputs_prompt,extra_args=[],no_send=False):
|
def regtest_user_txdo(self,name,user,fee,outputs_cl,outputs_prompt,extra_args=[],wf=None,pw='abc',no_send=False,do_label=False):
|
||||||
os.environ['MMGEN_BOGUS_SEND'] = ''
|
os.environ['MMGEN_BOGUS_SEND'] = ''
|
||||||
t = MMGenExpect(name,'mmgen-txdo',
|
t = MMGenExpect(name,'mmgen-txdo',
|
||||||
['-d',cfg['tmpdir'],'-B','--'+user,'--tx-fee='+fee] + extra_args + outputs_cl)
|
['-d',cfg['tmpdir'],'-B','--'+user,'--tx-fee='+fee]
|
||||||
|
+ extra_args + ([],[wf])[bool(wf)] + outputs_cl)
|
||||||
os.environ['MMGEN_BOGUS_SEND'] = '1'
|
os.environ['MMGEN_BOGUS_SEND'] = '1'
|
||||||
|
|
||||||
t.expect(r"'q'=quit view, .*?:.",'M',regex=True) # sort by mmid
|
t.expect(r"'q'=quit view, .*?:.",'M',regex=True) # sort by mmid
|
||||||
|
|
@ -1992,10 +2144,15 @@ class MMGenTestSuite(object):
|
||||||
t.expect('outputs to spend: ',outputs_prompt+'\n')
|
t.expect('outputs to spend: ',outputs_prompt+'\n')
|
||||||
t.expect('OK? (Y/n): ','y') # fee OK?
|
t.expect('OK? (Y/n): ','y') # fee OK?
|
||||||
t.expect('OK? (Y/n): ','y') # change OK?
|
t.expect('OK? (Y/n): ','y') # change OK?
|
||||||
|
if do_label:
|
||||||
|
t.expect('Add a comment to transaction? (y/N): ','y')
|
||||||
|
t.expect('Comment: ',ref_tx_label.encode('utf8')+'\n')
|
||||||
|
t.expect('View decoded transaction\? .*?: ','n',regex=True)
|
||||||
|
else:
|
||||||
t.expect('Add a comment to transaction? (y/N): ','\n')
|
t.expect('Add a comment to transaction? (y/N): ','\n')
|
||||||
t.expect('View decoded transaction\? .*?: ','t',regex=True)
|
t.expect('View decoded transaction\? .*?: ','t',regex=True)
|
||||||
t.expect('to continue: ','\n')
|
t.expect('to continue: ','\n')
|
||||||
t.passphrase('MMGen wallet','abc')
|
t.passphrase('MMGen wallet',pw)
|
||||||
t.written_to_file('Signed transaction')
|
t.written_to_file('Signed transaction')
|
||||||
if not no_send:
|
if not no_send:
|
||||||
t.expect('to confirm: ','YES, I REALLY WANT TO DO THIS\n')
|
t.expect('to confirm: ','YES, I REALLY WANT TO DO THIS\n')
|
||||||
|
|
@ -2005,16 +2162,18 @@ class MMGenTestSuite(object):
|
||||||
|
|
||||||
def regtest_bob_split1(self,name):
|
def regtest_bob_split1(self,name):
|
||||||
sid = self.regtest_user_sid('bob')
|
sid = self.regtest_user_sid('bob')
|
||||||
outputs_cl = [sid+':C:1,100', sid+':L:2,200',sid+':S:2']
|
outputs_cl = [sid+':C:1,100', sid+':L:2,200',sid+':'+rtBobOp3]
|
||||||
return self.regtest_user_txdo(name,'bob','20s',outputs_cl,'1')
|
return self.regtest_user_txdo(name,'bob',rtFee[0],outputs_cl,'1',do_label=True)
|
||||||
|
|
||||||
def get_addr_from_regtest_addrlist(self,user,sid,mmtype,idx):
|
def get_addr_from_regtest_addrlist(self,user,sid,mmtype,idx,addr_range='1-5'):
|
||||||
id_str = { 'L':'', 'S':'-S', 'C':'-C' }[mmtype]
|
id_str = { 'L':'', 'S':'-S', 'C':'-C' }[mmtype]
|
||||||
fn = get_file_with_ext('{}{}[1-5].addrs'.format(sid,id_str),self.regtest_user_dir(user),no_dot=True)
|
ext = '{}{}{}[{}].addrs'.format(sid,altcoin_pfx,id_str,addr_range)
|
||||||
|
fn = get_file_with_ext(ext,self.regtest_user_dir(user),no_dot=True)
|
||||||
silence()
|
silence()
|
||||||
g.proto = get_coin_protocol(g.coin,True)
|
psave = g.proto
|
||||||
|
g.proto = CoinProtocol(g.coin,True)
|
||||||
addr = AddrList(fn).data[idx].addr
|
addr = AddrList(fn).data[idx].addr
|
||||||
g.proto = get_coin_protocol(g.coin,g.testnet)
|
g.proto = psave
|
||||||
end_silence()
|
end_silence()
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
|
|
@ -2024,15 +2183,21 @@ class MMGenTestSuite(object):
|
||||||
|
|
||||||
def regtest_bob_rbf_send(self,name):
|
def regtest_bob_rbf_send(self,name):
|
||||||
outputs_cl = self.create_tx_outputs('alice',(('L',1,',60'),('C',1,',40'))) # alice_sid:L:1, alice_sid:C:1
|
outputs_cl = self.create_tx_outputs('alice',(('L',1,',60'),('C',1,',40'))) # alice_sid:L:1, alice_sid:C:1
|
||||||
outputs_cl += [self.regtest_user_sid('bob')+':S:2']
|
outputs_cl += [self.regtest_user_sid('bob')+':'+rtBobOp3]
|
||||||
return self.regtest_user_txdo(name,'bob','10s',outputs_cl,'3',extra_args=['--rbf'])
|
return self.regtest_user_txdo(name,'bob',rtFee[1],outputs_cl,'3',
|
||||||
|
extra_args=([],['--rbf'])[g.proto.cap('rbf')])
|
||||||
|
|
||||||
def regtest_bob_send_non_mmgen(self,name):
|
def regtest_bob_send_non_mmgen(self,name):
|
||||||
outputs_cl = self.create_tx_outputs('alice',(('S',2,',10'),('S',3,''))) # alice_sid:S:2, alice_sid:S:3
|
outputs_cl = self.create_tx_outputs('alice',(
|
||||||
|
(('L','S')[g.proto.cap('segwit')],2,',10'),
|
||||||
|
(('L','S')[g.proto.cap('segwit')],3,'')
|
||||||
|
)) # alice_sid:S:2, alice_sid:S:3
|
||||||
fn = os.path.join(cfg['tmpdir'],'non-mmgen.keys')
|
fn = os.path.join(cfg['tmpdir'],'non-mmgen.keys')
|
||||||
return self.regtest_user_txdo(name,'bob','0.0001',outputs_cl,'3-9',extra_args=['--keys-from-file='+fn])
|
return self.regtest_user_txdo(name,'bob',rtFee[3],outputs_cl,'3-9',extra_args=['--keys-from-file='+fn])
|
||||||
|
|
||||||
def regtest_user_txbump(self,name,user,txfile,fee,red_op,no_send=False):
|
def regtest_user_txbump(self,name,user,txfile,fee,red_op,no_send=False):
|
||||||
|
if not g.proto.cap('rbf'):
|
||||||
|
msg('Skipping RBF'); return True
|
||||||
os.environ['MMGEN_BOGUS_SEND'] = ''
|
os.environ['MMGEN_BOGUS_SEND'] = ''
|
||||||
t = MMGenExpect(name,'mmgen-txbump',
|
t = MMGenExpect(name,'mmgen-txbump',
|
||||||
['-d',cfg['tmpdir'],'--send','--'+user,'--tx-fee='+fee,'--output-to-reduce='+red_op] + [txfile])
|
['-d',cfg['tmpdir'],'--send','--'+user,'--tx-fee='+fee,'--output-to-reduce='+red_op] + [txfile])
|
||||||
|
|
@ -2050,8 +2215,8 @@ class MMGenTestSuite(object):
|
||||||
t.ok()
|
t.ok()
|
||||||
|
|
||||||
def regtest_bob_rbf_bump(self,name):
|
def regtest_bob_rbf_bump(self,name):
|
||||||
txfile = get_file_with_ext(',10].sigtx',cfg['tmpdir'],delete=False,no_dot=True)
|
txfile = get_file_with_ext(',{}].sigtx'.format(rtFee[1][:-1]),cfg['tmpdir'],delete=False,no_dot=True)
|
||||||
return self.regtest_user_txbump(name,'bob',txfile,'60s','c')
|
return self.regtest_user_txbump(name,'bob',txfile,rtFee[2],'c')
|
||||||
|
|
||||||
def regtest_generate(self,name):
|
def regtest_generate(self,name):
|
||||||
t = MMGenExpect(name,'mmgen-regtest',['generate'])
|
t = MMGenExpect(name,'mmgen-regtest',['generate'])
|
||||||
|
|
@ -2070,6 +2235,8 @@ class MMGenTestSuite(object):
|
||||||
ok()
|
ok()
|
||||||
|
|
||||||
def regtest_get_mempool2(self,name):
|
def regtest_get_mempool2(self,name):
|
||||||
|
if not g.proto.cap('rbf'):
|
||||||
|
msg('Skipping post-RBF mempool check'); return True
|
||||||
mp = self.regtest_get_mempool(name)
|
mp = self.regtest_get_mempool(name)
|
||||||
if len(mp) != 1:
|
if len(mp) != 1:
|
||||||
rdie(2,'Mempool has more or less than one TX!')
|
rdie(2,'Mempool has more or less than one TX!')
|
||||||
|
|
@ -2081,14 +2248,14 @@ class MMGenTestSuite(object):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def gen_pairs(n):
|
def gen_pairs(n):
|
||||||
return [subprocess.check_output(
|
return [subprocess.check_output(
|
||||||
['python','mmgen-tool','--testnet=1','-r0','randpair','compressed={}'.format((i+1)%2)]).split()
|
['python',os.path.join('cmds','mmgen-tool'),'--testnet=1','-r0','randpair','compressed={}'.format((i+1)%2)]).split()
|
||||||
for i in range(n)]
|
for i in range(n)]
|
||||||
|
|
||||||
def regtest_bob_pre_import(self,name):
|
def regtest_bob_pre_import(self,name):
|
||||||
pairs = self.gen_pairs(5)
|
pairs = self.gen_pairs(5)
|
||||||
write_to_tmpfile(cfg,'non-mmgen.keys','\n'.join([a[0] for a in pairs])+'\n')
|
write_to_tmpfile(cfg,'non-mmgen.keys','\n'.join([a[0] for a in pairs])+'\n')
|
||||||
write_to_tmpfile(cfg,'non-mmgen.addrs','\n'.join([a[1] for a in pairs])+'\n')
|
write_to_tmpfile(cfg,'non-mmgen.addrs','\n'.join([a[1] for a in pairs])+'\n')
|
||||||
return self.regtest_user_txdo(name,'bob','10s',[pairs[0][1]],'3')
|
return self.regtest_user_txdo(name,'bob',rtFee[4],[pairs[0][1]],'3')
|
||||||
|
|
||||||
def regtest_user_import(self,name,user,args):
|
def regtest_user_import(self,name,user,args):
|
||||||
t = MMGenExpect(name,'mmgen-addrimport',['--quiet','--'+user]+args)
|
t = MMGenExpect(name,'mmgen-addrimport',['--quiet','--'+user]+args)
|
||||||
|
|
@ -2109,8 +2276,8 @@ class MMGenTestSuite(object):
|
||||||
amts = (a for a in (1.12345678,2.87654321,3.33443344,4.00990099,5.43214321))
|
amts = (a for a in (1.12345678,2.87654321,3.33443344,4.00990099,5.43214321))
|
||||||
outputs1 = ['{},{}'.format(a,amts.next()) for a in addrs]
|
outputs1 = ['{},{}'.format(a,amts.next()) for a in addrs]
|
||||||
sid = self.regtest_user_sid('bob')
|
sid = self.regtest_user_sid('bob')
|
||||||
outputs2 = [sid+':C:2,6', sid+':L:3,7',sid+':S:3']
|
outputs2 = [sid+':C:2,6', sid+':L:3,7',sid+(':L:1',':S:3')[g.proto.cap('segwit')]]
|
||||||
return self.regtest_user_txdo(name,'bob','20s',outputs1+outputs2,'1-2')
|
return self.regtest_user_txdo(name,'bob',rtFee[5],outputs1+outputs2,'1-2')
|
||||||
|
|
||||||
def regtest_user_add_label(self,name,user,addr,label):
|
def regtest_user_add_label(self,name,user,addr,label):
|
||||||
t = MMGenExpect(name,'mmgen-tool',['--'+user,'add_label',addr,label])
|
t = MMGenExpect(name,'mmgen-tool',['--'+user,'add_label',addr,label])
|
||||||
|
|
@ -2124,15 +2291,15 @@ class MMGenTestSuite(object):
|
||||||
|
|
||||||
def regtest_alice_add_label1(self,name):
|
def regtest_alice_add_label1(self,name):
|
||||||
sid = self.regtest_user_sid('alice')
|
sid = self.regtest_user_sid('alice')
|
||||||
return self.regtest_user_add_label(name,'alice',sid+':S:1','Original Label')
|
return self.regtest_user_add_label(name,'alice',sid+':C:1','Original Label')
|
||||||
|
|
||||||
def regtest_alice_add_label2(self,name):
|
def regtest_alice_add_label2(self,name):
|
||||||
sid = self.regtest_user_sid('alice')
|
sid = self.regtest_user_sid('alice')
|
||||||
return self.regtest_user_add_label(name,'alice',sid+':S:1','Replacement Label')
|
return self.regtest_user_add_label(name,'alice',sid+':C:1','Replacement Label')
|
||||||
|
|
||||||
def regtest_alice_remove_label1(self,name):
|
def regtest_alice_remove_label1(self,name):
|
||||||
sid = self.regtest_user_sid('alice')
|
sid = self.regtest_user_sid('alice')
|
||||||
return self.regtest_user_remove_label(name,'alice',sid+':S:1')
|
return self.regtest_user_remove_label(name,'alice',sid+':C:1')
|
||||||
|
|
||||||
def regtest_user_chk_label(self,name,user,addr,label):
|
def regtest_user_chk_label(self,name,user,addr,label):
|
||||||
t = MMGenExpect(name,'mmgen-tool',['--'+user,'listaddresses','all_labels=1'])
|
t = MMGenExpect(name,'mmgen-tool',['--'+user,'listaddresses','all_labels=1'])
|
||||||
|
|
@ -2141,19 +2308,19 @@ class MMGenTestSuite(object):
|
||||||
|
|
||||||
def regtest_alice_chk_label1(self,name):
|
def regtest_alice_chk_label1(self,name):
|
||||||
sid = self.regtest_user_sid('alice')
|
sid = self.regtest_user_sid('alice')
|
||||||
return self.regtest_user_chk_label(name,'alice',sid+':S:1','Original Label')
|
return self.regtest_user_chk_label(name,'alice',sid+':C:1','Original Label')
|
||||||
|
|
||||||
def regtest_alice_chk_label2(self,name):
|
def regtest_alice_chk_label2(self,name):
|
||||||
sid = self.regtest_user_sid('alice')
|
sid = self.regtest_user_sid('alice')
|
||||||
return self.regtest_user_chk_label(name,'alice',sid+':S:1','Replacement Label')
|
return self.regtest_user_chk_label(name,'alice',sid+':C:1','Replacement Label')
|
||||||
|
|
||||||
def regtest_alice_chk_label3(self,name):
|
def regtest_alice_chk_label3(self,name):
|
||||||
sid = self.regtest_user_sid('alice')
|
sid = self.regtest_user_sid('alice')
|
||||||
return self.regtest_user_chk_label(name,'alice',sid+':S:1','Edited Label')
|
return self.regtest_user_chk_label(name,'alice',sid+':C:1','Edited Label')
|
||||||
|
|
||||||
def regtest_alice_chk_label4(self,name):
|
def regtest_alice_chk_label4(self,name):
|
||||||
sid = self.regtest_user_sid('alice')
|
sid = self.regtest_user_sid('alice')
|
||||||
return self.regtest_user_chk_label(name,'alice',sid+':S:1','-')
|
return self.regtest_user_chk_label(name,'alice',sid+':C:1','-')
|
||||||
|
|
||||||
def regtest_user_edit_label(self,name,user,output,label):
|
def regtest_user_edit_label(self,name,user,output,label):
|
||||||
t = MMGenExpect(name,'mmgen-txcreate',['-B','--'+user,'-i'])
|
t = MMGenExpect(name,'mmgen-txcreate',['-B','--'+user,'-i'])
|
||||||
|
|
@ -2165,12 +2332,99 @@ class MMGenTestSuite(object):
|
||||||
t.ok()
|
t.ok()
|
||||||
|
|
||||||
def regtest_alice_edit_label1(self,name):
|
def regtest_alice_edit_label1(self,name):
|
||||||
return self.regtest_user_edit_label(name,'alice','3','Edited Label')
|
return self.regtest_user_edit_label(name,'alice','1','Edited Label')
|
||||||
|
|
||||||
def regtest_stop(self,name):
|
def regtest_stop(self,name):
|
||||||
t = MMGenExpect(name,'mmgen-regtest',['stop'])
|
t = MMGenExpect(name,'mmgen-regtest',['stop'])
|
||||||
t.ok()
|
t.ok()
|
||||||
|
|
||||||
|
# regtest undocumented admin commands
|
||||||
|
ref_tx_setup = regtest_setup
|
||||||
|
ref_tx_generate = regtest_generate
|
||||||
|
|
||||||
|
def ref_tx_addrgen_bob_ref_wallet(self,name):
|
||||||
|
wf = os.path.join(ref_dir,cfg['ref_wallet'])
|
||||||
|
self.regtest_addrgen(name,'bob',wf=wf,passwd=cfg['wpasswd'],addr_range='1-100')
|
||||||
|
|
||||||
|
def ref_tx_addrimport_bob_ref_wallet(self,name):
|
||||||
|
self.regtest_addrimport(name,'bob',sid=cfg['seed_id'],addr_range='1-100',num_addrs=100)
|
||||||
|
|
||||||
|
def ref_tx_fund_bob(self,name):
|
||||||
|
mmtype = g.proto.mmtypes[-1]
|
||||||
|
self.regtest_fund_wallet(name,'bob',mmtype,rtFundAmt,sid=cfg['seed_id'],addr_range='1-100')
|
||||||
|
|
||||||
|
def ref_tx_bob_split(self,name):
|
||||||
|
sid = cfg['seed_id']
|
||||||
|
outputs_cl = [sid+':C:1,100', sid+':L:2,200',sid+':'+rtBobOp3]
|
||||||
|
wf = os.path.join(ref_dir,cfg['ref_wallet'])
|
||||||
|
return self.regtest_user_txdo(name,'bob',rtFee[0],outputs_cl,'1',do_label=True,wf=wf,pw=cfg['wpasswd'])
|
||||||
|
|
||||||
|
def ref_tx_bob_create_tx(self,name):
|
||||||
|
sid = cfg['seed_id']
|
||||||
|
psave = g.proto
|
||||||
|
g.proto = CoinProtocol(g.coin,True)
|
||||||
|
privhex = PrivKey(os.urandom(32),compressed=True)
|
||||||
|
addr = AddrGenerator('p2pkh').to_addr(KeyGenerator().to_pubhex(privhex))
|
||||||
|
g.proto = psave
|
||||||
|
outputs_cl = [sid+':{}:3,1.1234'.format(g.proto.mmtypes[-1]), sid+':C:5,5.5555',sid+':L:4',addr+',100']
|
||||||
|
pw = cfg['wpasswd']
|
||||||
|
# create tx in cwd
|
||||||
|
t = MMGenExpect(name,'mmgen-txcreate',['-B','--bob','--tx-fee='+rtFee[0]] + outputs_cl)
|
||||||
|
# [os.path.join(ref_dir,cfg['ref_wallet'])])
|
||||||
|
t.expect(r"'q'=quit view, .*?:.",'M',regex=True) # sort by mmid
|
||||||
|
t.expect(r"'q'=quit view, .*?:.",'q',regex=True)
|
||||||
|
t.expect('outputs to spend: ','1 2 3\n')
|
||||||
|
t.expect('OK? (Y/n): ','y') # fee OK?
|
||||||
|
t.expect('OK? (Y/n): ','y') # change OK?
|
||||||
|
t.expect('Add a comment to transaction? (y/N): ','y')
|
||||||
|
t.expect('Comment: ',ref_tx_label.encode('utf8')+'\n')
|
||||||
|
t.expect('View decoded transaction\? .*?: ','n',regex=True)
|
||||||
|
t.expect('Save transaction? (y/N): ','y')
|
||||||
|
fn = t.written_to_file('Transaction')
|
||||||
|
write_to_tmpfile(cfg,'ref_tx_fn',fn)
|
||||||
|
t.ok()
|
||||||
|
|
||||||
|
def ref_tx_bob_modify_tx(self,name):
|
||||||
|
MMGenExpect(name,'',msg_only=True)
|
||||||
|
fn = read_from_tmpfile(cfg,'ref_tx_fn')
|
||||||
|
with open(fn) as f:
|
||||||
|
lines = f.read().splitlines()
|
||||||
|
|
||||||
|
from mmgen.obj import BTCAmt,LTCAmt,BCHAmt
|
||||||
|
tx = {}
|
||||||
|
for k,i in (('in',3),('out',4)):
|
||||||
|
tx[k] = eval(lines[i])
|
||||||
|
tx[k+'_addrs'] = [i['addr'] for i in tx[k]]
|
||||||
|
|
||||||
|
psave = g.proto
|
||||||
|
g.proto = CoinProtocol(g.coin,True)
|
||||||
|
from mmgen.obj import CoinAddr
|
||||||
|
for k in ('in_addrs','out_addrs'):
|
||||||
|
tx[k+'_hex'] = [(CoinAddr(a).hex,CoinAddr(a).addr_fmt) for a in tx[k]]
|
||||||
|
g.proto = psave
|
||||||
|
|
||||||
|
for k in ('in_addrs','out_addrs'):
|
||||||
|
tx[k+'_conv'] = [g.proto.hexaddr2addr(h,(False,True)[f=='p2sh']) for h,f in tx[k+'_hex']]
|
||||||
|
|
||||||
|
for k in ('in','out'):
|
||||||
|
for i in range(len(tx[k])):
|
||||||
|
tx[k][i]['addr'] = tx[k+'_addrs_conv'][i]
|
||||||
|
|
||||||
|
lines_tn = [lines[1].replace('REGTEST','TESTNET')] + lines[2:]
|
||||||
|
o_tn = '\n'.join([make_chksum_6(' '.join(lines_tn))] + lines_tn)+'\n'
|
||||||
|
fn_tn = fn.replace('.rawtx','.testnet.rawtx')
|
||||||
|
|
||||||
|
lines_mn = [lines[1].replace('REGTEST','MAINNET'),
|
||||||
|
lines[2],
|
||||||
|
repr(tx['in']),
|
||||||
|
repr(tx['out'])] + lines[5:]
|
||||||
|
o_mn = '\n'.join([make_chksum_6(' '.join(lines_mn))] + lines_mn)+'\n'
|
||||||
|
fn_mn = fn.replace('.rawtx','.mainnet.rawtx')
|
||||||
|
ok()
|
||||||
|
|
||||||
|
write_data_to_file(fn_tn,o_tn,'testnet TX data',ask_overwrite=False)
|
||||||
|
write_data_to_file(fn_mn,o_mn,'mainnet TX data',ask_overwrite=False)
|
||||||
|
|
||||||
# END methods
|
# END methods
|
||||||
for k in (
|
for k in (
|
||||||
'ref_wallet_conv',
|
'ref_wallet_conv',
|
||||||
|
|
@ -2244,6 +2498,11 @@ def end_msg():
|
||||||
|
|
||||||
ts = MMGenTestSuite()
|
ts = MMGenTestSuite()
|
||||||
|
|
||||||
|
if cmd_args and cmd_args[0] == 'admin':
|
||||||
|
cmd_args.pop(0)
|
||||||
|
cmd_data = cmd_data_admin
|
||||||
|
cmd_list = cmd_list_admin
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if cmd_args:
|
if cmd_args:
|
||||||
for arg in cmd_args:
|
for arg in cmd_args:
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,9 @@ test/tooltest.py: Tests for the 'mmgen-tool' utility
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys,os,subprocess
|
import sys,os,subprocess
|
||||||
pn = os.path.dirname(sys.argv[0])
|
repo_root = os.path.normpath(os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]),os.pardir)))
|
||||||
os.chdir(os.path.join(pn,os.pardir))
|
os.chdir(repo_root)
|
||||||
sys.path.__setitem__(0,os.path.abspath(os.curdir))
|
sys.path.__setitem__(0,repo_root)
|
||||||
|
|
||||||
# Import this _after_ local path's been added to sys.path
|
# Import this _after_ local path's been added to sys.path
|
||||||
from mmgen.common import *
|
from mmgen.common import *
|
||||||
|
|
@ -54,8 +54,8 @@ cmd_data = OrderedDict([
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
('bitcoin', {
|
('cryptocoin', {
|
||||||
'desc': 'Bitcoin address/key commands',
|
'desc': 'Cryptocoin address/key commands',
|
||||||
'cmd_data': OrderedDict([
|
'cmd_data': OrderedDict([
|
||||||
('Randwif', ()),
|
('Randwif', ()),
|
||||||
('Randpair', ()), # create 3 pairs: uncomp,comp,segwit
|
('Randpair', ()), # create 3 pairs: uncomp,comp,segwit
|
||||||
|
|
@ -91,7 +91,7 @@ cmd_data = OrderedDict([
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
('rpc', {
|
('rpc', {
|
||||||
'desc': 'Bitcoind RPC commands',
|
'desc': 'Coin daemon RPC commands',
|
||||||
'cmd_data': OrderedDict([
|
'cmd_data': OrderedDict([
|
||||||
# ('keyaddrfile_chksum', ()), # interactive
|
# ('keyaddrfile_chksum', ()), # interactive
|
||||||
('Addrfile_chksum', ()),
|
('Addrfile_chksum', ()),
|
||||||
|
|
@ -110,9 +110,16 @@ cfg = {
|
||||||
'tmpdir': 'test/tmp10',
|
'tmpdir': 'test/tmp10',
|
||||||
'tmpdir_num': 10,
|
'tmpdir_num': 10,
|
||||||
'refdir': 'test/ref',
|
'refdir': 'test/ref',
|
||||||
'txfile': 'FFB367[1.234]{}.rawtx',
|
'txfile': {
|
||||||
'addrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.addrs',
|
'btc': 'FFB367[1.234]{}.rawtx',
|
||||||
'addrfile_chk': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'),
|
'bch': '99BE60-BCH[106.6789]{}.rawtx',
|
||||||
|
'ltc': '75F455-LTC[106.6789]{}.rawtx',
|
||||||
|
},
|
||||||
|
'addrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs',
|
||||||
|
'addrfile_chk': {
|
||||||
|
'btc': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'),
|
||||||
|
'ltc': ('AD52 C3FE 8924 AAF0','5738 5C4F 167C F9AE'),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
opts_data = lambda: {
|
opts_data = lambda: {
|
||||||
|
|
@ -136,14 +143,22 @@ If no command is given, the whole suite of tests is run.
|
||||||
sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
|
sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
|
||||||
|
|
||||||
cmd_args = opts.init(opts_data,add_opts=['exact_output','profile'])
|
cmd_args = opts.init(opts_data,add_opts=['exact_output','profile'])
|
||||||
spawn_cmd = ['python',os.path.join(os.curdir,'mmgen-tool') if not opt.system else 'mmgen-tool']
|
|
||||||
add_spawn_args = ' '.join(['{} {}'.format(
|
|
||||||
'--'+k.replace('_','-'),
|
|
||||||
getattr(opt,k) if getattr(opt,k) != True else ''
|
|
||||||
) for k in ('testnet','rpc_host','regtest') if getattr(opt,k)]).split()
|
|
||||||
add_spawn_args += [ '--data-dir', cfg['tmpdir']] # ignore ~/.mmgen
|
|
||||||
|
|
||||||
if opt.system: sys.path.pop(0)
|
ref_subdir = '' if g.proto.base_coin == 'BTC' else g.proto.name
|
||||||
|
altcoin_pfx = '' if g.proto.base_coin == 'BTC' else '-'+g.proto.base_coin
|
||||||
|
tn_ext = ('','.testnet')[g.testnet]
|
||||||
|
|
||||||
|
mmgen_cmd = 'mmgen-tool'
|
||||||
|
|
||||||
|
if not opt.system:
|
||||||
|
os.environ['PYTHONPATH'] = repo_root
|
||||||
|
mmgen_cmd = os.path.relpath(os.path.join(repo_root,'cmds',mmgen_cmd))
|
||||||
|
|
||||||
|
spawn_cmd = ([],['python'])[g.platform == 'win'] + [mmgen_cmd]
|
||||||
|
|
||||||
|
add_spawn_args = ['--data-dir='+cfg['tmpdir']] + ['--{}{}'.format(
|
||||||
|
k.replace('_','-'),'='+getattr(opt,k) if getattr(opt,k) != True else '')
|
||||||
|
for k in ('testnet','rpc_host','regtest','coin') if getattr(opt,k)]
|
||||||
|
|
||||||
if opt.list_cmds:
|
if opt.list_cmds:
|
||||||
fs = ' {:<{w}} - {}'
|
fs = ' {:<{w}} - {}'
|
||||||
|
|
@ -320,7 +335,7 @@ class MMGenToolTestSuite(object):
|
||||||
d = read_from_tmpfile(cfg,of,binary=True)
|
d = read_from_tmpfile(cfg,of,binary=True)
|
||||||
cmp_or_die(dlen,len(d))
|
cmp_or_die(dlen,len(d))
|
||||||
|
|
||||||
# Bitcoin
|
# Cryptocoin
|
||||||
def Randwif(self,name):
|
def Randwif(self,name):
|
||||||
for n,k in enumerate(['','compressed=1']):
|
for n,k in enumerate(['','compressed=1']):
|
||||||
ret = self.run_cmd_out(name,kwargs=k,Return=True,fn_idx=n+1)
|
ret = self.run_cmd_out(name,kwargs=k,Return=True,fn_idx=n+1)
|
||||||
|
|
@ -329,7 +344,7 @@ class MMGenToolTestSuite(object):
|
||||||
for n,k in enumerate(['','compressed=1','segwit=1 compressed=1']):
|
for n,k in enumerate(['','compressed=1','segwit=1 compressed=1']):
|
||||||
wif,addr = self.run_cmd_out(name,kwargs=k,Return=True,fn_idx=n+1).split()
|
wif,addr = self.run_cmd_out(name,kwargs=k,Return=True,fn_idx=n+1).split()
|
||||||
ok_or_die(wif,is_wif,'WIF key',skip_ok=True)
|
ok_or_die(wif,is_wif,'WIF key',skip_ok=True)
|
||||||
ok_or_die(addr,is_coin_addr,'Bitcoin address')
|
ok_or_die(addr,is_coin_addr,'Coin address')
|
||||||
def Wif2addr(self,name,f1,f2,f3):
|
def Wif2addr(self,name,f1,f2,f3):
|
||||||
for n,f,k,m in ((1,f1,'',''),(2,f2,'','compressed'),(3,f3,'segwit=1','compressed')):
|
for n,f,k,m in ((1,f1,'',''),(2,f2,'','compressed'),(3,f3,'segwit=1','compressed')):
|
||||||
wif = read_from_file(f).split()[0]
|
wif = read_from_file(f).split()[0]
|
||||||
|
|
@ -379,15 +394,22 @@ class MMGenToolTestSuite(object):
|
||||||
self.run_cmd_out(name,addr,fn_idx=3)
|
self.run_cmd_out(name,addr,fn_idx=3)
|
||||||
|
|
||||||
def Pipetest(self,name,f1,f2,f3):
|
def Pipetest(self,name,f1,f2,f3):
|
||||||
test_msg('command piping')
|
|
||||||
wif = read_from_file(f3).split()[0]
|
wif = read_from_file(f3).split()[0]
|
||||||
cmd = '{tc} {sa} wif2hex {wif} | {tc} privhex2pubhex - compressed=1 | {tc} pubhex2redeem_script - | {tc} {sa} pubhex2addr - p2sh=1'.format(wif=wif,sa=' '.join(add_spawn_args),tc=' '.join(spawn_cmd))
|
cmd = ( '{c} {a} wif2hex {wif} | ' +
|
||||||
|
'{c} {a} privhex2pubhex - compressed=1 | ' +
|
||||||
|
'{c} {a} pubhex2redeem_script - | ' +
|
||||||
|
'{c} {a} pubhex2addr - p2sh=1').format(
|
||||||
|
c=' '.join(spawn_cmd),
|
||||||
|
a=' '.join(add_spawn_args),
|
||||||
|
wif=wif)
|
||||||
|
test_msg('command piping')
|
||||||
|
if opt.verbose:
|
||||||
|
sys.stderr.write(green('Executing ') + cyan(cmd) + '\n')
|
||||||
p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
|
p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.PIPE,shell=True)
|
||||||
res = p.stdout.read().strip()
|
res = p.stdout.read().strip()
|
||||||
addr = read_from_tmpfile(cfg,'Wif2addr3.out').strip()
|
addr = read_from_tmpfile(cfg,'Wif2addr3.out').strip()
|
||||||
cmp_or_die(res,addr)
|
cmp_or_die(res,addr)
|
||||||
|
|
||||||
|
|
||||||
# Mnemonic
|
# Mnemonic
|
||||||
def Hex2mn(self,name):
|
def Hex2mn(self,name):
|
||||||
for n,size,m in ((1,16,'128-bit'),(2,24,'192-bit'),(3,32,'256-bit')):
|
for n,size,m in ((1,16,'128-bit'),(2,24,'192-bit'),(3,32,'256-bit')):
|
||||||
|
|
@ -395,7 +417,7 @@ class MMGenToolTestSuite(object):
|
||||||
self.run_cmd_out(name,hexnum,fn_idx=n,extra_msg=m)
|
self.run_cmd_out(name,hexnum,fn_idx=n,extra_msg=m)
|
||||||
def Mn2hex(self,name,f1,f2,f3,f4,f5,f6):
|
def Mn2hex(self,name,f1,f2,f3,f4,f5,f6):
|
||||||
for f_i,f_o,m in ((f1,f2,'128-bit'),(f3,f4,'192-bit'),(f5,f6,'256-bit')):
|
for f_i,f_o,m in ((f1,f2,'128-bit'),(f3,f4,'192-bit'),(f5,f6,'256-bit')):
|
||||||
self.run_cmd_chk(name,f_i,f_o,extra_msg=m)
|
self.run_cmd_chk(name,f_i,f_o,extra_msg=m,strip_hex=True)
|
||||||
def Mn_rand128(self,name): self.run_cmd_out(name)
|
def Mn_rand128(self,name): self.run_cmd_out(name)
|
||||||
def Mn_rand192(self,name): self.run_cmd_out(name)
|
def Mn_rand192(self,name): self.run_cmd_out(name)
|
||||||
def Mn_rand256(self,name): self.run_cmd_out(name)
|
def Mn_rand256(self,name): self.run_cmd_out(name)
|
||||||
|
|
@ -406,8 +428,8 @@ class MMGenToolTestSuite(object):
|
||||||
|
|
||||||
# RPC
|
# RPC
|
||||||
def Addrfile_chksum(self,name):
|
def Addrfile_chksum(self,name):
|
||||||
fn = os.path.join(cfg['refdir'],cfg['addrfile'].format(('','.testnet')[g.testnet]))
|
fn = os.path.join(cfg['refdir'],ref_subdir,cfg['addrfile'].format(altcoin_pfx,tn_ext))
|
||||||
self.run_cmd_out(name,fn,literal=True,chkdata=cfg['addrfile_chk'][g.testnet])
|
self.run_cmd_out(name,fn,literal=True,chkdata=cfg['addrfile_chk'][g.coin.lower()][g.testnet])
|
||||||
def Getbalance(self,name):
|
def Getbalance(self,name):
|
||||||
self.run_cmd_out(name,literal=True)
|
self.run_cmd_out(name,literal=True)
|
||||||
def Listaddresses(self,name):
|
def Listaddresses(self,name):
|
||||||
|
|
@ -415,7 +437,7 @@ class MMGenToolTestSuite(object):
|
||||||
def Twview(self,name):
|
def Twview(self,name):
|
||||||
self.run_cmd_out(name,literal=True)
|
self.run_cmd_out(name,literal=True)
|
||||||
def Txview(self,name):
|
def Txview(self,name):
|
||||||
fn = os.path.join(cfg['refdir'],cfg['txfile'].format(('','.testnet')[g.testnet]))
|
fn = os.path.join(cfg['refdir'],ref_subdir,cfg['txfile'][g.coin.lower()].format(tn_ext))
|
||||||
self.run_cmd_out(name,fn,literal=True)
|
self.run_cmd_out(name,fn,literal=True)
|
||||||
|
|
||||||
# main()
|
# main()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue