Browse Source

mmgen-regtest: test suite support, mswin bugfixes
tx.view(): refactor, bugfixes
new script: test-release.sh

philemon 7 years ago
parent
commit
50f55b9790
16 changed files with 652 additions and 162 deletions
  1. 2 1
      mmgen/main_regtest.py
  2. 15 3
      mmgen/obj.py
  3. 16 12
      mmgen/opts.py
  4. 35 32
      mmgen/regtest.py
  5. 5 2
      mmgen/rpc.py
  6. 1 1
      mmgen/test.py
  7. 2 2
      mmgen/tool.py
  8. 49 61
      mmgen/tx.py
  9. 1 1
      mmgen/txsign.py
  10. 5 8
      mmgen/util.py
  11. 68 0
      scripts/test-release.sh
  12. 3 1
      scripts/traceback.py
  13. 11 6
      test/mmgen_pexpect.py
  14. 109 0
      test/ref/btcwallet-testnet.dump
  15. 109 0
      test/ref/btcwallet.dump
  16. 221 32
      test/test.py

+ 2 - 1
mmgen/main_regtest.py

@@ -47,6 +47,7 @@ opts_data = lambda: {
     generate        - mine a block
     generate        - mine a block
     test_daemon     - test whether daemon is running
     test_daemon     - test whether daemon is running
     get_balances    - get balances of Bob and Alice
     get_balances    - get balances of Bob and Alice
+    show_mempool    - show transaction IDs in mempool
 	"""
 	"""
 }
 }
 
 
@@ -56,7 +57,7 @@ if len(cmd_args) != 1:
 	opts.usage()
 	opts.usage()
 
 
 cmds = ('setup','stop','generate','test_daemon','create_data_dir','bob','alice','user',
 cmds = ('setup','stop','generate','test_daemon','create_data_dir','bob','alice','user',
-		'wait_for_daemon','wait_for_exit','get_current_user','get_balances')
+		'wait_for_daemon','wait_for_exit','get_current_user','get_balances','show_mempool')
 
 
 if cmd_args[0] not in cmds:
 if cmd_args[0] not in cmds:
 	opts.usage()
 	opts.usage()

+ 15 - 3
mmgen/obj.py

@@ -661,9 +661,21 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
 	trunc_ok = False
 	trunc_ok = False
 	color = 'blue'
 	color = 'blue'
 	mmtypes = { # since 'name' is used to cook the seed, it must never change!
 	mmtypes = { # since 'name' is used to cook the seed, it must never change!
-'L': {'name':'legacy','comp':False,'gen':'p2pkh', 'fmt':'p2pkh','desc':'Legacy uncompressed Bitcoin address'},
-'S': {'name':'segwit','comp':True, 'gen':'segwit','fmt':'p2sh', 'desc':'Bitcoin Segwit P2SH-P2WPK address' },
-'C': {'name':'compressed','comp':True,'gen':'p2pkh','fmt':'p2pkh','desc':'Compressed Bitcoin P2PKH address'}
+		'L': {  'name':'legacy',
+				'comp':False,
+				'gen':'p2pkh',
+				'fmt':'p2pkh',
+				'desc':'Legacy uncompressed Bitcoin address'},
+		'S': {  'name':'segwit',
+				'comp':True,
+				'gen':'segwit',
+				'fmt':'p2sh',
+				'desc':'Bitcoin Segwit P2SH-P2WPK address' },
+		'C': {  'name':'compressed',
+				'comp':True,
+				'gen':'p2pkh',
+				'fmt':'p2pkh',
+				'desc':'Compressed Bitcoin P2PKH address'}
 # 		'l': 'litecoin',
 # 		'l': 'litecoin',
 # 		'e': 'ethereum',
 # 		'e': 'ethereum',
 # 		'E': 'ethereum_classic',
 # 		'E': 'ethereum_classic',

+ 16 - 12
mmgen/opts.py

@@ -227,10 +227,6 @@ def init(opts_f,add_opts=[],opt_filter=None):
 		else:
 		else:
 			setattr(opt,k,g.__dict__[k])
 			setattr(opt,k,g.__dict__[k])
 
 
-	# Check user-set opts without modifying them
-	if not check_opts(uopts):
-		sys.exit(1)
-
 	if opt.show_hash_presets:
 	if opt.show_hash_presets:
 		_show_hash_presets()
 		_show_hash_presets()
 		sys.exit(0)
 		sys.exit(0)
@@ -246,24 +242,27 @@ def init(opts_f,add_opts=[],opt_filter=None):
 		opts_data['long_options'] = common_opts_data
 		opts_data['long_options'] = common_opts_data
 		mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter)
 		mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter)
 
 
-	# We don't need this data anymore
-	del mmgen.share.Opts
-	del opts_f
-	for k in ('prog_name','desc','usage','options','notes'):
-		if k in opts_data: del opts_data[k]
-
 	if g.bob or g.alice:
 	if g.bob or g.alice:
+		g.data_dir = os.path.join(g.data_dir_root,'regtest')
 		import regtest as rt
 		import regtest as rt
-		rt.user(('alice','bob')[g.bob],quiet=True)
 		g.testnet = True
 		g.testnet = True
 		g.rpc_host = 'localhost'
 		g.rpc_host = 'localhost'
 		g.rpc_port = rt.rpc_port
 		g.rpc_port = rt.rpc_port
 		g.rpc_user = rt.rpc_user
 		g.rpc_user = rt.rpc_user
 		g.rpc_password = rt.rpc_password
 		g.rpc_password = rt.rpc_password
-		g.data_dir = os.path.join(g.home_dir,'.'+g.proj_name.lower(),'regtest')
+
+	# Check user-set opts without modifying them
+	if not check_opts(uopts):
+		sys.exit(1)
 
 
 	if g.debug: opt_postproc_debug()
 	if g.debug: opt_postproc_debug()
 
 
+	# We don't need this data anymore
+	del mmgen.share.Opts
+	del opts_f
+	for k in ('prog_name','desc','usage','options','notes'):
+		if k in opts_data: del opts_data[k]
+
 	return args
 	return args
 
 
 def check_opts(usr_opts):       # Returns false if any check fails
 def check_opts(usr_opts):       # Returns false if any check fails
@@ -414,6 +413,11 @@ def check_opts(usr_opts):       # Returns false if any check fails
 			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
 			if not opt_is_in_list(val.upper(),g.coins,'coin'): return False
+		elif key in ('bob','alice'):
+			from mmgen.regtest import regtest_dir
+			m = "{}'s wallet doesn't exist yet.  Run '{}-regtest setup' to initialize."
+			try: os.stat(regtest_dir)
+			except: die(1,m.format(key.capitalize(),g.proj_name.lower()))
 		else:
 		else:
 			if g.debug: Msg("check_opts(): No test for opt '%s'" % key)
 			if g.debug: Msg("check_opts(): No test for opt '%s'" % key)
 
 

+ 35 - 32
mmgen/regtest.py

@@ -22,26 +22,20 @@ regtest: Bitcoind regression test mode setup and operations for the MMGen suite
 
 
 import os,subprocess,time,shutil
 import os,subprocess,time,shutil
 from mmgen.common import *
 from mmgen.common import *
+PIPE = subprocess.PIPE
 
 
-data_dir = os.path.join(g.data_dir,'regtest')
-regtest_dir = os.path.join(data_dir,'regtest')
-rpc_port = 8552
-rpc_user = 'bobandalice'
+data_dir     = os.path.join(g.data_dir_root,'regtest')
+regtest_dir  = os.path.join(data_dir,'regtest')
+rpc_port     = 8552
+rpc_user     = 'bobandalice'
 rpc_password = 'hodltothemoon'
 rpc_password = 'hodltothemoon'
-init_amt = 500
-tr_wallet = {
-	'orig':  os.path.join(regtest_dir,'wallet.dat.orig'),
-	'bob':   os.path.join(regtest_dir,'wallet.dat.bob'),
-	'alice': os.path.join(regtest_dir,'wallet.dat.alice')
-}
-mmwords = {
-	'bob':   os.path.join(data_dir,'1163DDF1[128].mmwords'),
-	'alice': os.path.join(data_dir,'9304C211[128].mmwords')
-}
-mmaddrs = {
-	'bob':   os.path.join(data_dir,'1163DDF1{}[1-10].addrs'),
-	'alice': os.path.join(data_dir,'9304C211{}[1-10].addrs')
-}
+init_amt     = 500
+sids         = { 'bob':'1163DDF1', 'alice':'9304C211' }
+
+tr_wallet = lambda user: os.path.join(regtest_dir,'wallet.dat.'+user)
+mmwallet  = lambda user: os.path.join(data_dir,'{}[128].mmwords'.format(sids[user]))
+mmaddrs   = lambda user: os.path.join(data_dir,'{}{{}}[1-5].addrs'.format(sids[user]))
+
 mnemonic = {
 mnemonic = {
 	'bob':   'ignore bubble ignore crash stay long stay patient await glorious destination moon',
 	'bob':   'ignore bubble ignore crash stay long stay patient await glorious destination moon',
 	'alice': 'stay long guard secret await price rise destination moon enjoy rich future'
 	'alice': 'stay long guard secret await price rise destination moon enjoy rich future'
@@ -54,6 +48,7 @@ send_addr = {
 common_args = (
 common_args = (
 	'-rpcuser={}'.format(rpc_user),
 	'-rpcuser={}'.format(rpc_user),
 	'-rpcpassword={}'.format(rpc_password),
 	'-rpcpassword={}'.format(rpc_password),
+	'-rpcport={}'.format(rpc_port),
 	'-regtest',
 	'-regtest',
 	'-datadir={}'.format(data_dir))
 	'-datadir={}'.format(data_dir))
 
 
@@ -61,13 +56,11 @@ def start_daemon(user,quiet=False,daemon=True):
 	cmd = (
 	cmd = (
 		'bitcoind',
 		'bitcoind',
 		'-keypool=1',
 		'-keypool=1',
-		'-rpcbind=localhost:{}'.format(rpc_port),
-		'-rpcallowip=::1',
-		'-wallet={}'.format(os.path.basename(tr_wallet[user]))
+		'-wallet={}'.format(os.path.basename(tr_wallet(user)))
 	) + common_args
 	) + common_args
 	if daemon: cmd += ('-daemon',)
 	if daemon: cmd += ('-daemon',)
 	if not g.debug or quiet: vmsg('{}'.format(' '.join(cmd)))
 	if not g.debug or quiet: vmsg('{}'.format(' '.join(cmd)))
-	p = subprocess.Popen(cmd,stdout=subprocess.PIPE,stderr=subprocess.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 Bitcoin daemon:\n{}'.format(err))
@@ -82,10 +75,11 @@ 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','-rpcconnect=localhost','-rpcport={}'.format(rpc_port)) + common_args + args[1:]
+		cmd = ('bitcoin-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)))
-	return subprocess.Popen(cmd,stdin=subprocess.PIPE,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
+	io=(PIPE,None)['no_pipe' in kwargs and kwargs['no_pipe']]
+	return subprocess.Popen(cmd,stdin=io,stdout=io,stderr=io)
 
 
 def test_daemon():
 def test_daemon():
 	p = start_cmd('cli','getblockcount',quiet=True)
 	p = start_cmd('cli','getblockcount',quiet=True)
@@ -113,7 +107,9 @@ def get_balances():
 	tbal = 0
 	tbal = 0
 	from mmgen.obj import BTCAmt
 	from mmgen.obj import BTCAmt
 	for user in (user1,user2):
 	for user in (user1,user2):
-		p = start_cmd('python','mmgen-tool','--{}'.format(user),'getbalance','quiet=1')
+		p = start_cmd('python','mmgen-tool',
+				'--{}'.format(user),'--data-dir='+g.data_dir,
+					'getbalance','quiet=1')
 		bal = BTCAmt(p.stdout.read())
 		bal = BTCAmt(p.stdout.read())
 		ustr = "{}'s balance:".format(user.capitalize())
 		ustr = "{}'s balance:".format(user.capitalize())
 		msg('{:<16} {:12}'.format(ustr,bal))
 		msg('{:<16} {:12}'.format(ustr,bal))
@@ -122,7 +118,6 @@ def get_balances():
 	msg('{:<16} {:12}'.format('Miner fees:',1000-tbal))
 	msg('{:<16} {:12}'.format('Miner fees:',1000-tbal))
 
 
 def create_data_dir():
 def create_data_dir():
-#def keypress_confirm(prompt,default_yes=False,verbose=False,no_nl=False):
 	try: os.stat(regtest_dir)
 	try: os.stat(regtest_dir)
 	except: pass
 	except: pass
 	else:
 	else:
@@ -136,6 +131,7 @@ def create_data_dir():
 
 
 def process_output(p,silent=False):
 def process_output(p,silent=False):
 	out = p.stdout.read()
 	out = p.stdout.read()
+	if not opt.verbose: Msg_r(' \b')
 	err = p.stderr.read()
 	err = p.stderr.read()
 	if g.debug or not silent:
 	if g.debug or not silent:
 		vmsg('stdout: [{}]'.format(out.strip()))
 		vmsg('stdout: [{}]'.format(out.strip()))
@@ -155,9 +151,11 @@ def create_mmgen_wallet(user):
 def create_mmgen_addrs(user,addr_type):
 def create_mmgen_addrs(user,addr_type):
 	gmsg('Creating MMGen addresses for user {} (type: {})'.format(user.capitalize(),addr_type))
 	gmsg('Creating MMGen addresses for user {} (type: {})'.format(user.capitalize(),addr_type))
 	suf = ('-'+addr_type,'')[addr_type=='L']
 	suf = ('-'+addr_type,'')[addr_type=='L']
-	try: os.unlink(mmaddrs[user].format(suf))
+	try: os.unlink(mmaddrs(user).format(suf))
 	except: pass
 	except: pass
-	p = start_cmd('python','mmgen-addrgen','--{}'.format(user),'-d',data_dir,'--type',addr_type,mmwords[user],'1-10')
+	p = start_cmd('python','mmgen-addrgen',
+			'--{}'.format(user),'--data-dir='+g.data_dir,
+				'-d',data_dir,'--type',addr_type,mmwallet(user),'1-5')
 	p.stdin.write(mnemonic[user]+'\n')
 	p.stdin.write(mnemonic[user]+'\n')
 	p.stdin.close()
 	p.stdin.close()
 	err = process_output(p)[1]
 	err = process_output(p)[1]
@@ -168,9 +166,9 @@ def create_mmgen_addrs(user,addr_type):
 def import_mmgen_addrs(user,addr_type):
 def import_mmgen_addrs(user,addr_type):
 	gmsg_r('Importing MMGen addresses for user {} (type: {})'.format(user.capitalize(),addr_type))
 	gmsg_r('Importing MMGen addresses for user {} (type: {})'.format(user.capitalize(),addr_type))
 	suf = ('-'+addr_type,'')[addr_type=='L']
 	suf = ('-'+addr_type,'')[addr_type=='L']
-	p = start_cmd('python','mmgen-addrimport','--{}'.format(user),'-q','--batch',mmaddrs[user].format(suf))
-	p.stdin.write(mnemonic[user]+'\n')
-	p.stdin.close()
+	p = start_cmd('python','mmgen-addrimport',
+			'--{}'.format(user),'--data-dir='+g.data_dir,
+				'-q','--batch',mmaddrs(user).format(suf))
 	err = process_output(p)[1]
 	err = process_output(p)[1]
 	if not 'addresses imported' in err:
 	if not 'addresses imported' in err:
 		rdie(1,'Error importing MMGen addresses')
 		rdie(1,'Error importing MMGen addresses')
@@ -211,6 +209,11 @@ def fund_wallet(user,amt):
 	process_output(p)
 	process_output(p)
 	p.wait()
 	p.wait()
 
 
+def show_mempool():
+	p = start_cmd('cli','getrawmempool')
+	msg_r(p.stdout.read())
+	p.wait()
+
 def setup():
 def setup():
 	try: os.mkdir(data_dir)
 	try: os.mkdir(data_dir)
 	except: pass
 	except: pass
@@ -245,7 +248,7 @@ def setup():
 
 
 def get_current_user_win(quiet=False):
 def get_current_user_win(quiet=False):
 	if test_daemon() == 'stopped': return None
 	if test_daemon() == 'stopped': return None
-	p = start_cmd('grep','Using wallet',os.path.join(regtest_dir,'debug.log'))
+	p = start_cmd('grep','Using wallet',os.path.join(regtest_dir,'debug.log'),quiet=True)
 	try: wallet_fn = p.stdout.readlines()[-1].split()[-1]
 	try: wallet_fn = p.stdout.readlines()[-1].split()[-1]
 	except: return None
 	except: return None
 	for k in ('orig','bob','alice'):
 	for k in ('orig','bob','alice'):

+ 5 - 2
mmgen/rpc.py

@@ -75,7 +75,9 @@ class BitcoinRPCConnection(object):
 			if cf['on_fail'] in ('return','silent'):
 			if cf['on_fail'] in ('return','silent'):
 				return 'rpcfail',args
 				return 'rpcfail',args
 			else:
 			else:
-				die(args[1],yellow(args[2]))
+				try:    s = u'{}'.format(args[2])
+				except: s = repr(args[2])
+				die(args[1],yellow(s))
 
 
 		dmsg('=== request() debug ===')
 		dmsg('=== request() debug ===')
 		dmsg('    RPC POST data ==> %s\n' % p)
 		dmsg('    RPC POST data ==> %s\n' % p)
@@ -91,7 +93,8 @@ class BitcoinRPCConnection(object):
 		# 	dump = json.dumps(p,cls=MyJSONEncoder,ensure_ascii=False)
 		# 	dump = json.dumps(p,cls=MyJSONEncoder,ensure_ascii=False)
 		# 	print(dump)
 		# 	print(dump)
 
 
-		dmsg('    RPC AUTHORIZATION data ==> [Basic {}]\n'.format(base64.b64encode(self.auth_str)))
+		dmsg('    RPC AUTHORIZATION data ==> raw: [{}]\n{}enc: [Basic {}]\n'.format(
+			self.auth_str,' '*31,base64.b64encode(self.auth_str)))
 		try:
 		try:
 			hc.request('POST', '/', json.dumps(p,cls=MyJSONEncoder), {
 			hc.request('POST', '/', json.dumps(p,cls=MyJSONEncoder), {
 				'Host': self.host,
 				'Host': self.host,

+ 1 - 1
mmgen/test.py

@@ -49,7 +49,7 @@ def mk_tmpdir(d):
 	except OSError as e:
 	except OSError as e:
 		if e.errno != 17: raise
 		if e.errno != 17: raise
 	else:
 	else:
-		qmsg("Created directory '%s'" % d)
+		vmsg("Created directory '%s'" % d)
 
 
 def mk_tmpdir_path(path,cfg):
 def mk_tmpdir_path(path,cfg):
 	try:
 	try:

+ 2 - 2
mmgen/tool.py

@@ -78,7 +78,7 @@ 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=False]','all_labels [bool=False]']),
+	('Listaddresses',["addrs [str='']",'minconf [int=1]','showempty [bool=False]','pager [bool=False]','showbtcaddrs [bool=True]','all_labels [bool=False]']),
 	('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]']),
@@ -354,7 +354,7 @@ def Listaddress(addr,minconf=1,pager=False,showempty=True,showbtcaddr=True):
 	return Listaddresses(addrs=addr,minconf=minconf,pager=pager,showempty=showempty,showbtcaddrs=showbtcaddr)
 	return Listaddresses(addrs=addr,minconf=minconf,pager=pager,showempty=showempty,showbtcaddrs=showbtcaddr)
 
 
 # List MMGen addresses and their balances.  TODO: move this code to AddrList
 # List MMGen addresses and their balances.  TODO: move this code to AddrList
-def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=False,all_labels=False):
+def Listaddresses(addrs='',minconf=1,showempty=False,pager=False,showbtcaddrs=True,all_labels=False):
 
 
 	c = rpc_connection()
 	c = rpc_connection()
 
 

+ 49 - 61
mmgen/tx.py

@@ -134,7 +134,7 @@ class MMGenTX(MMGenObject):
 	class MMGenTxInput(MMGenListItem):
 	class MMGenTxInput(MMGenListItem):
 		for k in txio_attrs: locals()[k] = txio_attrs[k] # in lieu of inheritance
 		for k in txio_attrs: locals()[k] = txio_attrs[k] # in lieu of inheritance
 		scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr')
 		scriptPubKey = MMGenListItemAttr('scriptPubKey','HexStr')
-		sequence = MMGenListItemAttr('sequence',int,typeconv=False)
+		sequence = MMGenListItemAttr('sequence',(int,long)[g.platform=='win'],typeconv=False)
 
 
 	class MMGenTxOutput(MMGenListItem):
 	class MMGenTxOutput(MMGenListItem):
 		for k in txio_attrs: locals()[k] = txio_attrs[k]
 		for k in txio_attrs: locals()[k] = txio_attrs[k]
@@ -632,7 +632,7 @@ class MMGenTX(MMGenObject):
 		o = self.format_view(terse=terse).encode('utf8')
 		o = self.format_view(terse=terse).encode('utf8')
 		if pager: do_pager(o)
 		if pager: do_pager(o)
 		else:
 		else:
-			Msg_r(o)
+			msg_r(o)
 			from mmgen.term import get_char
 			from mmgen.term import get_char
 			if pause:
 			if pause:
 				get_char('Press any key to continue: ')
 				get_char('Press any key to continue: ')
@@ -663,76 +663,65 @@ class MMGenTX(MMGenObject):
 			'TRANSACTION DATA\n\n[ID:{}] [{} {}] [{} UTC] [RBF:{}] [Signed:{}]\n',
 			'TRANSACTION DATA\n\n[ID:{}] [{} {}] [{} UTC] [RBF:{}] [Signed:{}]\n',
 			'Transaction {} {} {} ({} UTC) RBF={} Signed={}\n'
 			'Transaction {} {} {} ({} UTC) RBF={} Signed={}\n'
 		)[bool(terse)]
 		)[bool(terse)]
+		nonmm_str = '(non-{pnm} address)'.format(pnm=g.proj_name)
+
+		def get_max_mmwid(io):
+			if io == self.inputs:
+				sel_f = lambda o: len(o.mmid) + 2 # len('()')
+			else:
+				sel_f = lambda o: len(o.mmid) + (2,8)[bool(o.is_chg)] # + len(' (chg)')
+			return  max(max([sel_f(o) for o in io if o.mmid] or [0]),len(nonmm_str))
+
+		max_mmwid = max(get_max_mmwid(self.inputs),get_max_mmwid(self.outputs))
+
+		def format_io(io):
+			ip = io == self.inputs
+			io_out = ''
+			for n,e in enumerate(sorted(io,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)):
+				if ip and blockcount:
+					confs = e.confs + blockcount - self.blockcount
+					days = int(confs * g.mins_per_block / (60*24))
+				if e.mmid:
+					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')
+				else:
+					mmid_fmt = MMGenID.fmtc(nonmm_str,width=max_mmwid)
+				if terse:
+					io_out += '{:3} {} {} {} {}\n'.format(n+1,e.addr.fmt(color=True),mmid_fmt,e.amt.hl(),g.coin)
+				else:
+					icommon = [
+						((n+1,'')[ip],    'address:', e.addr.fmt(color=True) + ' ' + mmid_fmt),
+						('',  'comment:', e.label.hl() if e.label else ''),
+						('',  'amount:',  '{} {}'.format(e.amt.hl(),g.coin))]
+					items = [(n+1, 'tx,vout:', '%s,%s' % (e.txid, e.vout))] + icommon + [
+						('',  'confirmations:', '%s (around %s days)' % (confs,days) if blockcount else '')
+					] if ip else icommon + [
+						('',  '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'
+			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))
 				self.is_rbf(color=True),self.marked_signed(color=True))
-
+		if self.chain in ('testnet','regtest'):
+			out += green('Chain: {}\n'.format(self.chain.upper()))
+		if self.btc_txid:
+			out += '{} TxID: {}\n'.format(g.coin,self.btc_txid.hl())
 		enl = ('\n','')[bool(terse)]
 		enl = ('\n','')[bool(terse)]
-		if self.chain in ('testnet','regtest'): out += green('Chain: {}\n'.format(self.chain.upper()))
-		if self.btc_txid: out += '{} TxID: {}\n'.format(g.coin,self.btc_txid.hl())
 		out += enl
 		out += enl
-
 		if self.label:
 		if self.label:
 			out += 'Comment: %s\n%s' % (self.label.hl(),enl)
 			out += 'Comment: %s\n%s' % (self.label.hl(),enl)
-		out += 'Inputs:\n' + enl
-
-		nonmm_str = '(non-{pnm} address)'.format(pnm=g.proj_name)
-		max_mmwid = max(max([len(i.mmid) for i in self.inputs if i.mmid] or [0])+len('()'),len(nonmm_str))
-		for n,e in enumerate(sorted(self.inputs,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)):
-			if blockcount:
-				confs = e.confs + blockcount - self.blockcount
-				days = int(confs * g.mins_per_block / (60*24))
-			mmid_fmt = e.mmid.fmt(width=max_mmwid,encl='()',color=True) if e.mmid else MMGenID.hlc(nonmm_str)
-			if terse:
-				out += '{:3} {} {} {} {}'.format(n+1,e.addr.fmt(color=True),mmid_fmt,e.amt.hl(),g.coin)
-			else:
-				for d in (
-	(n+1, 'tx,vout:',       '%s,%s' % (e.txid, e.vout)),
-	('',  'address:',       e.addr.fmt(color=True) + ' ' + mmid_fmt),
-	('',  'comment:',       e.label.hl() if e.label else ''),
-	('',  'amount:',        '{} {}'.format(e.amt.hl(),g.coin)),
-	('',  'confirmations:', '%s (around %s days)' % (confs,days) if blockcount else '')
-				):
-					if d[2]: out += ('%3s %-8s %s\n' % d)
-			out += '\n'
-
-		out += 'Outputs:\n' + enl
-		sel_f = lambda o: (len(o.mmid),len(o.mmid)+len(' (chg)'))[bool(o.is_chg)]
-		max_mmwid = max([sel_f(o) for o in self.outputs if o.mmid] or [0])
-		max_mmwid = max(max_mmwid+len('()'),len(nonmm_str))
-		for n,e in enumerate(sorted(self.outputs,key=lambda o: o.mmid.sort_key if o.mmid else o.addr)):
-			if e.mmid:
-				app=('',' (chg)')[bool(e.is_chg and terse)]
-				mmid_fmt = e.mmid.fmt(width=max_mmwid,encl='()',color=True,
-										app=app,appcolor='green')
-			else:
-				mmid_fmt = MMGenID.hlc(nonmm_str)
-			if terse:
-				out += '{:3} {} {} {} {}'.format(n+1,e.addr.fmt(color=True),mmid_fmt,e.amt.hl(),g.coin)
-			else:
-				for d in (
-						(n+1, 'address:',  e.addr.fmt(color=True) + ' ' + mmid_fmt),
-						('',  'comment:',  e.label.hl() if e.label else ''),
-						('',  'amount:',   '{} {}'.format(e.amt.hl(),g.coin)),
-						('',  'change:',   green('True') if e.is_chg else '')
-					):
-					if d[2]: out += ('%3s %-8s %s\n' % d)
-			out += '\n'
+		out += 'Inputs:\n' + enl + format_io(self.inputs)
+		out += 'Outputs:\n' + enl + format_io(self.outputs)
 
 
 		fs = (
 		fs = (
 			'Total input:  {} {c}\nTotal output: {} {c}\nTX fee:       {} {c} ({} satoshis per byte)\n',
 			'Total input:  {} {c}\nTotal output: {} {c}\nTX fee:       {} {c} ({} satoshis per byte)\n',
 			'In {} {c} - Out {} {c} - Fee {} {c} ({} satoshis/byte)\n'
 			'In {} {c} - Out {} {c} - Fee {} {c} ({} satoshis/byte)\n'
 		)[bool(terse)]
 		)[bool(terse)]
 
 
-		total_in  = self.sum_inputs()
-		total_out = self.sum_outputs()
-		out += fs.format(
-			total_in.hl(),
-			total_out.hl(),
-			(total_in-total_out).hl(),
-			pink(str(self.btc2spb(total_in-total_out))),
-			c=g.coin
-		)
+		t_in,t_out = self.sum_inputs(),self.sum_outputs()
+		fee = t_in-t_out
+		out += fs.format(t_in.hl(),t_out.hl(),fee.hl(),pink(str(self.btc2spb(fee))),c=g.coin)
+
 		if opt.verbose:
 		if opt.verbose:
 			ts = len(self.hex)/2 if self.hex else 'unknown'
 			ts = len(self.hex)/2 if self.hex else 'unknown'
 			out += 'Transaction size: Vsize={} Actual={}'.format(self.estimate_size(),ts)
 			out += 'Transaction size: Vsize={} Actual={}'.format(self.estimate_size(),ts)
@@ -741,8 +730,7 @@ class MMGenTX(MMGenObject):
 				out += ' Base={} Witness={}'.format(ts-ws,ws)
 				out += ' Base={} Witness={}'.format(ts-ws,ws)
 			out += '\n'
 			out += '\n'
 
 
-		# TX label might contain non-ascii chars
-		return out
+		return out # TX label might contain non-ascii chars
 
 
 	def parse_tx_file(self,infile):
 	def parse_tx_file(self,infile):
 
 

+ 1 - 1
mmgen/txsign.py

@@ -141,7 +141,7 @@ def get_seed_files(opt,args):
 	from mmgen.filename import find_file_in_dir,find_files_in_dir
 	from mmgen.filename import find_file_in_dir,find_files_in_dir
 	if g.bob or g.alice:
 	if g.bob or g.alice:
 		import regtest as rt
 		import regtest as rt
-		wf = rt.mmwords[('alice','bob')[g.bob]]
+		wf = rt.mmwallet(('alice','bob')[g.bob])
 	else:
 	else:
 		wf = find_file_in_dir(Wallet,g.data_dir) # Make this the first encrypted ss in the list
 		wf = find_file_in_dir(Wallet,g.data_dir) # Make this the first encrypted ss in the list
 	if wf: ret.append(wf)
 	if wf: ret.append(wf)

+ 5 - 8
mmgen/util.py

@@ -470,7 +470,7 @@ def get_seed_file(cmd_args,nargs,invoked_as=None):
 	from mmgen.seed import Wallet
 	from mmgen.seed import Wallet
 	if g.bob or g.alice:
 	if g.bob or g.alice:
 		import regtest as rt
 		import regtest as rt
-		wf = rt.mmwords[('alice','bob')[g.bob]]
+		wf = rt.mmwallet(('alice','bob')[g.bob])
 	else:
 	else:
 		wf = find_file_in_dir(Wallet,g.data_dir)
 		wf = find_file_in_dir(Wallet,g.data_dir)
 
 
@@ -610,7 +610,6 @@ def write_data_to_file(
 	else:
 	else:
 		do_file(outfile,ask_write_prompt)
 		do_file(outfile,ask_write_prompt)
 
 
-
 def get_words_from_user(prompt):
 def get_words_from_user(prompt):
 	# split() also strips
 	# split() also strips
 	words = my_raw_input(prompt, echo=opt.echo_passphrase).split()
 	words = my_raw_input(prompt, echo=opt.echo_passphrase).split()
@@ -797,13 +796,8 @@ def get_bitcoind_cfg_options(cfg_keys):
 	return cfg
 	return cfg
 
 
 def get_bitcoind_auth_cookie():
 def get_bitcoind_auth_cookie():
-
 	f = os.path.join(g.bitcoin_data_dir,('',g.testnet_name)[g.testnet],'.cookie')
 	f = os.path.join(g.bitcoin_data_dir,('',g.testnet_name)[g.testnet],'.cookie')
-
-	if file_is_readable(f):
-		return get_lines_from_file(f,'')[0]
-	else:
-		return ''
+	return get_lines_from_file(f,'')[0] if file_is_readable(f) else ''
 
 
 def rpc_connection():
 def rpc_connection():
 
 
@@ -840,6 +834,9 @@ def rpc_connection():
 				auth_cookie=get_bitcoind_auth_cookie())
 				auth_cookie=get_bitcoind_auth_cookie())
 
 
 	if not g.bitcoind_version: # First call
 	if not g.bitcoind_version: # First call
+		if g.bob or g.alice:
+			import regtest as rt
+			rt.user(('alice','bob')[g.bob],quiet=True)
 		g.bitcoind_version = int(c.getnetworkinfo()['version'])
 		g.bitcoind_version = int(c.getnetworkinfo()['version'])
 		g.chain = c.getblockchaininfo()['chain']
 		g.chain = c.getblockchaininfo()['chain']
 		if g.chain != 'regtest':
 		if g.chain != 'regtest':

+ 68 - 0
scripts/test-release.sh

@@ -0,0 +1,68 @@
+#!/bin/bash
+# Tested on Linux, MinGW-64
+
+set -e
+GREEN="\e[32;1m" YELLOW="\e[33;1m" RESET="\e[0m" BRANCH=$1
+REFDIR=test/ref
+if uname -a | grep -qi mingw; then SUDO='' MINGW=1; else SUDO='sudo' MINGW=''; fi
+
+function check {
+	[ "$BRANCH" ] || { echo 'No branch specified.  Exiting'; exit; }
+	[ "$(git diff $BRANCH)" == "" ] || {
+		echo "Unmerged changes from branch '$BRANCH'. Exiting"
+		exit
+	}
+	git diff $BRANCH >/dev/null 2>&1 || exit
+}
+
+function install {
+	set -x
+	eval "$SUDO rm -rf .test-release"
+	git clone --branch $BRANCH --single-branch . .test-release
+	cd .test-release
+	./setup.py sdist
+	mkdir pydist && cd pydist
+	if [ "$MINGW" ]; then unzip ../dist/mmgen-*.zip; else tar zxvf ../dist/mmgen-*gz; fi
+	cd mmgen-*
+	scripts/deinstall.sh
+
+	[ "$MINGW" ] && ./setup.py build --compiler=mingw32
+	eval "$SUDO ./setup.py install"
+}
+
+function do_test {
+	set +x
+	for i in "${CMDS[@]}"; do
+		echo -e "\n${GREEN}Running:$RESET $YELLOW$i$RESET"
+		eval "$i"
+	done
+}
+
+check
+(install)
+
+eval "cd .test-release/pydist/mmgen-*"
+
+CMDS=(
+	'test/test.py -On'
+	'test/test.py -On --segwit dfl_wallet main ref ref_other'
+	'test/test.py -On --segwit-random dfl_wallet main'
+)
+do_test
+
+CMDS=('test/test.py -On regtest')
+do_test
+
+# tooltest tests both segwit and non-segwit
+CMDS=(
+	'test/tooltest.py'
+	"test/gentest.py -q 2 $REFDIR/btcwallet.dump"
+	"test/gentest.py -q --testnet=1 2 $REFDIR/btcwallet-testnet.dump"
+	'test/gentest.py -q 1:2 10'
+	'test/gentest.py -q --segwit 1:2 10'
+#	"scripts/tx-old2new.py -S $REFDIR/tx_*raw >/dev/null 2>&1"
+	"scripts/compute-file-chksum.py $REFDIR/*testnet.rawtx >/dev/null 2>&1"
+)
+do_test
+
+echo -e "\n${GREEN}All OK$RESET"

+ 3 - 1
scripts/traceback.py

@@ -10,7 +10,8 @@ try:
 	sys.argv.pop(0)
 	sys.argv.pop(0)
 	execfile(sys.argv[0])
 	execfile(sys.argv[0])
 except SystemExit:
 except SystemExit:
-	sys.exit(int(str(sys.exc_info()[1])))
+	e = sys.exc_info()
+	sys.exit(int(str(e[1])))
 except:
 except:
 	l = traceback.format_exception(*sys.exc_info())
 	l = traceback.format_exception(*sys.exc_info())
 	exc = l.pop()
 	exc = l.pop()
@@ -18,3 +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)
+

+ 11 - 6
test/mmgen_pexpect.py

@@ -35,6 +35,10 @@ else:
 	send_delay = 0
 	send_delay = 0
 	os.environ['MMGEN_DISABLE_HOLD_PROTECT'] = '1'
 	os.environ['MMGEN_DISABLE_HOLD_PROTECT'] = '1'
 
 
+stderr_save = sys.stderr
+def errmsg(s): stderr_save.write(s+'\n')
+def errmsg_r(s): stderr_save.write(s)
+
 def my_send(p,t,delay=send_delay,s=False):
 def my_send(p,t,delay=send_delay,s=False):
 	if delay: time.sleep(delay)
 	if delay: time.sleep(delay)
 	ret = p.send(t) # returns num bytes written
 	ret = p.send(t) # returns num bytes written
@@ -45,11 +49,12 @@ def my_send(p,t,delay=send_delay,s=False):
 		msg('%sSEND %s%s' % (ls,es,yellow("'%s'"%t.replace('\n',r'\n'))))
 		msg('%sSEND %s%s' % (ls,es,yellow("'%s'"%t.replace('\n',r'\n'))))
 	return ret
 	return ret
 
 
-def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False):
+def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False,silent=False):
 	quo = ('',"'")[type(s) == str]
 	quo = ('',"'")[type(s) == str]
 
 
-	if opt.verbose: msg_r('EXPECT %s' % yellow(quo+str(s)+quo))
-	else:       msg_r('+')
+	if not silent:
+		if opt.verbose: msg_r('EXPECT %s' % yellow(quo+str(s)+quo))
+		elif not opt.exact_output: msg_r('+')
 
 
 	try:
 	try:
 		if s == '': ret = 0
 		if s == '': ret = 0
@@ -69,7 +74,7 @@ def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False):
 		sys.exit(1)
 		sys.exit(1)
 	else:
 	else:
 		if t == '':
 		if t == '':
-			if not nonl: vmsg('')
+			if not nonl and not silent: vmsg('')
 		else:
 		else:
 			my_send(p,t,delay,s)
 			my_send(p,t,delay,s)
 		return ret
 		return ret
@@ -233,14 +238,14 @@ class MMGenPexpect(object):
 		debug_pexpect_msg(self.p)
 		debug_pexpect_msg(self.p)
 #		end = self.readline().strip()
 #		end = self.readline().strip()
 		# readline() of partial lines doesn't work with PopenSpawn, so do this instead:
 		# readline() of partial lines doesn't work with PopenSpawn, so do this instead:
-		self.expect(self.NL,nonl=True)
+		self.expect(self.NL,nonl=True,silent=True)
 		debug_pexpect_msg(self.p)
 		debug_pexpect_msg(self.p)
 		end = self.p.before
 		end = self.p.before
 		vmsg(' ==> %s' % cyan(end))
 		vmsg(' ==> %s' % cyan(end))
 		return end
 		return end
 
 
 	def interactive(self):
 	def interactive(self):
-		return self.p.interact()
+		return self.p.interact() # interact() not available with popen_spawn
 
 
 	def logfile(self,arg):
 	def logfile(self,arg):
 		self.p.logfile = arg
 		self.p.logfile = arg

+ 109 - 0
test/ref/btcwallet-testnet.dump

@@ -0,0 +1,109 @@
+# Wallet dump created by Bitcoin v0.15.0
+# * Created on 2017-09-18T19:13:26Z
+# * Best block at time of backup was 0 (000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943),
+#   mined on 2011-02-02T23:16:42Z
+
+# extended private masterkey: tprv8ZgxMBicQKsPdiu5xpPn3bx1GCYK7sMSgcF8WGUoZgFxkXvQiX1nowCK4FqcRqVGKNrPGqdGUvQaWGvhoBQGZTXARyNSVaa1ZN4hPizUCK8
+
+cUEusQd9JM1yAs2PfZ6g5SMdjSxWnmr1MwGA2JhUxtiSTypkqH4Y 2017-09-18T19:12:26Z reserve=1 # addr=mfX7cEamwkYkRbUJrrXXvoatWbAoGZj199 hdkeypath=m/0'/1'/30'
+cPRnb6khn1kgQjsQDqsqUUoqcsfMhYBRstXyAL1GbXv4mCJ6i7qn 2017-09-18T19:12:26Z reserve=1 # addr=mfXTxzK38HGtyFYXQfLkUxPCqHyJQvPGZL hdkeypath=m/0'/0'/845'
+cTcfhngLaH21pbhBNGoBBAZ9VUMtnidg2soESoTRUtcs1gPRDCLZ 2017-09-18T19:12:26Z reserve=1 # addr=mfY2NMWSumDYrS9qUAHWd6yVpX58P7cYqy hdkeypath=m/0'/0'/725'
+cQv25Qu3CdbMv8WBNwcNWAmzgeRHkyuvhkhxjVwoyYkL8wFDdK8K 2017-09-18T19:12:26Z reserve=1 # addr=mfYXm11FhF4BrxBMLzBXoviA4dmA6s4ao2 hdkeypath=m/0'/0'/195'
+cRuFFcZ7Bh9RKJFjVYsRnFfPeoHmU1g9po2jiAFV4b99oSQawXo3 2017-09-18T19:12:26Z reserve=1 # addr=mfaNEGCWTUSTjV74csgU48M28XeHxuKVMZ hdkeypath=m/0'/1'/60'
+cTDgAs627gcpYGmDqTFgdMCZFqHHgdx4uvGMBQvo4bLJdLGV7apk 2017-09-18T19:12:26Z reserve=1 # addr=mfafQVAcuDD7zrek7V2hXH9tpQVkRyWreG hdkeypath=m/0'/0'/460'
+cVVuidrFg911sww3f8JaAqGacQQUyq3LcqTbpAo6rzR3hmKkmdZV 2017-09-18T19:12:26Z reserve=1 # addr=mfcY5a9q2TX2K8QgjS1uerraSp28JAJqDo hdkeypath=m/0'/1'/116'
+cMoZ8voCBxkWTKSLJV5NoN1gcJ7SA9sh3RbMnLPmtMcUY4B5ZCaL 2017-09-18T19:12:26Z reserve=1 # addr=mfdJh6gnZin9iWA8wwZWcJhwPgdEoAEHdG hdkeypath=m/0'/0'/635'
+cRwQpvfsgHL1YfP32QCLZp1rZiJCDg7tTwYy14xoPttNJ8W4mjJo 2017-09-18T19:12:26Z reserve=1 # addr=mfeCBSvj9oAmdGMG2phoXNfHSg4dVavk1h hdkeypath=m/0'/0'/325'
+cNHFKMBXzbKp65ytTJBiF9E4cu4qpScDotNQL6r9yG4Uw6ipadim 2017-09-18T19:12:26Z reserve=1 # addr=mfftEQuszis2f7yKcCJuRC2JkF7jPQR4Wq hdkeypath=m/0'/0'/783'
+cPVbgWezdXVijdzjAMayFtQWepzVrYTVX3Wk949gk7vZDaNpNMvj 2017-09-18T19:12:26Z reserve=1 # addr=mfgmonsaJzLFS1rPZRvAF1UNCEURsqGhut hdkeypath=m/0'/0'/870'
+cVjHGeShf8qGH5yu1vMu357hCjz6ToHmgHUbdKkTTxLucZzW3s6Q 2017-09-18T19:12:26Z reserve=1 # addr=mfja4BQr7tBoJRhVFzFLTPEPkhwWuTk2Ma hdkeypath=m/0'/1'/5'
+cPd4rtRPXBFPorMa395Jaz43kYkWdKBEL14qEgZnnKTqsd9uCGSK 2017-09-18T19:12:26Z reserve=1 # addr=mfmcBLvT4btK41jF4q78FhHqJuXVEPvCpB hdkeypath=m/0'/0'/20'
+cUy7KrF3ngkHWcs3h5DPVRKJw74HzUG5H4mUHHDfp5NKqYftb9ob 2017-09-18T19:12:26Z reserve=1 # addr=mfo2braQsYCHiv9TSZDTMpRGo5jUNGV6tF hdkeypath=m/0'/1'/128'
+cUDfnjK92pvR4GW5sALwkJr8Do9qJ3hPUWTtsLQrbZG4UGQF8Kzp 2017-09-18T19:12:26Z reserve=1 # addr=mfoWT5kXtHNo9NxKoCCjRJf2tfJ3iRju7s hdkeypath=m/0'/0'/788'
+cVAcUB3FKDH8qRmu26S1Xn3dHPbVSWbE5u48QmQzNxYnkZBFgSv6 2017-09-18T19:12:26Z reserve=1 # addr=mfomDSHg7Dkvxr4DkELRFVd9jjGeLhBGFj hdkeypath=m/0'/0'/750'
+cSMp98cCCdkpNF8Xvnqg4XCT77a1RhPLZZ6fSMMSLRMWkyFo4Mrf 2017-09-18T19:12:26Z reserve=1 # addr=mfp6J6m4H7XjKnP2ypqRYcmpR1zBSn4AZj hdkeypath=m/0'/0'/875'
+cQ9oJ1zCFzAq1HrMMZJ8vAP9FDeQ5PhsUP6vHsJfPa4faxo8L7XC 2017-09-18T19:12:26Z reserve=1 # addr=mfqMqsiFJ6NerkhRJuGoxqJ6vTsrJytobc hdkeypath=m/0'/0'/521'
+cMhZjftGBWaxRWuK6SeVb5u5xdX35YpcAh19zFKuBKuprjVAHCuw 2017-09-18T19:12:26Z reserve=1 # addr=mfsxPH6zuw32xmr5dhX3kzFcrqUayXrGoK hdkeypath=m/0'/0'/198'
+cV3bwqAR8LdxgRaXBV4vAhHTMUHwpNLCBuuAn7dgyJBhEwpdAu88 2017-09-18T19:12:26Z reserve=1 # addr=mfvn5bzozCoSgz9Bz3AVd6AGCAMKos1wdE hdkeypath=m/0'/0'/166'
+cUgZr68qTJQ8wNFVTCReMpfv28VHVMsKLCfTeocfNCMSXaHtGEkM 2017-09-18T19:12:26Z reserve=1 # addr=mfwe3oEA13AtNAFYTeNVeHfDBRe8Y7pfXk hdkeypath=m/0'/1'/189'
+cNBaTKHUZJtXQgrBQFpVcVu1ftXnfXH8iEXTjPwiqMKNjcXUUb4o 2017-09-18T19:12:26Z reserve=1 # addr=mfyYF66eFMZZEW1YNU8ko3AEc1NxPDpwQa hdkeypath=m/0'/0'/100'
+cMdKZvGmNygfKGR1qYSxaSLnjvdVxLpVwNEvLsXdAdx8GBTG4xy5 2017-09-18T19:12:26Z reserve=1 # addr=mfyc28cLaBNU5eagreMbhE7ZJBy331q7ud hdkeypath=m/0'/1'/103'
+cQkQd9vxFGhY83NhiWcTvsT98VvJV332LN5k9rAJzfdqbWspXTcB 2017-09-18T19:12:26Z reserve=1 # addr=mfyhcaQVVdhtnkxk73VAdSKJbRDmD8zsvS hdkeypath=m/0'/1'/181'
+cU3U3JXz3Nmr6yar9viqqSmMf81DQeaogbZN9LDwBaA7LK4CiJmY 2017-09-18T19:12:26Z reserve=1 # addr=mfymvVPDE34qmBo5CpbsHHkTM78WCVcdeS hdkeypath=m/0'/1'/198'
+cQEF1CVs2KT3WTJ5Djk6ndQUazqhUphtHHsrrR2GyAyWwbf8TgfR 2017-09-18T19:12:26Z reserve=1 # addr=mfzML9isnEGuRZNxspkMN6PquGaKbUbApm hdkeypath=m/0'/1'/6'
+cUe51W5TH3qyYwdwUb3fSn3qZJPfpUrUP5CRFQXx9bdcQCNeXtQi 2017-09-18T19:12:26Z reserve=1 # addr=mfzYpdTDEoP1ZDQFGFLzCH4dNXuP5496wg hdkeypath=m/0'/0'/524'
+cUKeW7Lgog8EJnaVvCTHM2VJt9y4LScpoUhhjiXCkVjZrpja8Lxf 2017-09-18T19:12:26Z reserve=1 # addr=mfzqd1SazPjMmFEtgcpnDtwcNuoCNkS5Ns hdkeypath=m/0'/0'/963'
+cVgy2ciTigtCsxSP8xWJww8e3i5HJnvvQs2AQR7pyrao6472jrw4 2017-09-18T19:12:26Z reserve=1 # addr=mfzy1bZ9KS5TVpYp6hhxU2KdXTn5NfqEGc hdkeypath=m/0'/0'/363'
+cUGrwHz1hKn2RE9QPQePsnUDa8Hr1JzsNq6naA62NQ5tv6AMq9Ed 2017-09-18T19:12:26Z reserve=1 # addr=mg24nppTicboNBREawhno4h7oYrn5Ehzca hdkeypath=m/0'/1'/301'
+cUVrcE74icmrtCUAuUNvnbWrthv4uptojywg82By8JyfGBnj5Jjk 2017-09-18T19:12:26Z reserve=1 # addr=mg5dgtgL48jcyvKaWKEoSzuiTYL3np3432 hdkeypath=m/0'/0'/435'
+cS74qVN1ae87rFWs8zTaoSzoLEKoacCMHgBkohztkzmLfETHbRmf 2017-09-18T19:12:26Z reserve=1 # addr=mg66ivjMqTawMGErboBVk6YRpAoJ22FZmb hdkeypath=m/0'/0'/693'
+cUMGyiiGH7QH9dYfo5bKhNAj9maMWScXd9uPKNrTwxAnnaE5ptK1 2017-09-18T19:12:26Z reserve=1 # addr=mg7vjMKssY9PLzATH1bmVg9GZYSozSNp9q hdkeypath=m/0'/0'/653'
+cRVAyqcZJsk3Kmo59e3Dqsu3BMFC4roJA9zFyRtjXqnEd5gmuPE4 2017-09-18T19:12:26Z reserve=1 # addr=mg9owk3CG996DLLXekkFdMno8JjjiT7ZZ3 hdkeypath=m/0'/1'/144'
+cMtiz9a9EethEytbyCCTiKabiDM2gPeiaxN6QTP3SUGKJ8ZcnodD 2017-09-18T19:12:26Z reserve=1 # addr=mgBJRBCeFRWZQfx8WCBBfXdbeDnZvNkLXH hdkeypath=m/0'/0'/22'
+cPwtj9HiYkMRxWpRy7NxBoBtwLeoBZDwy42MoKDk9RSCs5vYAwSb 2017-09-18T19:12:26Z reserve=1 # addr=mgEJhby7hjZpdwwyEjaGx2XJnkDy3prVqT hdkeypath=m/0'/0'/247'
+cRkUgMf21JGtsKdd8K1FYnnT39PZC1aAW3fob6okP9SxpSqaEHGQ 2017-09-18T19:12:26Z reserve=1 # addr=mgGp46ihVqEdmNsVntFfURqzkuTYCMLX6h hdkeypath=m/0'/0'/849'
+cP86ro7xPC56vcDeiE8VfASCVfRAySSWXiBG2LQWFntGgBNM2XBD 2017-09-18T19:12:26Z reserve=1 # addr=mgHhjD5PFQZJNQcArwXsFZSgPoRA2q5Xgd hdkeypath=m/0'/0'/968'
+cP5oSH7Vmryjx5f4ZawC8pDuwZf13ppZuQixQ9iyKk4UvVqxvNYo 2017-09-18T19:12:26Z reserve=1 # addr=mgJdTEGRJjgamgboWQQouxAkG68XMKNAth hdkeypath=m/0'/1'/156'
+cP36EPnWCX17rDC6eEDKAvABU54NJRBiPaYfGdjLxSYBjJV16XYF 2017-09-18T19:12:26Z reserve=1 # addr=mgLYtXM2o362mqgnu1L98GUD4BKNJMGZwH hdkeypath=m/0'/0'/625'
+cVhQcb83xJEjhUKsopmjSa7qGuLhWJEJUH8ohM4gwQg8x6WnxwMy 2017-09-18T19:12:26Z reserve=1 # addr=mgMST1dNEKbdpUL89JuJ7tucNLfbPDj4s6 hdkeypath=m/0'/0'/588'
+cSn5pZaz6GEqDXeiXyWiQHDvEd8i3rroNyRAJqBrWfhpq7eCqK4i 2017-09-18T19:12:26Z reserve=1 # addr=mgMwxCPC6ihG5YSysEPD1ZSbppuyA96MWJ hdkeypath=m/0'/0'/662'
+cRkjmnG3576D72kiyemsRD63zwVEiYQNCkorrGSUiE26ktXAanac 2017-09-18T19:12:26Z reserve=1 # addr=mgPRxzaNqXWBukmM5kd3hTia8Td1brng2o hdkeypath=m/0'/0'/380'
+cPb8emkLsQFgZ65Znf8tQ7jEKo6EikKFPb3LskJXUBQWGRcZAHhJ 2017-09-18T19:12:26Z reserve=1 # addr=mgUY817xR7aWq1N38nvrkJCQ5NL9iKFTfR hdkeypath=m/0'/0'/41'
+cMxBC4eGzBsdk7d2iMx2Vq4y4Nf5eLNsLbyh3DwG271TwikG6hhM 2017-09-18T19:12:26Z reserve=1 # addr=mgXMPYuhyuXYmyR42BYGca6XxwD1mGqv7p hdkeypath=m/0'/1'/153'
+cNqCVFqcxW5uqDvQ1hVfxSoQxdEjW6tZD5FoBbP2wLyVs9W6G5vn 2017-09-18T19:12:26Z reserve=1 # addr=mgYHWWh7okm2JVevMjztTaNHn4Rfsuc6rq hdkeypath=m/0'/0'/478'
+cUuAELbXdzPGuk5oxauQyZLoZvSTqVMbmhbJ8uHtPtExyze5ixzv 2017-09-18T19:12:26Z reserve=1 # addr=mgZnTfhTDE8ET6Gv837DMvv6mBPpTLZha6 hdkeypath=m/0'/0'/237'
+cSkABPcFdcNE5kVoeFhRUQczt61HaQFU5Eo162BXraHLyK9jtuzL 2017-09-18T19:12:26Z reserve=1 # addr=mgcnM7S3WST5DJxYEwh53RAHpZnXoxZBLJ hdkeypath=m/0'/0'/497'
+cQxRV2DziyuLoqy9TL3S7gyaafMyyfp21HTMrMkD7WsotKFtbHef 2017-09-18T19:12:26Z reserve=1 # addr=mgdcQj1CJX6Qi6F5C8Rec6D6FarM6y8M9a hdkeypath=m/0'/0'/261'
+cNt3pNZSBKFzKukxqWx5J2WAYs1jmE2yasiww8n3ry7QQ5FFiVGg 2017-09-18T19:12:26Z reserve=1 # addr=mggjpuKGAqPndHvqUhXdrGP8R22CrfvAZV hdkeypath=m/0'/0'/357'
+cN8iNvSqDgRgsZ6TCb3kJWiSdDq8WtM5Jj8zWS4seUehRsVtfg1f 2017-09-18T19:12:26Z reserve=1 # addr=mghQ9DYfth9wf1dyouwYdxP39zYMr9gp39 hdkeypath=m/0'/0'/611'
+cUR11zG7QB3nUZ1LRfarivSxPz7q8g3pak2W887rifVeE54JJgdE 2017-09-18T19:12:26Z reserve=1 # addr=mghYouw6KQyndkr4p15EMC7qE8YjvgUzdC hdkeypath=m/0'/0'/95'
+cPZZsqBsdn1cxMfr8a7bCqfChSP1SiWkK9MiC9Tgm9EMQBzMEaPV 2017-09-18T19:12:26Z reserve=1 # addr=mgiaATPDAjuuhiia1WgtefgKCcJk4MHhnw hdkeypath=m/0'/0'/359'
+cTZw76RGDARXZKdvaaQ7a54fg8FS6PkF2qTwsi4qWnZLitsvC1rZ 2017-09-18T19:12:26Z reserve=1 # addr=mgjGN46xC1xn5XPxoSzu66goMUZHBHLRsD hdkeypath=m/0'/0'/712'
+cVinRphzVCdV3PNX6zyecjj7DRTX6voUUaDUiqYmyTt9whhV53ZY 2017-09-18T19:12:26Z reserve=1 # addr=mgkakhnfWh6bXyJ8s6KHw4GEJqebASzE3g hdkeypath=m/0'/0'/150'
+cUWq9LEUq8uQjZdDgwSTFNkvHdRGpSFWH2Arz8oZnSvzKMVDiRBG 2017-09-18T19:12:26Z reserve=1 # addr=mgmCjiVVqzhNjwJZ8uTHwmiJpD3KaJiMxY hdkeypath=m/0'/0'/931'
+cRzPwRyxm7Lsdfb1tit6zAGMHPZxHD5pNgFn9VaHBisMgcx1kN79 2017-09-18T19:12:26Z reserve=1 # addr=mgmETogWwFfN3Q5MLxCnypt1HGDjNWzeK5 hdkeypath=m/0'/0'/418'
+cTwtS31QNgvwLyW4GjzWNDnzXKUXXDJ7suUkLAokSG66kLgTA3qJ 2017-09-18T19:12:26Z reserve=1 # addr=mgmQZd8n6r8yfiuxvrHEQbPc5cu1VX8Rjk hdkeypath=m/0'/1'/248'
+cSgbxaxtcvPP9KmY7hTRxtJC54nvVL22XQytTofdstTNCHB7odkF 2017-09-18T19:12:26Z reserve=1 # addr=mgojwBbnCmcy6h6aEUxkqQvq3LGNWw3QfE hdkeypath=m/0'/1'/240'
+cPK4C5rXbBpjyzwZwg285URb3AwUeNUwG2LPMVaewdjpRAJBnLkE 2017-09-18T19:12:26Z reserve=1 # addr=mgqBL6VNo4JDdUiXpbSrbqeyMLgKh7eVAT hdkeypath=m/0'/0'/111'
+cVzkdVL55VQS1ZtZheUG3BJHF5STtqjQG1wxogxYbXmSxKbZx1Mo 2017-09-18T19:12:26Z reserve=1 # addr=mgrFMXnHpZYFsrNnv5RFvzZUb3z6Xgs1Q7 hdkeypath=m/0'/0'/879'
+cUtCRTwfNUwRtRZ4tb2MePoK1hW8mdbrYnmJAoa2Lu3NcR2NL99o 2017-09-18T19:12:26Z reserve=1 # addr=mgrxNJXe4kcrmfxXfvZT1ju2GNqJsWDP3P hdkeypath=m/0'/0'/165'
+cTXju6oD74QJCAB8uDKhgB3WNUBXGuPvk3wXU9oT45tfiGp4rsfn 2017-09-18T19:12:26Z reserve=1 # addr=mgtRpaaDbYg1nFtLj1e7ArYbGkc2VH3mmw hdkeypath=m/0'/1'/124'
+cS6fMWZCs4D7FJthikDr3kgN7zvjUSG6WZHsV7aMbXfd6isBjuT7 2017-09-18T19:12:26Z reserve=1 # addr=mguHHUWELMJWvvaudfdJYf3nuCWGCx2A5w hdkeypath=m/0'/0'/952'
+cRsZUuoHMDVxFCoBqsJaVN4q1xjb9Qen7brkqHyRJgUqSw2hKueJ 2017-09-18T19:12:26Z reserve=1 # addr=mgun8z5o5KyFnPJKyVcENKU8iaFCBiDibt hdkeypath=m/0'/0'/83'
+cRNVw8Lv9NfvbLbbmoqN7YjXW21jnqMgdJ5xfhi78zLYzfwGpwfz 2017-09-18T19:12:26Z reserve=1 # addr=mgvwYbA3QJAU4JMDui3yVdviLv98SiHUZB hdkeypath=m/0'/0'/139'
+cPJ5CLyxkbeRLHCUUcA5AFCbo1R8W1kStU1p6H3NHVZwYEJR8CCP 2017-09-18T19:12:26Z reserve=1 # addr=mgvzrhhFuKmRNsrwVv8yPGVUFzJjDfeqvf hdkeypath=m/0'/0'/844'
+cRhtQYF3zhzjSuRM1mhZFPLZgoNUHtDNren39WnVai32CsfhDLRS 2017-09-18T19:12:26Z reserve=1 # addr=mgy28x85PnY49QZoimF3HMLJBoLqmre9JB hdkeypath=m/0'/0'/577'
+cTkUtTZBQTzBphoVkNBx1WgnJ8Z6LvfiJsmyATKDHw9Hv8j7guXi 2017-09-18T19:12:26Z reserve=1 # addr=mgygzo4CmednkXqruhcxG19yJqVbg3BXFz hdkeypath=m/0'/1'/54'
+cUiEZUtqJ1n3ora9u3miFtjJH7KgVZxtPnSotPT88PUZVj538vJ4 2017-09-18T19:12:26Z reserve=1 # addr=mgz4mgyfX4TUVBeYGqLLNAsSkFVur1qaL1 hdkeypath=m/0'/0'/991'
+cPMUx7whzrCAaKmDcUm3Uw87XxGMmSujrFgjPm1ShUjdz1G41Ssp 2017-09-18T19:12:26Z reserve=1 # addr=mgzhexcGPnxu7kdcxVpaqzYPVNZnrWPEVE hdkeypath=m/0'/0'/432'
+cPtkoZmZTqRKPE5a7w7xFHmXkXsF4ZReytLA45ss6hZJDJsiJyF1 2017-09-18T19:12:26Z reserve=1 # addr=mh2TyYXZMosidMHj3UCKJbynkVLA9k88hZ hdkeypath=m/0'/0'/702'
+cNDeMQXduT1773V7tANE65kW8hTHdaqXLPxfZQv8VQzUD1jRHhmq 2017-09-18T19:12:26Z reserve=1 # addr=mh4VoC7FxTPyzLLE2hCMB9qjnYjUccAtjL hdkeypath=m/0'/1'/97'
+cTAkLCTj8d1ZKG4k2c222SGKmDu6MxuCzZcEuUBvJ1EvgB3AeSuY 2017-09-18T19:12:26Z reserve=1 # addr=mh5Fs7qQ5hVVHBV95CTd7TiL6R6xKDCtmZ hdkeypath=m/0'/1'/7'
+cSCR2oEupHhrBj7wxYp6rt5vho9KZrW4TFokj3Ta9AyZJ4xPfEVj 2017-09-18T19:12:26Z reserve=1 # addr=mh6W5iiDmApTbzoWWbX5oz74sqAKS8mboS hdkeypath=m/0'/0'/956'
+cUFfbDFCetV6dyenFxMamsD69YFSxAMfjRMns55frjw1j4zNVSAB 2017-09-18T19:12:26Z reserve=1 # addr=mh7Qbvv13b7Ej9Qk3yhbV6SCmXdL6QGs8Z hdkeypath=m/0'/0'/542'
+cQHJAxkPzshvHEJr6DDpZTpQtpsgdxAXS6Sq5Wm6Z151Trt4jeeW 2017-09-18T19:12:26Z reserve=1 # addr=mh8LuZEZkmjDiT54mhkAQZZdNMoWGoQQ6n hdkeypath=m/0'/0'/458'
+cTWm6AYc5a6txkkyinv3LzDbXUgwwpXbc7KctH1grQ8ehYPR1Kzh 2017-09-18T19:12:26Z reserve=1 # addr=mh8XtNtizKKTbJm8M53vcMnGccchkW1xqT hdkeypath=m/0'/0'/690'
+cV4GF3MrngdszfuMYcPeXvEA2MD8eJ6tJ859VX6UPct7PSwfBkZq 2017-09-18T19:12:26Z reserve=1 # addr=mh9bHKZaUwvpZA91gCZCyqMsWz4GSj9gCG hdkeypath=m/0'/0'/379'
+cVT46Quvai57PtVThyWGsYuDKdpP6meEdVUdEqkJYjp2RNrwWmxG 2017-09-18T19:12:26Z reserve=1 # addr=mh9t7dKSRLxRWbihfssoT1XGFJCDs5FsrR hdkeypath=m/0'/1'/221'
+cQcfMPiaZAvxuTCoxRWFvqDNQqNcUYhfu3kNbAd13bXpx3KnAZvt 2017-09-18T19:12:26Z reserve=1 # addr=mhAsFXqHTqZCHCz984ezCzTZ1ouSa85iCW hdkeypath=m/0'/0'/957'
+cSV9iw52bDtnKauFT99tPokaFsATJH6wTmA7y5wU7L1W1HbYjPfq 2017-09-18T19:12:26Z reserve=1 # addr=mhD5aMUvvBidi51aPtGX5qut6vrJNewMee hdkeypath=m/0'/0'/876'
+cPpTAoez3UFBs7Ss87XwLpATK7YfUVKjJF85VHpkWBnvTKWG1xvR 2017-09-18T19:12:26Z reserve=1 # addr=mhD7kSbqambret4VykN31hUkgCqvKw7D3H hdkeypath=m/0'/1'/257'
+cRiGkRMKjzEP2LMT15z4BunT5X8ByrZUZgaEpXbRNAwESkDDQVay 2017-09-18T19:12:26Z reserve=1 # addr=mhDBJfHZ4zeA1w2AxsChfW45so6v2DJzAq hdkeypath=m/0'/0'/107'
+cPQDVwnnEQWcd9FDbbdMi6iZBg46BcEKJVZzYxeui4bbnBHbffrt 2017-09-18T19:12:26Z reserve=1 # addr=mhDTTNW8Cjuqamw69MhaVnpmqh98sVThZU hdkeypath=m/0'/0'/973'
+cTPzU3FsrwYhtDmwceNjmGgrGFq8zpaMwrQAz85iHw3PYAaHkG8D 2017-09-18T19:12:26Z reserve=1 # addr=mhDcJUuV1ZxYbV5xWjYP7zz99TXJYwEeTX hdkeypath=m/0'/0'/246'
+cPYTA7xxPEkGV4NL8gFmSCe6R4eu69SLCLDmQM816JuiJnUjp5ao 2017-09-18T19:12:26Z reserve=1 # addr=mhEpZkVypE67XGxM8Aemh1x1ZDKzew5U2q hdkeypath=m/0'/0'/490'
+cS95FLFX9vCK9joVhZEArmN7gtHAHDRNLvxj5f5rtWER4hPBu4Zw 2017-09-18T19:12:26Z reserve=1 # addr=mhFM3JHBJFx9scEU6ruErv8puj6cJfyC3y hdkeypath=m/0'/0'/556'
+cTVTztRBrW3LqwtxfnKg1zou4Y194h2HcQdzSeNiAmUHDw1cX5mS 2017-09-18T19:12:26Z reserve=1 # addr=mhHCVRyyNiwTzTNLzVBXrbJMzoZ57uGX8M hdkeypath=m/0'/0'/60'
+cRUrrM8eDyAmA4dmhc6rRyk1nwAA1BHmKkLa9N5P7qP68hUK96PY 2017-09-18T19:12:26Z reserve=1 # addr=mhHEFFqRY2yinZgvrzj1QVnGTEgZRreKY4 hdkeypath=m/0'/1'/53'
+cSrqGuJeguaiSMcXsJh3ucGffDGsawATM1boex8CcMJRRFHjW15G 2017-09-18T19:12:26Z reserve=1 # addr=mhHu5fHf4GUof9s2TF9dkM5H23A9kE8qYa hdkeypath=m/0'/0'/574'
+cRtRWMDFSK2ptwjdWSZhYppWn8i85UJMgSZkkoTsVoyc5bhLksYA 2017-09-18T19:12:26Z reserve=1 # addr=mhJm8kuu8tLUz4gZJiRKceWY9rsxx6XDwC hdkeypath=m/0'/0'/26'
+cUqoD1fKbbmiynJn1CE4Mudua9aXCBobojRVvQFYUJbiXke4F2pF 2017-09-18T19:12:26Z reserve=1 # addr=mhL7NSncF4nx8zvyqptWrp6nGVKyaSrNJ6 hdkeypath=m/0'/0'/865'
+cPUboeZpUxcDgQZq9E5Cgf4P5sXvKh7PbKQn6tDQKUjo8dnW2hja 2017-09-18T19:12:26Z reserve=1 # addr=mhLRN4micxcLs8BcLNxfNauakVZHGFytSd hdkeypath=m/0'/0'/446'
+cRYzbeAzJej1hVjQt4GkAyzxhVrfExty8ozTfRNyKVQPpD99FCAA 2017-09-18T19:12:26Z reserve=1 # addr=mhMRk21dfTQbAyE7azEzkWH27qoLrMysEN hdkeypath=m/0'/0'/121'
+cPvc5bBm14Y3CXNqXwdvjaQEMrVVz7wvpZ3gs1ShwzU2oJPsZeXn 2017-09-18T19:12:26Z reserve=1 # addr=mhMw17TSYgE4jzoJHiayhqn8Sc9ViMYzyT hdkeypath=m/0'/0'/241'
+cVF6hweqSccJn7Vfoi5dx3dJnq3nNawQAuNedPLJvJWnghwJGh7k 2017-09-18T19:12:26Z reserve=1 # addr=mhNq4Xi1vWuT7CHME9XU8QBpA23TjuTS8j hdkeypath=m/0'/0'/216'
+cU9p4ZV2JUjGoSh6KoXEQGuNASy4JtP8MD2ntV2mLSauZG3W6e2W 2017-09-18T19:12:26Z reserve=1 # addr=mhPsQVgPqoaw6bZ32jMckskYotgun7SLgD hdkeypath=m/0'/1'/159'
+cW9Q5J49iCqXUwv22wc4ss3NxxdqVopCUDvdeD2V2TZhTv2UjoCd 2017-09-18T19:12:26Z reserve=1 # addr=mhQvmLiUUMrxi3j9ci2RmXvCtNiCvdztdY hdkeypath=m/0'/1'/170'
+cNcbni84FPTzqv7sDHXtGiXpWZLdXTB36Z2JFFFYVz3UGM9Ygobh 2017-09-18T19:12:26Z reserve=1 # addr=mhSSb6xfyEF4MobdGzurSkcYH5zduniwWf hdkeypath=m/0'/1'/280'
+
+# End of dump

+ 109 - 0
test/ref/btcwallet.dump

@@ -0,0 +1,109 @@
+# Wallet dump created by Bitcoin v0.15.0
+# * Created on 2017-09-18T19:09:18Z
+# * Best block at time of backup was 0 (000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f),
+#   mined on 2009-01-03T18:15:05Z
+
+# extended private masterkey: xprv9s21ZrQH143K354NmfyK5jphFPnQrgpbN7q8gB9ybaFhvJB7KXbDsRywFp9pxZAK94DW7mFJ86F2qD6oYYLTwuNJ6DSbzRKaf9NM9gjKTo1
+
+L5LUujY27o8vP719sKccwRr3XmdC1adSfAf6xhZFa8Gv6dmgY6BG 2017-09-18T19:07:26Z hdmaster=1 # addr=16DKtHYwdUQ4bvTfVk4ztsGaee55c4q92u hdkeypath=m
+L1x2Fesucugt9HVTVdkUjanj36ecsx26Ab8fhkQuuK41tY6TgnHe 2017-09-18T19:07:27Z reserve=1 # addr=113dkRcsZF8oubiFvt9hy9D22zgk3pjmVT hdkeypath=m/0'/0'/336'
+L4yrXL47zC9kpm7ziShPNMAAtjMZnHZMNBWaBwa7SgKqgTsBH6W2 2017-09-18T19:07:27Z reserve=1 # addr=113pS8xCuWnhUKRBw22LFEnBxTXnK6poDb hdkeypath=m/0'/0'/679'
+L1yU69qpjdV51R4381J25QYus1kJx9ruxwqAEgVHRBT7a35bZ6g7 2017-09-18T19:07:27Z reserve=1 # addr=114Lpo71r2iHoPuths1oQkh8kFSxu6BCer hdkeypath=m/0'/0'/287'
+L27BH682oja5y3RmNf2TtoJwAZ3kEQCiL3YLsZGFdWNVUXwjK9Dq 2017-09-18T19:07:27Z reserve=1 # addr=18aiMArDWheSKw3CZ9sNo5YaXyUwEimTZ hdkeypath=m/0'/0'/381'
+KwFqLv7eAY8juMYrNXS8Pq4wxq343uksWk6JP2w26UbTpddZ8odp 2017-09-18T19:07:27Z reserve=1 # addr=1APUKkXm2c7a8cxGvQqXNu1Xpm8gTXXsD hdkeypath=m/0'/1'/384'
+L4FpgfDg3Woo9fN3ZcfX5LY6VFQYrzBgFYWAAFnYcgkRRVCnNe3T 2017-09-18T19:07:27Z reserve=1 # addr=1BpQS3NHZbRhgW3cDmJZUSm6THmHKTe5e hdkeypath=m/0'/0'/184'
+L5iYZMCdEUXruDsxLmFfhbCLUZJxLquJf1STVNq9XWGydQTSV9or 2017-09-18T19:07:27Z reserve=1 # addr=1C5bSNmBHhPykTMQ6uvKnpmkgyURkj6iF hdkeypath=m/0'/0'/668'
+L5U4tDLoVZf8bxRG7DdYp6QRcPD9FqNqX4zNhnW2wKK1MXLTXwtT 2017-09-18T19:07:27Z reserve=1 # addr=1CbV3RYCf8esKdvNpDDahyYVLvG3etjt5 hdkeypath=m/0'/1'/135'
+KzXLu5rU4mZLwxF9qqdSmjF1ZBV1tQter5R9nXBAtrWZymkyfztc 2017-09-18T19:07:27Z reserve=1 # addr=1CtKBXTHhF4cUUdEMCdJVFs9jwn9cDRJx hdkeypath=m/0'/0'/460'
+L3yRscb2sCE6jTgCiVWLByPLUxvUHbN2SUUjH3RxP6GEgQaxtajt 2017-09-18T19:07:27Z reserve=1 # addr=1E5icionAv42fy8CKfwW5RckZZytsznua hdkeypath=m/0'/0'/278'
+Kx8Jhn21p8gPqmLZjWwSPSPei4wDATADAsXQEm4Nogqy8W6SvzBs 2017-09-18T19:07:27Z reserve=1 # addr=1FfTDTzp3AJiWytPLk9PdXRNE3sfVghPN hdkeypath=m/0'/0'/681'
+L4dxASzxJSk7jcjW6qpwFXpmdBqQudbcA8xjo7q1qd9E1DADVakn 2017-09-18T19:07:27Z label= # addr=1FmABfe81aZ2aikRaxSyFFAT9y6bHb627 hdkeypath=m/0'/0'/0'
+L3Twqq7LmXhf5boqxxrguJPt9exvAqVV7KjdRVGpGhkJ3R25jrbb 2017-09-18T19:07:27Z reserve=1 # addr=1FqESrZPXGNRda2CTVt2AocNJ1QbVQgh7 hdkeypath=m/0'/0'/546'
+KyGs5ZaXzpfJbkx46UDehGjEjHSNHSSKWMXwkgDW2cgNXERkMGU3 2017-09-18T19:07:27Z reserve=1 # addr=1GiJttRW18rVVGrT5y6R38Hyhfw8tnurt hdkeypath=m/0'/0'/417'
+KxJ3agJDYe2qnd9ZNSenxjAhzFnoC776ptJdWC1cRC6sxf1MfD68 2017-09-18T19:07:27Z reserve=1 # addr=1GrUMXM5yEcXLWgegqqnPAHTJJ2NHbgms hdkeypath=m/0'/0'/8'
+L3aZ2jfMJ9YBWSfG5LQ7owZgnLJkDuBanuB7dpn5b7TCoqHpbnZe 2017-09-18T19:07:27Z reserve=1 # addr=1K5eBDNPXNLH8FJKNtWm11B86LgusXMBH hdkeypath=m/0'/0'/449'
+L5WwGbDYfBGsdP1ptTbhRUb4vxNzbJ6j5SA1py33doAcG3zqgmpN 2017-09-18T19:07:27Z reserve=1 # addr=1KuuABVjF3YeBssdagXP9nGATaGg571D5 hdkeypath=m/0'/1'/228'
+L5J4jJjdDv8uQ2LMxr19HHXixyDMps8B4fWdJ99v3MyadgY2ekia 2017-09-18T19:07:27Z reserve=1 # addr=1LmvAqEE215Vy4zi7Qywcjagp893JrvYU hdkeypath=m/0'/1'/481'
+L4fw5jvacrg1ZgULLabaDN6RPoHkv8V46QTahms7T2mC7rttjk2t 2017-09-18T19:07:27Z reserve=1 # addr=1MSx2SQZVfgo5bt3kD2nMhjuvXgMjZgno hdkeypath=m/0'/0'/784'
+KyAxJbSbHnNLQr11e91J4SNPfynS3tAkMeh888FMAJZhyseEwa9b 2017-09-18T19:07:27Z reserve=1 # addr=1PnJpTMpyMhg2Hd88vm9JemJ6AmzPHUNx hdkeypath=m/0'/0'/740'
+L1YWjG83Pbegm8fNKEu3g2JM6jQuYcfvAFFhFo21yVGJksW3kDw3 2017-09-18T19:07:27Z reserve=1 # addr=1QMKxPV1xmqsLvpjLGYdRVhsk1r2WwMuo hdkeypath=m/0'/1'/256'
+L4zp65kGkCbWaHWbFu1LqGjs7kYgYeKWapKxQtuc1BAmGS2qwKrX 2017-09-18T19:07:27Z reserve=1 # addr=1QnZD4EQVCv7NUQdwCNstw5CAQ5aFa3UF hdkeypath=m/0'/1'/425'
+L2SzJD8DqasrVpLvZhzm11pJ1UYhDDBKFNoCwpWNTZUuJSjxoY3n 2017-09-18T19:07:27Z reserve=1 # addr=1RAZnDTjUntih19Cv67YHCpxJXoMtQWQK hdkeypath=m/0'/0'/137'
+KxoyRUoDQoqcqpHSKwY9JpNaM7hSvkBn8WwwCvNQA79gH6jYynAT 2017-09-18T19:07:27Z reserve=1 # addr=1RyxjcAbMxDApAWhLGiw2E6K27mW6PTgm hdkeypath=m/0'/1'/213'
+L411UbAd5dHVVHEkS7BtLXSQvRhB69aL2H57bzzAAEn6dP413Xhu 2017-09-18T19:07:27Z reserve=1 # addr=1S8fDdBWnu8xMw3fPDqMGBfvzGFcPTGXs hdkeypath=m/0'/1'/433'
+L3VsCkFJavPZuGB7uLDSYDDbHJh3f5x68xb37zZt35oQTrAadtNe 2017-09-18T19:07:27Z reserve=1 # addr=1ScMi71DJ5KqtyZLkHLkNqMKB915tJf67 hdkeypath=m/0'/1'/448'
+L3AVSiPVrewmkV1TKnpLWAgTxDb9VQp2gxGxP7K2AJXANfqqPGW4 2017-09-18T19:07:27Z reserve=1 # addr=1SizkyFfRsTP4jNWpZBff2Z9HzfeuBxta hdkeypath=m/0'/1'/294'
+KxA9ZwF4gYdqTDrR5YBAJo9SwqWnyTn9VpbLFNenQ11nfUrq82Tk 2017-09-18T19:07:27Z reserve=1 # addr=1TuVu4VC6KRiay6Z5hapW9qXXYj5TTWCT hdkeypath=m/0'/0'/52'
+L42jH2MdBvPxUwTcnokrvKHFnygwkee47pJ6ESQEAF24KLsYQYJy 2017-09-18T19:07:27Z reserve=1 # addr=1V73irXxmr8LyK2s4w1vPfBPSeq85md5M hdkeypath=m/0'/1'/194'
+L1vAFgS3X6zYTkGNq4EybZRxvnkq4vu9NLLFqm3QznEVu6XuDQEC 2017-09-18T19:07:27Z reserve=1 # addr=1VVAzE5m1rwBUyaWbpbwHEM13SptJDGkm hdkeypath=m/0'/0'/202'
+L4pUrVxupTRQeU2SzM8sGJ1ZvUkaJT7oKFtS3Wkot2yo1CcYeeKG 2017-09-18T19:07:27Z reserve=1 # addr=1WNJAZboJJJ9mQ4bw6bo9VaiqRnWfdNEV hdkeypath=m/0'/0'/803'
+L2WVZ5PoTmpDaGfSyHykJ1DxKa3GyNqX9Lhf3ZEw1zKHEMFZHRwU 2017-09-18T19:07:27Z reserve=1 # addr=1WcgqbW92nsvbrxkJ6ZnKphTNwzbz7NW2 hdkeypath=m/0'/1'/159'
+Kyk9Kik13wFdMJWbbFqWHVxEJeWVjqXERWECx3vH9yAS1LzNcbBy 2017-09-18T19:07:27Z reserve=1 # addr=1WrsK2Wbt7JLC76xvRKcDsbjt81Z8ytNW hdkeypath=m/0'/0'/228'
+L3S7bt5qqSkW72yjc75wU8LChNdRD9amZgZegrc5UCRrKe1apugG 2017-09-18T19:07:27Z reserve=1 # addr=1WwzHnRGAUqpaw6PCQcXV761uchpArajZ hdkeypath=m/0'/0'/430'
+KwNQqjWrbDpsQGJQsWcgk56Kc2kbryF6289YcW2Fe83VdwCiJHcB 2017-09-18T19:07:27Z reserve=1 # addr=1XNwBbZLbau3a9a3aDF3A8SQrX6bgpFMB hdkeypath=m/0'/0'/126'
+L1t9fwEWmnL6WJbNaP8kMbHVMhwSwBvn5WipH1kexb5zMUru9ydL 2017-09-18T19:07:27Z reserve=1 # addr=1XPBFJypcpFdxGYybE1rrvqNC27XtfKQB hdkeypath=m/0'/0'/132'
+L3AtB99RYL9gnp85Yf6PXmL5D19SefFHqn4WUxfDy4ZEAn7u6ASR 2017-09-18T19:07:27Z reserve=1 # addr=1YAVDkpVPNeyoG11jRDp6tB2GQgAoZuWb hdkeypath=m/0'/0'/869'
+KyNMmHu2sqPYo2LTXRS2FNiJ2mJGpXSLp3XFuEZNuKKJ2hcukAdB 2017-09-18T19:07:27Z reserve=1 # addr=1YHqhhg32w2BYc3oHUVkbWLiHdiiKPU8K hdkeypath=m/0'/0'/484'
+KyCf7Reuf4QgLXjAqaVzjorSsyfmNLmFYuU2RhuQy1kpTzb1raRZ 2017-09-18T19:07:27Z reserve=1 # addr=1YMCcHgEJvTBhDhCUHTzvGVvPM949gvjd hdkeypath=m/0'/1'/83'
+L47bETaCU1aTwqcpHovqcyGABNLGFTK2FctcWP5p2wMLa4cMtYs5 2017-09-18T19:07:27Z reserve=1 # addr=1a2u23iCFefbSszBfme4gv9fBHJ776xaw hdkeypath=m/0'/1'/148'
+L2zjRo6EUeyzgw9nH3cCTB2zKH5rQy6xV51gZTowJWGbkrWBQt2L 2017-09-18T19:07:27Z reserve=1 # addr=1aJiMdsDbsaL9cEsXnRFsnvLevFj72NZs hdkeypath=m/0'/1'/466'
+L2VnhKdSrU43zbxdFbgBf4qVhCabjA84MtVazF5GE5QXMgBeHk6D 2017-09-18T19:07:27Z reserve=1 # addr=1aN8Uj5jbJ7iFpYXNDoW4kiWCmSmKB3wU hdkeypath=m/0'/0'/520'
+L5ic4PBs8sExopkP6KUcRJNpxSzjdsX1Zzrd7c97WhqZkaKpd2qE 2017-09-18T19:07:27Z reserve=1 # addr=1ateLA5VY2PcnUeGNAdbiN3UrF1iUUmMo hdkeypath=m/0'/0'/671'
+KwL84Ni5ErcS4FwFVNCR84JPonMZ68TsAi1n7HhYv5Zvei87UDHH 2017-09-18T19:07:27Z reserve=1 # addr=1bYvPtsEyNQPQ3j4fdiRzSGg7682sMGeU hdkeypath=m/0'/0'/149'
+KyEfXPW34fjrMND4xrDxBDNArXwZf7vi94zzoXjdX2jwwC3ArtR5 2017-09-18T19:07:27Z reserve=1 # addr=1eztp2sRfJ8xmpFGAEfT5y5JNqtuDTyzY hdkeypath=m/0'/1'/74'
+KxJCF2wz7H6y6M5nUbRVg5LHDPq3UUZPEp5nYDaPFtK8rrPyUxwa 2017-09-18T19:07:27Z reserve=1 # addr=1fS8XPt2Rq2vqtgr7Ag5rPiXFxR4Y7Avv hdkeypath=m/0'/0'/486'
+Ky2qZAt5bNMj29Sc8xTL5iXw3CJTHPavvJv6r1oBHDiAJ94hx76V 2017-09-18T19:07:27Z reserve=1 # addr=1hdoi7LTQPEKVnX9UAvDJJa5X3nhZaReq hdkeypath=m/0'/0'/161'
+L3UdsWgTqP5ptzzepwr8rB7dWiJdsfqQYhpHioNgu7bKSXat74UF 2017-09-18T19:07:27Z reserve=1 # addr=1huWRtuss4wFiU4jsMhwPS4mMZDEhhkEv hdkeypath=m/0'/1'/55'
+L12Hj3c8Jf9m4k4uhGJ4na2yJkwr5Nz951jHCcYbQqVKYFp2VuwH 2017-09-18T19:07:27Z reserve=1 # addr=1kVs2qc7qbVHDWydM8zDD6SZbX3geCmZn hdkeypath=m/0'/0'/243'
+KxGFokkLAR3ChbHYF7A1dhgqm2zg2AtEF7rqKeA8eUCAKFEU9qfy 2017-09-18T19:07:27Z reserve=1 # addr=1mAPwUHuHFC55m99vjW8HGjBDcXHpFoRG hdkeypath=m/0'/1'/338'
+L1sD7D5FgQfWXHucPYmSfoVysVkF9y5n9Ra3R3Xk2grEDDCFqUf4 2017-09-18T19:07:27Z reserve=1 # addr=1mSKeNhNryewnjEFRfXYAosY9N4JMRKYi hdkeypath=m/0'/0'/394'
+L4iNbbeUUZMqkteAcgv4Mt5nTtnwnCKZxARvbkeamgrYut7DCFvZ 2017-09-18T19:07:27Z reserve=1 # addr=1na9m5Lf7PXLw147SGbmM5S9CqzvvmCdv hdkeypath=m/0'/0'/840'
+L5TNqp59BKW3kiMdNEQy6Yy4uRxzy9vYgJ8DMYBPv2Hf3q5eQ134 2017-09-18T19:07:27Z reserve=1 # addr=1njSR5CtSZAjzYWsQofmU2DvwpcCGKBA2 hdkeypath=m/0'/1'/151'
+L3K1PXVicWZu2M1WjojZ5LRFNJzGZt9YH8b8RsfeKH91VBSCtUvu 2017-09-18T19:07:27Z reserve=1 # addr=1oyce6ctVtzu7mHSieMR1boyupeEbxCYY hdkeypath=m/0'/1'/38'
+L2MMwuFDfqLsUQBBJwYPX8RBN1u3XozzVLzTAuuVPJz9PBsdEkDp 2017-09-18T19:07:27Z reserve=1 # addr=1qFybRffWdNrRKA5t5HykBzGfB3yDoqd6 hdkeypath=m/0'/0'/985'
+L3ZtdKSjKBsTn81nyUBay7mVYQ3wHmqdz4GKpX4oeytQGAKDPDQS 2017-09-18T19:07:27Z reserve=1 # addr=1s8K5CVkGNqLV14QvRPqYEhJVDsGwjtoe hdkeypath=m/0'/0'/518'
+KzzDhtHdDU2ZnHW6ZNYLjoZC1Y5Yc65EoHK2deWWiygWdd7PABtG 2017-09-18T19:07:27Z reserve=1 # addr=1sJeE5EM4rKZm9eocsFV99K2wWxCGFuAW hdkeypath=m/0'/0'/167'
+L3cWnxWhBNX1SPexR6qDU3Zhxo63eYsUWMrLXNHA7j3KD4MUfQAF 2017-09-18T19:07:27Z reserve=1 # addr=1smSBaXcZZ2W6iaNXTT7CdxwnqZrQmPjq hdkeypath=m/0'/1'/121'
+KwS6fT5VB2KQv2txK2dvGMBpZVADg3uYKYABzCisgSAFoHsavezu 2017-09-18T19:07:27Z reserve=1 # addr=1ttvzcWJuS7bWMVEX9tLXhcNTAwhsxYfg hdkeypath=m/0'/1'/200'
+KzKPeGCmFb4DLnZQFvGqLs1zCRq8NnCDkdpfn86nJyPBnuQMtvqP 2017-09-18T19:07:27Z reserve=1 # addr=1w98cTmKX6SfhYybTGniCtEb6c5JMxwZU hdkeypath=m/0'/1'/365'
+L3XWYmoZnK7pJcDYAFi9K8rSRLPPuj3puqsxRBkWh5dDvodTL55U 2017-09-18T19:07:27Z reserve=1 # addr=1wadqSYW14di7zmdxad8buiuy69WCQpr8 hdkeypath=m/0'/0'/941'
+L2eTpDCZSTGAtQh5vSMvfrETSUQY5tXhvjkXPUDFX4CAH25cii28 2017-09-18T19:07:27Z reserve=1 # addr=1y5ZbqPBomrpMvinhCQhUj1vu5FZUP3C8 hdkeypath=m/0'/1'/93'
+L3HXmcTnhqxprC86dahcxQN5aZT825jp7831NKbyNvDqzekciuad 2017-09-18T19:07:27Z reserve=1 # addr=1yMhG2ac9cKpave8198HnVB52bT76oMCV hdkeypath=m/0'/0'/198'
+L3ATcSGuZotRwcs1AyyEgyCk8f1GAPof2dtLWF1myb1Ag5RSMTwf 2017-09-18T19:07:27Z reserve=1 # addr=1yghmi4bY8MictksXCrYC4fBuZeKfGYPo hdkeypath=m/0'/0'/223'
+L1jDMrcjep9PyivZFqnDWbZ1w2dU7Un3UJyLdjqUsofsMGmaw9Pk 2017-09-18T19:07:27Z reserve=1 # addr=1222f2BYuV2i24qAC7pnzesvWZvr9yfJ61 hdkeypath=m/0'/1'/142'
+L2BcBHkpgauyEJVtGB3VrK9FthdHSv85xyoS6j6CBvCa9wNLjYgU 2017-09-18T19:07:27Z reserve=1 # addr=123q3BXwTR84fKceHEgypj9nLNY6r6URQL hdkeypath=m/0'/0'/276'
+KzyiQE6hysH1FgtXVs9xBUPNipEoAoJ5ouZAfsbGZ17CDS6x587u 2017-09-18T19:07:27Z reserve=1 # addr=124EWXxBgVFiLAZihD4fwWBADn9Hn7srPk hdkeypath=m/0'/0'/217'
+L1xM4m7nLXZaqfRmyWWiWLcKkCXcqKbXz6t4S8DVd37fTvCxCmA7 2017-09-18T19:07:27Z reserve=1 # addr=126HaZCXCBjdxb6mEEZz5q6SCeHjdspGoW hdkeypath=m/0'/1'/231'
+L2Kwksa8c4h8e1NsWA7LVXNnNTXPpxivqCsPXjCG5Qg7LdJd8sxw 2017-09-18T19:07:27Z reserve=1 # addr=126NinSEzkey9VoNNfHcJpYaN43Bse4HnP hdkeypath=m/0'/1'/255'
+KxGp54hzb61WAcXLhpMw51MXCrQf8pkwUB7PWxZyQfcLbEppS5ki 2017-09-18T19:07:27Z reserve=1 # addr=126oMQPf2SqStNtai78HHjBq1pD3MbPhdY hdkeypath=m/0'/0'/900'
+KxE3xagi1xBAPwLEU4KfKGJrB4EP8xWcQkNc4nJebYC8MQ433ZFq 2017-09-18T19:07:27Z reserve=1 # addr=127z4EXPBfb3WjxcTafrG9nTaWyRsXj2eN hdkeypath=m/0'/0'/277'
+L2KgsusgBr4b6akZnmTMKDHAEM88246zURbGRj8vQfSF8EukFYFC 2017-09-18T19:07:27Z reserve=1 # addr=128rEXMhruANwihRd9fyWio3pDV42qnPCR hdkeypath=m/0'/0'/295'
+KyHaLBKZscV3GeBVc1znAC6DbnrW1XeJtVmMnJhttC1fyK3if1aD 2017-09-18T19:07:27Z reserve=1 # addr=129fx9Gn3Vw7ALJANiCee7qiGhjih5Gcx4 hdkeypath=m/0'/1'/250'
+L3e6tGJkVg7wJGgcW1ZBvgk1d4HB4g28VMhhXp8WJD9PGiXNAnB5 2017-09-18T19:07:27Z reserve=1 # addr=12AiV2mp4PsjTagfieLZJYUSvJ9wP2TNNm hdkeypath=m/0'/0'/662'
+L1jA9JekAdMh7fBtddTjTyh1TGsXuWttc1g9LBn22qmpLMyFn8nr 2017-09-18T19:07:27Z reserve=1 # addr=12CtM6k7EsYFoYQFgKjiCECYuvaxVjE2Uc hdkeypath=m/0'/0'/790'
+KzFdNVfg1Tzeh6CcaED94g5W3AsW6f81bDGKySzeTFStoq4WvFyJ 2017-09-18T19:07:27Z reserve=1 # addr=12D4i8v7ij52hvso3woAPbm2KPA7ucQX7h hdkeypath=m/0'/0'/128'
+KyZskTR6nmW1kydh9f5g9eyXnXRwnJDX1NiU4JLkd8ptseBDoeWN 2017-09-18T19:07:27Z reserve=1 # addr=12D8X4qLZ7tVHFMzwdFdXYdmZ55X3qD5DX hdkeypath=m/0'/0'/319'
+KwFDGuCnShh9BTZV22FyjTfrRNeVwRHTAZCgsF7i9xeqLRJrg6QM 2017-09-18T19:07:27Z reserve=1 # addr=12Dr1yMBpNeampwYsEEScMiZSiE6vC9ooY hdkeypath=m/0'/0'/72'
+KzMLxb5XgZtgSGY64T9ekTe6aTnNPt1oYzoPa2F3dCgkMmPvuSiY 2017-09-18T19:07:27Z reserve=1 # addr=12EGgUN3xEZwXcipz41TEyVcm5X2u1Lzf1 hdkeypath=m/0'/0'/636'
+KyQ645daANAfhYv1c2ya37wg8WRmGe4Pgbt5bkcYeKnu3xXkUrCt 2017-09-18T19:07:27Z reserve=1 # addr=12FB9PR8oP58KoPSAWicyjgv2PK6qtkpX8 hdkeypath=m/0'/0'/346'
+KzA6B9uu6eUgytq69F2WLjvLHea34Xy5DUcpRG32Cr2zWoLw3fqh 2017-09-18T19:07:27Z reserve=1 # addr=12Fznbw763E6VqGTFK4oXXjDDKQnnDWwVG hdkeypath=m/0'/0'/928'
+KzLhYhBZwrbcHFmZnuGniN7Xn88u1zHuhEoUj8zVViMuNKeETXdG 2017-09-18T19:07:27Z reserve=1 # addr=12G9f4aFiobRY5nDTN6aR7zWZpR8KYR6im hdkeypath=m/0'/0'/7'
+KydySFGfowWh74Tx6aXPzS1a9WkoRnoWLM2p5QY2FjQW8ufvBmr6 2017-09-18T19:07:27Z reserve=1 # addr=12GdJv9KVvoBaQhZuTxnFRChtEoTLMyf9Q hdkeypath=m/0'/0'/830'
+L3TTZVZKd7obbkBPgY7fEzoKhQJpZqNnLJz1pqESiQn2Uo6JHNuj 2017-09-18T19:07:27Z reserve=1 # addr=12HcLKceVsqxcTGX3dWAk9qJ3KaofZES2Y hdkeypath=m/0'/1'/352'
+KzauKVLNW2ZRornVzaFB5WGbipZjvLAb9UjD8HrqkiVhuZabfrdY 2017-09-18T19:07:27Z reserve=1 # addr=12JvNdkfQmMhrXFiTaPLBRQaGmj47v54Cs hdkeypath=m/0'/0'/547'
+L2jYNEHVsSmDd3Dr68hiRZdbGoTNtyqymzNmjLdER8YkscKBumic 2017-09-18T19:07:27Z reserve=1 # addr=12KJW1CRbAbESzZD6oxcDCTg9Ac6NDdb9C hdkeypath=m/0'/0'/914'
+Kz1U26YUceKUDj6rw6TtSEUbVxqxDxMsAioeAkQQs8RttSE1nmYa 2017-09-18T19:07:27Z reserve=1 # addr=12LZZ6xC7X3gXGY3t9e128YdePhjDjPrSa hdkeypath=m/0'/0'/125'
+L1yKAoC6irDo83KiXFzLh6af9sE7YjmKN375E4b3nX97bRTSfG3g 2017-09-18T19:07:27Z reserve=1 # addr=12M6yokHDBEJ15aAyFS7euRggf8CUwsko2 hdkeypath=m/0'/1'/432'
+KzTC5PAZQ5LfnFGz683pM9UZemkTJ2dahb6Mdat4TEJr12zofoqU 2017-09-18T19:07:27Z reserve=1 # addr=12MpKkHPZNQaEG6fLAkHc4vbr1KyXvJPp1 hdkeypath=m/0'/1'/309'
+KwWe1KDu6tQ122p54rZEN5qegPDHgPUp86258GkdW2mBRALSyk43 2017-09-18T19:07:27Z reserve=1 # addr=12NPB1tYPtPaEuxBdHcVQfdP3eNotpHyzF hdkeypath=m/0'/1'/66'
+KypmNhq9ptrvkz1rCWSszcEMcnruL1Hdm9LiGwroQMHESbp5FsBa 2017-09-18T19:07:27Z reserve=1 # addr=12QaWB8EhG8wFAgQ4rbJzq1KpWgHFzDb2W hdkeypath=m/0'/0'/79'
+L5iNfXhyF1ZYP9WrM6hyWZSEPHiTKA1cnwfHKzK1rkQZEgRFJx5P 2017-09-18T19:07:27Z reserve=1 # addr=12R7bNadKXfqbCXA6P7efNDWz81a1F3E9d hdkeypath=m/0'/0'/372'
+L2MJYNGatcetU5LdcWKo6fu8My54jQYpDdkyfQVyugawu7SSguAc 2017-09-18T19:07:27Z reserve=1 # addr=12RQfKkwfyVkMNpRG7FnxNK7ZNun52BTvD hdkeypath=m/0'/0'/455'
+KwypYV7K3emewSJ9bCT4uTa2xSBD7ZHCAK8aSnqQLdLmpWnW9eje 2017-09-18T19:07:27Z reserve=1 # addr=12SzoA4sow2eyCspHaSHzGcLv3jVLKVK8R hdkeypath=m/0'/1'/6'
+KzCABRNQXJCMrScYxLXVipVaNV1puY6SVPoCZY979FMmNjps1tVF 2017-09-18T19:07:27Z reserve=1 # addr=12VWSvvzqT6CLFC6qHA9C5LBxEVqrSHRih hdkeypath=m/0'/0'/799'
+KyaN2XahbkmL4Nua1vedpoehvuvmTmqA9BJksYiZGkV2NX9h5Cx1 2017-09-18T19:07:27Z reserve=1 # addr=12WBprMrLCAphMsBGopTdBAjfqzfbqKikN hdkeypath=m/0'/0'/454'
+L3wfGdGiD1FazmVq3onXKZfKJeZQUPgxKxBjLp9d47GVX4x4oxJS 2017-09-18T19:07:27Z reserve=1 # addr=12Wb3qFBpNMmCv3q587qAMX59ADwGBDCYJ hdkeypath=m/0'/1'/465'
+L112v3tnDrYmWVwcPsim3aMFsUdcuDDo3siCo7V3WDTqfifXA5Ku 2017-09-18T19:07:27Z reserve=1 # addr=12Xb5nGyU14UeJEdkfo1B1dPucA6ULpAHt hdkeypath=m/0'/0'/758'
+L3b4N8pobwj3daPFTxcaRxoDYYcj6yaJJpZK9McK5MsGLXnbMNsr 2017-09-18T19:07:27Z reserve=1 # addr=12ZTexE3VGRJn1qk51UdGnA7T9RoYtGEKe hdkeypath=m/0'/0'/144'
+
+# End of dump

+ 221 - 32
test/test.py

@@ -20,7 +20,7 @@
 test/test.py:  Test suite for the MMGen suite
 test/test.py:  Test suite for the MMGen suite
 """
 """
 
 
-import sys,os
+import sys,os,subprocess,shutil,time,re
 
 
 pn = os.path.dirname(sys.argv[0])
 pn = os.path.dirname(sys.argv[0])
 os.chdir(os.path.join(pn,os.pardir))
 os.chdir(os.path.join(pn,os.pardir))
@@ -80,13 +80,17 @@ if not any(e in ('--skip-deps','--resume','-S','-r') for e in sys.argv+shortopts
 		try: os.listdir(data_dir)
 		try: os.listdir(data_dir)
 		except: pass
 		except: pass
 		else:
 		else:
-			import shutil
-			shutil.rmtree(data_dir)
+			try: shutil.rmtree(data_dir)
+			except: # we couldn't remove data dir - perhaps regtest daemon is running
+				try: subprocess.call(['python','mmgen-regtest','stop'])
+				except: rdie(1,'Unable to remove data dir!')
+				else:
+					time.sleep(2)
+					shutil.rmtree(data_dir)
 		os.mkdir(data_dir,0755)
 		os.mkdir(data_dir,0755)
 	else:
 	else:
 		d,pfx = '/dev/shm','mmgen-test-'
 		d,pfx = '/dev/shm','mmgen-test-'
 		try:
 		try:
-			import subprocess
 			subprocess.call('rm -rf %s/%s*'%(d,pfx),shell=True)
 			subprocess.call('rm -rf %s/%s*'%(d,pfx),shell=True)
 		except Exception as e:
 		except Exception as e:
 			die(2,'Unable to delete directory tree %s/%s* (%s)'%(d,pfx,e))
 			die(2,'Unable to delete directory tree %s/%s* (%s)'%(d,pfx,e))
@@ -119,7 +123,7 @@ opts_data = lambda: {
 -l, --list-cmds     List and describe the commands in the test suite
 -l, --list-cmds     List and describe the commands in the test suite
 -L, --log           Log commands to file {lf}
 -L, --log           Log commands to file {lf}
 -n, --names         Display command names instead of descriptions
 -n, --names         Display command names instead of descriptions
--O, --popen-spawn   Use pexpect's popen_spawn instead of popen
+-O, --popen-spawn   Use pexpect's popen_spawn instead of popen (always true, so ignored)
 -p, --pause         Pause between tests, resuming on keypress
 -p, --pause         Pause between tests, resuming on keypress
 -P, --profile       Record the execution time of each script
 -P, --profile       Record the execution time of each script
 -q, --quiet         Produce minimal output.  Suppress dependency info
 -q, --quiet         Produce minimal output.  Suppress dependency info
@@ -141,6 +145,7 @@ If no command is given, the whole suite of tests is run.
 sys.argv = [sys.argv[0]] + ['--data-dir',data_dir] + sys.argv[1:]
 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
 
 
 tn_desc = ('','.testnet')[g.testnet]
 tn_desc = ('','.testnet')[g.testnet]
 
 
@@ -174,6 +179,9 @@ cfgs = {
 		},
 		},
 		'segwit': get_segwit_val()
 		'segwit': get_segwit_val()
 	},
 	},
+	'17': {
+		'tmpdir':        os.path.join('test','tmp17'),
+	},
 	'1': {
 	'1': {
 		'tmpdir':        os.path.join('test','tmp1'),
 		'tmpdir':        os.path.join('test','tmp1'),
 		'wpasswd':       'Dorian',
 		'wpasswd':       'Dorian',
@@ -268,8 +276,12 @@ 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','9914 6D10 2307 F348','7DBF 441F E188 8B37'),
-'keyaddrfile_chk':('CF83 32FB 8A8B 08E2','FEBF 7878 97BB CC35','C13B F717 D4E8 CF59','4DB5 BAF0 45B7 6E81'),
+		'addrfile_chk':            ('B230 7526 638F 38CB','B64D 7327 EF2A 60FE'),
+		'addrfile_segwit_chk':     ('9914 6D10 2307 F348','7DBF 441F E188 8B37'),
+		'addrfile_compressed_chk': ('95EB 8CC0 7B3B 7856','629D FDE4 CDC0 F276'),
+		'keyaddrfile_chk':            ('CF83 32FB 8A8B 08E2','FEBF 7878 97BB CC35'),
+		'keyaddrfile_segwit_chk':     ('C13B F717 D4E8 CF59','4DB5 BAF0 45B7 6E81'),
+		'keyaddrfile_compressed_chk': ('E43A FA46 5751 720A','B995 A6CF D1CD FAD0'),
 		'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',
@@ -297,8 +309,12 @@ 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','91C4 0414 89E4 2089','3BA6 7494 8E2B 858D'),
-'keyaddrfile_chk': ('9648 5132 B98E 3AD9','2F72 C83F 44C5 0FAC','C98B DF08 A3D5 204B','25F2 AEB6 AAAC 8BBE'),
+		'addrfile_chk':            ('8C17 A5FA 0470 6E89','0A59 C8CD 9439 8B81'),
+		'addrfile_segwit_chk':     ('91C4 0414 89E4 2089','3BA6 7494 8E2B 858D'),
+		'addrfile_compressed_chk': ('2615 8401 2E98 7ECA','DF38 22AB AAB0 124E'),
+		'keyaddrfile_chk':            ('9648 5132 B98E 3AD9','2F72 C83F 44C5 0FAC'),
+		'keyaddrfile_segwit_chk':     ('C98B DF08 A3D5 204B','25F2 AEB6 AAAC 8BBE'),
+		'keyaddrfile_compressed_chk': ('6D6D 3D35 04FD B9C3','B345 9CD8 9EAE 5489'),
 		'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',
@@ -326,8 +342,12 @@ 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','06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14'),
-'keyaddrfile_chk': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2','A447 12C2 DD14 5A9B','0690 460D A600 D315'),
+		'addrfile_chk':            ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E'),
+		'addrfile_segwit_chk':     ('06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14'),
+		'addrfile_compressed_chk': ('A33C 4FDE F515 F5BC','5186 02C2 535E B7D5'),
+		'keyaddrfile_chk':            ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2'),
+		'keyaddrfile_segwit_chk':     ('A447 12C2 DD14 5A9B','0690 460D A600 D315'),
+		'keyaddrfile_compressed_chk': ('420A 8EB5 A9E2 7814','3243 DD92 809E FE8D'),
 		'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',
@@ -475,6 +495,8 @@ cmd_group['ref'] = (
 	('refwalletgen',   ([],'gen new refwallet')),
 	('refwalletgen',   ([],'gen new refwallet')),
 	('refaddrgen',     (['mmdat',pwfile],'new refwallet addr chksum')),
 	('refaddrgen',     (['mmdat',pwfile],'new refwallet addr chksum')),
 	('refkeyaddrgen',  (['mmdat',pwfile],'new refwallet key-addr chksum')),
 	('refkeyaddrgen',  (['mmdat',pwfile],'new refwallet key-addr chksum')),
+	('refaddrgen_compressed',    (['mmdat',pwfile],'new refwallet addr chksum (compressed)')),
+	('refkeyaddrgen_compressed', (['mmdat',pwfile],'new refwallet key-addr chksum (compressed)')),
 	('refpasswdgen',   (['mmdat',pwfile],'new refwallet passwd file chksum')),
 	('refpasswdgen',   (['mmdat',pwfile],'new refwallet passwd file chksum')),
 	('ref_b32passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
 	('ref_b32passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
 )
 )
@@ -515,6 +537,23 @@ cmd_group['conv_out'] = ( # writing
 	('ref_hincog_conv_out', 'ref seed conversion to hidden incog data')
 	('ref_hincog_conv_out', 'ref seed conversion to hidden incog data')
 )
 )
 
 
+cmd_group['regtest'] = (
+	('regtest_setup',       'regtest (Bob and Alice) mode setup'),
+	('regtest_alice_bal1',  "Alice's balance"),
+	('regtest_bob_bal1',    "Bob's balance"),
+	('regtest_bob_split',   "splitting Bob's funds"),
+	('regtest_generate',    'mining a block'),
+	('regtest_bob_bal2',    "Bob's balance"),
+	('regtest_bob_rbf_send','sending funds to Alice (RBF)'),
+	('regtest_get_mempool1','mempool (before RBF bump)'),
+	('regtest_bob_rbf_bump1','bumping RBF transaction'),
+	('regtest_get_mempool2','mempool (after RBF bump)'),
+	('regtest_generate',    'mining a block'),
+	('regtest_bob_bal3',    "Bob's balance"),
+	('regtest_alice_bal2',  "Alice's balance"),
+	('regtest_stop',        'stopping regtest daemon'),
+)
+
 cmd_list = OrderedDict()
 cmd_list = OrderedDict()
 for k in cmd_group: cmd_list[k] = []
 for k in cmd_group: cmd_list[k] = []
 
 
@@ -556,6 +595,11 @@ for a,b in cmd_group['conv_out']:
 		cmd_list['conv_out'].append(k)
 		cmd_list['conv_out'].append(k)
 		cmd_data[k] = (10+i,'%s (%s-bit)' % (b,j),[[[],10+i]])
 		cmd_data[k] = (10+i,'%s (%s-bit)' % (b,j),[[[],10+i]])
 
 
+cmd_data['info_regtest'] = 'regtest mode',[17]
+for a,b in cmd_group['regtest']:
+	cmd_list['regtest'].append(a)
+	cmd_data[a] = (17,b,[[[],17]])
+
 utils = {
 utils = {
 	'check_deps': 'check dependencies for specified command',
 	'check_deps': 'check dependencies for specified command',
 	'clean':      'clean specified tmp dir(s) 1,2,3,4,5 or 6 (no arg = all dirs)',
 	'clean':      'clean specified tmp dir(s) 1,2,3,4,5 or 6 (no arg = all dirs)',
@@ -596,6 +640,8 @@ meta_cmds = OrderedDict([
 	['saved_ref_conv_out1', [c[0]+'1' for c in cmd_group['conv_out']]],
 	['saved_ref_conv_out1', [c[0]+'1' for c in cmd_group['conv_out']]],
 	['saved_ref_conv_out2', [c[0]+'2' for c in cmd_group['conv_out']]],
 	['saved_ref_conv_out2', [c[0]+'2' for c in cmd_group['conv_out']]],
 	['saved_ref_conv_out3', [c[0]+'3' for c in cmd_group['conv_out']]],
 	['saved_ref_conv_out3', [c[0]+'3' for c in cmd_group['conv_out']]],
+
+	['regtest', dict(cmd_group['regtest']).keys()],
 ])
 ])
 
 
 del cmd_group
 del cmd_group
@@ -677,7 +723,6 @@ if opt.list_cmds:
 		Msg(fs.format(cmd,utils[cmd],w=w))
 		Msg(fs.format(cmd,utils[cmd],w=w))
 	sys.exit(0)
 	sys.exit(0)
 
 
-import time,re
 NL = ('\r\n','\n')[g.platform=='linux' and bool(opt.popen_spawn)]
 NL = ('\r\n','\n')[g.platform=='linux' and bool(opt.popen_spawn)]
 
 
 def get_file_with_ext(ext,mydir,delete=True,no_dot=False):
 def get_file_with_ext(ext,mydir,delete=True,no_dot=False):
@@ -1136,20 +1181,27 @@ class MMGenTestSuite(object):
 		have_dfl_wallet = False
 		have_dfl_wallet = False
 		ok()
 		ok()
 
 
-	def addrgen(self,name,wf,pf=None,check_ref=False,ftype='addr',id_str=None,extra_args=[]):
-		ftype,chkfile = ((ftype,'{}file_chk'.format(ftype)),('pass','passfile32_chk'))[ftype=='pass32']
-		add_args = extra_args + (get_segwit_arg(cfg),[])[ftype[:4]=='pass']
-		dlist = [id_str] if id_str else []
-		t = MMGenExpect(name,'mmgen-{}gen'.format(ftype), add_args +
-				['-d',cfg['tmpdir']] + ([],[wf])[bool(wf)] + dlist + [cfg['{}_idx_list'.format(ftype)]])
+	def addrgen(self,name,wf,pf=None,check_ref=False,ftype='addr',id_str=None,extra_args=[],mmtype=None):
+		if cfg['segwit'] and ftype[:4] != 'pass' and not mmtype: mmtype = 'segwit'
+		cmd_pfx = (ftype,'pass')[ftype[:4]=='pass']
+		t = MMGenExpect(name,'mmgen-{}gen'.format(cmd_pfx),
+				['-d',cfg['tmpdir']] +
+				extra_args +
+				([],['--type='+str(mmtype)])[bool(mmtype)] +
+				([],[wf])[bool(wf)] +
+				([],[id_str])[bool(id_str)] +
+				[cfg['{}_idx_list'.format(cmd_pfx)]])
 		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')
-		desc = ('address','password')[ftype=='pass']
+		desc = ('address','password')[ftype[:4]=='pass']
 		chk = t.expect_getend(r'Checksum for {} data .*?: '.format(desc),regex=True)
 		chk = t.expect_getend(r'Checksum for {} data .*?: '.format(desc),regex=True)
 		if check_ref:
 		if check_ref:
-			c = (cfg[chkfile][g.testnet + 2*cfg['segwit']],cfg[chkfile])[ftype=='pass']
-			refcheck('address data checksum',chk,c)
+			k = 'passfile32_chk' if ftype == 'pass32' \
+					else 'passfile_chk' if ftype == 'pass' \
+						else '{}file{}_chk'.format(ftype,'_'+mmtype if mmtype else '')
+			chk_ref = cfg[k] if ftype[:4] == 'pass' else cfg[k][g.testnet]
+			refcheck('address data checksum',chk,chk_ref)
 			return
 			return
 		t.written_to_file('Addresses',oo=True)
 		t.written_to_file('Addresses',oo=True)
 		t.ok()
 		t.ok()
@@ -1158,9 +1210,11 @@ class MMGenTestSuite(object):
 		return self.addrgen(name,wf=None,pf=pf,check_ref=check_ref)
 		return self.addrgen(name,wf=None,pf=pf,check_ref=check_ref)
 
 
 	def refaddrgen(self,name,wf,pf):
 	def refaddrgen(self,name,wf,pf):
-		d = ' (%s-bit seed)' % cfg['seed_len']
 		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):
+		self.addrgen(name,wf,pf=pf,check_ref=True,mmtype='compressed')
+
 	def addrimport(self,name,addrfile):
 	def addrimport(self,name,addrfile):
 		outfile = os.path.join(cfg['tmpdir'],'addrfile_w_comments')
 		outfile = os.path.join(cfg['tmpdir'],'addrfile_w_comments')
 		add_comments_to_addr_file(addrfile,outfile)
 		add_comments_to_addr_file(addrfile,outfile)
@@ -1210,8 +1264,7 @@ class MMGenTestSuite(object):
 		t.expect(r"'q'=quit view, .*?:.",'q', regex=True)
 		t.expect(r"'q'=quit view, .*?:.",'q', regex=True)
 		outputs_list = [(addrs_per_wallet+1)*i + 1 for i in range(len(tx_data))]
 		outputs_list = [(addrs_per_wallet+1)*i + 1 for i in range(len(tx_data))]
 		if non_mmgen_input: outputs_list.append(len(tx_data)*(addrs_per_wallet+1) + 1)
 		if non_mmgen_input: outputs_list.append(len(tx_data)*(addrs_per_wallet+1) + 1)
-		t.expect('Enter a range or space-separated list of outputs to spend: ',
-				' '.join([str(i) for i in outputs_list])+'\n')
+		t.expect('outputs to spend: ',' '.join([str(i) for i in outputs_list])+'\n')
 		if non_mmgen_input and not txdo_args: t.expect('Accept? (y/N): ','y')
 		if non_mmgen_input and not txdo_args: t.expect('Accept? (y/N): ','y')
 		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?
@@ -1408,14 +1461,17 @@ class MMGenTestSuite(object):
 		self.addrgen_incog(name,[],'',in_fmt='hi',desc='hidden incognito data',
 		self.addrgen_incog(name,[],'',in_fmt='hi',desc='hidden incognito data',
 			args=['-H','%s,%s'%(rf,hincog_offset),'-l',str(hincog_seedlen)])
 			args=['-H','%s,%s'%(rf,hincog_offset),'-l',str(hincog_seedlen)])
 
 
-	def keyaddrgen(self,name,wf,pf=None,check_ref=False):
-		args = get_segwit_arg(cfg) + ['-d',cfg['tmpdir'],usr_rand_arg,wf,cfg['addr_idx_list']]
-		t = MMGenExpect(name,'mmgen-keygen', args)
+	def keyaddrgen(self,name,wf,pf=None,check_ref=False,mmtype=None):
+		if cfg['segwit'] and not mmtype: mmtype = 'segwit'
+		args = ['-d',cfg['tmpdir'],usr_rand_arg,wf,cfg['addr_idx_list']]
+		t = MMGenExpect(name,'mmgen-keygen',
+				([],['--type='+str(mmtype)])[bool(mmtype)] + args)
 		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:
-			refcheck('key-address data checksum',chk,cfg['keyaddrfile_chk'][g.testnet + 2*cfg['segwit']])
+			k = 'keyaddrfile{}_chk'.format('_'+mmtype if mmtype else '')
+			refcheck('key-address data checksum',chk,cfg[k][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)
@@ -1428,6 +1484,9 @@ class MMGenTestSuite(object):
 	def refkeyaddrgen(self,name,wf,pf):
 	def refkeyaddrgen(self,name,wf,pf):
 		self.keyaddrgen(name,wf,pf,check_ref=True)
 		self.keyaddrgen(name,wf,pf,check_ref=True)
 
 
+	def refkeyaddrgen_compressed(self,name,wf,pf):
+		self.keyaddrgen(name,wf,pf,check_ref=True,mmtype='compressed')
+
 	def refpasswdgen(self,name,wf,pf):
 	def refpasswdgen(self,name,wf,pf):
 		self.addrgen(name,wf,pf,check_ref=True,ftype='pass',id_str='alice@crypto.org')
 		self.addrgen(name,wf,pf,check_ref=True,ftype='pass',id_str='alice@crypto.org')
 
 
@@ -1724,10 +1783,9 @@ class MMGenTestSuite(object):
 
 
 	def ref_tool_decrypt(self,name):
 	def ref_tool_decrypt(self,name):
 		f = os.path.join(ref_dir,ref_enc_fn)
 		f = os.path.join(ref_dir,ref_enc_fn)
-		aa = []
-		t = MMGenExpect(name,'mmgen-tool',
-				aa + ['-q','decrypt',f,'outfile=-','hash_preset=1'])
+		t = MMGenExpect(name,'mmgen-tool', ['-q','decrypt',f,'outfile=-','hash_preset=1'])
 		t.passphrase('user data',tool_enc_passwd)
 		t.passphrase('user data',tool_enc_passwd)
+#		t.expect("Type uppercase 'YES' to confirm: ",'YES\n') # comment out with popen_spawn
 		t.expect(NL,nonl=True)
 		t.expect(NL,nonl=True)
 		import re
 		import re
 		o = re.sub('\r\n','\n',t.read())
 		o = re.sub('\r\n','\n',t.read())
@@ -1794,6 +1852,134 @@ class MMGenTestSuite(object):
 			add_args=add_args,
 			add_args=add_args,
 			extra_desc='(check)')
 			extra_desc='(check)')
 
 
+	def regtest_setup(self,name):
+		try: shutil.rmtree(os.path.join(data_dir,'regtest'))
+		except: pass
+		os.environ['MMGEN_TEST_SUITE'] = '' # mnemonic is piped to stdin, so stop being a terminal
+		t = MMGenExpect(name,'mmgen-regtest',['-m','--data-dir='+data_dir,'setup'])
+		os.environ['MMGEN_TEST_SUITE'] = '1'
+		t.expect('Mined')
+		t.expect('Setting up')
+		t.expect('Creating')
+		t.expect('Creating')
+		t.expect('Importing')
+		t.expect('Importing')
+		t.expect('Importing')
+		t.expect('Setting up')
+		t.expect('Creating')
+		t.expect('Creating')
+		t.expect('Importing')
+		t.expect('Importing')
+		t.expect('Importing')
+		t.expect('Sending')
+		t.expect('Sending')
+		t.expect('Mined')
+		t.expect('Setup complete')
+		t.ok()
+
+	def regtest_user_bal(self,name,user,bal):
+		t = MMGenExpect(name,'mmgen-tool',['--'+user,'listaddresses','showempty=1'])
+		total = t.expect_getend('TOTAL: ')
+		cmp_or_die(total,'{} BTC'.format(bal))
+
+	def regtest_alice_bal1(self,name):
+		return self.regtest_user_bal(name,'alice','500')
+
+	def regtest_alice_bal2(self,name):
+		return self.regtest_user_bal(name,'alice','600')
+
+	def regtest_bob_bal1(self,name):
+		return self.regtest_user_bal(name,'bob','500')
+
+	def regtest_bob_bal2(self,name):
+		return self.regtest_user_bal(name,'bob','499.999942')
+
+	def regtest_bob_bal3(self,name):
+		return self.regtest_user_bal(name,'bob','399.9998214')
+
+	def regtest_user_txdo(self,name,user,fee,outputs_cl,outputs_prompt,extra_args=[],no_send=False):
+		os.environ['MMGEN_BOGUS_SEND'] = ''
+		t = MMGenExpect(name,'mmgen-txdo',
+			['-d',cfg['tmpdir'],'-B','--'+user,'--tx-fee='+fee] + extra_args + outputs_cl)
+		os.environ['MMGEN_BOGUS_SEND'] = '1'
+
+		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: ',outputs_prompt+'\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): ','\n')
+		t.expect('View decoded transaction\? .*?: ','t',regex=True)
+		t.expect('to continue: ','\n')
+		t.written_to_file('Signed transaction')
+		if not no_send:
+			t.expect('to confirm: ','YES, I REALLY WANT TO DO THIS\n')
+			t.expect('Transaction sent')
+		t.read()
+		t.ok()
+
+	def regtest_bob_split(self,name):
+		from mmgen.regtest import sids
+		outputs_cl = [sids['bob']+':C:1,100', sids['bob']+':L:2,200',sids['bob']+':S:2']
+		return self.regtest_user_txdo(name,'bob','20s',outputs_cl,'1')
+
+	def regtest_bob_rbf_send(self,name):
+		from mmgen.regtest import sids
+		outputs_cl = [
+			'n2XovQAmdtRBS7H1PUnRFk1FR5n8wDAsXB,60', # sids['alice']:L:1
+			'mn67MDDa16eV2H6yDtPi3mKTAqqDxoWpJ3,40', # sids['alice']:C:1
+			sids['bob']+':S:2']
+		return self.regtest_user_txdo(name,'bob','10s',outputs_cl,'3',extra_args=['--rbf'])
+
+	def regtest_user_txbump(self,name,user,txfile,fee,red_op,no_send=False):
+		os.environ['MMGEN_BOGUS_SEND'] = ''
+		t = MMGenExpect(name,'mmgen-txbump',
+			['-d',cfg['tmpdir'],'--send','--'+user,'--tx-fee='+fee,'--output-to-reduce='+red_op] + [txfile])
+		os.environ['MMGEN_BOGUS_SEND'] = '1'
+		t.expect('OK? (Y/n): ','y') # output OK?
+		t.expect('OK? (Y/n): ','y') # fee OK?
+		t.expect('Add a comment to transaction? (y/N): ','n')
+		t.written_to_file('Signed transaction')
+		if not no_send:
+			t.expect('to confirm: ','YES, I REALLY WANT TO DO THIS\n')
+			t.expect('Transaction sent')
+			t.written_to_file('Signed transaction')
+		t.read()
+		t.ok()
+
+	def regtest_bob_rbf_bump1(self,name):
+		txfile = get_file_with_ext(',10].sigtx',cfg['tmpdir'],delete=False,no_dot=True)
+		return self.regtest_user_txbump(name,'bob',txfile,'60s','c')
+
+	def regtest_generate(self,name):
+		t = MMGenExpect(name,'mmgen-regtest',['generate'])
+		t.expect('Mined 1 block')
+		t.ok()
+
+	def regtest_get_mempool(self,name):
+		t = MMGenExpect(name,'mmgen-regtest',['show_mempool'])
+		ret = eval(t.read())
+		return ret
+
+	def regtest_get_mempool1(self,name):
+		mp = self.regtest_get_mempool(name)
+		if len(mp) != 1:
+			rdie(2,'Mempool has more or less than one TX!')
+		write_to_tmpfile(cfg,'rbf_txid',mp[0]+'\n')
+		ok()
+
+	def regtest_get_mempool2(self,name):
+		mp = self.regtest_get_mempool(name)
+		if len(mp) != 1:
+			rdie(2,'Mempool has more or less than one TX!')
+		chk = read_from_tmpfile(cfg,'rbf_txid')
+		if chk.strip() == mp[0]:
+			rdie(2,'TX in mempool has not changed!  RBF bump failed')
+		ok()
+
+	def regtest_stop(self,name):
+		t = MMGenExpect(name,'mmgen-regtest',['stop'])
+		t.ok()
 
 
 	# END methods
 	# END methods
 	for k in (
 	for k in (
@@ -1815,13 +2001,15 @@ class MMGenTestSuite(object):
 			'ref_hincog_conv_out',
 			'ref_hincog_conv_out',
 			'ref_wallet_chk',
 			'ref_wallet_chk',
 			'refwalletgen',
 			'refwalletgen',
-			'refaddrgen',
 			'ref_seed_chk',
 			'ref_seed_chk',
 			'ref_hex_chk',
 			'ref_hex_chk',
 			'ref_mn_chk',
 			'ref_mn_chk',
 			'ref_brain_chk',
 			'ref_brain_chk',
 			'ref_hincog_chk',
 			'ref_hincog_chk',
+			'refaddrgen',
 			'refkeyaddrgen',
 			'refkeyaddrgen',
+			'refaddrgen_compressed',
+			'refkeyaddrgen_compressed',
 			'refpasswdgen',
 			'refpasswdgen',
 			'ref_b32passwdgen'
 			'ref_b32passwdgen'
 		):
 		):
@@ -1887,6 +2075,7 @@ try:
 	else:
 	else:
 		clean()
 		clean()
 		for cmd in cmd_data:
 		for cmd in cmd_data:
+			if cmd == 'info_regtest': break # don't run these by default
 			if cmd[:5] == 'info_':
 			if cmd[:5] == 'info_':
 				msg(green('%sTesting %s' % (('\n','')[bool(opt.resume)],cmd_data[cmd][0])))
 				msg(green('%sTesting %s' % (('\n','')[bool(opt.resume)],cmd_data[cmd][0])))
 				continue
 				continue