Browse Source

Monero: new automated wallet syncing utility

- mmgen-tool: `syncmonerowallets`: batch-sync Monero wallets non-interactively
- mmgen-tool: `keyaddrlist2monerowallet` -> `keyaddrlist2monerowallets`
MMGen 7 years ago
parent
commit
258651a531
4 changed files with 107 additions and 65 deletions
  1. 3 0
      mmgen/main_tool.py
  2. 93 57
      mmgen/tool.py
  3. 11 7
      scripts/test-release.sh
  4. 0 1
      setup.py

+ 3 - 0
mmgen/main_tool.py

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

+ 93 - 57
mmgen/tool.py

@@ -94,7 +94,8 @@ cmd_data = OrderedDict([
 	('Decrypt',      ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
 	('Decrypt',      ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
 	('Bytespec',     ['<bytespec> [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):
 def usage(command):
@@ -475,10 +476,12 @@ def Rand2file(outfile,nbytes,threads=4,silent=False):
 
 
 def Bytespec(s): Msg(str(parse_nbytes(s)))
 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')
+
+def Keyaddrlist2monerowallets(infile,blockheight=None):
+	monero_wallet_ops(infile=infile,op='create',blockheight=blockheight)
 
 
-	if blockheight != None and int(blockheight) < 0: blockheight = 0
+def monero_wallet_ops(infile,op,blockheight=None):
 
 
 	def run_cmd(cmd):
 	def run_cmd(cmd):
 		import subprocess as sp
 		import subprocess as sp
@@ -495,15 +498,6 @@ def Keyaddrlist2monerowallet(infile,blockheight=None):
 			die(1,'Unable to connect to monerod!')
 			die(1,'Unable to connect to monerod!')
 		return int(ret[8:].split('/')[0])
 		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):
 	def my_expect(p,m,s,regex=False):
 		if m: msg_r('  {}...'.format(m))
 		if m: msg_r('  {}...'.format(m))
 		ret = (p.expect_exact,p.expect)[regex](s)
 		ret = (p.expect_exact,p.expect)[regex](s)
@@ -520,53 +514,95 @@ def Keyaddrlist2monerowallet(infile,blockheight=None):
 		if m: msg('OK')
 		if m: msg('OK')
 		vmsg("sendline: '{}' => {}".format(s,ret))
 		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(
 			fn = '{}{}-{}-MoneroWallet'.format(
 				(opt.outdir+'/' if opt.outdir else ''),
 				(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()
-
-	dl = len(al.data)
+				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()
+
 	try:
 	try:
-		create()
-		gmsg('\n{} wallet{} created'.format(dl,suf(dl)))
+		process_wallets()
 	except KeyboardInterrupt:
 	except KeyboardInterrupt:
 		rdie(1,'\nUser interrupt\n')
 		rdie(1,'\nUser interrupt\n')
 	except EOFError:
 	except EOFError:

+ 11 - 7
scripts/test-release.sh

@@ -12,7 +12,7 @@ while getopts hinPt OPT
 do
 do
 	case "$OPT" in
 	case "$OPT" in
 	h)  printf "  %-16s Test MMGen release\n" "${PROGNAME}:"
 	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   "  OPTIONS: '-h'  Print this help message"
 		echo   "           '-i'  Install only; don't run tests"
 		echo   "           '-i'  Install only; don't run tests"
 		echo   "           '-n'  Don't install; test in place"
 		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"
 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
 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'
 s_monero='The monerod (mainnet) daemon must be running for the following tests'
 ROUNDS=1000
 ROUNDS=1000
 t_monero=(
 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")
 [ "$MINGW" ] && t_monero=("$t_monero")
 f_monero='Monero tests completed'
 f_monero='Monero tests completed'
 
 

+ 0 - 1
setup.py

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