8 Commits d4c46ad28e ... 851335106f

Author SHA1 Message Date
  The MMGen Project 851335106f cmdtest.py xmr_autosign: support compat mode testing 2 weeks ago
  The MMGen Project 967d17c06e cmdtest.py ethdev, rune: add user component to tw path 2 weeks ago
  The MMGen Project 4a24d2153c minor whitespace, cleanups 2 weeks ago
  The MMGen Project 20af051e43 cfg: `regtest_user` -> `test_user` 2 weeks ago
  The MMGen Project f0dfb8e4c1 cmdtest.py xmr_autosign: whitespace, variable renames 2 weeks ago
  The MMGen Project 68fba89e96 mmgen-xmrwallet: new `--compat` option 2 weeks ago
  The MMGen Project f05858a01f tw.store: new `get_tw_dir()` classmethod 2 weeks ago
  The MMGen Project ddd633c6b4 xmrwallet: minor arg parsing cleanup 2 weeks ago

+ 1 - 0
mmgen/autosign.py

@@ -801,6 +801,7 @@ class Autosign:
 				'autosign': True,
 				'autosign_mountpoint': str(self.mountpoint),
 				'offline': True,
+				'compat': False,
 				'passwd_file': str(self.keyfile)})
 		return self._xmrwallet_cfg
 

+ 33 - 14
mmgen/cfg.py

@@ -231,7 +231,8 @@ class Config(Lockable):
 	bob          = False
 	alice        = False
 	carol        = False
-	regtest_user = ''
+	miner        = False
+	test_user    = ''
 
 	# altcoin:
 	cashaddr = True
@@ -239,6 +240,7 @@ class Config(Lockable):
 	# Monero:
 	monero_wallet_rpc_user     = 'monero'
 	monero_wallet_rpc_password = ''
+	xmrwallet_compat           = False
 	priority                   = 0
 
 	# test suite:
@@ -283,7 +285,7 @@ class Config(Lockable):
 
 	_incompatible_opts = (
 		('help', 'longhelp'),
-		('bob', 'alice', 'carol'),
+		('bob', 'alice', 'carol', 'miner'),
 		('label', 'keep_label'),
 		('tx_id', 'info'),
 		('tx_id', 'terse_info'),
@@ -308,6 +310,7 @@ class Config(Lockable):
 		'max_input_size',
 		'max_tx_file_size',
 		'mnemonic_entry_modes',
+		'xmrwallet_compat',
 		'monero_wallet_rpc_password',
 		'monero_wallet_rpc_user',
 		'no_license',
@@ -436,14 +439,18 @@ class Config(Lockable):
 	@property
 	def data_dir(self):
 		"""
-		location of wallet and other data - same as data_dir_root for mainnet
+		location of wallet and other data
 		"""
 		if not hasattr(self, '_data_dir'):
-			self._data_dir = os.path.normpath(os.path.join(*{
-				'regtest': (self.data_dir_root, 'regtest', (self.regtest_user or 'none')),
-				'testnet': (self.data_dir_root, 'testnet'),
-				'mainnet': (self.data_dir_root,),
-			}[self.network]))
+			def make_path():
+				match self.network:
+					case 'mainnet':
+						return (self.data_dir_root, self.test_user)
+					case 'testnet':
+						return (self.data_dir_root, 'testnet', self.test_user)
+					case 'regtest':
+						return (self.data_dir_root, 'regtest', (self.test_user or 'none'))
+			self._data_dir = os.path.normpath(os.path.join(*make_path()))
 		return self._data_dir
 
 	def __init__(
@@ -545,15 +552,27 @@ class Config(Lockable):
 		if opts_data and 'sets' in opts_data:
 			self._set_opts_data_sets_opts(opts_data)
 
-		if self.regtest or self.bob or self.alice or self.carol or gc.prog_name == f'{gc.proj_id}-regtest':
-			self.network = 'regtest'
-			self.regtest_user = 'bob' if self.bob else 'alice' if self.alice else 'carol' if self.carol else None
-		else:
-			self.network = 'testnet' if self.testnet else 'mainnet'
-
 		self.coin = self.coin.upper()
 		self.token = self.token.upper() if self.token else None
 
+		if (
+				self.regtest or
+				self.bob or
+				self.alice or
+				self.carol or
+				self.miner or
+				gc.prog_name == f'{gc.proj_id}-regtest'):
+			if self.coin != 'XMR':
+				self.network = 'regtest'
+			self.test_user = (
+				'bob' if self.bob else
+				'alice' if self.alice else
+				'carol' if self.carol else
+				'miner' if self.miner else
+				'')
+		else:
+			self.network = 'testnet' if self.testnet else 'mainnet'
+
 		if 'usage' in self._uopts: # requires self.coin
 			import importlib
 			getattr(importlib.import_module(UserOpts.help_pkg), 'usage')(self) # exits

+ 4 - 0
mmgen/data/mmgen.cfg

@@ -152,6 +152,10 @@
 # Set the Monero wallet RPC password to something secure:
 # monero_wallet_rpc_password passw0rd
 
+# Configure mmgen-xmrwallet for compatibility with mmgen-tx{create,sign,send}
+# family of commands (equivalent to mmgen-xmrwallet --compat option)
+# xmrwallet_compat true
+
 #######################################################################
 ## The following options are probably of interest only to developers ##
 #######################################################################

+ 1 - 1
mmgen/data/version

@@ -1 +1 @@
-16.1.dev13
+16.1.dev14

+ 8 - 0
mmgen/help/help_notes.py

@@ -51,6 +51,14 @@ class help_notes:
 		from ..proto.btc.rpc.local import BitcoinRPCClient
 		return BitcoinRPCClient.dfl_twname
 
+	def tw_dir(self):
+		from ..tw.ctl import TwCtl
+		twctl_cls = self.proto.base_proto_subclass(TwCtl, 'tw.ctl')
+		if hasattr(twctl_cls, 'get_tw_dir'):
+			return twctl_cls.get_tw_dir(self.cfg, self.proto)
+		else:
+			raise ValueError(f'protocol {self.proto.name} does not support tracking wallet with store')
+
 	def MasterShareIdx(self):
 		from ..seedsplit import MasterShareIdx
 		return MasterShareIdx

+ 11 - 5
mmgen/main_xmrwallet.py

@@ -47,6 +47,10 @@ opts_data = {
                                  When this option is in effect, filename argu-
                                  ments must be omitted, as files are located
                                  automatically.
+-c, --compat                     Adjust configuration for compatibility with
+                                 the mmgen-tx{{create,sign,send}} family of
+                                 commands.  Currently equivalent to
+                                 ‘-w {tw_dir}’
 -f, --priority=N                 Specify an integer priority ‘N’ for inclusion
                                  of a transaction in the blockchain (higher
                                  number means higher fee).  Valid parameters:
@@ -88,11 +92,12 @@ opts_data = {
 """
 	},
 	'code': {
-		'options': lambda cfg, s: s.format(
+		'options': lambda cfg, help_notes, s: s.format(
 			D   = xmrwallet.uarg_info['daemon'].annot,
 			R   = xmrwallet.uarg_info['tx_relay_daemon'].annot,
 			cfg = cfg,
 			gc  = gc,
+			tw_dir = help_notes('tw_dir'),
 			tp  = fmt_dict(xmrwallet.tx_priorities, fmt='equal_compact')
 		),
 		'notes': lambda help_mod, s: s.format(
@@ -117,7 +122,8 @@ if cmd_args and cfg.autosign and (
 if len(cmd_args) < 2:
 	cfg._usage()
 
-op     = cmd_args.pop(0)
+usr_op = cmd_args.pop(0)
+op     = usr_op.replace('-', '_')
 infile = cmd_args.pop(0)
 wallets = spec = None
 
@@ -135,14 +141,14 @@ match op:
 		if len(cmd_args) != 1:
 			cfg._usage()
 		spec = cmd_args[0]
-	case 'export-outputs' | 'export-outputs-sign' | 'import-key-images':
+	case 'export_outputs' | 'export_outputs_sign' | 'import_key_images':
 		if not cfg.autosign:
-			die(1, f'--autosign must be used with command {op!r}')
+			die(1, f'--autosign must be used with command {usr_op!r}')
 		if len(cmd_args) > 1:
 			cfg._usage()
 		wallets = cmd_args.pop(0) if cmd_args else None
 	case _:
-		die(1, f'{op!r}: unrecognized operation')
+		die(1, f'{usr_op!r}: unrecognized operation')
 
 m = xmrwallet.op(op, cfg, infile, wallets, spec=spec)
 

+ 4 - 3
mmgen/opts.py

@@ -317,9 +317,10 @@ class UserOpts(Opts):
 			-- --skip-cfg-file        Skip reading the configuration file
 			-- --version              Print version information and exit
 			-- --usage                Print usage information and exit
-			b- --bob                  Specify user ‘Bob’ in MMGen regtest mode
-			b- --alice                Specify user ‘Alice’ in MMGen regtest mode
-			b- --carol                Specify user ‘Carol’ in MMGen regtest mode
+			x- --bob                  Specify user ‘Bob’ in MMGen regtest or test mode
+			x- --alice                Specify user ‘Alice’ in MMGen regtest or test mode
+			x- --carol                Specify user ‘Carol’ in MMGen regtest or test mode
+			x- --miner                Specify user ‘Miner’ in MMGen regtest or test mode
 			rr COIN-SPECIFIC OPTIONS:
 			rr   For descriptions, refer to the non-prefixed versions of these options above
 			rr   Prefixed options override their non-prefixed counterparts

+ 3 - 3
mmgen/proto/btc/rpc/local.py

@@ -213,7 +213,7 @@ class BitcoinRPCClient(RPCClient, metaclass=AsyncInit):
 		self.proto = proto
 		self.daemon = daemon
 		self.call_sigs = getattr(CallSigs, daemon.id)(cfg, self)
-		self.twname = TrackingWalletName(cfg.regtest_user or proto.tw_name or cfg.tw_name or self.dfl_twname)
+		self.twname = TrackingWalletName(cfg.test_user or proto.tw_name or cfg.tw_name or self.dfl_twname)
 
 		super().__init__(
 			cfg  = cfg,
@@ -287,7 +287,7 @@ class BitcoinRPCClient(RPCClient, metaclass=AsyncInit):
 			await self.check_or_create_daemon_wallet()
 
 		# for regtest, wallet_path must remain '/' until Carol’s user wallet has been created
-		if self.chain != 'regtest' or cfg.regtest_user:
+		if self.chain != 'regtest' or cfg.test_user:
 			self.wallet_path = f'/wallet/{self.twname}'
 
 	@property
@@ -334,7 +334,7 @@ class BitcoinRPCClient(RPCClient, metaclass=AsyncInit):
 
 	async def check_or_create_daemon_wallet(self):
 
-		if self.chain == 'regtest' and self.cfg.regtest_user != 'carol':
+		if self.chain == 'regtest' and self.cfg.test_user != 'carol':
 			return
 
 		loaded_wnames = await self.call('listwallets')

+ 1 - 0
mmgen/proto/xmr/params.py

@@ -25,6 +25,7 @@ class MoneroViewKey(HexStr):
 # https://github.com/monero-project/monero/blob/master/src/cryptonote_config.h
 class mainnet(CoinProtocol.RPC, CoinProtocol.DummyWIF, CoinProtocol.Base):
 
+	mod_clsname    = 'Monero'
 	network_names  = _nw('mainnet', 'stagenet', None)
 	base_proto     = 'Monero'
 	base_proto_coin = 'XMR'

+ 19 - 0
mmgen/proto/xmr/tw/ctl.py

@@ -0,0 +1,19 @@
+#!/usr/bin/env python3
+#
+# MMGen Wallet, a terminal-based cryptocurrency wallet
+# Copyright (C)2013-2025 The MMGen Project <mmgen@tuta.io>
+# Licensed under the GNU General Public License, Version 3:
+#   https://www.gnu.org/licenses
+# Public project repositories:
+#   https://github.com/mmgen/mmgen-wallet
+#   https://gitlab.com/mmgen/mmgen-wallet
+
+"""
+proto.xmr.tw.ctl: Monero tracking wallet control class
+"""
+
+from ....tw.store import TwCtlWithStore
+
+class MoneroTwCtl(TwCtlWithStore):
+
+	tw_subdir = 'tracking-wallets'

+ 12 - 6
mmgen/tw/store.py

@@ -26,6 +26,7 @@ from .ctl import TwCtl, write_mode, label_addr_pair
 class TwCtlWithStore(TwCtl, metaclass=AsyncInit):
 
 	caps = ('batch',)
+	tw_subdir = None
 	tw_fn = 'tracking-wallet.json'
 	aggressive_sync = False
 
@@ -47,12 +48,7 @@ class TwCtlWithStore(TwCtl, metaclass=AsyncInit):
 		if cfg.cached_balances:
 			self.use_cached_balances = True
 
-		self.tw_dir = Path(
-			self.cfg.data_dir_root,
-			'altcoins',
-			self.proto.coin.lower(),
-			('' if self.proto.network == 'mainnet' else self.proto.network)
-		)
+		self.tw_dir = type(self).get_tw_dir(self.cfg, self.proto)
 		self.tw_path = self.tw_dir / self.tw_fn
 
 		if no_wallet_init:
@@ -84,6 +80,16 @@ class TwCtlWithStore(TwCtl, metaclass=AsyncInit):
 		elif self.cfg.debug:
 			msg('read-only wallet, doing nothing')
 
+	@classmethod
+	def get_tw_dir(cls, cfg, proto):
+		return Path(
+			cfg.data_dir_root,
+			cfg.test_user,
+			'altcoins',
+			proto.coin.lower(),
+			('' if proto.network == 'mainnet' else proto.network),
+			(cls.tw_subdir or ''))
+
 	def upgrade_wallet_maybe(self):
 		pass
 

+ 12 - 2
mmgen/xmrwallet/__init__.py

@@ -17,7 +17,7 @@ from collections import namedtuple
 
 from ..proto.btc.common import b58a
 
-from ..util import capfirst
+from ..util import die, capfirst
 
 tx_priorities = {
 	1: 'low',
@@ -113,4 +113,14 @@ def op_cls(op_name):
 	return cls
 
 def op(op, cfg, infile, wallets, *, spec=None):
-	return op_cls(op.replace('-', '_'))(cfg, uargs(infile, wallets, spec))
+	if cfg.compat if cfg.compat is not None else cfg.xmrwallet_compat:
+		if cfg.wallet_dir:
+			die(1, '--wallet-dir can not be specified in xmrwallet compatibility mode')
+		from ..tw.ctl import TwCtl
+		from ..cfg import Config
+		twctl_cls = cfg._proto.base_proto_subclass(TwCtl, 'tw.ctl')
+		cfg = Config({
+			'_clone': cfg,
+			'compat': True,
+			'wallet_dir': twctl_cls.get_tw_dir(cfg, cfg._proto)})
+	return op_cls(op)(cfg, uargs(infile, wallets, spec))

+ 2 - 0
mmgen/xmrwallet/ops/create.py

@@ -28,6 +28,8 @@ class OpCreate(OpWallet):
 		if self.cfg.restore_height != 'current':
 			if int(self.cfg.restore_height or 0) < 0:
 				die(1, f'{self.cfg.restore_height}: invalid value for --restore-height (less than zero)')
+		if self.cfg.compat:
+			self.cfg.wallet_dir.mkdir(parents=True, exist_ok=True)
 
 	async def process_wallet(self, d, fn, last):
 		msg_r('') # for pexpect

+ 1 - 0
setup.cfg

@@ -104,6 +104,7 @@ packages =
 	mmgen.proto.vm.tx
 	mmgen.proto.xchain
 	mmgen.proto.xmr
+	mmgen.proto.xmr.tw
 	mmgen.proto.zec
 	mmgen.rpc
 	mmgen.rpc.backends

+ 3 - 2
test/cmdtest_d/ethbump.py

@@ -128,6 +128,7 @@ class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMe
 	token_fund_amt = 1000
 	cross_group = 'ethbump_ltc'
 	cross_coin = 'ltc'
+	add_eth_opts = ['--bob']
 
 	cmd_group_in = (
 		('subgroup.ltc_init',           []),
@@ -312,10 +313,10 @@ class CmdTestEthBump(CmdTestEthBumpMethods, CmdTestEthSwapMethods, CmdTestSwapMe
 		return self._swaptxsend()
 
 	def swaptxbump1(self):
-		return self._swaptxbump('41.1G')
+		return self._swaptxbump('41.1G', add_opts=['--bob'])
 
 	def swaptxbump2(self):
-		return self._swaptxbump('1.9G', output_args=[f'{dfl_sid}:E:12,4444.3333'])
+		return self._swaptxbump('1.9G', add_opts=['--bob'], output_args=[f'{dfl_sid}:E:12,4444.3333'])
 
 	def bal1(self):
 		return self._bal_check(pat=rf'{dfl_sid}:E:1\s+99012\.9999727\s')

+ 14 - 5
test/cmdtest_d/ethdev.py

@@ -130,6 +130,8 @@ coin = cfg.coin
 
 class CmdTestEthdevMethods:
 
+	add_eth_opts = []
+
 	def _del_addr(self, addr):
 		t = self.spawn('mmgen-tool', self.eth_opts + ['remove_address', addr])
 		t.expect(f"'{addr}' deleted")
@@ -153,7 +155,10 @@ class CmdTestEthdevMethods:
 			exit_val  = None):
 		ext = ext.format('-α' if self.cfg.debug_utf8 else '')
 		fn = self.get_file_with_ext(ext, no_dot=True, delete=False)
-		t = self.spawn('mmgen-addrimport', ['--regtest=1'] + add_args + [fn], exit_val=exit_val)
+		t = self.spawn(
+			'mmgen-addrimport',
+			['--regtest=1'] + self.add_eth_opts + add_args + [fn],
+			exit_val=exit_val)
 		if bad_input:
 			return t
 		t.expect('Importing')
@@ -223,7 +228,9 @@ class CmdTestEthdevMethods:
 
 	def _bal_check(self, *, pat, add_opts=[]):
 		self.mining_delay()
-		t = self.spawn('mmgen-tool', ['--regtest=1'] + add_opts + ['twview', 'wide=1'])
+		t = self.spawn(
+			'mmgen-tool',
+			['--regtest=1'] + self.add_eth_opts + add_opts + ['twview', 'wide=1'])
 		text = t.read(strip_color=True)
 		assert re.search(pat, text, re.DOTALL), f'output failed to match regex {pat}'
 		return t
@@ -721,8 +728,8 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 		global coin
 		coin = cfg.coin
 
-		self.eth_opts         = [f'--outdir={self.tmpdir}', '--regtest=1', '--quiet']
-		self.eth_opts_noquiet = [f'--outdir={self.tmpdir}', '--regtest=1']
+		self.eth_opts         = [f'--outdir={self.tmpdir}', '--regtest=1', '--quiet'] + self.add_eth_opts
+		self.eth_opts_noquiet = [f'--outdir={self.tmpdir}', '--regtest=1'] + self.add_eth_opts
 
 		from mmgen.protocol import init_proto
 		self.proto = init_proto(cfg, network_id=self.proto.coin+'_rt', need_amt=True)
@@ -947,7 +954,9 @@ class CmdTestEthdev(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 		return self._addrimport()
 
 	def _addrimport_one_addr(self, addr=None, extra_args=[]):
-		t = self.spawn('mmgen-addrimport', ['--regtest=1', '--quiet', f'--address={addr}'] + extra_args)
+		t = self.spawn(
+			'mmgen-addrimport',
+			['--regtest=1', '--quiet', f'--address={addr}'] + self.add_eth_opts + extra_args)
 		t.expect('OK')
 		return t
 

+ 3 - 1
test/cmdtest_d/ethswap.py

@@ -88,7 +88,8 @@ class CmdTestEthSwapMethods:
 		token_addr = self.read_from_tmpfile('token_addr1').strip()
 		return self.spawn(
 			'mmgen-addrimport',
-			['--quiet', '--regtest=1', f'--token-addr={token_addr}', f'--address={eth_inbound_addr}'])
+			['--quiet', '--regtest=1', f'--token-addr={token_addr}', f'--address={eth_inbound_addr}']
+			+ self.add_eth_opts)
 
 	def token_bal1(self):
 		return self._token_bal_check(pat=rf'{dfl_sid}:E:1\s+{self.token_fund_amt}\s')
@@ -322,6 +323,7 @@ class CmdTestEthSwapEth(CmdTestEthSwapMethods, CmdTestSwapMethods, CmdTestEthdev
 	fund_amt = '123.456'
 	token_fund_amt = 1000
 	is_helper = True
+	add_eth_opts = ['--bob']
 
 	bals = lambda self, k: {
 		'swap1': [('98831F3A:E:1', '123.456')],

+ 1 - 0
test/cmdtest_d/include/cfg.py

@@ -61,6 +61,7 @@ cmd_groups_extra = {
 	'autosign_live':          gd('CmdTestAutosignLive',         {'modname': 'autosign'}),
 	'autosign_live_simulate': gd('CmdTestAutosignLiveSimulate', {'modname': 'autosign'}),
 	'create_ref_tx':          gd('CmdTestRefTX',                {'modname': 'misc', 'full_data': True}),
+	'xmr_autosign_nocompat':  gd('CmdTestXMRAutosignNoCompat',  {'modname': 'xmr_autosign'}),
 }
 
 cfgs = { # addr_idx_lists (except 31, 32, 33, 34) must contain exactly 8 addresses

+ 4 - 2
test/cmdtest_d/rune.py

@@ -84,10 +84,12 @@ class CmdTestRune(CmdTestEthdevMethods, CmdTestBase, CmdTestShared):
 		return self._addrimport()
 
 	def twview(self):
-		return self.spawn('mmgen-tool', self.rune_opts + ['twview'])
+		return self.spawn('mmgen-tool', self.rune_opts + self.add_eth_opts + ['twview'])
 
 	def bal_refresh(self):
-		t = self.spawn('mmgen-tool', self.rune_opts + ['listaddresses', 'interactive=1'])
+		t = self.spawn(
+			'mmgen-tool',
+			self.rune_opts + self.add_eth_opts + ['listaddresses', 'interactive=1'])
 		t.expect(self.menu_prompt, 'R')
 		t.expect('menu): ', '3\n')
 		t.expect('(y/N): ', 'y')

+ 1 - 0
test/cmdtest_d/runeswap.py

@@ -94,6 +94,7 @@ class CmdTestRuneSwapRune(CmdTestSwapMethods, CmdTestRune):
 	input_sels_prompt = 'to spend from: '
 	is_helper = True
 	txhex_chksum = '34980b41'
+	add_eth_opts = ['--bob']
 
 	cmd_group_in = CmdTestRune.cmd_group_in + (
 		# rune_swap:

+ 28 - 13
test/cmdtest_d/xmr_autosign.py

@@ -33,20 +33,20 @@ def make_burn_addr(cfg):
 
 class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
 	"""
-	Monero autosigning operations
+	Monero autosigning operations (xmrwallet compat mode)
 	"""
 	tmpdir_nums = [39]
 
 	# xmrwallet attrs:
 	tx_relay_user = 'miner'
-	#    user     sid      autosign  shift kal_range add_coind_args
+	# user sid autosign port_shift kal_range add_coind_args
 	user_data = (
 		('miner', '98831F3A', False, 130, '1', []),
-		('alice', 'FE3C6545', True,  150, '1-2', []),
-	)
+		('alice', 'FE3C6545', True,  150, '1-2', []))
 
 	# autosign attrs:
 	coins = ['xmr']
+	compat = True
 
 	cmd_group = (
 		('daemon_version',           'checking daemon version'),
@@ -110,10 +110,14 @@ class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
 		self.alice_cfg = Config({
 			'coin': 'XMR',
 			'outdir': self.users['alice'].udir,
-			'wallet_dir': self.users['alice'].udir,
 			'wallet_rpc_password': 'passwOrd',
 			'test_suite': True,
-		})
+		} | ({
+			'alice': True,
+			'compat': True
+		} if self.compat else {
+			'wallet_dir': self.users['alice'].udir
+		}))
 
 		self.burn_addr = make_burn_addr(cfg)
 
@@ -179,7 +183,8 @@ class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
 		t = self.spawn(
 			'mmgen-xmrwallet',
 			self.extra_opts
-			+ [f'--wallet-dir={data.udir}', f'--daemon=localhost:{data.md.rpc_port}']
+			+ (['--alice', '--compat'] if self.compat else [f'--wallet-dir={data.udir}'])
+			+ [f'--daemon=localhost:{data.md.rpc_port}']
 			+ (self.autosign_opts if autosign else [])
 			+ ['dump']
 			+ ([] if autosign else [get_file_with_ext(data.udir, 'akeys')]))
@@ -191,8 +196,9 @@ class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
 	def _delete_files(self, *ext_list):
 		data = self.users['alice']
 		self.spawn(msg_only=True)
+		wdir = data.wd.wallet_dir if self.compat else data.udir
 		for ext in ext_list:
-			get_file_with_ext(data.udir, ext, no_dot=True, delete_all=True)
+			get_file_with_ext(wdir, ext, no_dot=True, delete_all=True)
 		return 'ok'
 
 	def delete_tmp_wallets(self):
@@ -248,11 +254,14 @@ class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
 		return t
 
 	def create_watchonly_wallets(self):
-		return self.restore_wallets()
+		return self._create_wallets('restore')
 
 	def restore_wallets(self):
+		return self._create_wallets('restore')
+
+	def _create_wallets(self, op='create'):
 		self.insert_device_online()
-		t = self.create_wallets('alice', op='restore')
+		t = self.create_wallets('alice', op=op)
 		t.read() # required!
 		self.remove_device_online()
 		return t
@@ -309,12 +318,12 @@ class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
 		args = (
 			self.extra_opts
 			+ self.autosign_opts
-			+ [f'--wallet-dir={data.udir}', f'--daemon=localhost:{data.md.rpc_port}']
+			+ (['--alice', '--compat'] if self.compat else [f'--wallet-dir={data.udir}'])
+			+ [f'--daemon=localhost:{data.md.rpc_port}']
 			+ add_opts
 			+ [op]
 			+ ([get_file_with_ext(self.asi.xmr_tx_dir, ext)] if ext else [])
-			+ ([wallet_arg] if wallet_arg else [])
-		)
+			+ ([wallet_arg] if wallet_arg else []))
 		desc_pfx = f'{desc}, ' if desc else ''
 		self.insert_device_online() # device must be removed by calling method
 		return self.spawn('mmgen-xmrwallet', args, extra_desc=f'({desc_pfx}Alice)')
@@ -469,3 +478,9 @@ class CmdTestXMRAutosign(CmdTestXMRWallet, CmdTestAutosignThreaded):
 
 	def listview(self):
 		return self.sync_wallets('alice', op='listview', wallets='2')
+
+class CmdTestXMRAutosignNoCompat(CmdTestXMRAutosign):
+	"""
+	Monero autosigning operations (non-xmrwallet compat mode)
+	"""
+	compat = False

+ 37 - 39
test/cmdtest_d/xmrwallet.py

@@ -71,7 +71,8 @@ class CmdTestXMRWallet(CmdTestBase):
 		('bob',   '1378FC64', False, 140, None,  ['--restricted-rpc']),
 	)
 	tx_relay_user = 'bob'
-	datadir_base = os.path.join('test', 'daemons', 'xmrtest')
+	daemon_datadir_base = os.path.join('test', 'daemons', 'xmrtest')
+	compat = False
 
 	cmd_group = (
 		('daemon_version',                'checking daemon version'),
@@ -137,9 +138,9 @@ class CmdTestXMRWallet(CmdTestBase):
 		if not cfg.no_daemon_autostart:
 			stop_daemons(self)
 			time.sleep(0.2)
-			if os.path.exists(self.datadir_base):
-				shutil.rmtree(self.datadir_base)
-			os.makedirs(self.datadir_base)
+			if os.path.exists(self.daemon_datadir_base):
+				shutil.rmtree(self.daemon_datadir_base)
+			os.makedirs(self.daemon_datadir_base)
 			TestProxy(self, cfg)
 			self.start_daemons()
 
@@ -153,12 +154,13 @@ class CmdTestXMRWallet(CmdTestBase):
 		from mmgen.proto.xmr.rpc import MoneroRPCClient, MoneroWalletRPCClient
 		self.users = {}
 		tmpdir_num = self.tmpdir_nums[0]
+
 		ud = namedtuple('user_data', [
 			'sid',
 			'mmwords',
 			'autosign',
 			'udir',
-			'datadir',
+			'daemon_datadir',
 			'kal_range',
 			'kafile',
 			'walletfile_fs',
@@ -177,16 +179,25 @@ class CmdTestXMRWallet(CmdTestBase):
 				shift,
 				kal_range,
 				add_coind_args) in self.user_data:
+
 			tmpdir = os.path.join('test', 'tmp', str(tmpdir_num))
 			udir = os.path.join(tmpdir, user)
-			datadir = os.path.join(self.datadir_base, user)
+			daemon_datadir = os.path.join(self.daemon_datadir_base, user)
+
+			if self.compat:
+				from mmgen.tw.ctl import TwCtl
+				twctl_cls = self.proto.base_proto_subclass(TwCtl, 'tw.ctl')
+				wallet_dir = os.path.join(self.tr.data_dir, user, 'altcoins', 'xmr', twctl_cls.tw_subdir)
+			else:
+				wallet_dir = udir
+
 			md = CoinDaemon(
 				cfg        = self.cfg,
 				proto      = self.proto,
 				test_suite = True,
 				port_shift = shift,
 				opts       = ['online'],
-				datadir    = datadir
+				datadir    = daemon_datadir
 			)
 			md_rpc = MoneroRPCClient(
 				cfg    = self.cfg,
@@ -202,7 +213,7 @@ class CmdTestXMRWallet(CmdTestBase):
 				cfg          = self.cfg,
 				proto        = self.proto,
 				test_suite   = True,
-				wallet_dir   = udir,
+				wallet_dir   = wallet_dir,
 				user         = 'foo',
 				passwd       = 'bar',
 				port_shift   = shift,
@@ -226,7 +237,7 @@ class CmdTestXMRWallet(CmdTestBase):
 				mmwords        = f'test/ref/{sid}.mmwords',
 				autosign       = autosign,
 				udir           = udir,
-				datadir        = datadir,
+				daemon_datadir = daemon_datadir,
 				kal_range      = kal_range,
 				kafile         = f'{kafile_dir}/{sid}-XMR-M[{kal_range}].{kafile_suf}',
 				walletfile_fs  = f'{udir}/{sid}-{{}}-{fn_stem}',
@@ -235,8 +246,7 @@ class CmdTestXMRWallet(CmdTestBase):
 				md_rpc         = md_rpc,
 				wd             = wd,
 				wd_rpc         = wd_rpc,
-				add_coind_args = add_coind_args,
-			)
+				add_coind_args = add_coind_args)
 
 	def init_daemon_args(self):
 		common_args = ['--p2p-bind-ip=127.0.0.1', '--fixed-difficulty=1', '--regtest'] # --rpc-ssl-allow-any-cert
@@ -291,21 +301,19 @@ class CmdTestXMRWallet(CmdTestBase):
 			run(f'rm -f {glob}', shell=True)
 		t = self.spawn(
 			'mmgen-xmrwallet',
-			[f'--wallet-dir={data.udir}']
-			+ self.extra_opts
+			self.extra_opts
+			+ ([f'--{user}', '--compat'] if self.compat else [f'--wallet-dir={data.udir}'])
 			+ (self.autosign_opts if data.autosign else [])
 			+ add_opts
 			+ [op]
 			+ ([] if data.autosign else [data.kafile])
-			+ [wallet or data.kal_range]
-		)
+			+ [wallet or data.kal_range])
 		for i in MMGenRange(wallet or data.kal_range).items:
 			write_data_to_file(
 				self.cfg,
 				self.users[user].addrfile_fs.format(i),
 				t.expect_getend('Address: '),
-				quiet = True
-			)
+				quiet = True)
 		return t
 
 	def new_addr_alice(self, spec, cfg, expect, kafile=None):
@@ -313,7 +321,7 @@ class CmdTestXMRWallet(CmdTestBase):
 		t = self.spawn(
 			'mmgen-xmrwallet',
 			self.extra_opts
-			+ [f'--wallet-dir={data.udir}']
+			+ (['--alice', '--compat'] if self.compat else [f'--wallet-dir={data.udir}'])
 			+ [f'--daemon=localhost:{data.md.rpc_port}']
 			+ (['--no-start-wallet-daemon'] if cfg in ('continue', 'stop') else [])
 			+ (['--no-stop-wallet-daemon'] if cfg in ('start', 'continue') else [])
@@ -359,16 +367,14 @@ class CmdTestXMRWallet(CmdTestBase):
 		await self.transfer(
 			'miner',
 			amt,
-			read_from_file(self.users['alice'].addrfile_fs.format(wallet)),
-		)
+			read_from_file(self.users['alice'].addrfile_fs.format(wallet)))
 		return 'ok'
 
 	async def check_bal_alice(self, wallet=1, bal='1.234567891234'):
 		return await self.mine_chk(
 			'alice', wallet, 0,
 			lambda x: str(x.ub) == bal, f'unlocked balance == {bal}',
-			random_txs = self.dfl_random_txs
-		)
+			random_txs = self.dfl_random_txs)
 
 	def set_label_miner(self):
 		return self.set_label_user('miner', '1:0:0,"Miner’s new primary account label [1:0:0]"', 'updated')
@@ -387,8 +393,7 @@ class CmdTestXMRWallet(CmdTestBase):
 			self.extra_opts
 			+ add_opts
 			+ cmd_opts
-			+ ['label', data.kafile, label_spec]
-		)
+			+ ['label', data.kafile, label_spec])
 		t.expect('(y/N): ', 'y')
 		t.expect(f'Label successfully {expect}')
 		return t
@@ -415,27 +420,22 @@ class CmdTestXMRWallet(CmdTestBase):
 		data = self.users[user]
 		if data.autosign:
 			self.insert_device_online()
-		cmd_opts = list_gen(
-			[f'--wallet-dir={data.udir}'],
-			[f'--daemon=localhost:{data.md.rpc_port}'],
-		)
 		t = self.spawn(
 			'mmgen-xmrwallet',
 			self.extra_opts
-			+ cmd_opts
+			+ ([f'--{user}', '--compat'] if self.compat else [f'--wallet-dir={data.udir}'])
+			+ [f'--daemon=localhost:{data.md.rpc_port}']
 			+ (self.autosign_opts if data.autosign else [])
 			+ add_opts
 			+ [op]
 			+ ([] if data.autosign else [data.kafile])
-			+ ([wallets] if wallets else [])
-		)
+			+ ([wallets] if wallets else []))
 		wlist = AddrIdxList(fmt_str=wallets) if wallets else MMGenRange(data.kal_range).items
 		for n, wnum in enumerate(wlist, 1):
 			t.expect('ing wallet {}/{} ({})'.format(
 				n,
 				len(wlist),
-				os.path.basename(data.walletfile_fs.format(wnum)),
-			))
+				os.path.basename(data.walletfile_fs.format(wnum))))
 			if op in ('view', 'listview'):
 				t.expect('Wallet height: ')
 			else:
@@ -465,17 +465,16 @@ class CmdTestXMRWallet(CmdTestBase):
 
 		data = self.users[user]
 		cmd_opts = list_gen(
-			[f'--wallet-dir={data.udir}'],
 			[f'--outdir={data.udir}', not data.autosign],
 			[f'--daemon=localhost:{data.md.rpc_port}'],
 			[f'--tx-relay-daemon={tx_relay_parm}', tx_relay_parm],
-			['--no-relay', no_relay and not data.autosign],
-		)
+			['--no-relay', no_relay and not data.autosign])
 		add_desc = (', ' + add_desc) if add_desc else ''
 
 		t = self.spawn(
 			'mmgen-xmrwallet',
 			self.extra_opts
+			+ ([f'--{user}', '--compat'] if self.compat else [f'--wallet-dir={data.udir}'])
 			+ cmd_opts
 			+ add_opts
 			+ (self.autosign_opts if data.autosign else [])
@@ -732,8 +731,7 @@ class CmdTestXMRWallet(CmdTestBase):
 				dest.wnum,
 				dest.account,
 				bal_info.b.hl(),
-				bal_info.ub.hl(),
-			))
+				bal_info.ub.hl()))
 
 		async def get_balance(dest, count):
 			data = self.users[dest.user]
@@ -821,7 +819,7 @@ class CmdTestXMRWallet(CmdTestBase):
 
 	def start_daemons(self):
 		for v in self.users.values():
-			run(['mkdir', '-p', v.datadir])
+			run(['mkdir', '-p', v.daemon_datadir])
 			v.md.start()
 
 	def stop_daemons(self):

+ 2 - 0
test/test-release.d/cfg.sh

@@ -295,7 +295,9 @@ init_tests() {
 	d_xmr="Monero xmrwallet operations"
 	t_xmr="
 		- $xmr_env1$xmr_env2$xmr_env3$cmdtest_py --coin=xmr --exclude help
+		s $xmr_env1$xmr_env2$xmr_env3$cmdtest_py --coin=xmr xmr_autosign_nocompat
 	"
+	[ "$FAST" ]  && t_xmr_skip='s'
 
 	d_tool2="'mmgen-tool' utility with data check"
 	t_tool2="