From cb98afda790a16b1fce2e413bd83cb55e12271e0 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Tue, 8 Jun 2021 11:46:13 +0000 Subject: [PATCH] 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 --- cmds/mmgen-xmrwallet | 24 +++++ mmgen/main_xmrwallet.py | 166 +++++++++++++++++++++++++++++++++ mmgen/tool.py | 77 --------------- mmgen/xmrwallet.py | 8 +- setup.py | 3 + test/test_py_d/ts_xmrwallet.py | 31 +++--- test/tooltest.py | 1 - 7 files changed, 210 insertions(+), 100 deletions(-) create mode 100755 cmds/mmgen-xmrwallet create mode 100755 mmgen/main_xmrwallet.py diff --git a/cmds/mmgen-xmrwallet b/cmds/mmgen-xmrwallet new file mode 100755 index 00000000..d47c6e41 --- /dev/null +++ b/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 +# +# 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 . + +""" +mmgen-xmrwallet: Monero wallet ops for the MMGen suite +""" + +from mmgen.main import launch +launch("xmrwallet") diff --git a/mmgen/main_xmrwallet.py b/mmgen/main_xmrwallet.py new file mode 100755 index 00000000..359a6abb --- /dev/null +++ b/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 +# +# 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 . + +""" +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 [wallets]', + '[opts] sync [wallets]', + '[opts] sweep ', + ], + '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() diff --git a/mmgen/tool.py b/mmgen/tool.py index 9ba3f91b..fd73f43e 100755 --- a/mmgen/tool.py +++ b/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, diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index f3132ad3..9713cb10 100755 --- a/mmgen/xmrwallet.py +++ b/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') ): diff --git a/setup.py b/setup.py index ac235a44..d1ce0992 100755 --- a/setup.py +++ b/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', ] ) diff --git a/test/test_py_d/ts_xmrwallet.py b/test/test_py_d/ts_xmrwallet.py index 119692e6..16d4320f 100755 --- a/test/test_py_d/ts_xmrwallet.py +++ b/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( diff --git a/test/tooltest.py b/test/tooltest.py index 8b7a4117..267160b6 100755 --- a/test/tooltest.py +++ b/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():