Browse Source

regtest: load all wallets, use rpcwallet mechanism to select user

The MMGen Project 4 years ago
parent
commit
2fcb336d57
4 changed files with 51 additions and 136 deletions
  1. 40 95
      mmgen/regtest.py
  2. 6 10
      mmgen/rpc.py
  3. 2 21
      test/start-coin-daemons.py
  4. 3 10
      test/test_py_d/ts_regtest.py

+ 40 - 95
mmgen/regtest.py

@@ -46,10 +46,7 @@ class MMGenRegtest(MMGenObject):
 	rpc_password = 'hodltothemoon'
 	users        = ('bob','alice','miner')
 	coins        = ('btc','bch','ltc')
-	usr_cmds     = (
-		'bob','alice','miner','user','state',
-		'setup','generate','send','stop',
-		'balances','mempool','cli' )
+	usr_cmds     = ('setup','generate','send','stop', 'state', 'balances','mempool','cli')
 
 	def __init__(self,coin):
 		self.coin = coin.lower()
@@ -61,14 +58,13 @@ class MMGenRegtest(MMGenObject):
 	async def generate(self,blocks=1,silent=False):
 
 		blocks = int(blocks)
-		await self.switch_user('miner',quiet=True)
 
 		async def have_generatetoaddress():
-			ret = await self.rpc_call('help','generatetoaddress')
+			ret = await self.rpc_call('help','generatetoaddress',wallet='miner')
 			return not 'unknown command:' in ret
 
 		async def get_miner_address():
-			return await self.rpc_call('getnewaddress')
+			return await self.rpc_call('getnewaddress',wallet='miner')
 
 		if self.d.state == 'stopped':
 			die(1,'Regtest daemon is not running')
@@ -80,27 +76,13 @@ class MMGenRegtest(MMGenObject):
 		else:
 			cmd_args = ( 'generate', blocks )
 
-		out = await self.rpc_call(*cmd_args)
+		out = await self.rpc_call(*cmd_args,wallet='miner')
 
 		if len(out) != blocks:
 			rdie(1,'Error generating blocks')
 
 		gmsg('Mined {} block{}'.format(blocks,suf(blocks)))
 
-	async def create_tracking_wallet(self,user):
-		try:
-			await self.rpc_call('getbalance')
-		except:
-			await self.rpc_call('createwallet',
-					user,            # wallet_name
-					user != 'miner', # disable_private_keys
-					user != 'miner', # blank (no keys or seed)
-					'',              # passphrase (empty string for non-encrypted)
-					False,           # avoid_reuse (track address reuse)
-					False,           # descriptors (native descriptor wallet)
-					False            # load_on_startup
-				)
-
 	async def setup(self):
 
 		try: os.makedirs(self.d.datadir)
@@ -113,47 +95,46 @@ class MMGenRegtest(MMGenObject):
 
 		gmsg('Starting {} regtest setup'.format(self.coin.upper()))
 
-		gmsg('Creating miner wallet')
-		self.start_daemon('miner')
-		await self.create_tracking_wallet('miner')
+		self.d.start(silent=True)
+
+		rpc = await rpc_init(self.proto,backend=None,daemon=self.d)
+		for user in ('miner','bob','alice'):
+			gmsg(f'Creating {capfirst(user)}’s wallet')
+			await rpc.icall(
+				'createwallet',
+				wallet_name     = user,
+				no_keys         = user != 'miner',
+				load_on_startup = False )
 
 		await self.generate(432,silent=True)
-		await self.rpc_call('stop')
-		time.sleep(1.2) # race condition?
-
-		for user in ('alice','bob'):
-			gmsg("Creating {}'s tracking wallet".format(user.capitalize()))
-			self.start_daemon(user)
-			await self.create_tracking_wallet(user)
-			if user == 'bob' and opt.setup_no_stop_daemon:
-				msg('Leaving daemon running with Bob as current user')
-			else:
-				await self.rpc_call('stop')
-				time.sleep(0.2) # race condition?
 
 		gmsg('Setup complete')
 
-	def init_daemon(self,user,reindex=False):
-		assert user is None or user in self.users,'{!r}: invalid user for regtest'.format(user)
+		if opt.setup_no_stop_daemon:
+			msg('Leaving regtest daemon running')
+		else:
+			msg('Stopping regtest daemon')
+			await self.rpc_call('stop')
+
+	def init_daemon(self,reindex=False):
 		self.d.net_desc = self.coin.upper()
-		self.d.usr_coind_args = [f'--wallet={user}']
 		if reindex:
 			self.d.usr_coind_args.append('--reindex')
 
-	def start_daemon(self,user,reindex=False,silent=True):
-		self.init_daemon(user=user,reindex=reindex)
+	async def start_daemon(self,reindex=False,silent=True):
+		self.init_daemon(reindex=reindex)
 		self.d.start(silent=silent)
-
-	async def rpc_call(self,*args):
-		rpc = await rpc_init(self.proto,backend=None,daemon=self.d,caller='regtest')
-		return await rpc.call(*args)
-
-	async def current_user(self):
-		try:
-			return (await self.rpc_call('getwalletinfo'))['walletname']
-		except SocketError as e:
-			msg(str(e))
-			return None
+		for user in ('miner','bob','alice'):
+			msg(f'Loading {capfirst(user)}’s wallet')
+			await self.rpc_call('loadwallet',user,start_daemon=False)
+
+	async def rpc_call(self,*args,wallet=None,start_daemon=True):
+		# g.prog_name == 'mmgen-regtest' test is used by .rpc to identify caller, so require this:
+		assert g.prog_name == 'mmgen-regtest', 'only mmgen-regtest util is allowed to use this method'
+		if start_daemon and self.d.state == 'stopped':
+			await self.start_daemon()
+		rpc = await rpc_init(self.proto,backend=None,daemon=self.d)
+		return await rpc.call(*args,wallet=wallet)
 
 	async def stop(self):
 		await self.rpc_call('stop')
@@ -161,27 +142,21 @@ class MMGenRegtest(MMGenObject):
 	def state(self):
 		msg(self.d.state)
 
-	async def balances(self,*users):
-		users = list(set(users or ['bob','alice']))
-		cur_user = await self.current_user()
-		if cur_user in users:
-			users.remove(cur_user)
-			users = [cur_user] + users
+	async def balances(self):
 		bal = {}
+		users = ('bob','alice')
 		for user in users:
-			await self.switch_user(user,quiet=True)
-			out = await self.rpc_call('listunspent',0)
+			out = await self.rpc_call('listunspent',0,wallet=user)
 			bal[user] = sum(e['amount'] for e in out)
 
 		fs = '{:<16} {:18.8f}'
-		for user in sorted(users):
+		for user in users:
 			msg(fs.format(user.capitalize()+"'s balance:",bal[user]))
 		msg(fs.format('Total balance:',sum(v for k,v in bal.items())))
 
 	async def send(self,addr,amt):
-		await self.switch_user('miner',quiet=True)
 		gmsg('Sending {} miner {} to address {}'.format(amt,self.d.daemon_id.upper(),addr))
-		cp = await self.rpc_call('sendtoaddress',addr,str(amt))
+		cp = await self.rpc_call('sendtoaddress',addr,str(amt),wallet='miner')
 		await self.generate(1)
 
 	async def mempool(self):
@@ -197,36 +172,6 @@ class MMGenRegtest(MMGenObject):
 		ret = getattr(self,args[0])(*args[1:])
 		return (await ret) if type(ret).__name__ == 'coroutine' else ret
 
-	async def user(self):
-		ret = await self.current_user()
-		msg(ret.capitalize() if ret else 'None')
-
-	async def bob(self):   await self.switch_user('bob')
-	async def alice(self): await self.switch_user('alice')
-	async def miner(self): await self.switch_user('miner')
-
-	async def switch_user(self,user,quiet=False):
-
-		if self.d.state == 'busy':
-			self.d.wait_for_state('ready')
-
-		if self.d.state == 'ready':
-			cur_user = await self.current_user()
-			if user == cur_user:
-				if not quiet:
-					msg('{} is already the current user for {}'.format(user.capitalize(),self.d.net_desc))
-				return
-			gmsg_r('Switching to user {} for {}'.format(user.capitalize(),self.d.net_desc))
-			if cur_user:
-				await self.rpc_call('unloadwallet',cur_user)
-			await self.rpc_call('loadwallet',user)
-		else:
-			m = 'Starting {} {} with current user {}'
-			gmsg_r(m.format(self.d.net_desc,self.d.desc,user.capitalize()))
-			self.start_daemon(user,silent=True)
-
-		gmsg('...done')
-
 	async def fork(self,coin): # currently disabled
 
 		proto = init_proto(coin,False)
@@ -254,7 +199,7 @@ class MMGenRegtest(MMGenObject):
 		create_data_dir(self.d.datadir)
 		os.rmdir(self.d.datadir)
 		shutil.copytree(source_data_dir,self.d.datadir,symlinks=True)
-		self.start_daemon('miner',reindex=True)
+		await self.start_daemon(reindex=True)
 		await self.rpc_call('stop')
 
 		gmsg('Fork {} successfully created'.format(proto.coin))

+ 6 - 10
mmgen/rpc.py

@@ -391,7 +391,7 @@ class BitcoinRPCClient(RPCClient,metaclass=aInitMeta):
 	def __init__(self,*args,**kwargs):
 		pass
 
-	async def __ainit__(self,proto,daemon,backend,caller):
+	async def __ainit__(self,proto,daemon,backend):
 
 		self.proto = proto
 		self.daemon = daemon
@@ -404,10 +404,6 @@ class BitcoinRPCClient(RPCClient,metaclass=aInitMeta):
 		self.set_auth() # set_auth() requires cookie, so must be called after __init__() tests daemon is listening
 		self.set_backend(backend) # backend requires self.auth
 
-		if caller != 'regtest' and (g.bob or g.alice):
-			from .regtest import MMGenRegtest
-			await MMGenRegtest(self.proto.coin).switch_user(('alice','bob')[g.bob],quiet=True)
-
 		self.cached = {}
 		(
 			self.cached['networkinfo'],
@@ -452,7 +448,7 @@ class BitcoinRPCClient(RPCClient,metaclass=aInitMeta):
 			if len((await self.call('help',func)).split('\n')) > 3:
 				self.caps += (cap,)
 
-		if caller != 'regtest':
+		if not (g.prog_name == 'mmgen-regtest' or g.bob or g.alice):
 			await self.check_tracking_wallet()
 
 	async def check_tracking_wallet(self,wallet_checked=[]):
@@ -503,6 +499,7 @@ class BitcoinRPCClient(RPCClient,metaclass=aInitMeta):
 	@staticmethod
 	def make_host_path(wallet):
 		return (
+			'/wallet/{}'.format('bob' if g.bob else 'alice') if (g.bob or g.alice) else
 			'/wallet/{}'.format(wallet) if wallet else '/'
 		)
 
@@ -573,7 +570,7 @@ class EthereumRPCClient(RPCClient,metaclass=aInitMeta):
 	def __init__(self,*args,**kwargs):
 		pass
 
-	async def __ainit__(self,proto,daemon,backend,caller):
+	async def __ainit__(self,proto,daemon,backend):
 		self.proto = proto
 		self.daemon = daemon
 		self.call_sigs = getattr(getattr(CallSigs,proto.base_proto),daemon.id)
@@ -706,7 +703,7 @@ def handle_unsupported_daemon_version(rpc,proto,ignore_daemon_version,warning_sh
 					rpc.daemon.coind_version_str,
 					),indent='    ').rstrip())
 
-async def rpc_init(proto,backend=None,daemon=None,ignore_daemon_version=False,caller=None):
+async def rpc_init(proto,backend=None,daemon=None,ignore_daemon_version=False):
 
 	if not 'rpc' in proto.mmcaps:
 		die(1,f'Coin daemon operations not supported for {proto.name} protocol!')
@@ -718,8 +715,7 @@ async def rpc_init(proto,backend=None,daemon=None,ignore_daemon_version=False,ca
 	}[proto.base_proto](
 		proto   = proto,
 		daemon  = daemon or CoinDaemon(proto=proto,test_suite=g.test_suite),
-		backend = backend or opt.rpc_backend,
-		caller  = caller )
+		backend = backend or opt.rpc_backend )
 
 	if rpc.daemon_version > rpc.daemon.coind_version:
 		handle_unsupported_daemon_version(rpc,proto,ignore_daemon_version)

+ 2 - 21
test/start-coin-daemons.py

@@ -4,7 +4,6 @@ import sys
 from include.tests_header import repo_root
 from mmgen.common import *
 from mmgen.daemon import CoinDaemon
-from mmgen.regtest import MMGenRegtest
 
 action = g.prog_name.split('-')[0]
 
@@ -18,7 +17,6 @@ opts_data = {
 --, --longhelp       Print help message for long options (common options)
 -d, --debug          Produce debugging output (implies --verbose)
 -D, --no-daemonize   Don't fork daemon to background
--r, --regtest-user=U {a} a regtest daemon for user 'U'
 -s, --get-state      Get the state of the daemon(s) and exit
 -t, --testing        Testing mode.  Print commands but don't execute them
 -v, --verbose        Produce more verbose output
@@ -26,17 +24,11 @@ opts_data = {
 """,
 	'notes': """
 Valid network IDs: {nid}, all, or no_xmr
-Valid Regtest network IDs: {rid}, or all
-Valid Regtest users:       {ru}
 """
 	},
 	'code': {
 		'options': lambda s: s.format(a=action.capitalize(),pn=g.prog_name),
-		'notes': lambda s: s.format(
-			nid=', '.join(CoinDaemon.network_ids),
-			rid=', '.join(MMGenRegtest.coins),
-			ru=', '.join(MMGenRegtest.users),
-		)
+		'notes': lambda s: s.format(nid=', '.join(CoinDaemon.network_ids))
 	}
 }
 
@@ -45,8 +37,6 @@ cmd_args = opts.init(opts_data)
 if 'all' in cmd_args or 'no_xmr' in cmd_args:
 	if len(cmd_args) != 1:
 		die(1,"'all' or 'no_xmr' must be the sole argument")
-	if opt.regtest_user:
-		ids = MMGenRegtest.coins
 	else:
 		ids = list(CoinDaemon.network_ids)
 		if cmd_args[0] == 'no_xmr':
@@ -58,10 +48,6 @@ else:
 	for i in ids:
 		if i not in CoinDaemon.network_ids:
 			die(1,f'{i!r}: invalid network ID')
-		if i.endswith('_rt'):
-			die(1,'For regtest, use the plain coin symbol and the --regtest-user option')
-		if opt.regtest_user and i not in MMGenRegtest.coins:
-			die(1,f'For regtest, coin symbol must be one of {MMGenRegtest.coins}')
 
 if 'eth' in ids and 'etc' in ids:
 	msg('Cannot run ETH and ETC simultaneously, so skipping ETC')
@@ -69,12 +55,7 @@ if 'eth' in ids and 'etc' in ids:
 
 for network_id in ids:
 	network_id = network_id.lower()
-	if opt.regtest_user:
-		rt = MMGenRegtest(network_id)
-		rt.init_daemon(opt.regtest_user)
-		d = rt.d
-	else:
-		d = CoinDaemon(network_id,test_suite=True,flags=['no_daemonize'] if opt.no_daemonize else None)
+	d = CoinDaemon(network_id,test_suite=True,flags=['no_daemonize'] if opt.no_daemonize else None)
 	d.debug = opt.debug
 	d.wait = not opt.no_wait
 	if opt.get_state:

+ 3 - 10
test/test_py_d/ts_regtest.py

@@ -144,7 +144,6 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 	cmd_group = (
 		('setup',                    'regtest (Bob and Alice) mode setup'),
 		('daemon_version',           'mmgen-tool daemon_version'),
-		('current_user',             'current user'),
 		('halving_calculator_bob',   'halving calculator (Bob)'),
 		('walletgen_bob',            'wallet generation (Bob)'),
 		('walletgen_alice',          'wallet generation (Alice)'),
@@ -276,7 +275,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		os.environ['MMGEN_TEST_SUITE'] = '' # mnemonic is piped to stdin, so stop being a terminal
 		t = self.spawn('mmgen-regtest',['-n','setup'])
 		os.environ['MMGEN_TEST_SUITE'] = '1'
-		for s in ('Starting','Creating','Mined','Creating','Creating','Setup complete'):
+		for s in ('Starting','Creating','Creating','Creating','Mined','Setup complete'):
 			t.expect(s)
 		return t
 
@@ -285,11 +284,6 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 		t.expect('version')
 		return t
 
-	def current_user(self):
-		t = self.spawn('mmgen-regtest', ['user'])
-		t.expect('Bob')
-		return t
-
 	def halving_calculator_bob(self):
 		t = self.spawn('halving-calculator.py',['--bob'],cmd_dir='examples')
 		t.expect('time until halving')
@@ -526,11 +520,10 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 
 	def bob_alice_bal(self):
 		t = self.spawn('mmgen-regtest',['balances'])
-		t.expect('Switching')
-		ret = t.expect_getend("Alice's balance:").strip()
-		cmp_or_die(rtBals[5],ret)
 		ret = t.expect_getend("Bob's balance:").strip()
 		cmp_or_die(rtBals[4],ret)
+		ret = t.expect_getend("Alice's balance:").strip()
+		cmp_or_die(rtBals[5],ret)
 		ret = t.expect_getend("Total balance:").strip()
 		cmp_or_die(rtBals[6],ret)
 		return t