Browse Source

mmgen-regtest: final implementation; additional test suite tests

philemon 7 years ago
parent
commit
12ea12e4e2
7 changed files with 228 additions and 181 deletions
  1. 23 20
      mmgen/main_regtest.py
  2. 6 5
      mmgen/opts.py
  3. 37 89
      mmgen/regtest.py
  4. 2 6
      mmgen/txsign.py
  5. 1 5
      mmgen/util.py
  6. 43 13
      scripts/test-release.sh
  7. 116 43
      test/test.py

+ 23 - 20
mmgen/main_regtest.py

@@ -30,38 +30,41 @@ opts_data = lambda: {
 -h, --help          Print this help message
 --, --longhelp      Print help message for long options (common options)
 -e, --empty         Don't fund Bob and Alice's wallets on setup
--m, --mixed         Create Bob and Alice's wallets with mixed address types
+-n, --setup-no-stop-daemon  Don't stop daemon after setup is finished
 -q, --quiet         Produce quieter output
 -v, --verbose       Produce more verbose output
 """,
 	'notes': """
 
 
-                           AVAILABLE COMMANDS
+                         AVAILABLE COMMANDS
 
-    setup           - setup up system for regtest operation with MMGen
-    stop            - stop the regtest bitcoind
-    bob             - switch to Bob's wallet, starting daemon if necessary
-    alice           - switch to Alice's wallet, starting daemon if necessary
-    user            - show current user
-    generate        - mine a block
-    test_daemon     - test whether daemon is running
-    get_balances    - get balances of Bob and Alice
-    show_mempool    - show transaction IDs in mempool
+  setup          - setup up system for regtest operation with MMGen
+  stop           - stop the regtest bitcoind
+  bob            - switch to Bob's wallet, starting daemon if necessary
+  alice          - switch to Alice's wallet, starting daemon if necessary
+  user           - show current user
+  generate       - mine a block
+  send ADDR AMT  - send amount AMT to address ADDR
+  test_daemon    - test whether daemon is running
+  get_balances   - get balances of Bob and Alice
+  show_mempool   - show transaction IDs in mempool
 	"""
 }
 
 cmd_args = opts.init(opts_data)
 
-if len(cmd_args) != 1:
-	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','miner','user','send',
 		'wait_for_daemon','wait_for_exit','get_current_user','get_balances','show_mempool')
 
-if cmd_args[0] not in cmds:
+try:
+	if cmd_args[0] == 'send':
+		assert len(cmd_args) == 3
+	else:
+		assert cmd_args[0] in cmds and len(cmd_args) == 1
+except:
 	opts.usage()
-
-from mmgen.regtest import *
-
-globals()[cmd_args[0]]()
+else:
+	args = cmd_args[1:]
+	from mmgen.regtest import *
+	globals()[cmd_args[0]](*args)

+ 6 - 5
mmgen/opts.py

@@ -243,7 +243,8 @@ def init(opts_f,add_opts=[],opt_filter=None):
 		mmgen.share.Opts.parse_opts(sys.argv,opts_data,opt_filter=opt_filter)
 
 	if g.bob or g.alice:
-		g.data_dir = os.path.join(g.data_dir_root,'regtest')
+		g.data_dir = os.path.join(g.data_dir_root,'regtest',('alice','bob')[g.bob])
+		check_or_create_dir(g.data_dir)
 		import regtest as rt
 		g.testnet = True
 		g.rpc_host = 'localhost'
@@ -414,10 +415,10 @@ def check_opts(usr_opts):       # Returns false if any check fails
 		elif key == 'coin':
 			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()))
+			from mmgen.regtest import daemon_dir
+			m = "Regtest (Bob and Alice) mode not set up yet.  Run '{}-regtest setup' to initialize."
+			try: os.stat(daemon_dir)
+			except: die(1,m.format(g.proj_name.lower()))
 		else:
 			if g.debug: Msg("check_opts(): No test for opt '%s'" % key)
 

+ 37 - 89
mmgen/regtest.py

@@ -25,25 +25,13 @@ from mmgen.common import *
 PIPE = subprocess.PIPE
 
 data_dir     = os.path.join(g.data_dir_root,'regtest')
-regtest_dir  = os.path.join(data_dir,'regtest')
+daemon_dir   = os.path.join(data_dir,'regtest')
 rpc_port     = 8552
 rpc_user     = 'bobandalice'
 rpc_password = 'hodltothemoon'
 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 = {
-	'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'
-}
-send_addr = {
-	'bob':   'mw42oJ94yRA6ZUNSzmMpjZDR74JNyvqzzZ',
-	'alice': '2N3HhxasbRvrJyHg72JNVCCPi9EUGrEbFnu',
-}
+tr_wallet = lambda user: os.path.join(daemon_dir,'wallet.dat.'+user)
 
 common_args = (
 	'-rpcuser={}'.format(rpc_user),
@@ -78,8 +66,9 @@ def start_cmd(*args,**kwargs):
 		cmd = ('bitcoin-cli',) + common_args + args[1:]
 	if g.debug or not 'quiet' in kwargs:
 		vmsg('{}'.format(' '.join(cmd)))
-	io=(PIPE,None)['no_pipe' in kwargs and kwargs['no_pipe']]
-	return subprocess.Popen(cmd,stdin=io,stdout=io,stderr=io)
+	ip = op = ep = (PIPE,None)['no_pipe' in kwargs and kwargs['no_pipe']]
+	if 'pipe_stdout_only' in kwargs and kwargs['pipe_stdout_only']: ip = ep = None
+	return subprocess.Popen(cmd,stdin=ip,stdout=op,stderr=ep)
 
 def test_daemon():
 	p = start_cmd('cli','getblockcount',quiet=True)
@@ -103,22 +92,25 @@ def wait_for_daemon(state,silent=False,nonl=False):
 
 def get_balances():
 	user1 = get_current_user(quiet=True)
+	if user1 == None:
+		die(1,'Regtest daemon not running')
 	user2 = ('bob','alice')[user1=='bob']
 	tbal = 0
 	from mmgen.obj import BTCAmt
-	for user in (user1,user2):
+	for u in (user1,user2):
 		p = start_cmd('python','mmgen-tool',
-				'--{}'.format(user),'--data-dir='+g.data_dir,
+				'--{}'.format(u),'--data-dir='+g.data_dir,
 					'getbalance','quiet=1')
-		bal = BTCAmt(p.stdout.read())
-		ustr = "{}'s balance:".format(user.capitalize())
+		bal = p.stdout.read().replace(' \b','') # hack
+		if u == user1: user(user2)
+		bal = BTCAmt(bal)
+		ustr = "{}'s balance:".format(u.capitalize())
 		msg('{:<16} {:12}'.format(ustr,bal))
 		tbal += bal
 	msg('{:<16} {:12}'.format('Total balance:',tbal))
-	msg('{:<16} {:12}'.format('Miner fees:',1000-tbal))
 
 def create_data_dir():
-	try: os.stat(regtest_dir)
+	try: os.stat(daemon_dir)
 	except: pass
 	else:
 		if keypress_confirm('Delete your existing MMGen regtest setup and create a new one?'):
@@ -131,48 +123,13 @@ def create_data_dir():
 
 def process_output(p,silent=False):
 	out = p.stdout.read()
-	if not opt.verbose: Msg_r(' \b')
+	if g.platform == 'win' and not opt.verbose: Msg_r(' \b')
 	err = p.stderr.read()
 	if g.debug or not silent:
 		vmsg('stdout: [{}]'.format(out.strip()))
 		vmsg('stderr: [{}]'.format(err.strip()))
 	return out,err
 
-def create_mmgen_wallet(user):
-	gmsg("Creating {}'s MMGen wallet".format(user.capitalize()))
-	p = start_cmd('python','mmgen-walletconv','-d',data_dir,'-i','words','-o','words')
-	p.stdin.write(mnemonic[user]+'\n')
-	p.stdin.close()
-	err = process_output(p)[1]
-	if not 'written to file' in err:
-		rdie(1,'Error creating MMGen wallet')
-	p.wait()
-
-def create_mmgen_addrs(user,addr_type):
-	gmsg('Creating MMGen addresses for user {} (type {})'.format(user.capitalize(),addr_type))
-	suf = ('-'+addr_type,'')[addr_type=='L']
-	try: os.unlink(mmaddrs(user).format(suf))
-	except: pass
-	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.close()
-	err = process_output(p)[1]
-	if not 'written to file' in err:
-		rdie(1,'Error creating MMGen addresses')
-	p.wait()
-
-def import_mmgen_addrs(user,addr_type):
-	gmsg_r('Importing MMGen addresses for user {} (type {})'.format(user.capitalize(),addr_type))
-	suf = ('-'+addr_type,'')[addr_type=='L']
-	p = start_cmd('python','mmgen-addrimport','-q','--batch',
-			'--{}'.format(user),'--data-dir='+g.data_dir,mmaddrs(user).format(suf))
-	err = process_output(p)[1]
-	if not 'addresses imported' in err:
-		rdie(1,'Error importing MMGen addresses')
-	p.wait()
-
 def start_and_wait(user,silent=False,nonl=False):
 	vmsg('Starting bitcoin regtest daemon')
 	(start_daemon_mswin,start_daemon)[g.platform=='linux'](user)
@@ -182,27 +139,18 @@ def stop_and_wait(silent=False,nonl=False,stop_silent=False,ignore_noconnect_err
 	stop(silent=stop_silent,ignore_noconnect_error=ignore_noconnect_error)
 	wait_for_daemon('stopped',silent=silent,nonl=nonl)
 
-def setup_wallet(user,funded,stop=True):
-	gmsg_r("Setting up {}'s tracking wallet".format(user.capitalize()))
-	start_and_wait(user)
-	create_mmgen_wallet(user)
-	mmtypes = ([funded],['L','C','S'])[bool(opt.mixed)]
-	for mmtype in mmtypes:
-		create_mmgen_addrs(user,mmtype)
-	for mmtype in mmtypes:
-		import_mmgen_addrs(user,mmtype); msg('')
-	if stop:
-		stop_and_wait(silent=True,stop_silent=True)
-
-def fund_wallet(user,amt):
-	gmsg('Sending {} BTC to {}'.format(amt,user.capitalize()))
-	p = start_cmd('cli','sendtoaddress',send_addr[user],str(amt))
+def send(addr,amt):
+	user('miner')
+	gmsg('Sending {} BTC to address {}'.format(amt,addr))
+	p = start_cmd('cli','sendtoaddress',addr,str(amt))
 	process_output(p)
 	p.wait()
+	generate(1)
 
 def show_mempool():
 	p = start_cmd('cli','getrawmempool')
-	msg_r(p.stdout.read())
+	from pprint import pformat
+	msg(pformat(eval(p.stdout.read())))
 	p.wait()
 
 def setup():
@@ -214,28 +162,27 @@ def setup():
 
 	gmsg('Starting setup')
 
-	setup_wallet('alice','S')
-	setup_wallet('bob','C')
+	gmsg_r('Creating miner wallet')
+	start_and_wait('miner')
+	generate(432,silent=True)
+	stop_and_wait(silent=True,stop_silent=True)
 
-	if opt.empty:
-		ymsg("'--empty' selected: skipping funding of wallets")
-	else:
-		gmsg_r('Funding wallets')
-		start_and_wait('orig')
-		generate(432,silent=True)
-		fund_wallet('bob',init_amt)
-		fund_wallet('alice',init_amt)
-		generate(1)
-		stop_and_wait(silent=True,stop_silent=True)
+	for user in ('alice','bob'):
+		gmsg_r("Creating {}'s tracking wallet".format(user.capitalize()))
+		start_and_wait(user)
+		if user == 'bob' and opt.setup_no_stop_daemon:
+			msg('Leaving daemon running with Bob as current user')
+		else:
+			stop_and_wait(silent=True,stop_silent=True)
 
 	gmsg('Setup complete')
 
 def get_current_user_win(quiet=False):
 	if test_daemon() == 'stopped': return None
-	p = start_cmd('grep','Using wallet',os.path.join(regtest_dir,'debug.log'),quiet=True)
+	p = start_cmd('grep','Using wallet',os.path.join(daemon_dir,'debug.log'),quiet=True)
 	try: wallet_fn = p.stdout.readlines()[-1].split()[-1]
 	except: return None
-	for k in ('orig','bob','alice'):
+	for k in ('miner','bob','alice'):
 		if wallet_fn == 'wallet.dat.'+k:
 			if not quiet: msg('Current user is {}'.format(k.capitalize()))
 			return k
@@ -245,7 +192,7 @@ def get_current_user_unix(quiet=False):
 	p = start_cmd('pgrep','-af', 'bitcoind.*-rpcuser={}.*'.format(rpc_user))
 	cmdline = p.stdout.read()
 	if not cmdline: return None
-	for k in ('orig','bob','alice'):
+	for k in ('miner','bob','alice'):
 		if 'wallet.dat.'+k in cmdline:
 			if not quiet: msg('Current user is {}'.format(k.capitalize()))
 			return k
@@ -255,6 +202,7 @@ get_current_user = (get_current_user_win,get_current_user_unix)[g.platform=='lin
 
 def bob():   return user('bob',quiet=False)
 def alice(): return user('alice',quiet=False)
+def miner(): return user('miner',quiet=False)
 def user(user=None,quiet=False):
 	if user==None:
 		get_current_user()

+ 2 - 6
mmgen/txsign.py

@@ -138,12 +138,8 @@ def get_seed_files(opt,args):
 	# favor unencrypted seed sources first, as they don't require passwords
 	u,e = SeedSourceUnenc,SeedSourceEnc
 	ret = _pop_and_return(args,u.get_extensions())
-	from mmgen.filename import find_file_in_dir,find_files_in_dir
-	if g.bob or g.alice:
-		import regtest as rt
-		wf = rt.mmwallet(('alice','bob')[g.bob])
-	else:
-		wf = find_file_in_dir(Wallet,g.data_dir) # Make this the first encrypted ss in the list
+	from mmgen.filename import find_file_in_dir
+	wf = find_file_in_dir(Wallet,g.data_dir) # Make this the first encrypted ss in the list
 	if wf: ret.append(wf)
 	ret += _pop_and_return(args,e.get_extensions())
 	if not (ret or opt.mmgen_keys_from_file or opt.keys_from_file): # or opt.use_wallet_dat

+ 1 - 5
mmgen/util.py

@@ -468,11 +468,7 @@ def make_full_path(outdir,outfile):
 def get_seed_file(cmd_args,nargs,invoked_as=None):
 	from mmgen.filename import find_file_in_dir
 	from mmgen.seed import Wallet
-	if g.bob or g.alice:
-		import regtest as rt
-		wf = rt.mmwallet(('alice','bob')[g.bob])
-	else:
-		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?
 

+ 43 - 13
scripts/test-release.sh

@@ -1,8 +1,35 @@
 #!/bin/bash
 # Tested on Linux, MinGW-64
 
+PROGNAME=$(basename $0)
+while getopts hint OPT
+do
+	case "$OPT" in
+	h)  printf "  %-16s Test MMGen release\n" "${PROGNAME^^}:"
+		echo   "  USAGE:           $PROGNAME [options] branch [tests]"
+		echo   "  OPTIONS: '-h'  Print this help message"
+		echo   "           '-i'  Install only; don't run tests"
+		echo   "           '-n'  Don't install; test in place"
+		echo   "           '-t'  Print the tests without running them"
+		echo   "  AVAILABLE TESTS:"
+		echo   "     1 - main"
+		echo   "     2 - regtest"
+		echo   "     3 - tool"
+		echo   "     4 - gen"
+		echo   "  By default, all tests are run"
+		exit ;;
+	i)  INSTALL_ONLY=1 ;;
+	n)  NO_INSTALL=1 ;;
+	t)  TESTING=1 ;;
+	*)  exit ;;
+	esac
+done
+
+shift $((OPTIND-1))
+
 set -e
-GREEN="\e[32;1m" YELLOW="\e[33;1m" RESET="\e[0m" BRANCH=$1
+GREEN="\e[32;1m" YELLOW="\e[33;1m" RESET="\e[0m"
+BRANCH=$1; shift
 REFDIR=test/ref
 if uname -a | grep -qi mingw; then SUDO='' MINGW=1; else SUDO='sudo' MINGW=''; fi
 
@@ -29,12 +56,12 @@ function install {
 	[ "$MINGW" ] && ./setup.py build --compiler=mingw32
 	eval "$SUDO ./setup.py install"
 }
-
+[ -z "$TESTING" ] && LS='\n'
 function do_test {
 	set +x
 	for i in "$@"; do
-		echo -e "\n${GREEN}Running:$RESET $YELLOW$i$RESET"
-		eval "$i"
+		echo -e "$LS${GREEN}Running:$RESET $YELLOW$i$RESET"
+		[ "$TESTING" ] || eval "$i"
 	done
 }
 
@@ -50,13 +77,16 @@ T4=("test/gentest.py -q 2 $REFDIR/btcwallet.dump"
 #	"scripts/tx-old2new.py -S $REFDIR/tx_*raw >/dev/null 2>&1"
 	"scripts/compute-file-chksum.py $REFDIR/*testnet.rawtx >/dev/null 2>&1")
 
-check
-(install)
-eval "cd .test-release/pydist/mmgen-*"
-
-do_test "${T1[@]}"
-do_test "${T2[@]}"
-do_test "${T3[@]}"
-do_test "${T4[@]}"
+[ -d .git -a -z "$NO_INSTALL"  -a -z "$TESTING" ] && {
+	check
+	(install)
+	eval "cd .test-release/pydist/mmgen-*"
+}
+[ "$INSTALL_ONLY" ] && exit
 
-echo -e "\n${GREEN}All OK$RESET"
+if [ "$*" ]; then TESTS=$@; else TESTS='1 2 3 4'; fi
+for t in $TESTS; do
+	[ $t == 4 ] && LS=''
+	eval "do_test \"\${T$t[@]}\""
+done
+echo -e "$LS${GREEN}All OK$RESET"

+ 116 - 43
test/test.py

@@ -539,6 +539,14 @@ cmd_group['conv_out'] = ( # writing
 
 cmd_group['regtest'] = (
 	('regtest_setup',              'regtest (Bob and Alice) mode setup'),
+	('regtest_walletgen_bob',      'wallet generation (Bob)'),
+	('regtest_walletgen_alice',    'wallet generation (Alice)'),
+	('regtest_addrgen_bob',        'address generation (Bob)'),
+	('regtest_addrgen_alice',      'address generation (Alice)'),
+	('regtest_addrimport_bob',     "importing Bob's addresses"),
+	('regtest_addrimport_alice',   "importing Alice's addresses"),
+	('regtest_fund_bob',           "funding Bob's wallet"),
+	('regtest_fund_alice',         "funding Alice's wallet"),
 	('regtest_bob_bal1',           "Bob's balance"),
 	('regtest_bob_split1',         "splitting Bob's funds"),
 	('regtest_generate',           'mining a block'),
@@ -559,8 +567,7 @@ cmd_group['regtest'] = (
 	('regtest_bob_bal5',           "Bob's balance"),
 	('regtest_bob_send_non_mmgen', 'sending funds to Alice (from non-MMGen addrs)'),
 	('regtest_generate',           'mining a block'),
-	('regtest_bob_bal6',           "Bob's balance"),
-	('regtest_alice_bal2',         "Alice's balance"),
+	('regtest_bob_alice_bal',      "Bob and Alice's balances"),
 	('regtest_alice_add_label1',   'adding a label'),
 	('regtest_alice_chk_label1',   'the label'),
 	('regtest_alice_add_label2',   'adding a label'),
@@ -743,13 +750,14 @@ if opt.list_cmds:
 
 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,return_list=False):
 
 	dot = ('.','')[bool(no_dot)]
 	flist = [os.path.join(mydir,f) for f in os.listdir(mydir)
 				if f == ext or f[-len(dot+ext):] == dot+ext]
 
 	if not flist: return False
+	if return_list: return flist
 
 	if len(flist) > 1:
 		if delete:
@@ -1874,20 +1882,69 @@ class MMGenTestSuite(object):
 		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'])
+		t = MMGenExpect(name,'mmgen-regtest',['-n','setup'])
 		os.environ['MMGEN_TEST_SUITE'] = '1'
-		t.expect('Starting setup')
+		for s in 'Starting setup','Creating','Mined','Creating','Creating','Setup complete':
+			t.expect(s)
+		t.ok()
 
-		for user in ('alice','bob'):
-			t.expect('Setting up')
-			for i in range(4): t.expect('Creating')
-			for i in range(3): t.expect('Importing')
+	def regtest_walletgen(self,name,user):
+		t = MMGenExpect(name,'mmgen-walletgen',['-q','-r0','-p1','--'+user])
+		t.passphrase_new('new MMGen wallet','abc')
+		t.label()
+		t.expect('move it to the data directory? (Y/n): ','y')
+		t.written_to_file('MMGen wallet')
+		t.ok()
 
-		for s in ('Funding','Mined','Sending','Sending','Mined','Setup complete'):
-			t.expect(s)
+	def regtest_walletgen_bob(self,name):   return self.regtest_walletgen(name,'bob')
+	def regtest_walletgen_alice(self,name): return self.regtest_walletgen(name,'alice')
+
+	@staticmethod
+	def regtest_user_dir(user):
+		return os.path.join(data_dir,'regtest',user)
+
+	def regtest_user_sid(self,user):
+		return os.path.basename(get_file_with_ext('mmdat',self.regtest_user_dir(user)))[:8]
+
+	def regtest_addrgen(self,name,user):
+		for mmtype in ('legacy','compressed','segwit'):
+			t = MMGenExpect(name,'mmgen-addrgen',
+				['--quiet','--'+user,'--type='+mmtype,
+				'--outdir={}'.format(self.regtest_user_dir(user)),
+				'1-5'],extra_desc='({})'.format(mmtype))
+			t.passphrase('MMGen wallet','abc')
+			t.written_to_file('Addresses')
+			t.ok()
+
+	def regtest_addrgen_bob(self,name):   self.regtest_addrgen(name,'bob')
+	def regtest_addrgen_alice(self,name): self.regtest_addrgen(name,'alice')
+
+	def regtest_addrimport(self,name,user):
+		id_strs = { 'legacy':'', 'compressed':'-C', 'segwit':'-S' }
+		sid = self.regtest_user_sid(user)
+		for desc in ('legacy','compressed','segwit'):
+			fn = os.path.join(self.regtest_user_dir(user),'{}{}[1-5].addrs'.format(sid,id_strs[desc]))
+			t = MMGenExpect(name,'mmgen-addrimport', ['--quiet','--'+user,'--batch',fn],extra_desc='('+desc+')')
+			t.expect('Importing')
+			t.expect('5 addresses imported')
+			t.ok()
 
+	def regtest_addrimport_bob(self,name):   self.regtest_addrimport(name,'bob')
+	def regtest_addrimport_alice(self,name): self.regtest_addrimport(name,'alice')
+
+	def regtest_fund_wallet(self,name,user,mmtype,amt):
+		fn = get_file_with_ext('-{}[1-5].addrs'.format(mmtype),self.regtest_user_dir(user),no_dot=True)
+		silence()
+		addr = AddrList(fn).data[0].addr
+		end_silence()
+		t = MMGenExpect(name,'mmgen-regtest', ['send',str(addr),str(amt)])
+		t.expect('Sending {} BTC'.format(amt))
+		t.expect('Mined 1 block')
 		t.ok()
 
+	def regtest_fund_bob(self,name):   self.regtest_fund_wallet(name,'bob','C',500)
+	def regtest_fund_alice(self,name): self.regtest_fund_wallet(name,'alice','S',500)
+
 	def regtest_user_bal(self,name,user,bal):
 		t = MMGenExpect(name,'mmgen-tool',['--'+user,'listaddresses','showempty=1'])
 		total = t.expect_getend('TOTAL: ')
@@ -1896,9 +1953,6 @@ class MMGenTestSuite(object):
 	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','986.9995799')
-
 	def regtest_bob_bal1(self,name):
 		return self.regtest_user_bal(name,'bob','500')
 
@@ -1914,8 +1968,14 @@ class MMGenTestSuite(object):
 	def regtest_bob_bal5(self,name):
 		return self.regtest_user_bal(name,'bob','399.9996799')
 
-	def regtest_bob_bal6(self,name):
-		return self.regtest_user_bal(name,'bob','13')
+	def regtest_bob_alice_bal(self,name):
+		t = MMGenExpect(name,'mmgen-regtest',['get_balances'])
+		t.expect('Switching')
+		ret = t.expect_getend("Bob's balance:").strip()
+		cmp_or_die(ret,'13.00000000',skip_ok=True)
+		ret = t.expect_getend("Alice's balance:").strip()
+		cmp_or_die(ret,'986.99957990',skip_ok=True)
+		t.ok()
 
 	def regtest_user_txdo(self,name,user,fee,outputs_cl,outputs_prompt,extra_args=[],no_send=False):
 		os.environ['MMGEN_BOGUS_SEND'] = ''
@@ -1931,6 +1991,7 @@ class MMGenTestSuite(object):
 		t.expect('Add a comment to transaction? (y/N): ','\n')
 		t.expect('View decoded transaction\? .*?: ','t',regex=True)
 		t.expect('to continue: ','\n')
+		t.passphrase('MMGen wallet','abc')
 		t.written_to_file('Signed transaction')
 		if not no_send:
 			t.expect('to confirm: ','YES, I REALLY WANT TO DO THIS\n')
@@ -1939,24 +2000,28 @@ class MMGenTestSuite(object):
 		t.ok()
 
 	def regtest_bob_split1(self,name):
-		from mmgen.regtest import sids
-		outputs_cl = [sids['bob']+':C:1,100', sids['bob']+':L:2,200',sids['bob']+':S:2']
+		sid = self.regtest_user_sid('bob')
+		outputs_cl = [sid+':C:1,100', sid+':L:2,200',sid+':S:2']
 		return self.regtest_user_txdo(name,'bob','20s',outputs_cl,'1')
 
+	def create_tx_outputs(self,user,data):
+		o,sid = [],self.regtest_user_sid(user)
+		for id_str,idx,amt_str in data:
+			fn = get_file_with_ext('{}{}[1-5].addrs'.format(sid,id_str),self.regtest_user_dir(user),no_dot=True)
+			silence()
+			addr = AddrList(fn).data[idx-1].addr
+			end_silence()
+			o.append(addr+amt_str)
+		return o
+
 	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']
+		outputs_cl = self.create_tx_outputs('alice',(('',1,',60'),('-C',1,',40'))) # alice_sid:L:1, alice_sid:C:1
+		outputs_cl += [self.regtest_user_sid('bob')+':S:2']
 		return self.regtest_user_txdo(name,'bob','10s',outputs_cl,'3',extra_args=['--rbf'])
 
 	def regtest_bob_send_non_mmgen(self,name):
-		from mmgen.regtest import sids
+		outputs_cl = self.create_tx_outputs('alice',(('-S',2,',10'),('-S',3,''))) # alice_sid:S:2, alice_sid:S:3
 		fn = os.path.join(cfg['tmpdir'],'non-mmgen.keys')
-		outputs_cl = [
-			'2N8w8qTupvd9L9wLFbrn6UhdfF1gadDAmFD,10', # sids['alice']:S:2
-			'2NF4y3y4CEjQCcssjX2BDLHT88XHn8z53JS']    # sids['alice']:S:3
 		return self.regtest_user_txdo(name,'bob','0.0001',outputs_cl,'3-9',extra_args=['--keys-from-file='+fn])
 
 	def regtest_user_txbump(self,name,user,txfile,fee,red_op,no_send=False):
@@ -1967,6 +2032,7 @@ class MMGenTestSuite(object):
 		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.passphrase('MMGen wallet','abc')
 		t.written_to_file('Signed transaction')
 		if not no_send:
 			t.expect('to confirm: ','YES, I REALLY WANT TO DO THIS\n')
@@ -1986,8 +2052,7 @@ class MMGenTestSuite(object):
 
 	def regtest_get_mempool(self,name):
 		t = MMGenExpect(name,'mmgen-regtest',['show_mempool'])
-		ret = eval(t.read())
-		return ret
+		return eval(t.read())
 
 	def regtest_get_mempool1(self,name):
 		mp = self.regtest_get_mempool(name)
@@ -2005,11 +2070,6 @@ class MMGenTestSuite(object):
 			rdie(2,'TX in mempool has not changed!  RBF bump failed')
 		ok()
 
-	def regtest_user_import(self,name,user,args):
-		t = MMGenExpect(name,'mmgen-addrimport',['--quiet','--'+user]+args)
-		t.read()
-		t.ok()
-
 	@staticmethod
 	def gen_pairs(n):
 		return [subprocess.check_output(
@@ -2022,6 +2082,12 @@ class MMGenTestSuite(object):
 		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')
 
+	def regtest_user_import(self,name,user,args):
+		t = MMGenExpect(name,'mmgen-addrimport',['--quiet','--'+user]+args)
+		t.expect('Importing')
+		t.expect('OK')
+		t.ok()
+
 	def regtest_bob_import_addr(self,name):
 		addr = read_from_tmpfile(cfg,'non-mmgen.addrs').split()[0]
 		return self.regtest_user_import(name,'bob',['--rescan','--address='+addr])
@@ -2034,8 +2100,8 @@ class MMGenTestSuite(object):
 		addrs = read_from_tmpfile(cfg,'non-mmgen.addrs').split()
 		amts = (a for a in (1.12345678,2.87654321,3.33443344,4.00990099,5.43214321))
 		outputs1 = ['{},{}'.format(a,amts.next()) for a in addrs]
-		from mmgen.regtest import sids
-		outputs2 = [sids['bob']+':C:2,6', sids['bob']+':L:3,7',sids['bob']+':S:3']
+		sid = self.regtest_user_sid('bob')
+		outputs2 = [sid+':C:2,6', sid+':L:3,7',sid+':S:3']
 		return self.regtest_user_txdo(name,'bob','20s',outputs1+outputs2,'1-2')
 
 	def regtest_user_add_label(self,name,user,addr,label):
@@ -2049,13 +2115,16 @@ class MMGenTestSuite(object):
 		t.ok()
 
 	def regtest_alice_add_label1(self,name):
-		return self.regtest_user_add_label(name,'alice','9304C211:S:1','Original Label')
+		sid = self.regtest_user_sid('alice')
+		return self.regtest_user_add_label(name,'alice',sid+':S:1','Original Label')
 
 	def regtest_alice_add_label2(self,name):
-		return self.regtest_user_add_label(name,'alice','9304C211:S:1','Replacement Label')
+		sid = self.regtest_user_sid('alice')
+		return self.regtest_user_add_label(name,'alice',sid+':S:1','Replacement Label')
 
 	def regtest_alice_remove_label1(self,name):
-		return self.regtest_user_remove_label(name,'alice','9304C211:S:1')
+		sid = self.regtest_user_sid('alice')
+		return self.regtest_user_remove_label(name,'alice',sid+':S:1')
 
 	def regtest_user_chk_label(self,name,user,addr,label):
 		t = MMGenExpect(name,'mmgen-tool',['--'+user,'listaddresses','all_labels=1'])
@@ -2063,16 +2132,20 @@ class MMGenTestSuite(object):
 		t.ok()
 
 	def regtest_alice_chk_label1(self,name):
-		return self.regtest_user_chk_label(name,'alice','9304C211:S:1','Original Label')
+		sid = self.regtest_user_sid('alice')
+		return self.regtest_user_chk_label(name,'alice',sid+':S:1','Original Label')
 
 	def regtest_alice_chk_label2(self,name):
-		return self.regtest_user_chk_label(name,'alice','9304C211:S:1','Replacement Label')
+		sid = self.regtest_user_sid('alice')
+		return self.regtest_user_chk_label(name,'alice',sid+':S:1','Replacement Label')
 
 	def regtest_alice_chk_label3(self,name):
-		return self.regtest_user_chk_label(name,'alice','9304C211:S:1','Edited Label')
+		sid = self.regtest_user_sid('alice')
+		return self.regtest_user_chk_label(name,'alice',sid+':S:1','Edited Label')
 
 	def regtest_alice_chk_label4(self,name):
-		return self.regtest_user_chk_label(name,'alice','9304C211:S:1','-')
+		sid = self.regtest_user_sid('alice')
+		return self.regtest_user_chk_label(name,'alice',sid+':S:1','-')
 
 	def regtest_user_edit_label(self,name,user,output,label):
 		t = MMGenExpect(name,'mmgen-txcreate',['-B','--'+user,'-i'])