Monero: new automated wallet syncing utility

- mmgen-tool: `syncmonerowallets`: batch-sync Monero wallets non-interactively
- mmgen-tool: `keyaddrlist2monerowallet` -> `keyaddrlist2monerowallets`
This commit is contained in:
The MMGen Project 2018-01-21 19:18:00 +03:00
commit 258651a531
Signed by: mmgen
GPG key ID: 62DBE9E5212F05BE
4 changed files with 107 additions and 65 deletions

View file

@ -53,6 +53,9 @@ Wallet/TX operations (coin daemon must be running):
txview - show raw/signed {pnm} transaction in human-readable form
twview - view tracking wallet
keyaddrlist2monerowallets - create Monero wallets from key-address list
syncmonerowallets - sync Monero wallets from key-address list
General utilities:
hexdump - encode data into formatted hexadecimal form (file or stdin)
unhexdump - decode formatted hexadecimal data (file or stdin)

View file

@ -94,7 +94,8 @@ cmd_data = OrderedDict([
('Decrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
('Bytespec', ['<bytespec> [str]']),
('Keyaddrlist2monerowallet',['<{} XMR key-address file> [str]'.format(pnm),'blockheight [int=(current height)]']),
('Keyaddrlist2monerowallets',['<{} XMR key-address file> [str]'.format(pnm),'blockheight [int=(current height)]']),
('Syncmonerowallets', ['<{} XMR key-address file> [str]'.format(pnm)]),
])
def usage(command):
@ -475,10 +476,12 @@ def Rand2file(outfile,nbytes,threads=4,silent=False):
def Bytespec(s): Msg(str(parse_nbytes(s)))
def Keyaddrlist2monerowallet(infile,blockheight=None):
import pexpect
def Syncmonerowallets(infile): monero_wallet_ops(infile=infile,op='sync')
if blockheight != None and int(blockheight) < 0: blockheight = 0
def Keyaddrlist2monerowallets(infile,blockheight=None):
monero_wallet_ops(infile=infile,op='create',blockheight=blockheight)
def monero_wallet_ops(infile,op,blockheight=None):
def run_cmd(cmd):
import subprocess as sp
@ -495,15 +498,6 @@ def Keyaddrlist2monerowallet(infile,blockheight=None):
die(1,'Unable to connect to monerod!')
return int(ret[8:].split('/')[0])
cur_height = test_rpc()
from mmgen.protocol import init_coin
init_coin('xmr')
from mmgen.addr import AddrList
al = KeyAddrList(infile)
sid = al.al_id.sid
os.environ['LANG'] = 'C'
def my_expect(p,m,s,regex=False):
if m: msg_r(' {}...'.format(m))
ret = (p.expect_exact,p.expect)[regex](s)
@ -520,53 +514,95 @@ def Keyaddrlist2monerowallet(infile,blockheight=None):
if m: msg('OK')
vmsg("sendline: '{}' => {}".format(s,ret))
def create():
gmsg('\nCreating {} wallet{}'.format(dl,suf(dl)))
for n,d in enumerate(al.data):
def create(n,d,fn):
try: os.stat(fn)
except: pass
else: die(1,"Wallet '{}' already exists!".format(fn))
p = pexpect.spawn('monero-wallet-cli --generate-from-spend-key {}'.format(fn))
my_expect(p,'Awaiting initial prompt','Secret spend key: ')
my_sendline(p,'',d.sec,65)
my_expect(p,'','Enter new wallet password: ')
my_sendline(p,'Sending password',d.wallet_passwd,33)
my_expect(p,'','Confirm password: ')
my_sendline(p,'Sending password again',d.wallet_passwd,33)
my_expect(p,'','of your choice: ')
my_sendline(p,'','1',2)
my_expect(p,'monerod generating wallet','Generated new wallet: ')
my_expect(p,'','\n')
if d.addr not in p.before:
die(3,'Addresses do not match!\n MMGen: {}\n Monero: {}'.format(d.addr,p.before))
my_expect(p,'','View key: ')
my_expect(p,'','\n')
if d.viewkey not in p.before:
die(3,'View keys do not match!\n MMGen: {}\n Monero: {}'.format(d.viewkey,p.before))
my_expect(p,'','(YYYY-MM-DD): ')
h = str(blockheight or cur_height-1)
my_sendline(p,'',h,len(h)+1)
ret = my_expect(p,'',['Starting refresh','Still apply restore height? (Y/Yes/N/No): '])
if ret == 1:
my_sendline(p,'','Y',2)
m = ' Warning: {}: blockheight argument is higher than current blockheight'
ymsg(m.format(blockheight))
elif blockheight != None:
p.logfile = sys.stderr
my_expect(p,'Syncing wallet','\[wallet.*$',regex=True)
p.logfile = None
my_sendline(p,'Exiting','exit',5)
p.read()
def sync(n,d,fn):
try: os.stat(fn)
except: die(1,"Wallet '{}' does not exist!".format(fn))
p = pexpect.spawn('monero-wallet-cli --wallet-file={}'.format(fn))
my_expect(p,'Awaiting password prompt','Wallet password: ')
my_sendline(p,'Sending password',d.wallet_passwd,33)
msg(' Starting refresh...')
height = None
while True:
ret = p.expect([r' / .*',r'\[wallet.*:.*'])
if ret == 0: # TODO: coverage
height = p.after
msg_r('\r Block {}{}'.format(p.before.split()[-1],height))
elif ret == 1:
if height:
height = height.split()[-1]
msg('\r Block {h} / {h}'.format(h=height))
else:
msg(' Wallet in sync')
msg(' '+[l for l in p.before.splitlines() if l[:8] == 'Balance:'][0])
my_sendline(p,'Exiting','exit',5)
p.read()
break
else:
die(2,"\nExpect failed: (return value: {})".format(ret))
def process_wallets():
m = { 'create': ('Creat','Generat',create,False),
'sync': ('Sync', 'Sync', sync, True) }
opt.accept_defaults = opt.accept_defaults or m[op][3]
from mmgen.protocol import init_coin
init_coin('xmr')
from mmgen.addr import AddrList
al = KeyAddrList(infile)
dl = len(al.data)
gmsg('\n{}ing {} wallet{}'.format(m[op][0],dl,suf(dl)))
for n,d in enumerate(al.data): # [d.sec,d.wallet_passwd,d.viewkey,d.addr]
fn = '{}{}-{}-MoneroWallet'.format(
(opt.outdir+'/' if opt.outdir else ''),
sid,d.idx)
gmsg("\nGenerating wallet {}/{} ({})".format(n+1,dl,fn))
try: os.stat(fn)
except: pass
else: die(1,"Wallet '{}' already exists!".format(fn))
# pmsg([d.sec,d.wallet_passwd,d.viewkey,d.addr,fn])
p = pexpect.spawn('monero-wallet-cli --generate-from-spend-key {}'.format(fn))
my_expect(p,'Awaiting initial prompt','Secret spend key: ')
my_sendline(p,'',d.sec,65)
my_expect(p,'','Enter new wallet password: ')
my_sendline(p,'Sending password',d.wallet_passwd,33)
my_expect(p,'','Confirm password: ')
my_sendline(p,'Sending password again',d.wallet_passwd,33)
my_expect(p,'','of your choice: ')
my_sendline(p,'','1',2)
my_expect(p,'monerod generating wallet','Generated new wallet: ')
my_expect(p,'','\n')
if d.addr not in p.before:
die(3,'Addresses do not match!\n MMGen: {}\n Monero: {}'.format(d.addr,p.before))
my_expect(p,'','View key: ')
my_expect(p,'','\n')
if d.viewkey not in p.before:
die(3,'View keys do not match!\n MMGen: {}\n Monero: {}'.format(d.viewkey,p.before))
my_expect(p,'','(YYYY-MM-DD): ')
h = str(blockheight or cur_height-1)
my_sendline(p,'',h,len(h)+1)
ret = my_expect(p,'',['Starting refresh','Still apply restore height? (Y/Yes/N/No): '])
if ret == 1:
my_sendline(p,'','Y',2)
m = ' Warning: {}: blockheight argument is higher than current blockheight'
ymsg(m.format(blockheight))
elif blockheight != None:
p.logfile = sys.stderr
my_expect(p,'Syncing wallet','\[wallet.*$',regex=True)
p.logfile = None
my_sendline(p,'Exiting','exit',5)
p.read()
al.al_id.sid,
d.idx)
gmsg('\n{}ing wallet {}/{} ({})'.format(m[op][1],n+1,dl,fn))
m[op][2](n,d,fn)
gmsg('\n{} wallet{} {}ed'.format(dl,suf(dl),m[op][0].lower()))
os.environ['LANG'] = 'C'
import pexpect
if blockheight != None and int(blockheight) < 0: blockheight = 0 # TODO: non-zero coverage
cur_height = test_rpc()
dl = len(al.data)
try:
create()
gmsg('\n{} wallet{} created'.format(dl,suf(dl)))
process_wallets()
except KeyboardInterrupt:
rdie(1,'\nUser interrupt\n')
except EOFError:

View file

@ -12,7 +12,7 @@ while getopts hinPt OPT
do
case "$OPT" in
h) printf " %-16s Test MMGen release\n" "${PROGNAME}:"
echo " USAGE: $PROGNAME [options] branch [tests]"
echo " USAGE: $PROGNAME [options] [branch] [tests]"
echo " OPTIONS: '-h' Print this help message"
echo " '-i' Install only; don't run tests"
echo " '-n' Don't install; test in place"
@ -50,10 +50,12 @@ shift $((OPTIND-1))
RED="\e[31;1m" GREEN="\e[32;1m" YELLOW="\e[33;1m" RESET="\e[0m"
BRANCH=$1; shift
BRANCHES=$(git branch)
FOUND_BRANCH=$(for b in ${BRANCHES/\*}; do [ "$b" == "$BRANCH" ] && echo ok; done)
[ "$FOUND_BRANCH" ] || { echo "Branch '$BRANCH' not found!"; exit; }
[ "$NO_INSTALL" ] || {
BRANCH=$1; shift
BRANCHES=$(git branch)
FOUND_BRANCH=$(for b in ${BRANCHES/\*}; do [ "$b" == "$BRANCH" ] && echo ok; done)
[ "$FOUND_BRANCH" ] || { echo "Branch '$BRANCH' not found!"; exit; }
}
set -e
@ -158,8 +160,10 @@ s_monero='Testing generation and wallet creation operations for Monero'
s_monero='The monerod (mainnet) daemon must be running for the following tests'
ROUNDS=1000
t_monero=(
'python cmds/mmgen-keygen --accept-defaults --outdir $TMPDIR --coin=xmr test/ref/98831F3A.mmwords 3,99,2,22-29,101-109'
'python cmds/mmgen-tool -q --accept-defaults --outdir $TMPDIR keyaddrlist2monerowallet $TMPDIR/988*XMR*akeys')
'python cmds/mmgen-keygen --accept-defaults --outdir $TMPDIR --coin=xmr test/ref/98831F3A.mmwords 3,99,2,22-24,101-104'
'python cmds/mmgen-tool -q --accept-defaults --outdir $TMPDIR keyaddrlist2monerowallets $TMPDIR/988*XMR*akeys'
'python cmds/mmgen-tool -q --outdir $TMPDIR syncmonerowallets $TMPDIR/988*XMR*akeys'
)
[ "$MINGW" ] && t_monero=("$t_monero")
f_monero='Monero tests completed'

View file

@ -145,7 +145,6 @@ setup(
'mmgen.main_txsign',
'mmgen.main_txsend',
'mmgen.main_txdo',
'mmgen.txcreate',
'mmgen.txsign',
'mmgen.main_tool',