mmgen-tool xmrwallet: add 'daemon', 'tx_relay_daemon' opts

- If 'daemon' is specified, RPC wallet connects to this monerod instead of the
  default at localhost:18081

- If 'tx_relay_daemon' is specified, transactions are relayed via this daemon
  instead of the local one.  This option supports proxying via SOCKS (Tor)
This commit is contained in:
The MMGen Project 2021-05-07 16:49:01 +00:00
commit 0c42587978
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
2 changed files with 136 additions and 38 deletions

View file

@ -230,6 +230,8 @@ class MoneroWalletDaemon(Daemon):
host = None,
user = None,
passwd = None,
daemon_addr = None,
proxy = None,
rpc_port_shift = None ):
super().__init__()
@ -245,7 +247,10 @@ class MoneroWalletDaemon(Daemon):
self.pidfile = os.path.join(self.datadir,id_str+'.pid')
self.logfile = os.path.join(self.datadir,id_str+'.log')
self.daemon_port = CoinDaemon('xmr',test_suite=test_suite).rpc_port
self.proxy = proxy
self.daemon_addr = daemon_addr
if not daemon_addr:
self.daemon_port = CoinDaemon('xmr',test_suite=test_suite).rpc_port
if self.platform == 'win':
self.use_pidfile = False
@ -267,11 +272,20 @@ class MoneroWalletDaemon(Daemon):
def start_cmd(self):
cmd = [
'monero-wallet-rpc',
'--daemon-port={}'.format(self.daemon_port),
'--untrusted-daemon',
'--rpc-bind-port={}'.format(self.rpc_port),
'--wallet-dir='+self.wallet_dir,
'--log-file='+self.logfile,
'--rpc-login={}:{}'.format(self.user,self.passwd) ]
if self.daemon_addr:
cmd.append(f'--daemon-address={self.daemon_addr}')
else:
cmd.append(f'--daemon-port={self.daemon_port}')
if self.proxy:
cmd.append(f'--proxy={self.proxy}')
if self.platform == 'linux':
cmd += ['--pidfile={}'.format(self.pidfile)]
cmd += [] if 'no_daemonize' in self.flags else ['--detach']

View file

@ -1024,11 +1024,20 @@ class MMGenToolCmdMonero(MMGenToolCmds):
wallets: '(integer range or list, or sweep specifier)' = '',
start_wallet_daemon = True,
stop_wallet_daemon = True,
daemon: 'HOST:PORT' = '',
tx_relay_daemon: 'HOST:PORT[:PROXY_HOST:PROXY_PORT]' = '',
):
"""
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
@ -1061,6 +1070,7 @@ class MMGenToolCmdMonero(MMGenToolCmds):
class base:
wallet_exists = True
tx_relay = False
def __init__(self):
@ -1087,7 +1097,8 @@ class MMGenToolCmdMonero(MMGenToolCmds):
from .daemon import MoneroWalletDaemon
self.wd = MoneroWalletDaemon(
wallet_dir = opt.outdir or '.',
test_suite = g.test_suite
test_suite = g.test_suite,
daemon_addr = daemon or None,
)
if start_wallet_daemon:
@ -1115,6 +1126,8 @@ class MMGenToolCmdMonero(MMGenToolCmds):
def stop_daemons(self):
if stop_wallet_daemon:
self.wd.stop()
if tx_relay_daemon:
self.wd2.stop()
def post_init(self): pass
def post_process(self): pass
@ -1218,12 +1231,9 @@ class MMGenToolCmdMonero(MMGenToolCmds):
return True
def post_init(self):
from .daemon import CoinDaemon
md = CoinDaemon(network_id='xmr',test_suite=g.test_suite)
host,port = (md.host,md.rpc_port)
host,port = daemon.split(':') if daemon else ('localhost',self.wd.daemon_port)
from .rpc import MoneroRPCClient
self.dc = MoneroRPCClient(host=host, port=port, user=None, passwd=None)
self.dc = MoneroRPCClient(host=host, port=int(port), user=None, passwd=None)
self.accts_data = {}
def post_process(self):
@ -1252,6 +1262,7 @@ class MMGenToolCmdMonero(MMGenToolCmds):
name = 'sweep'
desc = 'Sweep'
past = 'swept'
tx_relay = True
def create_addr_data(self):
m = re.match('(\d+):(\d+)(?:,(\d+))?$',wallets,re.ASCII)
@ -1281,6 +1292,31 @@ class MMGenToolCmdMonero(MMGenToolCmds):
self.addr_data = list(gen())
self.account = int(m[2])
def post_init(self):
if tx_relay_daemon:
m = re.fullmatch(hostproxy_pat,tx_relay_daemon,re.ASCII)
from .daemon import MoneroWalletDaemon
self.wd2 = MoneroWalletDaemon(
wallet_dir = opt.outdir or '.',
test_suite = g.test_suite,
daemon_addr = m[1],
proxy = m[2],
rpc_port_shift = 16,
)
if start_wallet_daemon:
self.wd2.restart()
from .rpc import MoneroWalletRPCClient
self.c2 = MoneroWalletRPCClient(
host = self.wd2.host,
port = self.wd2.rpc_port,
user = self.wd2.user,
passwd = self.wd2.passwd
)
async def process_wallets(self):
gmsg(f'\nSweeping account #{self.account} of wallet {self.source.idx}' + (
' to new address' if self.dest is None else
@ -1306,31 +1342,48 @@ class MMGenToolCmdMonero(MMGenToolCmds):
die(1,'Exiting at user request')
await h.get_addrs(accts_data,self.account)
else:
await h.close_wallet('source')
bn = os.path.basename(self.get_wallet_fn(self.dest))
h = xmr_rpc_methods(self,self.dest)
await h.open_wallet('destination')
accts_data = (await h.get_accts())[0]
h2 = xmr_rpc_methods(self,self.dest)
await h2.open_wallet('destination')
accts_data = (await h2.get_accts())[0]
if keypress_confirm(f'\nCreate new account for wallet {bn!r}?'):
new_addr = await h.create_acct()
await h.get_accts()
new_addr = await h2.create_acct()
await h2.get_accts()
elif keypress_confirm(f'Sweep to last existing account of wallet {bn!r}?'):
new_addr = h.get_last_acct(accts_data)
new_addr = h2.get_last_acct(accts_data)
else:
die(1,'Exiting at user request')
h = xmr_rpc_methods(self,self.source)
await h2.close_wallet('destination')
await h.open_wallet('source')
if keypress_confirm(
'\nSweep balance of wallet {}, account #{} to {}?'.format(
self.source.idx,
self.account,
cyan(new_addr),
)):
await h.do_sweep(self.account,new_addr)
msg('\nCreating sweep transaction: balance of wallet {}, account #{} => {}'.format(
self.source.idx,
self.account,
cyan(new_addr),
))
sweep_tx = await h.make_sweep_tx(self.account,new_addr)
if keypress_confirm('Relay sweep transaction?'):
w_desc = 'source'
if tx_relay_daemon:
await h.close_wallet('source')
self.c = self.c2
h = xmr_rpc_methods(self,self.source)
w_desc = 'TX relay source'
await h.open_wallet(w_desc)
msg(f'\n Relaying sweep transaction...')
await h.relay_sweep_tx( sweep_tx['tx_metadata_list'][0] )
await h.close_wallet(w_desc)
gmsg('\n\nAll done')
else:
die(1,'Exiting at user request')
await h.close_wallet('source')
die(1,'\nExiting at user request')
return True
class xmr_rpc_methods:
@ -1348,6 +1401,11 @@ class MMGenToolCmdMonero(MMGenToolCmds):
password=self.d.wallet_passwd )
gmsg('done')
async def close_wallet(self,desc):
gmsg_r(f'\n Closing {desc} wallet...')
await self.c.call('close_wallet')
gmsg_r('done')
def print_accts(self,data,addrs_data,indent=' '):
d = data['subaddress_accounts']
msg('\n' + indent + f'Accounts of wallet {os.path.basename(self.fn)}:')
@ -1425,26 +1483,36 @@ class MMGenToolCmdMonero(MMGenToolCmds):
msg(' ' + cyan(ret))
return ret
async def do_sweep(self,account,addr):
msg(f'\n Sweeping account balance...')
ret = { # debug
'amount_list': [322222330000],
'fee_list': [10600000],
'tx_hash_list': ['deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'],
}
def display_sweep_tx(self,data):
from .obj import CoinTxID
msg(' TxID: {}\n Amount: {}\n Fee: {}'.format(
CoinTxID(data['tx_hash_list'][0]).hl(),
hlXMRamt(data['amount_list'][0]),
hlXMRamt(data['fee_list'][0]),
))
async def make_sweep_tx(self,account,addr):
ret = await self.c.call(
'sweep_all',
address = addr,
account_index = account,
do_not_relay = True,
get_tx_metadata = True
)
from .obj import CoinTxID
msg(' TxID: {}\n Amount: {}\n Fee: {}'.format(
CoinTxID(ret['tx_hash_list'][0]).hl(),
hlXMRamt(ret['amount_list'][0]),
hlXMRamt(ret['fee_list'][0]),
))
self.display_sweep_tx(ret)
return ret
def display_txid(self,data):
from .obj import CoinTxID
msg(' Relayed {}'.format( CoinTxID(data['tx_hash']).hl() ))
async def relay_sweep_tx(self,tx_hex):
ret = await self.c.call('relay_tx',hex=tx_hex)
try:
self.display_txid(ret)
except:
print(ret)
def fmtXMRamt(amt):
from .obj import XMRAmt
return XMRAmt(amt,from_unit='min_coin_unit').fmt(fs='5.12',color=True)
@ -1453,7 +1521,13 @@ class MMGenToolCmdMonero(MMGenToolCmds):
from .obj import XMRAmt
return XMRAmt(amt,from_unit='min_coin_unit').hl()
def check_args():
def check_args(localvars):
def check_host_arg(arg_name,pat):
val = localvars[arg_name]
if not re.fullmatch(pat,val,re.ASCII):
annot = MMGenToolCmdMonero.xmrwallet.__annotations__[arg_name]
die(1,f'{val!r}: invalid {arg_name!r} parameter: it must have format {annot!r}')
if blockheight < 0:
die(1,f"{blockheight}: invalid 'blockheight' arg (<0)")
@ -1464,8 +1538,18 @@ class MMGenToolCmdMonero(MMGenToolCmds):
if op == 'sync' and blockheight != 0:
die(1,'Sync operation does not support blockheight arg')
if daemon:
check_host_arg('daemon',host_pat)
if tx_relay_daemon:
if not getattr(MoneroWalletOps,op).tx_relay:
die(1,f"'tx_relay_daemon' arg is not recognized for operation {op!r}")
check_host_arg('tx_relay_daemon',hostproxy_pat)
# start execution
check_args()
host_pat = r'(?:[^:]+):(?:\d+)'
hostproxy_pat = r'({p})(?::({p}))?'.format(p=host_pat)
check_args(locals())
m = getattr(MoneroWalletOps,op)()