From 2fcb336d57edbfab9c756877d70e288f5ea74a38 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 2 Mar 2021 18:04:29 +0000 Subject: [PATCH] regtest: load all wallets, use rpcwallet mechanism to select user --- mmgen/regtest.py | 133 ++++++++++------------------------- mmgen/rpc.py | 16 ++--- test/start-coin-daemons.py | 23 +----- test/test_py_d/ts_regtest.py | 13 +--- 4 files changed, 50 insertions(+), 135 deletions(-) diff --git a/mmgen/regtest.py b/mmgen/regtest.py index 809e2add..45a8c55b 100755 --- a/mmgen/regtest.py +++ b/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) + 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): - 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 + 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)) diff --git a/mmgen/rpc.py b/mmgen/rpc.py index 93b562d5..01cb6a90 100755 --- a/mmgen/rpc.py +++ b/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) diff --git a/test/start-coin-daemons.py b/test/start-coin-daemons.py index 8e94f034..985fce43 100755 --- a/test/start-coin-daemons.py +++ b/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: diff --git a/test/test_py_d/ts_regtest.py b/test/test_py_d/ts_regtest.py index 2a9261b9..f7388cdb 100755 --- a/test/test_py_d/ts_regtest.py +++ b/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