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
This commit is contained in:
The MMGen Project 2021-06-08 11:46:13 +00:00
commit cb98afda79
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
7 changed files with 210 additions and 100 deletions

24
cmds/mmgen-xmrwallet Executable file
View file

@ -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
mmgen/main_xmrwallet.py Executable file
View file

@ -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 Dont start the wallet daemon at startup
-S, --no-stop-wallet-daemon Dont 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()

View file

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

View file

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

View file

@ -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',
]
)

View file

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

View file

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