Browse Source

new command: `mmgen-xmrwallet`, replacing `mmgen-tool xmrwallet`

- `wallets` arg is now 3rd positional arg
- tool keyword args are now command options

Old and new invocation examples:

    OLD: mmgen-tool xmrwallet sync *.akeys.mmenc wallets=3-5
    NEW: mmgen-xmrwallet sync *.akeys.mmenc 3-5

    OLD: mmgen-tool xmrwallet sweep *.akeys.mmenc wallets=3:2 tx_relay_daemon=foo.onion:18081:127.0.0.1:9052
    NEW: mmgen-xmrwallet --tx-relay-daemon=foo.onion:18081:127.0.0.1:9052 sweep *.akeys.mmenc 3:2
The MMGen Project 3 years ago
parent
commit
cb98afd
7 changed files with 210 additions and 100 deletions
  1. 24 0
      cmds/mmgen-xmrwallet
  2. 166 0
      mmgen/main_xmrwallet.py
  3. 0 77
      mmgen/tool.py
  4. 4 4
      mmgen/xmrwallet.py
  5. 3 0
      setup.py
  6. 13 18
      test/test_py_d/ts_xmrwallet.py
  7. 0 1
      test/tooltest.py

+ 24 - 0
cmds/mmgen-xmrwallet

@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2021 The MMGen Project <mmgen@tuta.io>
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free Software
+# Foundation, either version 3 of the License, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+# FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
+# details.
+#
+# You should have received a copy of the GNU General Public License along with
+# this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen-xmrwallet: Monero wallet ops for the MMGen suite
+"""
+
+from mmgen.main import launch
+launch("xmrwallet")

+ 166 - 0
mmgen/main_xmrwallet.py

@@ -0,0 +1,166 @@
+#!/usr/bin/env python3
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2021 The MMGen Project <mmgen@tuta.io>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+"""
+mmgen/main_xmrwallet: Perform various Monero wallet operations for addresses
+                      in an MMGen XMR key-address file
+"""
+
+from .common import *
+from .xmrwallet import xmrwallet_uarg_info,MoneroWalletOps
+
+opts_data = {
+	'text': {
+		'desc': """Perform various Monero wallet operations for addresses
+                   in an MMGen XMR key-address file""",
+		'usage2': [
+			'[opts] create <xmr_keyaddrfile> [wallets]',
+			'[opts] sync   <xmr_keyaddrfile> [wallets]',
+			'[opts] sweep  <xmr_keyaddrfile> <sweep_spec>',
+		],
+		'options': """
+-h, --help                       Print this help message
+--, --longhelp                   Print help message for long options (common
+                                 options)
+-d, --outdir=D                   Output or operate on wallets in directory 'D'
+                                 instead of working dir
+-D, --daemon=H:P                 Connect to monerod at {D}
+-R, --tx-relay-daemon=H:P[:H:P]  Relay transactions via monerod specified by
+                                 {R}
+-k, --use-internal-keccak-module Force use of the internal keccak module
+-p, --hash-preset=P              Use scrypt hash preset 'P' for password
+                                 hashing (default: '{g.dfl_hash_preset}')
+-r, --restore-height=H           Scan from height 'H' when creating wallets
+-s, --no-start-wallet-daemon     Don’t start the wallet daemon at startup
+-S, --no-stop-wallet-daemon      Don’t stop the wallet daemon at exit
+""",
+	'notes': """
+
+Command requires a running monerod daemon.  Unless --daemon is specified,
+monerod is assumed to be listening on localhost at the default RPC port.
+
+If --tx-relay-daemon is specified, the monerod daemon at HOST:PORT will be
+used to relay any created transactions.  PROXY_HOST:PROXY_PORT, if specified,
+may point to a SOCKS proxy, in which case HOST may be a Tor onion address.
+
+
+                        SUPPORTED OPERATIONS
+
+create - create wallet for all or specified addresses in key-address file
+sync   - sync wallet for all or specified addresses in key-address file
+sweep  - sweep funds in specified wallet:account to new address in same
+         account or new account in another wallet
+
+
+                   CREATE AND SYNC OPERATION NOTES
+
+These operations take an optional `wallets` argument: a comma-separated list,
+hyphenated range, or combination of both, of address indexes in the specified
+key-address file, each corresponding to a Monero wallet to be created or
+synced.  If omitted, all wallets are operated upon.
+
+
+                        SWEEP OPERATION NOTES
+
+The sweep operation takes a `sweep specifier` arg with the following format:
+
+    SOURCE:ACCOUNT[,DEST]
+
+where SOURCE and DEST are wallet indexes and ACCOUNT an account index.
+
+If DEST is omitted, a new address will be created in ACCOUNT of SOURCE and
+all funds from ACCOUNT of SOURCE will be swept into it.
+
+If DEST is included, a new account will be created in DEST and all funds
+from ACCOUNT of SOURCE will be swept into the new account.
+
+The user is prompted before addresses are created or funds are transferred.
+
+
+                              WARNING
+
+Note that the use of this command requires private data to be exposed on a
+network-connected machine in order to unlock the Monero wallets.  This is a
+violation of good security practice.
+"""
+	},
+	'code': {
+		'options': lambda s: s.format(
+			D=xmrwallet_uarg_info['daemon'].annot,
+			R=xmrwallet_uarg_info['tx_relay_daemon'].annot,
+			g=g,
+		),
+	}
+}
+
+cmd_args = opts.init(opts_data)
+
+if len(cmd_args) < 2:
+	opts.usage()
+
+op     = cmd_args.pop(0)
+infile = cmd_args.pop(0)
+
+if op not in MoneroWalletOps.ops:
+	die(1,f'{op!r}: unrecognized operation')
+
+wallets = spec = ''
+
+if op in ('create','sync'):
+	if len(cmd_args) not in (0,1):
+		opts.usage()
+	if cmd_args:
+		wallets = cmd_args[0]
+elif op == 'sweep':
+	if len(cmd_args) != 1:
+		opts.usage()
+	spec = cmd_args[0]
+
+ua = namedtuple('uargs',[
+	'op',
+	'xmr_keyaddrfile',
+	'wallets',
+	'spec',
+	'daemon',
+	'tx_relay_daemon',
+	'restore_height',
+	'start_wallet_daemon',
+	'stop_wallet_daemon',
+])
+
+uargs = ua(
+	op,
+	infile,
+	wallets,
+	spec,
+	opt.daemon or '',
+	opt.tx_relay_daemon or '',
+	opt.restore_height or 0,
+	not opt.no_start_wallet_daemon,
+	not opt.no_stop_wallet_daemon,
+)
+
+m = getattr(MoneroWalletOps,op)(uargs)
+
+try:
+	if run_session(m.process_wallets()):
+		m.post_process()
+except KeyboardInterrupt:
+	ymsg('\nUser interrupt')
+finally:
+	m.stop_daemons()

+ 0 - 77
mmgen/tool.py

@@ -1010,83 +1010,6 @@ class MMGenToolCmdRPC(MMGenToolCmds):
 			msg("Address '{}' deleted from tracking wallet".format(ret))
 		return ret
 
-class MMGenToolCmdMonero(MMGenToolCmds):
-	"""
-	Monero wallet operations
-
-	Note that the use of these commands requires private data to be exposed on
-	a network-connected machine in order to unlock the Monero wallets.  This is
-	a violation of good security practice.
-	"""
-
-	from .xmrwallet import xmrwallet_uarg_info
-
-	def xmrwallet(
-		self,
-		op:                  str,
-		xmr_keyaddrfile:     str,
-		restore_height       = 0,
-		wallets:             '(integer range or list, or sweep specifier)' = '',
-		start_wallet_daemon  = True,
-		stop_wallet_daemon   = True,
-		daemon:              xmrwallet_uarg_info['daemon'].annot = '',
-		tx_relay_daemon:     xmrwallet_uarg_info['tx_relay_daemon'].annot = '',
-	):
-
-		"""
-		perform various Monero wallet operations for addresses in XMR key-address file
-
-		  Requires a running monerod daemon.  Unless 'daemon' is specified, the daemon
-		  is assumed to be listening on localhost at the default RPC port.
-
-		  If 'tx_relay_daemon' is specified, the monerod daemon at HOST:PORT will be
-		  used to relay any created transactions.  PROXY_HOST:PROXY_PORT, if specified,
-		  may point to a Tor SOCKS proxy, in which case HOST may be a Tor onion address.
-
-		  Supported operations:
-
-		    create - create wallet for all or specified addresses in key-address file
-		    sync   - sync wallet for all or specified addresses in key-address file
-		    sweep  - sweep funds in specified wallet:account to new address in same
-		             account or new account in another wallet
-
-		                               SWEEP OPERATION NOTES
-
-		    For the sweep operation, the parameter to the 'wallets' arg has a different
-		    format, known as a 'sweep specifier':
-
-		        SOURCE:ACCOUNT[,DEST]
-
-		    where SOURCE and DEST are wallet numbers and ACCOUNT an account index.
-
-		    If DEST is omitted, a new address will be created in ACCOUNT of SOURCE and
-		    all funds from ACCOUNT of SOURCE will be swept into it.
-
-		    If DEST is included, a new account will be created in DEST and all funds
-		    from ACCOUNT of SOURCE will be swept into the new account.
-
-		    The user is prompted before addresses are created or funds are transferred.
-		"""
-
-		from .xmrwallet import MoneroWalletOps
-
-		if op not in MoneroWalletOps.ops:
-			die(1,f'{op!r}: unrecognized operation')
-
-		m = getattr(MoneroWalletOps,op)(
-			_create_argtuple( MMGenToolCmdMonero.xmrwallet, locals() )
-		)
-
-		try:
-			if run_session(m.process_wallets()):
-				m.post_process()
-		except KeyboardInterrupt:
-			ymsg('\nUser interrupt')
-		finally:
-			m.stop_daemons()
-
-		return True
-
 class tool_api(
 		MMGenToolCmdUtil,
 		MMGenToolCmdCoin,

+ 4 - 4
mmgen/xmrwallet.py

@@ -31,7 +31,7 @@ xmrwallet_uarg_info = (
 	lambda e,hp: {
 		'daemon':          e('HOST:PORT', hp),
 		'tx_relay_daemon': e('HOST:PORT[:PROXY_HOST:PROXY_PORT]', r'({p})(?::({p}))?'.format(p=hp)),
-		'wallets_sweep':   e('SOURCE_WALLET_NUM:ACCOUNT[,DEST_WALLET_NUM]', r'(\d+):(\d+)(?:,(\d+))?'),
+		'sweep_spec':      e('SOURCE_WALLET_NUM:ACCOUNT[,DEST_WALLET_NUM]', r'(\d+):(\d+)(?:,(\d+))?'),
 	})(
 		namedtuple('uarg_info_entry',['annot','pat']),
 		r'(?:[^:]+):(?:\d+)'
@@ -408,10 +408,10 @@ class MoneroWalletOps:
 		tx_relay = True
 
 		def create_addr_data(self):
-			m = re.fullmatch(uarg_info['wallets_sweep'].pat,uarg.wallets,re.ASCII)
+			m = re.fullmatch(uarg_info['sweep_spec'].pat,uarg.spec,re.ASCII)
 			if not m:
-				fs = "{!r}: invalid 'wallets' arg: for sweep operation, it must have format {!r}"
-				die(1,fs.format( uarg.wallets, uarg_info['wallets_sweep'].annot ))
+				fs = "{!r}: invalid 'sweep_spec' arg: for sweep operation, it must have format {!r}"
+				die(1,fs.format( uarg.spec, uarg_info['sweep_spec'].annot ))
 
 			def gen():
 				for i,k in ( (1,'source'), (3,'dest') ):

+ 3 - 0
setup.py

@@ -148,6 +148,7 @@ setup(
 			'mmgen.txsign',
 			'mmgen.util',
 			'mmgen.wallet',
+			'mmgen.xmrwallet',
 
 			'mmgen.altcoins.__init__',
 
@@ -189,6 +190,7 @@ setup(
 			'mmgen.main_txsend',
 			'mmgen.main_txsign',
 			'mmgen.main_wallet',
+			'mmgen.main_xmrwallet',
 
 			'mmgen.share.__init__',
 			'mmgen.share.Opts',
@@ -214,5 +216,6 @@ setup(
 			'cmds/mmgen-walletchk',
 			'cmds/mmgen-walletconv',
 			'cmds/mmgen-walletgen',
+			'cmds/mmgen-xmrwallet',
 		]
 	)

+ 13 - 18
test/test_py_d/ts_xmrwallet.py

@@ -71,7 +71,7 @@ class TestSuiteXMRWallet(TestSuiteBase):
 		from mmgen.protocol import init_proto
 		self.proto = init_proto('XMR',network='testnet')
 		self.datadir_base  = os.path.join('test','daemons','xmrtest')
-		self.tool_args = ['--testnet=1', '--monero-wallet-rpc-password=passw0rd']
+		self.long_opts = ['--testnet=1', '--monero-wallet-rpc-password=passw0rd']
 		self.init_users()
 		self.init_daemon_args()
 
@@ -283,11 +283,10 @@ class TestSuiteXMRWallet(TestSuiteBase):
 			if not data.kal_range:
 				continue
 			run('rm -f {}*'.format( data.walletfile_fs.format('*') ),shell=True)
-			dir_arg = [f'--outdir='+data.udir]
-			cmd_opts = ['wallets={}'.format(data.kal_range)]
+			dir_opt = [f'--outdir={data.udir}']
 			t = self.spawn(
-				'mmgen-tool',
-				self.tool_args + dir_arg + [ 'xmrwallet', 'create', data.kafile ] + cmd_opts,
+				'mmgen-xmrwallet',
+				self.long_opts + dir_opt + [ 'create', data.kafile, data.kal_range ],
 				extra_desc = f'({capfirst(user)})' )
 			t.expect('Check key-to-address validity? (y/N): ','n')
 			for i in MMGenRange(data.kal_range).items:
@@ -318,14 +317,11 @@ class TestSuiteXMRWallet(TestSuiteBase):
 
 	def sync_wallets(self,wallets=None):
 		data = self.users['alice']
-		dir_arg = [f'--outdir={data.udir}']
-		cmd_opts = list_gen(
-			[f'daemon=localhost:{data.md.rpc_port}'],
-			[f'wallets={wallets}', wallets],
-		)
+		dir_opt = [f'--outdir={data.udir}']
+		cmd_opts = [f'--daemon=localhost:{data.md.rpc_port}']
 		t = self.spawn(
-			'mmgen-tool',
-			self.tool_args + dir_arg + [ 'xmrwallet', 'sync', data.kafile ] + cmd_opts )
+			'mmgen-xmrwallet',
+			self.long_opts + dir_opt + cmd_opts + [ 'sync', data.kafile ] + ([wallets] if wallets else []) )
 		t.expect('Check key-to-address validity? (y/N): ','n')
 		wlist = AddrIdxList(wallets) if wallets else MMGenRange(data.kal_range).items
 		for n,wnum in enumerate(wlist):
@@ -342,15 +338,14 @@ class TestSuiteXMRWallet(TestSuiteBase):
 
 	def _sweep_user(self,user,spec,tx_relay_daemon=None):
 		data = self.users[user]
-		dir_arg = [f'--outdir='+data.udir]
+		dir_opt = [f'--outdir={data.udir}']
 		cmd_opts = list_gen(
-			[f'daemon=localhost:{data.md.rpc_port}'],
-			[f'wallets={spec}'],
-			[f'tx_relay_daemon={tx_relay_daemon}', tx_relay_daemon]
+			[f'--daemon=localhost:{data.md.rpc_port}'],
+			[f'--tx-relay-daemon={tx_relay_daemon}', tx_relay_daemon]
 		)
 		t = self.spawn(
-			'mmgen-tool',
-			self.tool_args + dir_arg + [ 'xmrwallet', 'sweep', data.kafile ] + cmd_opts,
+			'mmgen-xmrwallet',
+			self.long_opts + dir_opt + cmd_opts + [ 'sweep', data.kafile, spec ],
 			extra_desc = f'({capfirst(user)})' )
 		t.expect('Check key-to-address validity? (y/N): ','n')
 		t.expect(

+ 0 - 1
test/tooltest.py

@@ -159,7 +159,6 @@ if opt.list_names:
 			'addrfile_chksum','keyaddrfile_chksum','passwdfile_chksum',
 			'add_label','remove_label','remove_address','twview',
 			'getbalance','listaddresses','listaddress'),
-		'test-release.sh': ('xmrwallet',),
 		'tooltest2.py': run(tcmd,stdout=PIPE,check=True).stdout.decode().split()
 	}
 	for v in cmd_data.values():