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

This commit is contained in:
The MMGen Project 2021-03-02 18:04:29 +00:00
commit 2fcb336d57
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 51 additions and 136 deletions

View file

@ -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))

View file

@ -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)

View file

@ -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:

View file

@ -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