test.py: refactor, modularize, cleanup code, make fully OO

- test groups are now separate classes in separate modules
- test data and code is loaded on an as-needed basis
- new TestSuiteRunner and CmdGroupMgr classes
- simplified invocation: if arguments are omitted, all default tests relevant
  for given network and option are run.  The following set of invocations
  provides nearly complete coverage of MMGen's core functionality:

    test/test.py
    test/test.py --segwit-random
    test/test.py --bech32

    test/test.py --coin=ltc
    test/test.py --coin=ltc --segwit-random
    test/test.py --coin=ltc --bech32

    test/test.py --coin=bch
    test/test.py --coin=eth
    test/test.py --coin=etc
This commit is contained in:
The MMGen Project 2019-03-02 18:27:53 +00:00
commit 91410dd96c
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
24 changed files with 4953 additions and 4345 deletions

View file

@ -1,6 +1,7 @@
include README.md SIGNING_KEYS.pub LICENSE INSTALL include README.md SIGNING_KEYS.pub LICENSE INSTALL
include doc/wiki/using-mmgen/* include doc/wiki/using-mmgen/*
include test/*.py include test/*.py
include test/test_py_d/*.py
include test/ref/* include test/ref/*
include test/ref/litecoin/* include test/ref/litecoin/*
include test/ref/ethereum/* include test/ref/ethereum/*

View file

@ -171,7 +171,7 @@ i_alts='Gen-only altcoin'
s_alts='The following tests will test generation operations for all supported altcoins' s_alts='The following tests will test generation operations for all supported altcoins'
t_alts=( t_alts=(
"$scrambletest_py" "$scrambletest_py"
"$test_py ref_alt" "$test_py ref_altcoin"
"$gentest_py --coin=btc 2 $rounds" "$gentest_py --coin=btc 2 $rounds"
"$gentest_py --coin=btc --type=compressed 2 $rounds" "$gentest_py --coin=btc --type=compressed 2 $rounds"
"$gentest_py --coin=btc --type=segwit 2 $rounds" "$gentest_py --coin=btc --type=segwit 2 $rounds"
@ -247,9 +247,6 @@ f_monero='Monero tests completed'
i_eth='Ethereum' i_eth='Ethereum'
s_eth='Testing transaction and tracking wallet operations for Ethereum and Ethereum Classic' s_eth='Testing transaction and tracking wallet operations for Ethereum and Ethereum Classic'
t_eth=( t_eth=(
"$test_py --coin=eth ref_tx_chk"
"$test_py --coin=eth --testnet=1 ref_tx_chk"
"$test_py --coin=etc ref_tx_chk"
"$test_py --coin=eth ethdev" "$test_py --coin=eth ethdev"
"$test_py --coin=etc ethdev" "$test_py --coin=etc ethdev"
) )
@ -275,10 +272,10 @@ f_autosign_live='Autosign Live test complete'
i_btc='Bitcoin mainnet' i_btc='Bitcoin mainnet'
s_btc='The bitcoin (mainnet) daemon must both be running for the following tests' s_btc='The bitcoin (mainnet) daemon must both be running for the following tests'
t_btc=( t_btc=(
"$test_py" "$test_py --exclude regtest"
"$test_py --segwit dfl_wallet main ref ref_files" "$test_py --segwit"
"$test_py --segwit-random dfl_wallet main" "$test_py --segwit-random"
"$test_py --bech32 dfl_wallet main ref ref_files" "$test_py --bech32"
"$python scripts/compute-file-chksum.py $REFDIR/*testnet.rawtx >/dev/null 2>&1") "$python scripts/compute-file-chksum.py $REFDIR/*testnet.rawtx >/dev/null 2>&1")
f_btc='You may stop the bitcoin (mainnet) daemon if you wish' f_btc='You may stop the bitcoin (mainnet) daemon if you wish'
@ -286,9 +283,9 @@ i_btc_tn='Bitcoin testnet'
s_btc_tn='The bitcoin testnet daemon must both be running for the following tests' s_btc_tn='The bitcoin testnet daemon must both be running for the following tests'
t_btc_tn=( t_btc_tn=(
"$test_py --testnet=1" "$test_py --testnet=1"
"$test_py --testnet=1 --segwit dfl_wallet main ref ref_files" "$test_py --testnet=1 --segwit"
"$test_py --testnet=1 --segwit-random dfl_wallet main" "$test_py --testnet=1 --segwit-random"
"$test_py --testnet=1 --bech32 dfl_wallet main ref ref_files") "$test_py --testnet=1 --bech32")
f_btc_tn='You may stop the bitcoin testnet daemon if you wish' f_btc_tn='You may stop the bitcoin testnet daemon if you wish'
i_btc_rt='Bitcoin regtest' i_btc_rt='Bitcoin regtest'
@ -300,7 +297,7 @@ f_btc_rt='Regtest (Bob and Alice) mode tests for BTC completed'
i_bch='Bitcoin cash (BCH)' i_bch='Bitcoin cash (BCH)'
s_bch='The bitcoin cash daemon (Bitcoin ABC) must both be running for the following tests' s_bch='The bitcoin cash daemon (Bitcoin ABC) must both be running for the following tests'
t_bch=("$test_py --coin=bch dfl_wallet main ref ref_files") t_bch=("$test_py --coin=bch --exclude regtest")
f_bch='You may stop the Bitcoin ABC daemon if you wish' f_bch='You may stop the Bitcoin ABC daemon if you wish'
i_bch_rt='Bitcoin cash (BCH) regtest' i_bch_rt='Bitcoin cash (BCH) regtest'
@ -311,19 +308,19 @@ f_bch_rt='Regtest (Bob and Alice) mode tests for BCH completed'
i_ltc='Litecoin' i_ltc='Litecoin'
s_ltc='The litecoin daemon must both be running for the following tests' s_ltc='The litecoin daemon must both be running for the following tests'
t_ltc=( t_ltc=(
"$test_py --coin=ltc dfl_wallet main ref ref_files" "$test_py --coin=ltc --exclude regtest"
"$test_py --coin=ltc --segwit dfl_wallet main ref ref_files" "$test_py --coin=ltc --segwit"
"$test_py --coin=ltc --segwit-random dfl_wallet main" "$test_py --coin=ltc --segwit-random"
"$test_py --coin=ltc --bech32 dfl_wallet main ref ref_files") "$test_py --coin=ltc --bech32")
f_ltc='You may stop the litecoin daemon if you wish' f_ltc='You may stop the litecoin daemon if you wish'
i_ltc_tn='Litecoin testnet' i_ltc_tn='Litecoin testnet'
s_ltc_tn='The litecoin testnet daemon must both be running for the following tests' s_ltc_tn='The litecoin testnet daemon must both be running for the following tests'
t_ltc_tn=( t_ltc_tn=(
"$test_py --coin=ltc --testnet=1" "$test_py --coin=ltc --testnet=1 --exclude regtest"
"$test_py --coin=ltc --testnet=1 --segwit dfl_wallet main ref ref_files" "$test_py --coin=ltc --testnet=1 --segwit"
"$test_py --coin=ltc --testnet=1 --segwit-random dfl_wallet main" "$test_py --coin=ltc --testnet=1 --segwit-random"
"$test_py --coin=ltc --testnet=1 --bech32 dfl_wallet main ref ref_files") "$test_py --coin=ltc --testnet=1 --bech32")
f_ltc_tn='You may stop the litecoin testnet daemon if you wish' f_ltc_tn='You may stop the litecoin testnet daemon if you wish'
i_ltc_rt='Litecoin regtest' i_ltc_rt='Litecoin regtest'

View file

@ -138,7 +138,6 @@ setup(
'mmgen.seed', 'mmgen.seed',
'mmgen.sha256', 'mmgen.sha256',
'mmgen.term', 'mmgen.term',
'mmgen.test',
'mmgen.tool', 'mmgen.tool',
'mmgen.tw', 'mmgen.tw',
'mmgen.tx', 'mmgen.tx',

View file

@ -17,29 +17,16 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
""" """
test.py: Shared routines for the test suites common.py: Shared routines for the test suites
""" """
class TestSuiteException(Exception): pass
class TestSuiteFatalException(Exception): pass
import os import os
from binascii import hexlify from binascii import hexlify
from mmgen.common import * from mmgen.common import *
# Windows uses non-UTF8 encodings in filesystem, so use raw bytes here
def cleandir(d):
d_enc = d.encode()
try: files = os.listdir(d_enc)
except: return
from shutil import rmtree
gmsg("Cleaning directory '{}'".format(d))
for f in files:
try:
os.unlink(os.path.join(d_enc,f))
except:
rmtree(os.path.join(d_enc,f))
def getrandnum(n): return int(hexlify(os.urandom(n)),16) def getrandnum(n): return int(hexlify(os.urandom(n)),16)
def getrandhex(n): return hexlify(os.urandom(n)).decode() def getrandhex(n): return hexlify(os.urandom(n)).decode()
def getrandnum_range(nbytes,rn_max): def getrandnum_range(nbytes,rn_max):
@ -52,6 +39,21 @@ def getrandstr(num_chars,no_space=False):
if no_space: n,m = 94,33 if no_space: n,m = 94,33
return ''.join([chr(i%n+m) for i in list(os.urandom(num_chars))]) return ''.join([chr(i%n+m) for i in list(os.urandom(num_chars))])
# Windows uses non-UTF8 encodings in filesystem, so use raw bytes here
def cleandir(d,do_msg=False):
d_enc = d.encode()
try: files = os.listdir(d_enc)
except: return
from shutil import rmtree
if do_msg: gmsg("Cleaning directory '{}'".format(d))
for f in files:
try:
os.unlink(os.path.join(d_enc,f))
except:
rmtree(os.path.join(d_enc,f))
def mk_tmpdir(d): def mk_tmpdir(d):
try: os.mkdir(d,0o755) try: os.mkdir(d,0o755)
except OSError as e: except OSError as e:
@ -59,31 +61,33 @@ def mk_tmpdir(d):
else: else:
vmsg("Created directory '{}'".format(d)) vmsg("Created directory '{}'".format(d))
def mk_tmpdir_path(path,cfg): # def mk_tmpdir_path(path,cfg):
try: # try:
name = os.path.split(cfg['tmpdir'])[-1] # name = os.path.split(cfg['tmpdir'])[-1]
src = os.path.join(path,name) # src = os.path.join(path,name)
try: # try:
os.unlink(cfg['tmpdir']) # os.unlink(cfg['tmpdir'])
except OSError as e: # except OSError as e:
if e.errno != 2: raise # if e.errno != 2: raise
finally: # finally:
os.mkdir(src) # os.mkdir(src)
os.symlink(src,cfg['tmpdir']) # os.symlink(src,cfg['tmpdir'])
except OSError as e: # except OSError as e:
if e.errno != 17: raise # if e.errno != 17: raise
else: msg("Created directory '{}'".format(cfg['tmpdir'])) # else: msg("Created directory '{}'".format(cfg['tmpdir']))
def get_tmpfile_fn(cfg,fn): def get_tmpfile(cfg,fn):
return os.path.join(cfg['tmpdir'],fn) return os.path.join(cfg['tmpdir'],fn)
def write_to_tmpfile(cfg,fn,data,binary=False): def write_to_file(fn,data,binary=False):
write_data_to_file( write_data_to_file( fn,
os.path.join(cfg['tmpdir'],fn),
data, data,
silent=True, silent = True,
binary=binary, binary = binary,
ignore_opt_outdir=True) ignore_opt_outdir = True )
def write_to_tmpfile(cfg,fn,data,binary=False):
write_to_file( os.path.join(cfg['tmpdir'],fn), data=data, binary=binary )
def read_from_file(fn,binary=False): def read_from_file(fn,binary=False):
from mmgen.util import get_data_from_file from mmgen.util import get_data_from_file
@ -92,26 +96,20 @@ def read_from_file(fn,binary=False):
def read_from_tmpfile(cfg,fn,binary=False): def read_from_tmpfile(cfg,fn,binary=False):
return read_from_file(os.path.join(cfg['tmpdir'],fn),binary=binary) return read_from_file(os.path.join(cfg['tmpdir'],fn),binary=binary)
def joinpath(*args,**kwargs):
return os.path.join(*args,**kwargs)
def ok(): def ok():
if opt.profile: return if opt.profile: return
if opt.verbose or opt.exact_output: if opt.verbose or opt.exact_output:
gmsg('OK') gmsg('OK')
else: msg(' OK') else: msg(' OK')
def ok_or_die(val,chk_func,s,skip_ok=False): def cmp_or_die(s,t,desc=None):
try: ret = chk_func(val) if s != t:
except: ret = False m = 'ERROR: recoded data:\n{!r}\ndiffers from original data:\n{!r}'
if ret: if desc: m = 'For {}:\n{}'.format(desc,m)
if not skip_ok: ok() raise TestSuiteFatalException(m.format(t,s))
else:
rdie(3,"Returned value '{}' is not a {}".format((val,s)))
def cmp_or_die(s,t,skip_ok=False):
if s == t:
if not skip_ok: ok()
else:
m = 'ERROR: recoded data:\n{}\ndiffers from original data:\n{}'
rdie(3,m.format(repr(t),repr(s)))
def init_coverage(): def init_coverage():
coverdir = os.path.join('test','trace') coverdir = os.path.join('test','trace')

View file

@ -1,292 +0,0 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
test/mmgen_pexpect.py: pexpect implementation for MMGen test suites
"""
from mmgen.common import *
from mmgen.test import getrandstr,ok,init_coverage
try:
import pexpect
from pexpect.popen_spawn import PopenSpawn
except:
die(2,red('Pexpect module is missing. Cannnot run test suite'))
if opt.buf_keypress:
send_delay = 0.3
else:
send_delay = 0
os.environ['MMGEN_DISABLE_HOLD_PROTECT'] = '1'
def my_send(p,t,delay=send_delay,s=False):
if delay: time.sleep(delay)
ret = p.send(t) # returns num bytes written
if delay: time.sleep(delay)
if opt.verbose:
ls = (' ','')[bool(opt.debug or not s)]
es = (' ','')[bool(s)]
msg('{}SEND {}{}'.format(ls,es,yellow("'{}'".format(t.replace('\n',r'\n')))))
return ret
def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False,silent=False):
quo = ('',"'")[type(s) == str]
if not silent:
if opt.verbose: msg_r('EXPECT {}'.format(yellow(quo+str(s)+quo)))
elif not opt.exact_output: msg_r('+')
try:
if s == '': ret = 0
else:
f = (p.expect_exact,p.expect)[bool(regex)]
ret = f(s,timeout=(60,5)[bool(opt.debug_pexpect)])
except pexpect.TIMEOUT:
if opt.debug_pexpect: raise
rdie(1,red('\nERROR. Expect {}{}{} timed out. Exiting'.format(quo,s,quo)))
debug_pexpect_msg(p)
if opt.verbose and type(s) != str:
msg_r(' ==> {} '.format(ret))
if ret == -1:
rdie(1,'Error. Expect returned {}'.format(ret))
else:
if t == '':
if not nonl and not silent: vmsg('')
else:
my_send(p,t,delay,s)
return ret
def debug_pexpect_msg(p):
if opt.debug_pexpect:
msg('\n{}{}{}'.format(red('BEFORE ['),p.before,red(']')))
msg('{}{}{}'.format(red('MATCH ['),p.after,red(']')))
data_dir = os.path.join('test','data_dir'+('','')[bool(os.getenv('MMGEN_DEBUG_UTF8'))])
class MMGenPexpect(object):
NL = '\r\n'
if g.platform == 'linux' and opt.popen_spawn:
import atexit
atexit.register(lambda: os.system('stty sane'))
NL = '\n'
def __init__(self,name,mmgen_cmd,cmd_args,desc,
no_output=False,passthru_args=[],msg_only=False,no_msg=False,log_fd=None):
cmd_args = ['--{}{}'.format(k.replace('_','-'),
'='+getattr(opt,k) if getattr(opt,k) != True else ''
) for k in passthru_args if getattr(opt,k)] \
+ ['--data-dir='+data_dir] + cmd_args
if g.platform == 'win': cmd,args = 'python3',[mmgen_cmd]+cmd_args
else: cmd,args = mmgen_cmd,cmd_args
for i in args:
if type(i) is not str:
m1 = 'Error: missing input files in cmd line?:'
m2 = '\nName: {}\nCmd: {}\nCmd args: {}'
die(2,(m1+m2).format(name,cmd,args))
# if opt.popen_spawn:
if True:
args = ['{q}{}{q}'.format(a,q="'" if ' ' in a else '') for a in args]
cmd_str = '{} {}'.format(cmd,' '.join(args)).replace('\\','/')
if opt.coverage:
fs = 'python3 -m trace --count --coverdir={} --file={} {c}'
cmd_str = fs.format(*init_coverage(),c=cmd_str)
if opt.log:
log_fd.write(cmd_str.encode()+'\n')
if not no_msg:
if opt.verbose or opt.print_cmdline or opt.exact_output:
clr1,clr2,eol = ((green,cyan,'\n'),(nocolor,nocolor,' '))[bool(opt.print_cmdline)]
msg_r(green('Testing: {}\n'.format(desc)))
if not msg_only:
s = repr(cmd_str) if g.platform == 'win' else cmd_str
msg_r(clr1('Executing {}{}'.format(clr2(s),eol)))
else:
m = 'Testing {}: '.format(desc)
msg_r(m)
if msg_only: return
if opt.direct_exec:
msg('')
from subprocess import call,check_output
f = (call,check_output)[bool(no_output)]
ret = f([cmd] + args)
if f == call and ret != 0:
die(1,red('ERROR: process returned a non-zero exit status ({})'.format(ret)))
else:
if opt.traceback:
tc = 'scripts/traceback_run.py'
cmd,args = tc,[cmd]+args
cmd_str = tc + ' ' + cmd_str
# Msg('\ncmd_str: {}'.format(cmd_str))
if opt.popen_spawn:
# NOTE: the following is outdated for Python 3
# PopenSpawn() requires cmd string to be bytes. However, it autoconverts unicode
# input to bytes, though this behavior seems to be undocumented. Setting 'encoding'
# to 'UTF-8' will cause pexpect to reject non-unicode string input.
self.p = PopenSpawn(cmd_str,encoding='utf8')
else:
self.p = pexpect.spawn(cmd_str,encoding='utf8')
self.p.delaybeforesend = 0
if opt.exact_output: self.p.logfile = sys.stdout
def do_decrypt_ka_data(self,hp,pw,desc='key-address data',check=True):
self.hash_preset(desc,hp)
self.passphrase(desc,pw)
self.expect('Check key-to-address validity? (y/N): ',('n','y')[check])
def view_tx(self,view):
self.expect('View.* transaction.*\? .*: ',view,regex=True)
if view not in 'n\n':
self.expect('to continue: ','\n')
def do_comment(self,add_comment,has_label=False):
p = ('Add a comment to transaction','Edit transaction comment')[has_label]
self.expect('{}? (y/N): '.format(p),('n','y')[bool(add_comment)])
if add_comment:
self.expect('Comment: ',add_comment+'\n')
def ok(self,exit_val=0):
ret = self.p.wait()
if ret not in (exit_val,None) and not opt.coverage: # Some cmds exit with None
die(1,red('test.py: spawned program exited with value {}'.format(ret)))
if opt.profile: return
if opt.verbose or opt.exact_output:
sys.stderr.write(green('OK\n'))
else: msg(' OK')
def cmp_or_die(self,s,t,skip_ok=False,exit_val=0):
ret = self.p.wait()
if ret != exit_val:
rdie(1,'test.py: spawned program exited with value {}'.format(ret))
if s == t:
if not skip_ok: ok()
else:
fs = 'ERROR: recoded data:\n{}\ndiffers from original data:\n{}'
rdie(3,fs.format(repr(t),repr(s)))
def license(self):
if 'MMGEN_NO_LICENSE' in os.environ: return
p = "'w' for conditions and warranty info, or 'c' to continue: "
my_expect(self.p,p,'c')
def label(self,label='Test Label (UTF-8) α'):
p = 'Enter a wallet label, or hit ENTER for no label: '
my_expect(self.p,p,label+'\n')
def usr_rand_out(self,saved=False):
fs = 'Generating encryption key from OS random data plus {}user-supplied entropy'
my_expect(self.p,fs.format(('','saved ')[saved]))
def usr_rand(self,num_chars):
if opt.usr_random:
self.interactive()
my_send(self.p,'\n')
else:
rand_chars = list(getrandstr(num_chars,no_space=True))
vmsg_r('SEND ')
while rand_chars:
ch = rand_chars.pop(0)
msg_r(yellow(ch)+' ' if opt.verbose else '+')
ret = my_expect(self.p,'left: ',ch,delay=0.005)
my_expect(self.p,'ENTER to continue: ','\n')
def passphrase_new(self,desc,passphrase):
my_expect(self.p,'Enter passphrase for {}: '.format(desc),passphrase+'\n')
my_expect(self.p,'Repeat passphrase: ',passphrase+'\n')
def passphrase(self,desc,passphrase,pwtype=''):
if pwtype: pwtype += ' '
my_expect(self.p,
'Enter {}passphrase for {}.*?: '.format(pwtype,desc),
passphrase+'\n',regex=True)
def hash_preset(self,desc,preset=''):
my_expect(self.p,'Enter hash preset for {}'.format(desc))
my_expect(self.p,'or hit ENTER .*?:',str(preset)+'\n',regex=True)
def written_to_file(self,desc,overwrite_unlikely=False,query='Overwrite? ',oo=False):
s1 = '{} written to file '.format(desc)
s2 = query + "Type uppercase 'YES' to confirm: "
ret = my_expect(self.p,([s1,s2],s1)[overwrite_unlikely])
if ret == 1:
my_send(self.p,'YES\n')
# if oo:
outfile = self.expect_getend("Overwriting file '").rstrip("'")
return outfile
# else:
# ret = my_expect(self.p,s1)
self.expect(self.NL,nonl=True)
outfile = self.p.before.strip().strip("'")
if opt.debug_pexpect: rmsg('Outfile [{}]'.format(outfile))
vmsg('{} file: {}'.format(desc,cyan(outfile.replace("'",''))))
return outfile
def no_overwrite(self):
self.expect("Overwrite? Type uppercase 'YES' to confirm: ",'\n')
self.expect('Exiting at user request')
def expect_getend(self,s,regex=False):
ret = self.expect(s,regex=regex,nonl=True)
debug_pexpect_msg(self.p)
# end = self.readline().strip()
# readline() of partial lines doesn't work with PopenSpawn, so do this instead:
self.expect(self.NL,nonl=True,silent=True)
debug_pexpect_msg(self.p)
end = self.p.before
if not g.debug:
vmsg(' ==> {}'.format(cyan(end)))
return end
def interactive(self):
return self.p.interact() # interact() not available with popen_spawn
def kill(self,signal):
return self.p.kill(signal)
def logfile(self,arg):
self.p.logfile = arg
def expect(self,*args,**kwargs):
return my_expect(self.p,*args,**kwargs)
def send(self,*args,**kwargs):
return my_send(self.p,*args,**kwargs)
# def readline(self):
# return self.p.readline()
# def readlines(self):
# return [l.rstrip()+'\n' for l in self.p.readlines()]
def read(self,n=None):
if n: return self.p.read(n)
else: return self.p.read()
def close(self):
if not opt.popen_spawn:
self.p.close()

217
test/pexpect.py Executable file
View file

@ -0,0 +1,217 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
test/pexpect.py: pexpect implementation for MMGen test suites
"""
import sys,os,time
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.util import msg,msg_r,vmsg,vmsg_r,rmsg,red,yellow,green,cyan,die,rdie
from test.common import getrandstr
try:
import pexpect
from pexpect.popen_spawn import PopenSpawn
except:
die(2,red('Pexpect module is missing. Cannnot run test suite'))
def debug_pexpect_msg(p):
if opt.debug_pexpect:
msg('\n{}{}{}'.format(red('BEFORE ['),p.before,red(']')))
msg('{}{}{}'.format(red('MATCH ['),p.after,red(']')))
if g.platform == 'linux' and not opt.pexpect_spawn:
import atexit
atexit.register(lambda: os.system('stty sane'))
NL = '\n'
else:
NL = '\r\n'
class MMGenPexpect(object):
def __init__(self,args,no_output=False):
if opt.direct_exec:
msg('')
from subprocess import call,check_output
f = (call,check_output)[bool(no_output)]
ret = f([args[0]] + args[1:])
if f == call and ret != 0:
die(1,red('ERROR: process returned a non-zero exit status ({})'.format(ret)))
else:
if opt.pexpect_spawn:
self.p = pexpect.spawn(args[0],args[1:],encoding='utf8')
self.p.delaybeforesend = 0
else:
self.p = PopenSpawn(args,encoding='utf8')
# self.p.delaybeforesend = 0 # TODO: try this here too
if opt.exact_output: self.p.logfile = sys.stdout
self.req_exit_val = 0
self.skip_ok = False
def do_decrypt_ka_data(self,hp,pw,desc='key-address data',check=True):
self.hash_preset(desc,hp)
self.passphrase(desc,pw)
self.expect('Check key-to-address validity? (y/N): ',('n','y')[check])
def view_tx(self,view):
self.expect('View.* transaction.*\? .*: ',view,regex=True)
if view not in 'n\n':
self.expect('to continue: ','\n')
def do_comment(self,add_comment,has_label=False):
p = ('Add a comment to transaction','Edit transaction comment')[has_label]
self.expect('{}? (y/N): '.format(p),('n','y')[bool(add_comment)])
if add_comment:
self.expect('Comment: ',add_comment+'\n')
def ok(self):
ret = self.p.wait()
if ret != self.req_exit_val and not opt.coverage:
die(1,red('test.py: spawned program exited with value {}'.format(ret)))
if opt.profile: return
if not self.skip_ok:
sys.stderr.write(green('OK\n') if opt.exact_output or opt.verbose else (' OK\n'))
return self
def license(self):
if 'MMGEN_NO_LICENSE' in os.environ: return
self.expect("'w' for conditions and warranty info, or 'c' to continue: ",'c')
def label(self,label='Test Label (UTF-8) α'):
self.expect('Enter a wallet label, or hit ENTER for no label: ',label+'\n')
def usr_rand_out(self,saved=False):
fs = 'Generating encryption key from OS random data plus {}user-supplied entropy'
self.expect(fs.format(('','saved ')[saved]))
def usr_rand(self,num_chars):
if opt.usr_random:
self.interactive()
self.send('\n')
else:
rand_chars = list(getrandstr(num_chars,no_space=True))
vmsg_r('SEND ')
while rand_chars:
ch = rand_chars.pop(0)
msg_r(yellow(ch)+' ' if opt.verbose else '+')
ret = self.expect('left: ',ch,delay=0.005)
self.expect('ENTER to continue: ','\n')
def passphrase_new(self,desc,passphrase):
self.expect('Enter passphrase for {}: '.format(desc),passphrase+'\n')
self.expect('Repeat passphrase: ',passphrase+'\n')
def passphrase(self,desc,passphrase,pwtype=''):
if pwtype: pwtype += ' '
self.expect('Enter {}passphrase for {}.*?: '.format(pwtype,desc),passphrase+'\n',regex=True)
def hash_preset(self,desc,preset=''):
self.expect('Enter hash preset for {}'.format(desc))
self.expect('or hit ENTER .*?:',str(preset)+'\n',regex=True)
def written_to_file(self,desc,overwrite_unlikely=False,query='Overwrite? ',oo=False):
s1 = '{} written to file '.format(desc)
s2 = query + "Type uppercase 'YES' to confirm: "
ret = self.expect(([s1,s2],s1)[overwrite_unlikely])
if ret == 1:
self.send('YES\n')
return self.expect_getend("Overwriting file '").rstrip("'")
self.expect(NL,nonl=True)
outfile = self.p.before.strip().strip("'")
if opt.debug_pexpect:
rmsg('Outfile [{}]'.format(outfile))
vmsg('{} file: {}'.format(desc,cyan(outfile.replace("'",''))))
return outfile
def no_overwrite(self):
self.expect("Overwrite? Type uppercase 'YES' to confirm: ",'\n')
self.expect('Exiting at user request')
def expect_getend(self,s,regex=False):
ret = self.expect(s,regex=regex,nonl=True)
debug_pexpect_msg(self.p)
# readline() of partial lines doesn't work with PopenSpawn, so do this instead:
self.expect(NL,nonl=True,silent=True)
debug_pexpect_msg(self.p)
end = self.p.before
if not g.debug:
vmsg(' ==> {}'.format(cyan(end)))
return end
def interactive(self):
return self.p.interact() # interact() not available with popen_spawn
def kill(self,signal):
return self.p.kill(signal)
def expect(self,s,t='',delay=None,regex=False,nonl=False,silent=False):
delay = delay or (0,0.3)[bool(opt.buf_keypress)]
if not silent:
if opt.verbose:
quo = ('',"'")[type(s) == str]
msg_r('EXPECT {}'.format(yellow(quo+str(s)+quo)))
elif not opt.exact_output: msg_r('+')
try:
if s == '':
ret = 0
else:
f = (self.p.expect_exact,self.p.expect)[bool(regex)]
ret = f(s,timeout=(60,5)[bool(opt.debug_pexpect)])
except pexpect.TIMEOUT:
if opt.debug_pexpect: raise
quo = ('',"'")[type(s) == str]
rdie(1,red('\nERROR. Expect {}{}{} timed out. Exiting'.format(quo,s,quo)))
debug_pexpect_msg(self.p)
if opt.verbose and type(s) != str:
msg_r(' ==> {} '.format(ret))
if ret == -1:
rdie(1,'Error. Expect returned {}'.format(ret))
else:
if t == '':
if not nonl and not silent: vmsg('')
else:
self.send(t,delay,s)
return ret
def send(self,t,delay=None,s=False):
delay = delay or (0,0.3)[bool(opt.buf_keypress)]
if delay: time.sleep(delay)
ret = self.p.send(t) # returns num bytes written
if delay: time.sleep(delay)
if opt.verbose:
ls = (' ','')[bool(opt.debug or not s)]
es = (' ','')[bool(s)]
msg('{}SEND {}{}'.format(ls,es,yellow("'{}'".format(t.replace('\n',r'\n')))))
return ret
def read(self,n=-1):
return self.p.read(n)
def close(self):
if opt.pexpect_spawn:
self.p.close()

View file

@ -28,7 +28,7 @@ os.environ['MMGEN_TEST_SUITE'] = '1'
# Import this _after_ local path's been added to sys.path # Import this _after_ local path's been added to sys.path
from mmgen.common import * from mmgen.common import *
from mmgen.test import init_coverage from test.common import init_coverage
opts_data = lambda: { opts_data = lambda: {
'desc': 'Test seed scrambling and addrlist data generation for all supported altcoins', 'desc': 'Test seed scrambling and addrlist data generation for all supported altcoins',

File diff suppressed because it is too large Load diff

0
test/test_py_d/___init__.py Executable file
View file

174
test/test_py_d/common.py Executable file
View file

@ -0,0 +1,174 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
common.py: Shared routines and data for the test.py test suite
"""
import os,subprocess
from mmgen.common import *
log_file = 'test.py.log'
rt_pw = 'abc-α'
ref_wallet_brainpass = 'abc'
ref_wallet_hash_preset = '1'
ref_wallet_incog_offset = 123
dfl_seed_id = '98831F3A'
dfl_addr_idx_list = '1010,500-501,31-33,1,33,500,1011'
dfl_wpasswd = 'reference password'
pwfile = 'passwd_file'
hincog_fn = 'rand_data'
hincog_bytes = 1024*1024
hincog_offset = 98765
hincog_seedlen = 256
incog_id_fn = 'incog_id'
non_mmgen_fn = 'coinkey'
ref_dir = os.path.join('test','ref')
dfl_words_file = os.path.join(ref_dir,'98831F3A.mmwords')
from mmgen.obj import MMGenTXLabel
ref_tx_label_jp = '必要なのは、信用ではなく暗号化された証明に基づく電子取引システムであり、これにより希望する二者が信用できる第三者機関を介さずに直接取引できるよう' # 72 chars ('W'ide)
ref_tx_label_zh = '所以,我們非常需要這樣一種電子支付系統,它基於密碼學原理而不基於信用,使得任何達成一致的雙方,能夠直接進行支付,從而不需要協力廠商仲介的參與。。' # 72 chars ('F'ull + 'W'ide)
ref_tx_label_lat_cyr_gr = ''.join(map(chr,
list(range(65,91)) +
list(range(1040,1072)) + # cyrillic
list(range(913,939)) + # greek
list(range(97,123))))[:MMGenTXLabel.max_len] # 72 chars
utf8_label = ref_tx_label_zh[:40]
utf8_label_pat = utf8_label
ref_bw_hash_preset = '1'
ref_bw_file = 'wallet.mmbrain'
ref_bw_file_spc = 'wallet-spaced.mmbrain'
ref_kafile_pass = 'kafile password'
ref_kafile_hash_preset = '1'
ref_enc_fn = 'sample-text.mmenc'
tool_enc_passwd = "Scrypt it, don't hash it!"
sample_text = 'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks\n'
chksum_pat = r'\b[A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4} [A-F0-9]{4}\b'
def ok_msg():
if opt.profile: return
os.write(2,green('\nOK\n').encode() if opt.exact_output or opt.verbose else b' OK\n')
def skip(name,reason=None):
msg('Skipping {}{}'.format(name,' ({})'.format(reason) if reason else ''))
return 'skip'
def confirm_continue():
if keypress_confirm(blue('Continue? (Y/n): '),default_yes=True,complete_prompt=True):
if opt.verbose or opt.exact_output: sys.stderr.write('\n')
else:
raise KeyboardInterrupt('Exiting at user request')
def omsg(s):
os.write(2,s.encode() + b'\n')
def omsg_r(s):
os.write(2,s.encode())
def imsg(s):
if opt.exact_output or opt.verbose: omsg(s)
def imsg_r(s):
if opt.exact_output or opt.verbose: omsg_r(s)
def iqmsg(s):
if not opt.quiet: omsg(s)
def iqmsg_r(s):
if not opt.quiet: omsg_r(s)
devnull_fh = open('/dev/null','w')
def silence():
if not (opt.verbose or opt.exact_output):
g.stderr_fileno = g.stdout_fileno = devnull_fh.fileno()
def end_silence():
if not (opt.verbose or opt.exact_output):
g.stderr_fileno = 2
g.stdout_fileno = 1
def randbool():
return hexlify(os.urandom(1))[1] in b'12345678'
def disable_debug():
global save_debug
save_debug = {}
for k in g.env_opts:
if k[:11] == 'MMGEN_DEBUG':
save_debug[k] = os.getenv(k)
os.environ[k] = ''
def restore_debug():
for k in save_debug:
os.environ[k] = save_debug[k] or ''
def get_file_with_ext(tdir,ext,delete=True,no_dot=False,return_list=False,delete_all=False):
dot = ('.','')[bool(no_dot)]
flist = [os.path.join(tdir,f) for f in os.listdir(tdir) if f == ext or f[-len(dot+ext):] == dot+ext]
if not flist: return False
if return_list: return flist
if len(flist) > 1 or delete_all:
if delete or delete_all:
if not opt.quiet:
msg("Multiple *.{} files in '{}' - deleting".format(ext,tdir))
for f in flist:
os.unlink(f)
return False
else:
return flist[0]
labels = [
"Automotive",
"Travel expenses",
"Healthcare",
ref_tx_label_jp[:40],
ref_tx_label_zh[:40],
"Alice's allowance",
"Bob's bequest",
"House purchase",
"Real estate fund",
"Job 1",
"XYZ Corp.",
"Eddie's endowment",
"Emergency fund",
"Real estate fund",
"Ian's inheritance",
"",
"Rainy day",
"Fred's funds",
"Job 2",
"Carl's capital",
]
def get_label(do_shuffle=False):
from random import shuffle
global label_iter
try:
return next(label_iter)
except:
if do_shuffle: shuffle(labels)
label_iter = iter(labels)
return next(label_iter)

236
test/test_py_d/ts_autosign.py Executable file
View file

@ -0,0 +1,236 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_autosign.py: Autosign tests for the test.py test suite
"""
import os,shutil
from mmgen.globalvars import g
from mmgen.opts import opt
from test.common import read_from_file
from test.test_py_d.common import *
from test.test_py_d.ts_base import *
from test.test_py_d.ts_shared import *
class TestSuiteAutosign(TestSuiteBase):
'autosigning with BTC, BCH, LTC, ETH and ETC'
networks = ('btc',)
tmpdir_nums = [18]
cmd_group = (
('autosign', 'transaction autosigning (BTC,BCH,LTC,ETH,ETC)'),
)
def autosign_live(self):
return self.autosign_minimal(live=True)
def autosign_minimal(self,live=False):
return self.autosign(
coins=['btc','eth'],
txfiles=['btc','eth','mm1','etc'],
txcount=7,
live=live)
# tests everything except device detection, mount/unmount
def autosign( self,
coins=['btc','bch','ltc','eth'],
txfiles=['btc','bch','ltc','eth','mm1','etc'],
txcount=11,
live=False):
if self.skip_for_win(): return
def make_wallet(opts):
t = self.spawn('mmgen-autosign',opts+['gen_key'],extra_desc='(gen_key)')
t.expect_getend('Wrote key file ')
t.ok()
t = self.spawn('mmgen-autosign',opts+['setup'],extra_desc='(setup)')
t.expect('words: ','3')
t.expect('OK? (Y/n): ','\n')
mn_file = dfl_words_file
mn = read_from_file(mn_file).strip().split()
mn = ['foo'] + mn[:5] + ['realiz','realized'] + mn[5:]
wnum = 1
max_wordlen = 12
def get_pad_chars(n):
ret = ''
for i in range(n):
m = int(hexlify(os.urandom(1)),16) % 32
ret += r'123579!@#$%^&*()_+-=[]{}"?/,.<>|'[m]
return ret
for i in range(len(mn)):
w = mn[i]
if len(w) > 5:
w = w + '\n'
else:
w = get_pad_chars(3 if randbool() else 0) + w[0] + get_pad_chars(3) + w[1:] + get_pad_chars(7)
w = w[:max_wordlen+1]
em,rm = 'Enter word #{}: ','Repeat word #{}: '
ret = t.expect((em.format(wnum),rm.format(wnum-1)))
if ret == 0: wnum += 1
for j in range(len(w)):
t.send(w[j])
time.sleep(0.005)
wf = t.written_to_file('Autosign wallet')
t.ok()
def copy_files(mountpoint,remove_signed_only=False,include_bad_tx=True):
fdata_in = (('btc',''),
('bch',''),
('ltc','litecoin'),
('eth','ethereum'),
('mm1','ethereum'),
('etc','ethereum_classic'))
fdata = [e for e in fdata_in if e[0] in txfiles]
from test.test_py_d.ts_ref import TestSuiteRef
tfns = [TestSuiteRef.sources['ref_tx_file'][c][1] for c,d in fdata] + \
[TestSuiteRef.sources['ref_tx_file'][c][0] for c,d in fdata]
tfs = [joinpath(ref_dir,d[1],fn) for d,fn in zip(fdata+fdata,tfns)]
for f,fn in zip(tfs,tfns):
if fn: # use empty fn to skip file
target = joinpath(mountpoint,'tx',fn)
remove_signed_only or shutil.copyfile(f,target)
try: os.unlink(target.replace('.rawtx','.sigtx'))
except: pass
# make a bad tx file
bad_tx = joinpath(mountpoint,'tx','bad.rawtx')
if include_bad_tx and not remove_signed_only:
with open(bad_tx,'w') as f:
f.write('bad tx data')
if not include_bad_tx:
try: os.unlink(bad_tx)
except: pass
def do_autosign_live(opts,mountpoint,led_opts=[],gen_wallet=True):
def do_mount():
try: subprocess.check_call(['mount',mountpoint])
except: pass
def do_unmount():
try: subprocess.check_call(['umount',mountpoint])
except: pass
omsg_r(blue('\nRemove removable device and then hit ENTER '))
input()
if gen_wallet: make_wallet(opts)
else: do_mount()
copy_files(mountpoint,include_bad_tx=not led_opts)
desc = '(sign)'
m1 = "Running 'mmgen-autosign wait'"
m2 = 'Insert removable device '
if led_opts:
if led_opts == ['--led']:
m1 = "Running 'mmgen-autosign wait' with --led. The LED should start blinking slowly now"
elif led_opts == ['--stealth-led']:
m1 = "Running 'mmgen-autosign wait' with --stealth-led. You should see no LED activity now"
m2 = 'Insert removable device and watch for fast LED activity during signing'
desc = '(sign - {})'.format(led_opts[0])
def do_loop():
omsg(blue(m2))
t.expect('{} transactions signed'.format(txcount))
if not led_opts:
t.expect('1 transaction failed to sign')
t.expect('Waiting')
do_unmount()
omsg(green(m1))
t = self.spawn('mmgen-autosign',opts+led_opts+['wait'],extra_desc=desc)
if not opt.exact_output: omsg('')
do_loop()
do_mount() # race condition due to device insertion detection
copy_files(mountpoint,remove_signed_only=True,include_bad_tx=not led_opts)
do_unmount()
do_loop()
t.kill(2) # 2 = SIGINT
t.req_exit_val = 1
return t
def do_autosign(opts,mountpoint):
make_wallet(opts)
copy_files(mountpoint,include_bad_tx=True)
t = self.spawn('mmgen-autosign',opts+['wait'],extra_desc='(sign)')
t.expect('{} transactions signed'.format(txcount))
t.expect('1 transaction failed to sign')
t.expect('Waiting')
t.kill(2)
t.req_exit_val = 1
return t
if live:
mountpoint = '/mnt/tx'
if not os.path.ismount(mountpoint):
try:
subprocess.check_call(['mount',mountpoint])
imsg("Mounted '{}'".format(mountpoint))
except:
ydie(1,"Could not mount '{}'! Exiting".format(mountpoint))
txdir = joinpath(mountpoint,'tx')
if not os.path.isdir(txdir):
ydie(1,"Directory '{}' does not exist! Exiting".format(mountpoint))
opts = ['--coins='+','.join(coins)]
led_files = { 'opi': ('/sys/class/leds/orangepi:red:status/brightness',),
'rpi': ('/sys/class/leds/led0/brightness','/sys/class/leds/led0/trigger') }
for k in ('opi','rpi'):
if os.path.exists(led_files[k][0]):
led_support = k
break
else:
led_support = None
if led_support:
for fn in (led_files[led_support]):
subprocess.check_call(['sudo','chmod','0666',fn])
omsg(purple('Running autosign test with no LED'))
do_autosign_live(opts,mountpoint)
omsg(purple("Running autosign test with '--led'"))
do_autosign_live(opts,mountpoint,led_opts=['--led'],gen_wallet=False)
omsg(purple("Running autosign test with '--stealth-led'"))
return do_autosign_live(opts,mountpoint,led_opts=['--stealth-led'],gen_wallet=False)
else:
return do_autosign_live(opts,mountpoint)
else:
mountpoint = self.tmpdir
opts = ['--no-insert-check','--mountpoint='+mountpoint,'--coins='+','.join(coins)]
try: os.mkdir(joinpath(mountpoint,'tx'))
except: pass
return do_autosign(opts,mountpoint)
class TestSuiteAutosignMinimal(TestSuiteAutosign):
'autosigning with BTC, ETH and ETC'
cmd_group = (
('autosign_minimal', 'transaction autosigning (BTC,ETH,ETC)'),
)
class TestSuiteAutosignLive(TestSuiteAutosignMinimal):
'live autosigning operations'
cmd_group = (
('autosign_live', 'transaction autosigning (BTC,ETH,ETC - test device insertion/removal + LED)'),
)

73
test/test_py_d/ts_base.py Executable file
View file

@ -0,0 +1,73 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_base.py: Base class for the test.py test suite
"""
import os
from mmgen.globalvars import g
from mmgen.opts import opt
from test.common import *
from test.test_py_d.common import *
class TestSuiteBase(object):
'initializer class for the test.py test suite'
passthru_opts = ()
networks = ()
segwit_opts_ok = False
def __init__(self,trunner,cfgs,spawn):
self.tr = trunner
self.cfgs = cfgs
self.spawn = spawn
self.have_dfl_wallet = False
self.usr_rand_chars = (5,30)[bool(opt.usr_random)]
self.usr_rand_arg = '-r{}'.format(self.usr_rand_chars)
self.altcoin_pfx = '' if g.proto.base_coin == 'BTC' else '-'+g.proto.base_coin
self.tn_ext = ('','.testnet')[g.testnet]
d = {'bch':'btc','btc':'btc','ltc':'ltc'}
self.fork = d[g.coin.lower()] if g.coin.lower() in d else None
@property
def tmpdir(self):
return os.path.join('test','tmp{}{}'.format(self.tmpdir_num,'' if g.debug_utf8 else ''))
@property
def segwit_mmtype(self):
return ('segwit','bech32')[bool(opt.bech32)] if self.segwit else None
@property
def segwit_arg(self):
return ['--type=' + self.segwit_mmtype] if self.segwit_mmtype else []
def get_file_with_ext(self,ext,**kwargs):
return get_file_with_ext(self.tmpdir,ext,**kwargs)
def read_from_tmpfile(self,fn,binary=False):
return read_from_file(os.path.join(self.tmpdir,fn),binary=binary)
def write_to_tmpfile(self,fn,data,binary=False):
return write_to_file(os.path.join(self.tmpdir,fn),data,binary=binary)
def skip_for_win(self):
if g.platform == 'win':
msg("Skipping test '{}': not supported on MinGW platform".format(self.test_name))
return True
else:
return False

143
test/test_py_d/ts_chainsplit.py Executable file
View file

@ -0,0 +1,143 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_chainsplit.py: Forking scenario tests for the test.py test suite
This module is currently non-functional
"""
import os
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.util import die
from test.common import *
from test.test_py_d.common import *
from test.test_py_d.ts_base import *
from test.test_py_d.ts_shared import *
from test.test_py_d.ts_regtest import *
class TestSuiteChainsplit(TestSuiteRegtest):
'forking scenario tests for the test.py test suite'
cmd_group = (
('split_setup', 'regtest forking scenario setup'),
('walletgen_bob', "generating Bob's wallet"),
('addrgen_bob', "generating Bob's addresses"),
('addrimport_bob', "importing Bob's addresses"),
('fund_bob', "funding Bob's wallet"),
('split_fork', 'regtest split fork'),
('split_start_btc', 'start regtest daemon (BTC)'),
('split_start_b2x', 'start regtest daemon (B2X)'),
('split_gen_btc', 'mining a block (BTC)'),
('split_gen_b2x', 'mining 100 blocks (B2X)'),
('split_do_split', 'creating coin splitting transactions'),
('split_sign_b2x', 'signing B2X split transaction'),
('split_sign_btc', 'signing BTC split transaction'),
('split_send_b2x', 'sending B2X split transaction'),
('split_send_btc', 'sending BTC split transaction'),
('split_gen_btc', 'mining a block (BTC)'),
('split_gen_b2x2', 'mining a block (B2X)'),
('split_txdo_timelock_bad_btc', 'sending transaction with bad locktime (BTC)'),
('split_txdo_timelock_good_btc','sending transaction with good locktime (BTC)'),
('split_txdo_timelock_bad_b2x', 'sending transaction with bad locktime (B2X)'),
('split_txdo_timelock_good_b2x','sending transaction with good locktime (B2X)'),
)
def split_setup(self):
if g.coin != 'BTC': die(1,'Test valid only for coin BTC')
opt.coin = 'BTC'
return self.setup()
def split_fork(self):
opt.coin = 'B2X'
t = self.spawn('mmgen-regtest',['fork','btc'])
t.expect('Creating fork from coin')
t.expect('successfully created')
t.ok()
def split_start(self,coin):
opt.coin = coin
t = self.spawn('mmgen-regtest',['bob'])
t.expect('Starting')
t.expect('done')
t.ok()
def split_start_btc(self): self.regtest_start(coin='BTC')
def split_start_b2x(self): self.regtest_start(coin='B2X')
def split_gen_btc(self): self.regtest_generate(coin='BTC')
def split_gen_b2x(self): self.regtest_generate(coin='B2X',num_blocks=100)
def split_gen_b2x2(self): self.regtest_generate(coin='B2X')
def split_do_split(self):
opt.coin = 'B2X'
sid = self.regtest_user_sid('bob')
t = self.spawn('mmgen-split',[
'--bob',
'--outdir='+self.tmpdir,
'--tx-fees=0.0001,0.0003',
sid+':S:1',sid+':S:2'])
t.expect(r'\[q\]uit view, .*?:.','q', regex=True)
t.expect('outputs to spend: ','1\n')
for tx in ('timelocked','split'):
for q in ('fee','change'): t.expect('OK? (Y/n): ','y')
t.do_comment(False)
t.view_tx('t')
t.written_to_file('Long chain (timelocked) transaction')
t.written_to_file('Short chain transaction')
t.ok()
def split_sign(self,coin,ext):
wf = get_file_with_ext(self.regtest_user_dir('bob',coin=coin.lower()),'mmdat')
txfile = self.get_file_with_ext(ext,no_dot=True)
opt.coin = coin
self.txsign(txfile,wf,extra_opts=['--bob'])
def split_sign_b2x(self):
return self.regtest_sign(coin='B2X',ext='533].rawtx')
def split_sign_btc(self):
return self.regtest_sign(coin='BTC',ext='9997].rawtx')
def split_send(self,coin,ext):
opt.coin = coin
txfile = self.get_file_with_ext(ext,no_dot=True)
self.txsend(txfile,bogus_send=False,extra_opts=['--bob'])
def split_send_b2x(self):
return self.regtest_send(coin='B2X',ext='533].sigtx')
def split_send_btc(self):
return self.regtest_send(coin='BTC',ext='9997].sigtx')
def split_txdo_timelock(self,coin,locktime,bad_locktime):
opt.coin = coin
sid = self.regtest_user_sid('bob')
self.regtest_user_txdo( 'bob','0.0001',[sid+':S:5'],'1',pw=rt_pw,
extra_args=['--locktime='+str(locktime)],
bad_locktime=bad_locktime)
def split_txdo_timelock_bad_btc(self):
self.regtest_txdo_timelock('BTC',locktime=8888,bad_locktime=True)
def split_txdo_timelock_good_btc(self):
self.regtest_txdo_timelock('BTC',locktime=1321009871,bad_locktime=False)
def split_txdo_timelock_bad_b2x(self):
self.regtest_txdo_timelock('B2X',locktime=8888,bad_locktime=True)
def split_txdo_timelock_good_b2x(self):
self.regtest_txdo_timelock('B2X',locktime=1321009871,bad_locktime=False)

773
test/test_py_d/ts_ethdev.py Executable file
View file

@ -0,0 +1,773 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_ethdev.py: Ethdev tests for the test.py test suite
"""
import sys,os,subprocess,re,shutil
from decimal import Decimal
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.util import die
from mmgen.exception import *
from test.common import *
from test.test_py_d.common import *
del_addrs = ('4','1')
dfl_sid = '98831F3A'
# The Parity dev address with lots of coins. Create with "ethkey -b info ''":
dfl_addr = '00a329c0648769a73afac7f9381e08fb43dbea72'
dfl_addr_chk = '00a329c0648769A73afAc7F9381E08FB43dBEA72'
dfl_privkey = '4d5db4107d237df6a3d58ee5f70ae63d73d7658d4026f2eefd2f204c81682cb7'
burn_addr = 'deadbeef'*5
amt1 = '999999.12345689012345678'
amt2 = '888.111122223333444455'
parity_pid_fn = 'parity.pid'
parity_key_fn = 'parity.devkey'
# Token sends require varying amounts of gas, depending on compiler version
solc_ver = re.search(r'Version:\s*(.*)',
subprocess.Popen(['solc','--version'],stdout=subprocess.PIPE
).stdout.read().decode()).group(1)
if re.match(r'\b0.5.1\b',solc_ver): # Raspbian Stretch
vbal1 = '1.2288337'
vbal2 = '99.997085083'
vbal3 = '1.23142165'
vbal4 = '127.0287837'
elif re.match(r'\b0.5.3\b',solc_ver): # Ubuntu Bionic
vbal1 = '1.2288487'
vbal2 = '99.997092733'
vbal3 = '1.23142915'
vbal4 = '127.0287987'
bals = {
'1': [ ('98831F3A:E:1','123.456')],
'2': [ ('98831F3A:E:1','123.456'),('98831F3A:E:11','1.234')],
'3': [ ('98831F3A:E:1','123.456'),('98831F3A:E:11','1.234'),('98831F3A:E:21','2.345')],
'4': [ ('98831F3A:E:1','100'),
('98831F3A:E:2','23.45495'),
('98831F3A:E:11','1.234'),
('98831F3A:E:21','2.345')],
'5': [ ('98831F3A:E:1','100'),
('98831F3A:E:2','23.45495'),
('98831F3A:E:11','1.234'),
('98831F3A:E:21','2.345'),
(burn_addr + '\s+Non-MMGen',amt1)],
'8': [ ('98831F3A:E:1','0'),
('98831F3A:E:2','23.45495'),
('98831F3A:E:11',vbal1,'a'),
('98831F3A:E:12','99.99895'),
('98831F3A:E:21','2.345'),
(burn_addr + '\s+Non-MMGen',amt1)],
'9': [ ('98831F3A:E:1','0'),
('98831F3A:E:2','23.45495'),
('98831F3A:E:11',vbal1,'a'),
('98831F3A:E:12',vbal2),
('98831F3A:E:21','2.345'),
(burn_addr + '\s+Non-MMGen',amt1)]
}
token_bals = {
'1': [ ('98831F3A:E:11','1000','1.234')],
'2': [ ('98831F3A:E:11','998.76544',vbal3,'a'),
('98831F3A:E:12','1.23456','0')],
'3': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a'),
('98831F3A:E:12','1.23456','0')],
'4': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a'),
('98831F3A:E:12','1.23456','0'),
(burn_addr + '\s+Non-MMGen',amt2,amt1)],
'5': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a'),
('98831F3A:E:12','1.23456','99.99895'),
(burn_addr + '\s+Non-MMGen',amt2,amt1)],
'6': [ ('98831F3A:E:11','110.654317776666555545',vbal1,'a'),
('98831F3A:E:12','0',vbal2),
('98831F3A:E:13','1.23456','0'),
(burn_addr + '\s+Non-MMGen',amt2,amt1)]
}
token_bals_getbalance = {
'1': (vbal4,'999999.12345689012345678'),
'2': ('111.888877776666555545','888.111122223333444455')
}
from test.test_py_d.ts_base import *
from test.test_py_d.ts_shared import *
class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
'Ethereum transacting, token deployment and tracking wallet operations'
networks = ('eth','etc')
passthru_opts = ('coin',)
tmpdir_nums = [22]
cmd_group = (
('setup', 'Ethereum Parity dev mode tests for coin {} (start parity)'.format(g.coin)),
('addrgen', 'generating addresses'),
('addrimport', 'importing addresses'),
('addrimport_dev_addr', "importing Parity dev address 'Ox00a329c..'"),
('txcreate1', 'creating a transaction (spend from dev address to address :1)'),
('txsign1', 'signing the transaction'),
('txsign1_ni', 'signing the transaction (non-interactive)'),
('txsend1', 'sending the transaction'),
('bal1', 'the {} balance'.format(g.coin)),
('txcreate2', 'creating a transaction (spend from dev address to address :11)'),
('txsign2', 'signing the transaction'),
('txsend2', 'sending the transaction'),
('bal2', 'the {} balance'.format(g.coin)),
('txcreate3', 'creating a transaction (spend from dev address to address :21)'),
('txsign3', 'signing the transaction'),
('txsend3', 'sending the transaction'),
('bal3', 'the {} balance'.format(g.coin)),
('tx_status1', 'getting the transaction status'),
('txcreate4', 'creating a transaction (spend from MMGen address, low TX fee)'),
('txbump', 'bumping the transaction fee'),
('txsign4', 'signing the transaction'),
('txsend4', 'sending the transaction'),
('bal4', 'the {} balance'.format(g.coin)),
('txcreate5', 'creating a transaction (fund burn address)'),
('txsign5', 'signing the transaction'),
('txsend5', 'sending the transaction'),
('addrimport_burn_addr',"importing burn address"),
('bal5', 'the {} balance'.format(g.coin)),
('add_label', 'adding a UTF-8 label'),
('chk_label', 'the label'),
('remove_label', 'removing the label'),
('token_compile1', 'compiling ERC20 token #1'),
('token_deploy1a', 'deploying ERC20 token #1 (SafeMath)'),
('token_deploy1b', 'deploying ERC20 token #1 (Owned)'),
('token_deploy1c', 'deploying ERC20 token #1 (Token)'),
('tx_status2', 'getting the transaction status'),
('bal6', 'the {} balance'.format(g.coin)),
('token_compile2', 'compiling ERC20 token #2'),
('token_deploy2a', 'deploying ERC20 token #2 (SafeMath)'),
('token_deploy2b', 'deploying ERC20 token #2 (Owned)'),
('token_deploy2c', 'deploying ERC20 token #2 (Token)'),
('contract_deploy', 'deploying contract (create,sign,send)'),
('token_fund_users', 'transferring token funds from dev to user'),
('token_user_bals', 'show balances after transfer'),
('token_addrgen', 'generating token addresses'),
('token_addrimport_badaddr1','importing token addresses (no token address)'),
('token_addrimport_badaddr2','importing token addresses (bad token address)'),
('token_addrimport', 'importing token addresses'),
('bal7', 'the {} balance'.format(g.coin)),
('token_bal1', 'the {} balance and token balance'.format(g.coin)),
('token_txcreate1', 'creating a token transaction'),
('token_txsign1', 'signing the transaction'),
('token_txsend1', 'sending the transaction'),
('token_bal2', 'the {} balance and token balance'.format(g.coin)),
('token_txcreate2', 'creating a token transaction (to burn address)'),
('token_txbump', 'bumping the transaction fee'),
('token_txsign2', 'signing the transaction'),
('token_txsend2', 'sending the transaction'),
('token_bal3', 'the {} balance and token balance'.format(g.coin)),
('del_dev_addr', "deleting the dev address"),
('bal1_getbalance', 'the {} balance (getbalance)'.format(g.coin)),
('addrimport_token_burn_addr',"importing the token burn address"),
('token_bal4', 'the {} balance and token balance'.format(g.coin)),
('token_bal_getbalance','the token balance (getbalance)'),
('txcreate_noamt', 'creating a transaction (full amount send)'),
('txsign_noamt', 'signing the transaction'),
('txsend_noamt', 'sending the transaction'),
('bal8', 'the {} balance'.format(g.coin)),
('token_bal5', 'the token balance'),
('token_txcreate_noamt', 'creating a token transaction (full amount send)'),
('token_txsign_noamt', 'signing the transaction'),
('token_txsend_noamt', 'sending the transaction'),
('bal9', 'the {} balance'.format(g.coin)),
('token_bal6', 'the token balance'),
('listaddresses1', 'listaddresses'),
('listaddresses2', 'listaddresses minconf=999999999 (ignored)'),
('listaddresses3', 'listaddresses sort=age (ignored)'),
('listaddresses4', 'listaddresses showempty=1 sort=age (ignored)'),
('token_listaddresses1','listaddresses --token=mm1'),
('token_listaddresses2','listaddresses --token=mm1 showempty=1'),
('twview1','twview'),
('twview2','twview wide=1'),
('twview3','twview wide=1 sort=age (ignored)'),
('twview4','twview wide=1 minconf=999999999 (ignored)'),
('twview5','twview wide=1 minconf=0 (ignored)'),
('twview6','twview age_fmt=days (ignored)'),
('token_twview1','twview --token=mm1'),
('token_twview2','twview --token=mm1 wide=1'),
('token_twview3','twview --token=mm1 wide=1 sort=age (ignored)'),
('edit_label1','adding label to addr #{} in {} tracking wallet'.format(del_addrs[0],g.coin)),
('edit_label2','adding label to addr #{} in {} tracking wallet'.format(del_addrs[1],g.coin)),
('edit_label3','removing label from addr #{} in {} tracking wallet'.format(del_addrs[0],g.coin)),
('remove_addr1','removing addr #{} from {} tracking wallet'.format(del_addrs[0],g.coin)),
('remove_addr2','removing addr #{} from {} tracking wallet'.format(del_addrs[1],g.coin)),
('remove_token_addr1','removing addr #{} from {} token tracking wallet'.format(del_addrs[0],g.coin)),
('remove_token_addr2','removing addr #{} from {} token tracking wallet'.format(del_addrs[1],g.coin)),
('stop', 'stopping parity'),
)
@property
def eth_args(self):
return ['--outdir={}'.format(self.tmpdir),'--coin='+g.coin,'--rpc-port=8549','--quiet']
def setup(self):
self.spawn('',msg_only=True)
os.environ['MMGEN_BOGUS_WALLET_DATA'] = ''
if subprocess.call(['which','parity'],stdout=subprocess.PIPE) == 0:
lf_arg = '--log-file=' + joinpath(self.tr.data_dir,'parity.log')
ss = 'parity.*--log-file=test/data_dir.*/parity.log' # allow for UTF8_DEBUG
try:
pid = subprocess.check_output(['pgrep','-af',ss]).split()[0]
os.kill(int(pid),9)
except: pass
# '--base-path' doesn't work together with daemon mode, so we have to clobber the main dev chain
dc_dir = joinpath(os.environ['HOME'],'.local/share/io.parity.ethereum/chains/DevelopmentChain')
shutil.rmtree(dc_dir,ignore_errors=True)
bdir = joinpath(self.tr.data_dir,'parity')
try: os.mkdir(bdir)
except: pass
opts = ['--ports-shift=4','--config=dev']
redir = None if opt.exact_output else subprocess.PIPE
pidfile = joinpath(self.tmpdir,parity_pid_fn)
subprocess.check_call(['parity',lf_arg] + opts + ['daemon',pidfile],stderr=redir,stdout=redir)
time.sleep(3) # race condition
pid = self.read_from_tmpfile(parity_pid_fn)
elif subprocess.call('netstat -tnl | grep -q 127.0.0.1:8549',shell=True) == 0:
m1 = 'No parity executable found on system, but port 8549 is active!'
m2 = 'Before continuing, you should probably run the command'
m3 = 'test/test.py -X setup ethdev'
m4 = 'on the remote host.'
sys.stderr.write('{}\n{}\n{} {}\n'.format(m1,m2,cyan(m3),m4))
confirm_continue()
else:
die(1,'No parity executable found!')
return 'ok'
def addrgen(self,addrs='1-3,11-13,21-23'):
from mmgen.addr import MMGenAddrType
t = self.spawn('mmgen-addrgen', self.eth_args + [dfl_words_file,addrs])
t.written_to_file('Addresses')
t.read()
return t
def addrimport(self,ext='21-23]{}.addrs',expect='9/9',add_args=[],bad_input=False):
ext = ext.format('' if g.debug_utf8 else '')
fn = self.get_file_with_ext(ext,no_dot=True,delete=False)
t = self.spawn('mmgen-addrimport', self.eth_args[1:] + add_args + [fn])
if bad_input:
t.read()
t.req_exit_val = 2
return t
if g.debug: t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
t.expect('Importing')
t.expect(expect)
t.read()
return t
def addrimport_one_addr(self,addr=None,extra_args=[]):
t = self.spawn('mmgen-addrimport', self.eth_args[1:] + extra_args + ['--address='+addr])
t.expect('OK')
return t
def addrimport_dev_addr(self):
return self.addrimport_one_addr(addr=dfl_addr)
def addrimport_burn_addr(self):
return self.addrimport_one_addr(addr=burn_addr)
def txcreate(self,args=[],menu=[],acct='1',non_mmgen_inputs=0,
interactive_fee = '50G',
eth_fee_res = None,
fee_res_fs = '0.00105 {} (50 gas price in Gwei)',
fee_desc = 'gas price' ):
fee_res = fee_res_fs.format(g.coin)
t = self.spawn('mmgen-txcreate', self.eth_args + ['-B'] + args)
t.expect(r'add \[l\]abel, .*?:.','p', regex=True)
t.written_to_file('Account balances listing')
return self.txcreate_ui_common( t,self.test_name,menu=menu,
input_sels_prompt = 'to spend from',
inputs = acct,
file_desc = 'Ethereum transaction',
bad_input_sels = True,
non_mmgen_inputs = non_mmgen_inputs,
interactive_fee = interactive_fee,
fee_res = fee_res,
fee_desc = fee_desc,
eth_fee_res = eth_fee_res,
add_comment = ref_tx_label_jp )
def txsign(self,ni=False,ext='{}.rawtx',add_args=[]):
ext = ext.format('' if g.debug_utf8 else '')
keyfile = joinpath(self.tmpdir,parity_key_fn)
write_to_file(keyfile,dfl_privkey+'\n')
txfile = self.get_file_with_ext(ext,no_dot=True)
t = self.spawn( 'mmgen-txsign',
self.eth_args
+ add_args
+ ([],['--yes'])[ni]
+ ['-k', keyfile, txfile, dfl_words_file] )
return self.txsign_ui_common(t,self.test_name,ni=ni,has_label=True)
def txsend(self,ni=False,bogus_send=False,ext='{}.sigtx',add_args=[]):
ext = ext.format('' if g.debug_utf8 else '')
txfile = self.get_file_with_ext(ext,no_dot=True)
if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = ''
t = self.spawn('mmgen-txsend', self.eth_args + add_args + [txfile])
if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = '1'
txid = self.txsend_ui_common(t,self.test_name,quiet=True,bogus_send=bogus_send,has_label=True)
return t
def txcreate1(self):
# valid_keypresses = 'adrMmeqpvwl'
menu = ['a','d','r','M','D','e','m','m'] # include one invalid keypress, 'D'
args = ['98831F3A:E:1,123.456']
return self.txcreate(args=args,menu=menu,acct='1',non_mmgen_inputs=1)
def txsign1(self): return self.txsign()
def txsign1_ni(self): return self.txsign(ni=True)
def txsend1(self): return self.txsend()
def bal1(self): return self.bal(n='1')
def txcreate2(self):
args = ['98831F3A:E:11,1.234']
return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
def txsign2(self): return self.txsign(ni=True,ext='1.234,50000]{}.rawtx')
def txsend2(self): return self.txsend(ext='1.234,50000]{}.sigtx')
def bal2(self): return self.bal(n='2')
def txcreate3(self):
args = ['98831F3A:E:21,2.345']
return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
def txsign3(self): return self.txsign(ni=True,ext='2.345,50000]{}.rawtx')
def txsend3(self): return self.txsend(ext='2.345,50000]{}.sigtx')
def bal3(self): return self.bal(n='3')
def tx_status(self,ext,expect_str):
ext = ext.format('' if g.debug_utf8 else '')
txfile = self.get_file_with_ext(ext,no_dot=True)
t = self.spawn('mmgen-txsend', self.eth_args + ['--status',txfile])
t.expect(expect_str)
t.read()
return t
def tx_status1(self):
return self.tx_status(ext='2.345,50000]{}.sigtx',expect_str='has 1 confirmation')
def txcreate4(self):
args = ['98831F3A:E:2,23.45495']
interactive_fee='40G'
fee_res_fs='0.00084 {} (40 gas price in Gwei)'
return self.txcreate( args = args,
acct = '1',
non_mmgen_inputs = 0,
interactive_fee = interactive_fee,
fee_res_fs = fee_res_fs,
eth_fee_res = True)
def txbump(self,ext=',40000]{}.rawtx',fee='50G',add_args=[]):
ext = ext.format('' if g.debug_utf8 else '')
txfile = self.get_file_with_ext(ext,no_dot=True)
t = self.spawn('mmgen-txbump', self.eth_args + add_args + ['--yes',txfile])
t.expect('or gas price: ',fee+'\n')
t.read()
return t
def txsign4(self): return self.txsign(ni=True,ext='.45495,50000]{}.rawtx')
def txsend4(self): return self.txsend(ext='.45495,50000]{}.sigtx')
def bal4(self): return self.bal(n='4')
def txcreate5(self):
args = [burn_addr + ','+amt1]
return self.txcreate(args=args,acct='10',non_mmgen_inputs=1)
def txsign5(self): return self.txsign(ni=True,ext=amt1+',50000]{}.rawtx')
def txsend5(self): return self.txsend(ext=amt1+',50000]{}.sigtx')
def bal5(self): return self.bal(n='5')
bal_corr = Decimal('0.0000032') # gas use for token sends varies between ETH and ETC!
def bal(self,n=None):
t = self.spawn('mmgen-tool', self.eth_args + ['twview','wide=1'])
for b in bals[n]:
addr,amt,adj = b if len(b) == 3 else b + (False,)
if adj and g.coin == 'ETC': amt = str(Decimal(amt) + self.bal_corr)
pat = r'{}\s+{}\s'.format(addr,amt.replace('.',r'\.'))
t.expect(pat,regex=True)
t.read()
return t
def token_bal(self,n=None):
t = self.spawn('mmgen-tool', self.eth_args + ['--token=mm1','twview','wide=1'])
for b in token_bals[n]:
addr,_amt1,_amt2,adj = b if len(b) == 4 else b + (False,)
if adj and g.coin == 'ETC': _amt2 = str(Decimal(_amt2) + self.bal_corr)
pat = r'{}\s+{}\s+{}\s'.format(addr,_amt1.replace('.',r'\.'),_amt2.replace('.',r'\.'))
t.expect(pat,regex=True)
t.read()
return t
def bal_getbalance(self,idx,etc_adj=False,extra_args=[]):
bal1 = token_bals_getbalance[idx][0]
bal2 = token_bals_getbalance[idx][1]
bal1 = Decimal(bal1)
if etc_adj and g.coin == 'ETC': bal1 += self.bal_corr
t = self.spawn('mmgen-tool', self.eth_args + extra_args + ['getbalance'])
t.expect(r'\n[0-9A-F]{8}: .* '+str(bal1),regex=True)
t.expect(r'\nNon-MMGen: .* '+bal2,regex=True)
total = t.expect_getend(r'\nTOTAL:\s+',regex=True).split()[0]
t.read()
assert Decimal(bal1) + Decimal(bal2) == Decimal(total)
return t
def add_label(self,addr='98831F3A:E:3',lbl=utf8_label):
t = self.spawn('mmgen-tool', self.eth_args + ['add_label',addr,lbl])
t.expect('Added label.*in tracking wallet',regex=True)
return t
def chk_label(self,addr='98831F3A:E:3',label_pat=utf8_label_pat):
t = self.spawn('mmgen-tool', self.eth_args + ['listaddresses','all_labels=1'])
t.expect(r'{}\s+\S{{30}}\S+\s+{}\s+'.format(addr,(label_pat or label)),regex=True)
return t
def remove_label(self,addr='98831F3A:E:3'):
t = self.spawn('mmgen-tool', self.eth_args + ['remove_label',addr])
t.expect('Removed label.*in tracking wallet',regex=True)
return t
def token_compile(self,token_data={}):
self.spawn('',msg_only=True)
cmd_args = ['--{}={}'.format(k,v) for k,v in list(token_data.items())]
imsg("Compiling solidity token contract '{}' with 'solc'".format(token_data['symbol']))
cmd = ['scripts/create-token.py','--coin='+g.coin,'--outdir='+self.tmpdir] + cmd_args + [dfl_addr_chk]
imsg("Executing: {}".format(' '.join(cmd)))
subprocess.check_output(cmd,stderr=subprocess.STDOUT)
imsg("ERC20 token '{}' compiled".format(token_data['symbol']))
return 'ok'
def token_compile1(self):
token_data = { 'name':'MMGen Token 1', 'symbol':'MM1', 'supply':10**26, 'decimals':18 }
return self.token_compile(token_data)
def token_compile2(self):
token_data = { 'name':'MMGen Token 2', 'symbol':'MM2', 'supply':10**18, 'decimals':10 }
return self.token_compile(token_data)
def _rpc_init(self):
g.proto.rpc_port = 8549
rpc_init()
def token_deploy(self,num,key,gas,mmgen_cmd='txdo',tx_fee='8G'):
self._rpc_init()
keyfile = joinpath(self.tmpdir,parity_key_fn)
fn = joinpath(self.tmpdir,key+'.bin')
os.environ['MMGEN_BOGUS_SEND'] = ''
args = ['-B',
'--tx-fee='+tx_fee,
'--tx-gas={}'.format(gas),
'--contract-data='+fn,
'--inputs='+dfl_addr,
'--yes' ]
if mmgen_cmd == 'txdo': args += ['-k',keyfile]
t = self.spawn( 'mmgen-'+mmgen_cmd, self.eth_args + args)
if mmgen_cmd == 'txcreate':
t.written_to_file('Ethereum transaction')
ext = '[0,8000]{}.rawtx'.format('' if g.debug_utf8 else '')
txfile = self.get_file_with_ext(ext,no_dot=True)
t = self.spawn('mmgen-txsign', self.eth_args + ['--yes','-k',keyfile,txfile],no_msg=True)
self.txsign_ui_common(t,self.test_name,ni=True)
txfile = txfile.replace('.rawtx','.sigtx')
t = self.spawn('mmgen-txsend', self.eth_args + [txfile],no_msg=True)
os.environ['MMGEN_BOGUS_SEND'] = '1'
txid = self.txsend_ui_common(t,mmgen_cmd,quiet=True,bogus_send=False)
addr = t.expect_getend('Contract address: ')
from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
assert etx.get_exec_status(txid.encode(),True) != 0,(
"Contract '{}:{}' failed to execute. Aborting".format(num,key))
if key == 'Token':
self.write_to_tmpfile('token_addr{}'.format(num),addr+'\n')
imsg('\nToken #{} ({}) deployed!'.format(num,addr))
return t
def token_deploy1a(self): return self.token_deploy(num=1,key='SafeMath',gas=200000)
def token_deploy1b(self): return self.token_deploy(num=1,key='Owned',gas=250000)
def token_deploy1c(self): return self.token_deploy(num=1,key='Token',gas=1100000,tx_fee='7G')
def tx_status2(self):
return self.tx_status(ext=g.coin+'[0,7000]{}.sigtx',expect_str='successfully executed')
def bal6(self): return self.bal5()
def token_deploy2a(self): return self.token_deploy(num=2,key='SafeMath',gas=200000)
def token_deploy2b(self): return self.token_deploy(num=2,key='Owned',gas=250000)
def token_deploy2c(self): return self.token_deploy(num=2,key='Token',gas=1100000)
def contract_deploy(self): # test create,sign,send
return self.token_deploy(num=2,key='SafeMath',gas=1100000,mmgen_cmd='txcreate')
def token_transfer_ops(self,op,amt=1000):
self.spawn('',msg_only=True)
sid = dfl_sid
from mmgen.tool import MMGenToolCmd
usr_mmaddrs = ['{}:E:{}'.format(sid,i) for i in (11,21)]
usr_addrs = [MMGenToolCmd().gen_addr(addr,dfl_words_file) for addr in usr_mmaddrs]
self._rpc_init()
from mmgen.altcoins.eth.contract import Token
from mmgen.altcoins.eth.tx import EthereumMMGenTX as etx
def do_transfer():
for i in range(2):
tk = Token(self.read_from_tmpfile('token_addr{}'.format(i+1)).strip())
imsg_r('\n'+tk.info())
imsg('dev token balance (pre-send): {}'.format(tk.balance(dfl_addr)))
imsg('Sending {} {} to address {} ({})'.format(amt,g.coin,usr_addrs[i],usr_mmaddrs[i]))
from mmgen.obj import ETHAmt
txid = tk.transfer( dfl_addr, usr_addrs[i], amt, dfl_privkey,
start_gas = ETHAmt(60000,'wei'),
gasPrice = ETHAmt(8,'Gwei') )
assert etx.get_exec_status(txid,True) != 0,'Transfer of token funds failed. Aborting'
def show_bals():
for i in range(2):
tk = Token(self.read_from_tmpfile('token_addr{}'.format(i+1)).strip())
imsg('Token: {}'.format(tk.symbol()))
imsg('dev token balance: {}'.format(tk.balance(dfl_addr)))
imsg('usr token balance: {} ({} {})'.format(
tk.balance(usr_addrs[i]),usr_mmaddrs[i],usr_addrs[i]))
silence()
if op == 'show_bals': show_bals()
elif op == 'do_transfer': do_transfer()
end_silence()
return 'ok'
def token_fund_users(self):
return self.token_transfer_ops(op='do_transfer')
def token_user_bals(self):
return self.token_transfer_ops(op='show_bals')
def token_addrgen(self):
self.addrgen(addrs='11-13')
ok_msg()
return self.addrgen(addrs='21-23')
def token_addrimport_badaddr1(self):
t = self.addrimport(ext='[11-13]{}.addrs',add_args=['--token=abc'],bad_input=True)
t.req_exit_val = 2
return t
def token_addrimport_badaddr2(self):
t = self.addrimport(ext='[11-13]{}.addrs',add_args=['--token='+'00deadbeef'*4],bad_input=True)
t.req_exit_val = 2
return t
def token_addrimport(self):
for n,r in ('1','11-13'),('2','21-23'):
tk_addr = self.read_from_tmpfile('token_addr'+n).strip()
t = self.addrimport(ext='['+r+']{}.addrs',expect='3/3',add_args=['--token='+tk_addr])
t.p.wait()
ok_msg()
t.skip_ok = True
return t
def bal7(self): return self.bal5()
def token_bal1(self): return self.token_bal(n='1')
def token_txcreate(self,args=[],token='',inputs='1',fee='50G'):
t = self.spawn('mmgen-txcreate', self.eth_args + ['--token='+token,'-B','--tx-fee='+fee] + args)
return self.txcreate_ui_common( t,self.test_name,
menu = [],
inputs = inputs,
input_sels_prompt = 'to spend from',
file_desc = 'Ethereum token transaction',
add_comment = ref_tx_label_lat_cyr_gr)
def token_txsign(self,ext='',token=''):
return self.txsign(ni=True,ext=ext,add_args=['--token='+token])
def token_txsend(self,ext='',token=''):
return self.txsend(ext=ext,add_args=['--token=mm1'])
def token_txcreate1(self):
return self.token_txcreate(args=['98831F3A:E:12,1.23456'],token='mm1')
def token_txsign1(self):
return self.token_txsign(ext='1.23456,50000]{}.rawtx',token='mm1')
def token_txsend1(self):
return self.token_txsend(ext='1.23456,50000]{}.sigtx',token='mm1')
def token_bal2(self):
return self.token_bal(n='2')
def twview(self,args=[],expect_str='',tool_args=[],exit_val=0):
t = self.spawn('mmgen-tool', self.eth_args + args + ['twview'] + tool_args)
if expect_str:
t.expect(expect_str,regex=True)
t.read()
t.req_exit_val = exit_val
return t
def token_txcreate2(self):
return self.token_txcreate(args=[burn_addr+','+amt2],token='mm1')
def token_txbump(self):
return self.txbump(ext=amt2+',50000]{}.rawtx',fee='56G',add_args=['--token=mm1'])
def token_txsign2(self):
return self.token_txsign(ext=amt2+',50000]{}.rawtx',token='mm1')
def token_txsend2(self):
return self.token_txsend(ext=amt2+',50000]{}.sigtx',token='mm1')
def token_bal3(self):
return self.token_bal(n='3')
def del_dev_addr(self):
t = self.spawn('mmgen-tool', self.eth_args + ['remove_address',dfl_addr])
t.read() # TODO
return t
def bal1_getbalance(self):
return self.bal_getbalance('1',etc_adj=True)
def addrimport_token_burn_addr(self):
return self.addrimport_one_addr(addr=burn_addr,extra_args=['--token=mm1'])
def token_bal4(self):
return self.token_bal(n='4')
def token_bal_getbalance(self):
return self.bal_getbalance('2',extra_args=['--token=mm1'])
def txcreate_noamt(self):
return self.txcreate(args=['98831F3A:E:12'],eth_fee_res=True)
def txsign_noamt(self):
return self.txsign(ext='99.99895,50000]{}.rawtx')
def txsend_noamt(self):
return self.txsend(ext='99.99895,50000]{}.sigtx')
def bal8(self): return self.bal(n='8')
def token_bal5(self): return self.token_bal(n='5')
def token_txcreate_noamt(self):
return self.token_txcreate(args=['98831F3A:E:13'],token='mm1',inputs='2',fee='51G')
def token_txsign_noamt(self):
return self.token_txsign(ext='1.23456,51000]{}.rawtx',token='mm1')
def token_txsend_noamt(self):
return self.token_txsend(ext='1.23456,51000]{}.sigtx',token='mm1')
def bal9(self): return self.bal(n='9')
def token_bal6(self): return self.token_bal(n='6')
def listaddresses(self,args=[],tool_args=['all_labels=1'],exit_val=0):
t = self.spawn('mmgen-tool', self.eth_args + args + ['listaddresses'] + tool_args)
t.read()
t.req_exit_val = exit_val
return t
def listaddresses1(self):
return self.listaddresses()
def listaddresses2(self):
return self.listaddresses(tool_args=['minconf=999999999'])
def listaddresses3(self):
return self.listaddresses(tool_args=['sort=age'])
def listaddresses4(self):
return self.listaddresses(tool_args=['sort=age','showempty=1'])
def token_listaddresses1(self):
return self.listaddresses(args=['--token=mm1'])
def token_listaddresses2(self):
return self.listaddresses(args=['--token=mm1'],tool_args=['showempty=1'])
def twview1(self):
return self.twview()
def twview2(self):
return self.twview(tool_args=['wide=1'])
def twview3(self):
return self.twview(tool_args=['wide=1','sort=age'])
def twview4(self):
return self.twview(tool_args=['wide=1','minconf=999999999'])
def twview5(self):
return self.twview(tool_args=['wide=1','minconf=0'])
def twview6(self):
return self.twview(tool_args=['age_fmt=days'])
def token_twview1(self):
return self.twview(args=['--token=mm1'])
def token_twview2(self):
return self.twview(args=['--token=mm1'],tool_args=['wide=1'])
def token_twview3(self):
return self.twview(args=['--token=mm1'],tool_args=['wide=1','sort=age'])
def edit_label(self,out_num,args=[],action='l',label_text=None):
t = self.spawn('mmgen-txcreate', self.eth_args + args + ['-B','-i'])
p1,p2 = ('emove address:\b','return to main menu): ')
p3,r3 = (p2,label_text+'\n') if label_text is not None else ('(y/N): ','y')
p4,r4 = (('(y/N): ',),('y',)) if label_text == '' else ((),())
for p,r in zip((p1,p1,p2,p3)+p4+(p1,p1),('M',action,out_num+'\n',r3)+r4+('M','q')):
t.expect(p,r)
return t
def edit_label1(self):
return self.edit_label(out_num=del_addrs[0],label_text='First added label-α')
def edit_label2(self):
return self.edit_label(out_num=del_addrs[1],label_text='Second added label')
def edit_label3(self):
return self.edit_label(out_num=del_addrs[0],label_text='')
def remove_addr1(self):
return self.edit_label(out_num=del_addrs[0],action='R')
def remove_addr2(self):
return self.edit_label(out_num=del_addrs[1],action='R')
def remove_token_addr1(self):
return self.edit_label(out_num=del_addrs[0],args=['--token=mm1'],action='R')
def remove_token_addr2(self):
return self.edit_label(out_num=del_addrs[1],args=['--token=mm1'],action='R')
def stop(self):
self.spawn('',msg_only=True)
if subprocess.call(['which','parity'],stdout=subprocess.PIPE) == 0:
pid = self.read_from_tmpfile(parity_pid_fn)
if opt.no_daemon_stop:
msg_r('(leaving daemon running by user request)')
else:
subprocess.check_call(['kill',pid])
else:
imsg('No parity executable found on system. Ignoring')
return 'ok'

704
test/test_py_d/ts_main.py Executable file
View file

@ -0,0 +1,704 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_main.py: Basic operations tests for the test.py test suite
"""
from mmgen.globalvars import g
from mmgen.opts import opt
from test.common import *
from test.test_py_d.common import *
from test.test_py_d.ts_base import *
from test.test_py_d.ts_shared import *
def make_brainwallet_file(fn):
# Print random words with random whitespace in between
wl = rwords.split()
nwords,ws_list,max_spaces = 10,' \n',5
def rand_ws_seq():
nchars = getrandnum(1) % max_spaces + 1
return ''.join([ws_list[getrandnum_range(1,200) % len(ws_list)] for i in range(nchars)])
rand_pairs = [wl[getrandnum_range(1,200) % len(wl)] + rand_ws_seq() for i in range(nwords)]
d = ''.join(rand_pairs).rstrip() + '\n'
if opt.verbose: msg_r('Brainwallet password:\n{}'.format(cyan(d)))
write_data_to_file(fn,d,'brainwallet password',silent=True,ignore_opt_outdir=True)
def verify_checksum_or_exit(checksum,chk):
if checksum != chk:
raise TestSuiteFatalException('Checksum error: {}'.format(chk))
vmsg(green('Checksums match: ') + cyan(chk))
addrs_per_wallet = 8
# 100 words chosen randomly from here:
# https://github.com/bitcoin/bips/pull/432/files/6332230d63149a950d05db78964a03bfd344e6b0
rwords = """
алфавит алый амнезия амфора артист баян белый биатлон брат бульвар веревка вернуть весть возраст
восток горло горный десяток дятел ежевика жест жизнь жрать заговор здание зона изделие итог кабина
кавалер каждый канал керосин класс клятва князь кривой крыша крючок кузнец кукла ландшафт мальчик
масса масштаб матрос мрак муравей мычать негодяй носок ночной нрав оборот оружие открытие оттенок
палуба пароход период пехота печать письмо позор полтора понятие поцелуй почему приступ пруд пятно
ранее режим речь роса рынок рябой седой сердце сквозь смех снимок сойти соперник спичка стон
сувенир сугроб суть сцена театр тираж толк удивить улыбка фирма читатель эстония эстрада юность
"""
class TestSuiteMain(TestSuiteBase,TestSuiteShared):
'basic operations with emulated tracking wallet'
tmpdir_nums = [1,2,3,4,5,14,15,16,20,21]
networks = ('btc','btc_tn','ltc','ltc_tn','bch','bch_tn')
passthru_opts = ('coin','testnet')
segwit_opts_ok = True
cmd_group = (
('walletgen_dfl_wallet', (15,'wallet generation (default wallet)',[[[],15]])),
('export_seed_dfl_wallet',(15,'seed export to mmseed format (default wallet)',[[[pwfile],15]])),
('addrgen_dfl_wallet',(15,'address generation (default wallet)',[[[pwfile],15]])),
('txcreate_dfl_wallet',(15,'transaction creation (default wallet)',[[['addrs'],15]])),
('txsign_dfl_wallet',(15,'transaction signing (default wallet)',[[['rawtx',pwfile],15]])),
('passchg_dfl_wallet',(16,'password, label and hash preset change (default wallet)',[[[pwfile],15]])),
('walletchk_newpass_dfl_wallet',(16,'wallet check with new pw, label and hash preset',[[[pwfile],16]])),
('delete_dfl_wallet',(15,'delete default wallet',[[[pwfile],15]])),
('walletgen', (1,'wallet generation', [[['del_dw_run'],15]])),
# ('walletchk', (1,'wallet check', [[['mmdat'],1]])),
('passchg', (5,'password, label and hash preset change',[[['mmdat',pwfile],1]])),
('passchg_keeplabel',(5,'password, label and hash preset change (keep label)',[[['mmdat',pwfile],1]])),
('passchg_usrlabel',(5,'password, label and hash preset change (interactive label)',[[['mmdat',pwfile],1]])),
('walletchk_newpass',(5,'wallet check with new pw, label and hash preset',[[['mmdat',pwfile],5]])),
('addrgen', (1,'address generation', [[['mmdat',pwfile],1]])),
('txcreate', (1,'transaction creation', [[['addrs'],1]])),
('txbump', (1,'transaction fee bumping (no send)',[[['rawtx'],1]])),
('txsign', (1,'transaction signing', [[['mmdat','rawtx',pwfile,'txbump'],1]])),
('txsend', (1,'transaction sending', [[['sigtx'],1]])),
# txdo must go after txsign
('txdo', (1,'online transaction', [[['sigtx','mmdat'],1]])),
('export_seed', (1,'seed export to mmseed format', [[['mmdat'],1]])),
('export_hex', (1,'seed export to hexadecimal format', [[['mmdat'],1]])),
('export_mnemonic', (1,'seed export to mmwords format', [[['mmdat'],1]])),
('export_incog', (1,'seed export to mmincog format', [[['mmdat'],1]])),
('export_incog_hex',(1,'seed export to mmincog hex format', [[['mmdat'],1]])),
('export_incog_hidden',(1,'seed export to hidden mmincog format', [[['mmdat'],1]])),
('addrgen_seed', (1,'address generation from mmseed file', [[['mmseed','addrs'],1]])),
('addrgen_hex', (1,'address generation from mmhex file', [[['mmhex','addrs'],1]])),
('addrgen_mnemonic',(1,'address generation from mmwords file',[[['mmwords','addrs'],1]])),
('addrgen_incog', (1,'address generation from mmincog file',[[['mmincog','addrs'],1]])),
('addrgen_incog_hex',(1,'address generation from mmincog hex file',[[['mmincox','addrs'],1]])),
('addrgen_incog_hidden',(1,'address generation from hidden mmincog file', [[[hincog_fn,'addrs'],1]])),
('keyaddrgen', (1,'key-address file generation', [[['mmdat',pwfile],1]])),
('txsign_keyaddr',(1,'transaction signing with key-address file', [[['akeys.mmenc','rawtx'],1]])),
('txcreate_ni', (1,'transaction creation (non-interactive)', [[['addrs'],1]])),
('walletgen2',(2,'wallet generation (2), 128-bit seed', [[['del_dw_run'],15]])),
('addrgen2', (2,'address generation (2)', [[['mmdat'],2]])),
('txcreate2', (2,'transaction creation (2)', [[['addrs'],2]])),
('txsign2', (2,'transaction signing, two transactions',[[['mmdat','rawtx'],1],[['mmdat','rawtx'],2]])),
('export_mnemonic2', (2,'seed export to mmwords format (2)',[[['mmdat'],2]])),
('walletgen3',(3,'wallet generation (3)', [[['del_dw_run'],15]])),
('addrgen3', (3,'address generation (3)', [[['mmdat'],3]])),
('txcreate3', (3,'tx creation with inputs and outputs from two wallets', [[['addrs'],1],[['addrs'],3]])),
('txsign3', (3,'tx signing with inputs and outputs from two wallets',[[['mmdat'],1],[['mmdat','rawtx'],3]])),
('walletgen14', (14,'wallet generation (14)', [[['del_dw_run'],15]],14)),
('addrgen14', (14,'address generation (14)', [[['mmdat'],14]])),
('keyaddrgen14',(14,'key-address file generation (14)', [[['mmdat'],14]],14)),
('walletgen4',(4,'wallet generation (4) (brainwallet)', [[['del_dw_run'],15]])),
('addrgen4', (4,'address generation (4)', [[['mmdat'],4]])),
('txcreate4', (4,'tx creation with inputs and outputs from four seed sources, key-address file and non-MMGen inputs and outputs', [[['addrs'],1],[['addrs'],2],[['addrs'],3],[['addrs'],4],[['addrs','akeys.mmenc'],14]])),
('txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet, brainwallet, key-address file and non-MMGen inputs and outputs', [[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['mmbrain','rawtx'],4],[['akeys.mmenc'],14]])),
('txdo4', (4,'tx creation,signing and sending with inputs and outputs from four seed sources, key-address file and non-MMGen inputs and outputs', [[['addrs'],1],[['addrs'],2],[['addrs'],3],[['addrs'],4],[['addrs','akeys.mmenc'],14],[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['mmbrain','rawtx'],4],[['akeys.mmenc'],14]])), # must go after txsign4
('txbump4', (4,'tx fee bump + send with inputs and outputs from four seed sources, key-address file and non-MMGen inputs and outputs', [[['akeys.mmenc'],14],[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['akeys.mmenc'],14],[['mmbrain','sigtx','mmdat','txdo'],4]])), # must go after txsign4
('walletgen5',(20,'wallet generation (5)', [[['del_dw_run'],15]],20)),
('addrgen5', (20,'address generation (5)', [[['mmdat'],20]])),
('txcreate5', (20,'transaction creation with bad vsize (5)', [[['addrs'],20]])),
('txsign5', (20,'transaction signing with bad vsize', [[['mmdat','rawtx'],20]])),
('walletgen6',(21,'wallet generation (6)', [[['del_dw_run'],15]],21)),
('addrgen6', (21,'address generation (6)', [[['mmdat'],21]])),
('txcreate6', (21,'transaction creation with corrected vsize (6)', [[['addrs'],21]])),
('txsign6', (21,'transaction signing with corrected vsize', [[['mmdat','rawtx'],21]])),
)
def __init__(self,trunner,cfgs,spawn):
self.lbl_id = ('account','label')[g.coin=='BTC'] # update as other coins adopt Core's label API
if g.coin in ('BTC','BCH','LTC'):
self.tx_fee = {'btc':'0.0001','bch':'0.001','ltc':'0.01'}[g.coin.lower()]
self.txbump_fee = {'btc':'123s','bch':'567s','ltc':'12345s'}[g.coin.lower()]
return TestSuiteBase.__init__(self,trunner,cfgs,spawn)
def _get_addrfile_checksum(self,display=False):
addrfile = self.get_file_with_ext('addrs')
silence()
from mmgen.addr import AddrList
chk = AddrList(addrfile).chksum
if opt.verbose and display: msg('Checksum: {}'.format(cyan(chk)))
end_silence()
return chk
def walletgen_dfl_wallet(self,seed_len=None):
return self.walletgen(seed_len=seed_len,gen_dfl_wallet=True)
def export_seed_dfl_wallet(self,pf,desc='seed data',out_fmt='seed'):
return self.export_seed(wf=None,desc=desc,out_fmt=out_fmt,pf=pf)
def addrgen_dfl_wallet(self,pf=None,check_ref=False):
return self.addrgen(wf=None,pf=pf,check_ref=check_ref)
def txcreate_dfl_wallet(self,addrfile):
return self.txcreate_common(sources=['15'])
def txsign_dfl_wallet(self,txfile,pf='',save=True,has_label=False):
return self.txsign(txfile,wf=None,pf=pf,save=save,has_label=has_label)
def passchg_dfl_wallet(self,pf):
return self.passchg(wf=None,pf=pf)
def walletchk_newpass_dfl_wallet(self,pf):
return self.walletchk_newpass(wf=None,pf=pf)
def delete_dfl_wallet(self,pf):
self.write_to_tmpfile('del_dw_run',b'',binary=True)
if opt.no_dw_delete: return 'skip'
for wf in [f for f in os.listdir(g.data_dir) if f[-6:]=='.mmdat']:
os.unlink(joinpath(g.data_dir,wf))
self.spawn('',msg_only=True)
self.have_dfl_wallet = False
return 'ok'
def walletgen(self,del_dw_run='dummy',seed_len=None,gen_dfl_wallet=False):
self.write_to_tmpfile(pwfile,self.wpasswd+'\n')
args = ['-p1']
if not gen_dfl_wallet: args += ['-d',self.tmpdir]
if seed_len: args += ['-l',str(seed_len)]
t = self.spawn('mmgen-walletgen', args + [self.usr_rand_arg])
t.license()
t.usr_rand(self.usr_rand_chars)
t.expect('Generating')
t.passphrase_new('new MMGen wallet',self.wpasswd)
t.label()
if not self.have_dfl_wallet and gen_dfl_wallet:
t.expect('move it to the data directory? (Y/n): ','y')
self.have_dfl_wallet = True
t.written_to_file('MMGen wallet')
t.req_exit_val = 0
return t
def passchg(self,wf,pf,label_action='cmdline'):
silence()
self.write_to_tmpfile(pwfile,get_data_from_file(pf))
end_silence()
add_args = {'cmdline': ['-d',self.tmpdir,'-L','Changed label (UTF-8) α'],
'keep': ['-d',self.tr.trash_dir,'--keep-label'],
'user': ['-d',self.tr.trash_dir]
}[label_action]
t = self.spawn('mmgen-passchg', add_args + [self.usr_rand_arg, '-p2'] + ([],[wf])[bool(wf)])
t.license()
t.passphrase('MMGen wallet',self.cfgs['1']['wpasswd'],pwtype='old')
t.expect_getend('Hash preset changed to ')
t.passphrase('MMGen wallet',self.wpasswd,pwtype='new') # reuse passphrase?
t.expect('Repeat passphrase: ',self.wpasswd+'\n')
t.usr_rand(self.usr_rand_chars)
if label_action == 'user':
t.expect('Enter a wallet label.*: ','Interactive Label (UTF-8) α\n',regex=True)
t.expect_getend(('Label changed to ','Reusing label ')[label_action=='keep'])
# t.expect_getend('Key ID changed: ')
if not wf:
t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
t.written_to_file('New wallet')
t.expect('Securely deleting old wallet')
# t.expect('Okay to WIPE 1 regular file ? (Yes/No)','Yes\n')
t.expect('Wallet passphrase has changed')
t.expect_getend('has been changed to ')
else:
t.written_to_file('MMGen wallet')
return t
def passchg_keeplabel(self,wf,pf):
return self.passchg(wf,pf,label_action='keep')
def passchg_usrlabel(self,wf,pf):
return self.passchg(wf,pf,label_action='user')
def walletchk_newpass(self,wf,pf):
return self.walletchk(wf,pf,pw=True)
def _write_fake_data_to_file(self,d):
unspent_data_file = joinpath(self.tmpdir,'unspent.json')
write_data_to_file(unspent_data_file,d,'Unspent outputs',silent=True,ignore_opt_outdir=True)
os.environ['MMGEN_BOGUS_WALLET_DATA'] = unspent_data_file
bwd_msg = 'MMGEN_BOGUS_WALLET_DATA={}'.format(unspent_data_file)
if opt.print_cmdline: msg(bwd_msg)
if opt.log: self.tr.log_fd.write(bwd_msg + ' ')
if opt.verbose or opt.exact_output:
sys.stderr.write("Fake transaction wallet data written to file {!r}\n".format(unspent_data_file))
def _create_fake_unspent_entry(self,coinaddr,al_id=None,idx=None,lbl=None,non_mmgen=False,segwit=False):
if 'S' not in g.proto.mmtypes: segwit = False
if lbl: lbl = ' ' + lbl
k = coinaddr.addr_fmt
if not segwit and k == 'p2sh': k = 'p2pkh'
s_beg,s_end = { 'p2pkh': ('76a914','88ac'),
'p2sh': ('a914','87'),
'bech32': (g.proto.witness_vernum_hex.decode()+'14','') }[k]
amt1,amt2 = {'btc':(10,40),'bch':(10,40),'ltc':(1000,4000)}[g.coin.lower()]
ret = {
self.lbl_id: '{}:{}'.format(g.proto.base_coin.lower(),coinaddr) if non_mmgen \
else ('{}:{}{}'.format(al_id,idx,lbl)),
'vout': int(getrandnum(4) % 8),
'txid': hexlify(os.urandom(32)),
'amount': g.proto.coin_amt('{}.{}'.format(amt1 + getrandnum(4) % amt2, getrandnum(4) % 100000000)),
'address': coinaddr,
'spendable': False,
'scriptPubKey': '{}{}{}'.format(s_beg,coinaddr.hex.decode(),s_end).encode(),
'confirmations': getrandnum(3) // 2 # max: 8388608 (7 digits)
}
return ret
def _create_fake_unspent_data(self,adata,tx_data,non_mmgen_input='',non_mmgen_input_compressed=True):
out = []
for d in tx_data.values():
al = adata.addrlist(d['al_id'])
for n,(idx,coinaddr) in enumerate(al.addrpairs()):
lbl = get_label(do_shuffle=True)
out.append(self._create_fake_unspent_entry(coinaddr,d['al_id'],idx,lbl,segwit=d['segwit']))
if n == 0: # create a duplicate address. This means addrs_per_wallet += 1
out.append(self._create_fake_unspent_entry(coinaddr,d['al_id'],idx,lbl,segwit=d['segwit']))
if non_mmgen_input:
from mmgen.obj import PrivKey
privkey = PrivKey(os.urandom(32),compressed=non_mmgen_input_compressed,pubkey_type='std')
from mmgen.addr import AddrGenerator,KeyGenerator
rand_coinaddr = AddrGenerator('p2pkh').to_addr(KeyGenerator('std').to_pubhex(privkey))
of = joinpath(self.cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn)
write_data_to_file(of, privkey.wif+'\n','compressed {} key'.format(g.proto.name),
silent=True,ignore_opt_outdir=True)
out.append(self._create_fake_unspent_entry(rand_coinaddr,non_mmgen=True,segwit=False))
return out
def _create_tx_data(self,sources,addrs_per_wallet=addrs_per_wallet):
from mmgen.addr import AddrData,AddrList
from mmgen.obj import AddrIdxList
tx_data,ad = {},AddrData()
for s in sources:
afile = get_file_with_ext(self.cfgs[s]['tmpdir'],'addrs')
al = AddrList(afile)
ad.add(al)
aix = AddrIdxList(fmt_str=self.cfgs[s]['addr_idx_list'])
if len(aix) != addrs_per_wallet:
raise TestSuiteFatalException(
'Address index list length != {}: {}'.format(addrs_per_wallet,repr(aix)))
tx_data[s] = {
'addrfile': afile,
'chk': al.chksum,
'al_id': al.al_id,
'addr_idxs': aix[-2:],
'segwit': self.cfgs[s]['segwit']
}
return ad,tx_data
def _make_txcreate_cmdline(self,tx_data):
from mmgen.obj import PrivKey
privkey = PrivKey(os.urandom(32),compressed=True,pubkey_type='std')
t = ('p2pkh','segwit')['S' in g.proto.mmtypes]
from mmgen.addr import AddrGenerator,KeyGenerator
rand_coinaddr = AddrGenerator(t).to_addr(KeyGenerator('std').to_pubhex(privkey))
# total of two outputs must be < 10 BTC (<1000 LTC)
mods = {'btc':(6,4),'bch':(6,4),'ltc':(600,400)}[g.coin.lower()]
for k in self.cfgs:
self.cfgs[k]['amts'] = [None,None]
for idx,mod in enumerate(mods):
self.cfgs[k]['amts'][idx] = '{}.{}'.format(getrandnum(4) % mod, str(getrandnum(4))[:5])
cmd_args = ['--outdir='+self.tmpdir]
for num in tx_data:
s = tx_data[num]
cmd_args += [
'{}:{},{}'.format(s['al_id'],s['addr_idxs'][0],self.cfgs[num]['amts'][0]),
]
# + one change address and one BTC address
if num is list(tx_data.keys())[-1]:
cmd_args += ['{}:{}'.format(s['al_id'],s['addr_idxs'][1])]
cmd_args += ['{},{}'.format(rand_coinaddr,self.cfgs[num]['amts'][1])]
return cmd_args + [tx_data[num]['addrfile'] for num in tx_data]
def txcreate_common(self,
sources=['1'],
non_mmgen_input='',
do_label=False,
txdo_args=[],
add_args=[],
view='n',
addrs_per_wallet=addrs_per_wallet,
non_mmgen_input_compressed=True,
cmdline_inputs=False):
if opt.verbose or opt.exact_output:
sys.stderr.write(green('Generating fake tracking wallet info\n'))
silence()
ad,tx_data = self._create_tx_data(sources,addrs_per_wallet)
dfake = self._create_fake_unspent_data(ad,tx_data,non_mmgen_input,non_mmgen_input_compressed)
self._write_fake_data_to_file(repr(dfake))
cmd_args = self._make_txcreate_cmdline(tx_data)
if cmdline_inputs:
from mmgen.tx import TwLabel
cmd_args = ['--inputs={},{},{},{},{},{}'.format(
TwLabel(dfake[0][self.lbl_id]).mmid,dfake[1]['address'],
TwLabel(dfake[2][self.lbl_id]).mmid,dfake[3]['address'],
TwLabel(dfake[4][self.lbl_id]).mmid,dfake[5]['address']
),'--outdir='+self.tr.trash_dir] + cmd_args[1:]
end_silence()
if opt.verbose or opt.exact_output: sys.stderr.write('\n')
t = self.spawn(
'mmgen-'+('txcreate','txdo')[bool(txdo_args)],
([],['--rbf'])[g.proto.cap('rbf')] +
['-f',self.tx_fee,'-B'] + add_args + cmd_args + txdo_args)
if t.expect([('Get','Transac')[cmdline_inputs],'Unable to connect to \S+'],regex=True) == 1:
raise TestSuiteException('\n'+t.p.after)
if cmdline_inputs:
t.written_to_file('tion')
return t
t.license()
if txdo_args and add_args: # txdo4
t.do_decrypt_ka_data(hp='1',pw=self.cfgs['14']['kapasswd'])
for num in tx_data:
t.expect_getend('ting address data from file ')
chk=t.expect_getend(r'Checksum for address data .*?: ',regex=True)
verify_checksum_or_exit(tx_data[num]['chk'],chk)
# not in tracking wallet warning, (1 + num sources) times
for num in range(len(tx_data) + 1):
t.expect('Continue anyway? (y/N): ','y')
outputs_list = [(addrs_per_wallet+1)*i + 1 for i in range(len(tx_data))]
if non_mmgen_input: outputs_list.append(len(tx_data)*(addrs_per_wallet+1) + 1)
self.txcreate_ui_common(t,self.test_name,
menu=(['M'],['M','D','m','g'])[self.test_name=='txcreate'],
inputs=' '.join(map(str,outputs_list)),
add_comment=('',ref_tx_label_lat_cyr_gr)[do_label],
non_mmgen_inputs=(0,1)[bool(non_mmgen_input and not txdo_args)],
view=view)
return t
def txcreate(self,addrfile):
return self.txcreate_common(sources=['1'],add_args=['--vsize-adj=1.01'])
def txbump(self,txfile,prepend_args=[],seed_args=[]):
if not g.proto.cap('rbf'):
msg('Skipping RBF'); return 'skip'
args = prepend_args + ['--quiet','--outdir='+self.tmpdir,txfile] + seed_args
t = self.spawn('mmgen-txbump',args)
if seed_args:
t.do_decrypt_ka_data(hp='1',pw=self.cfgs['14']['kapasswd'])
t.expect('deduct the fee from (Hit ENTER for the change output): ','1\n')
# Fee must be > tx_fee + network relay fee (currently 0.00001)
t.expect('OK? (Y/n): ','\n')
t.expect('Enter transaction fee: ',self.txbump_fee+'\n')
t.expect('OK? (Y/n): ','\n')
if seed_args: # sign and send
t.do_comment(False,has_label=True)
for cnum,desc in (('1','incognito data'),('3','MMGen wallet'),('4','MMGen wallet')):
t.passphrase(desc,self.cfgs[cnum]['wpasswd'])
self._do_confirm_send(t,quiet=not g.debug,confirm_send=True)
if g.debug:
t.written_to_file('Transaction')
else:
t.do_comment(False)
t.expect('Save transaction? (y/N): ','y')
t.written_to_file('Transaction')
os.unlink(txfile) # our tx file replaces the original
cmd = 'touch ' + joinpath(self.tmpdir,'txbump')
os.system(cmd.encode())
return t
def txsend(self,sigfile,bogus_send=True,extra_opts=[]):
if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = ''
t = self.spawn('mmgen-txsend', extra_opts + ['-d',self.tmpdir,sigfile])
if not bogus_send: os.environ['MMGEN_BOGUS_SEND'] = '1'
self.txsend_ui_common(t,self.test_name,view='t',add_comment='')
return t
def txdo(self,addrfile,wallet):
t = self.txcreate_common(sources=['1'],txdo_args=[wallet])
self.txsign_ui_common(t,self.test_name,view='n',do_passwd=True)
self.txsend_ui_common(t,self.test_name)
return t
def _walletconv_export(self,wf,desc,uargs=[],out_fmt='w',pf=None,out_pw=False):
opts = ['-d',self.tmpdir,'-o',out_fmt] + uargs + \
([],[wf])[bool(wf)] + ([],['-P',pf])[bool(pf)]
t = self.spawn('mmgen-walletconv',opts)
t.license()
if not pf:
t.passphrase('MMGen wallet',self.wpasswd)
if out_pw:
t.passphrase_new('new '+desc,self.wpasswd)
t.usr_rand(self.usr_rand_chars)
if ' '.join(desc.split()[-2:]) == 'incognito data':
m = 'Generating encryption key from OS random data '
t.expect(m); t.expect(m)
incog_id = t.expect_getend('New Incog Wallet ID: ')
t.expect(m)
if desc == 'hidden incognito data':
self.write_to_tmpfile(incog_id_fn,incog_id)
ret = t.expect(['Create? (Y/n): ',"'YES' to confirm: "])
if ret == 0:
t.send('\n')
t.expect('Enter file size: ',str(hincog_bytes)+'\n')
else:
t.send('YES\n')
if out_fmt == 'w': t.label()
return t.written_to_file(capfirst(desc),oo=True),t
def export_seed(self,wf,desc='seed data',out_fmt='seed',pf=None):
f,t = self._walletconv_export(wf,desc=desc,out_fmt=out_fmt,pf=pf)
silence()
msg('{}: {}'.format(capfirst(desc),cyan(get_data_from_file(f,desc))))
end_silence()
return t
def export_hex(self,wf,desc='hexadecimal seed data',out_fmt='hex',pf=None):
return self.export_seed(wf,desc=desc,out_fmt=out_fmt,pf=pf)
def export_mnemonic(self,wf):
return self.export_seed(wf,desc='mnemonic data',out_fmt='words')
def export_incog(self,wf,desc='incognito data',out_fmt='i',add_args=[]):
uargs = ['-p1',self.usr_rand_arg] + add_args
f,t = self._walletconv_export(wf,desc=desc,out_fmt=out_fmt,uargs=uargs,out_pw=True)
return t
def export_incog_hex(self,wf):
return self.export_incog(wf,desc='hex incognito data',out_fmt='xi')
# TODO: make outdir and hidden incog compatible (ignore --outdir and warn user?)
def export_incog_hidden(self,wf):
rf = joinpath(self.tmpdir,hincog_fn)
add_args = ['-J','{},{}'.format(rf,hincog_offset)]
return self.export_incog(
wf,desc='hidden incognito data',out_fmt='hi',add_args=add_args)
def addrgen_seed(self,wf,foo,desc='seed data',in_fmt='seed'):
stdout = (False,True)[desc=='seed data'] #capture output to screen once
add_args = ([],['-S'])[bool(stdout)] + self.segwit_arg
t = self.spawn('mmgen-addrgen', add_args +
['-i'+in_fmt,'-d',self.tmpdir,wf,self.addr_idx_list])
t.license()
t.expect_getend('Valid {} for Seed ID '.format(desc))
vmsg('Comparing generated checksum with checksum from previous address file')
chk = t.expect_getend(r'Checksum for address data .*?: ',regex=True)
if stdout: t.read()
verify_checksum_or_exit(self._get_addrfile_checksum(),chk)
if in_fmt != 'seed':
t.no_overwrite()
t.req_exit_val = 1
return t
def addrgen_hex(self,wf,foo,desc='hexadecimal seed data',in_fmt='hex'):
return self.addrgen_seed(wf,foo,desc=desc,in_fmt=in_fmt)
def addrgen_mnemonic(self,wf,foo):
return self.addrgen_seed(wf,foo,desc='mnemonic data',in_fmt='words')
def addrgen_incog(self,wf=[],foo='',in_fmt='i',desc='incognito data',args=[]):
t = self.spawn('mmgen-addrgen', args + self.segwit_arg + ['-i'+in_fmt,'-d',self.tmpdir]+
([],[wf])[bool(wf)] + [self.addr_idx_list])
t.license()
t.expect_getend('Incog Wallet ID: ')
t.hash_preset(desc,'1')
t.passphrase('{} \w{{8}}'.format(desc),self.wpasswd)
vmsg('Comparing generated checksum with checksum from address file')
chk = t.expect_getend(r'Checksum for address data .*?: ',regex=True)
verify_checksum_or_exit(self._get_addrfile_checksum(),chk)
t.no_overwrite()
t.req_exit_val = 1
return t
def addrgen_incog_hex(self,wf,foo):
return self.addrgen_incog(wf,'',in_fmt='xi',desc='hex incognito data')
def addrgen_incog_hidden(self,wf,foo):
rf = joinpath(self.tmpdir,hincog_fn)
return self.addrgen_incog([],'',in_fmt='hi',desc='hidden incognito data',
args=['-H','{},{}'.format(rf,hincog_offset),'-l',str(hincog_seedlen)])
def txsign_keyaddr(self,keyaddr_file,txfile):
t = self.spawn('mmgen-txsign', ['-d',self.tmpdir,'-M',keyaddr_file,txfile])
t.license()
t.do_decrypt_ka_data(hp='1',pw=self.kapasswd)
t.view_tx('n')
self.txsign_end(t)
return t
def txcreate_ni(self,addrfile):
return self.txcreate_common(sources=['1'],cmdline_inputs=True,add_args=['--yes'])
def walletgen2(self,del_dw_run='dummy'):
return self.walletgen(seed_len=128)
def addrgen2(self,wf):
return self.addrgen(wf,pf='')
def txcreate2(self,addrfile):
return self.txcreate_common(sources=['2'])
def txsign2(self,txf1,wf1,txf2,wf2):
t = self.spawn('mmgen-txsign', ['-d',self.tmpdir,txf1,wf1,txf2,wf2])
t.license()
for cnum in ('1','2'):
t.view_tx('n')
t.passphrase('MMGen wallet',self.cfgs[cnum]['wpasswd'])
self.txsign_end(t,cnum)
return t
def export_mnemonic2(self,wf):
return self.export_mnemonic(wf)
def walletgen3(self,del_dw_run='dummy'):
return self.walletgen()
def addrgen3(self,wf):
return self.addrgen(wf,pf='')
def txcreate3(self,addrfile1,addrfile2):
return self.txcreate_common(sources=['1','3'])
def txsign3(self,wf1,wf2,txf2):
t = self.spawn('mmgen-txsign', ['-d',self.tmpdir,wf1,wf2,txf2])
t.license()
t.view_tx('n')
for cnum in ('1','3'):
t.passphrase('MMGen wallet',self.cfgs[cnum]['wpasswd'])
self.txsign_end(t)
return t
walletgen14 = walletgen
addrgen14 = TestSuiteShared.addrgen
keyaddrgen14 = TestSuiteShared.keyaddrgen
def walletgen4(self,del_dw_run='dummy'):
bwf = joinpath(self.tmpdir,self.bw_filename)
make_brainwallet_file(bwf)
seed_len = str(self.seed_len)
args = ['-d',self.tmpdir,'-p1',self.usr_rand_arg,'-l'+seed_len,'-ib']
t = self.spawn('mmgen-walletconv', args + [bwf])
t.license()
t.passphrase_new('new MMGen wallet',self.wpasswd)
t.usr_rand(self.usr_rand_chars)
t.label()
t.written_to_file('MMGen wallet')
return t
def addrgen4(self,wf):
return self.addrgen(wf,pf='')
def txcreate4(self,f1,f2,f3,f4,f5,f6):
return self.txcreate_common(sources=['1','2','3','4','14'],non_mmgen_input='4',do_label=True,view='y')
def txsign4(self,f1,f2,f3,f4,f5,f6):
non_mm_file = joinpath(self.tmpdir,non_mmgen_fn)
a = ['-d',self.tmpdir,'-i','brain','-b'+self.bw_params,'-p1','-k',non_mm_file,'-M',f6,f1,f2,f3,f4,f5]
t = self.spawn('mmgen-txsign',a)
t.license()
t.do_decrypt_ka_data(hp='1',pw=self.cfgs['14']['kapasswd'])
t.view_tx('t')
for cnum,desc in (('1','incognito data'),('3','MMGen wallet')):
t.passphrase('{}'.format(desc),self.cfgs[cnum]['wpasswd'])
self.txsign_end(t,has_label=True)
return t
def txdo4(self,f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12):
non_mm_file = joinpath(self.tmpdir,non_mmgen_fn)
add_args = ['-d',self.tmpdir,'-i','brain','-b'+self.bw_params,'-p1','-k',non_mm_file,'-M',f12]
self.get_file_with_ext('sigtx',delete_all=True) # delete tx signed by txsign4
t = self.txcreate_common(sources=['1','2','3','4','14'],
non_mmgen_input='4',do_label=True,txdo_args=[f7,f8,f9,f10],add_args=add_args)
for cnum,desc in (('1','incognito data'),('3','MMGen wallet')):
t.passphrase('{}'.format(desc),self.cfgs[cnum]['wpasswd'])
self.txsign_ui_common(t,self.test_name)
self.txsend_ui_common(t,self.test_name)
cmd = 'touch ' + joinpath(self.tmpdir,'txdo')
os.system(cmd.encode())
return t
def txbump4(self,f1,f2,f3,f4,f5,f6,f7,f8,f9): # f7:txfile,f9:'txdo'
non_mm_file = joinpath(self.tmpdir,non_mmgen_fn)
return self.txbump(f7,prepend_args=['-p1','-k',non_mm_file,'-M',f1],seed_args=[f2,f3,f4,f5,f6,f8])
def walletgen5(self,del_dw_run='dummy'):
return self.walletgen()
def addrgen5(self,wf):
return self.addrgen(wf,pf='')
def txcreate5(self,addrfile):
return self.txcreate_common(sources=['20'],non_mmgen_input='20',non_mmgen_input_compressed=False)
def txsign5(self,txf,wf,bad_vsize=True,add_args=[]):
non_mm_file = joinpath(self.tmpdir,non_mmgen_fn)
t = self.spawn('mmgen-txsign', add_args + ['-d',self.tmpdir,'-k',non_mm_file,txf,wf])
t.license()
t.view_tx('n')
t.passphrase('MMGen wallet',self.cfgs['20']['wpasswd'])
if bad_vsize:
t.expect('Estimated transaction vsize')
t.expect('1 transaction could not be signed')
exit_val = 2
else:
t.do_comment(False)
t.expect('Save signed transaction? (Y/n): ','y')
exit_val = 0
t.read()
t.req_exit_val = exit_val
return t
def walletgen6(self,del_dw_run='dummy'):
return self.walletgen()
def addrgen6(self,wf):
return self.addrgen(wf,pf='')
def txcreate6(self,addrfile):
return self.txcreate_common(
sources=['21'],non_mmgen_input='21',non_mmgen_input_compressed=False,add_args=['--vsize-adj=1.08'])
def txsign6(self,txf,wf):
return self.txsign5(txf,wf,bad_vsize=False,add_args=['--vsize-adj=1.08'])

162
test/test_py_d/ts_misc.py Executable file
View file

@ -0,0 +1,162 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_misc.py: Miscellaneous test groups for the test.py test suite
"""
from mmgen.globalvars import g
from test.common import *
from test.test_py_d.common import *
from test.test_py_d.ts_base import *
from test.test_py_d.ts_main import TestSuiteMain
class TestSuiteHelp(TestSuiteBase):
'help and usage screens'
tmpdir_nums = []
passthru_opts = ('coin','testnet')
cmd_group = (
('helpscreens', (1,'help screens', [])),
('longhelpscreens', (1,'help screens (--longhelp)',[])),
('tool_help', (1,"'mmgen-tool' usage screen",[])),
('test_help', (1,"'test.py' help screens",[])),
)
def helpscreens(self,arg='--help'):
scripts = (
'walletgen','walletconv','walletchk','txcreate','txsign','txsend','txdo','txbump',
'addrgen','addrimport','keygen','passchg','tool','passgen','regtest','autosign')
for s in scripts:
t = self._run_cmd('mmgen-'+s,[arg],extra_desc='(mmgen-{})'.format(s),no_output=True)
return t
def longhelpscreens(self):
return self.helpscreens(arg='--longhelp')
def _run_cmd( self, cmd_name,
cmd_args = [],
no_msg = False,
extra_desc = '',
cmd_dir = 'cmds',
no_output = False):
t = self.spawn( cmd_name,
args = cmd_args,
no_msg = no_msg,
extra_desc = extra_desc,
cmd_dir = cmd_dir,
no_output = no_output)
t.read()
ret = t.p.wait()
if ret == 0:
msg('OK')
else:
rdie(1,"\n'{}' returned {}".format(self.test_name,ret))
t.skip_ok = True
return t
def tool_help(self):
self._run_cmd('mmgen-tool',['help'],extra_desc="('mmgen-tool help')")
return self._run_cmd('mmgen-tool',['usage'],extra_desc="('mmgen-tool usage')")
def test_help(self):
self._run_cmd('test.py',['-h'],cmd_dir='test')
self._run_cmd('test.py',['-L'],cmd_dir='test',extra_desc='(cmd group list)')
return self._run_cmd('test.py',['-l'],cmd_dir='test',extra_desc='(cmd list)')
class TestSuiteTool(TestSuiteMain,TestSuiteBase):
"tests for interactive 'mmgen-tool' commands"
networks = ('btc',)
segwit_opts_ok = False
tmpdir_nums = [9]
enc_infn = 'tool_encrypt.in'
cmd_group = (
('tool_find_incog_data', (9,"'mmgen-tool find_incog_data'", [[[hincog_fn],1],[[incog_id_fn],1]])),
('tool_encrypt', (9,"'mmgen-tool encrypt' (random data)", [])),
('tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", [[[enc_infn+'.mmenc'],9]])),
# ('tool_encrypt_ref', (9,"'mmgen-tool encrypt' (reference text)", [])),
)
def tool_encrypt(self):
infile = joinpath(self.tmpdir,self.enc_infn)
write_to_file(infile,os.urandom(1033),binary=True)
t = self.spawn('mmgen-tool',['-d',self.tmpdir,self.usr_rand_arg,'encrypt',infile])
t.usr_rand(self.usr_rand_chars)
t.hash_preset('user data','1')
t.passphrase_new('user data',tool_enc_passwd)
t.written_to_file('Encrypted data')
return t
def tool_decrypt(self,f1):
out_fn = 'tool_encrypt.out'
t = self.spawn('mmgen-tool',['-d',self.tmpdir,'decrypt',f1,'outfile='+out_fn,'hash_preset=1'])
t.passphrase('user data',tool_enc_passwd)
t.written_to_file('Decrypted data')
d1 = self.read_from_tmpfile(self.enc_infn,binary=True)
d2 = self.read_from_tmpfile(out_fn,binary=True)
cmp_or_die(d1,d2)
return t
def tool_find_incog_data(self,f1,f2):
i_id = read_from_file(f2).rstrip()
vmsg('Incog ID: {}'.format(cyan(i_id)))
t = self.spawn('mmgen-tool',['-d',self.tmpdir,'find_incog_data',f1,i_id])
o = t.expect_getend('Incog data for ID {} found at offset '.format(i_id))
os.unlink(f1)
cmp_or_die(hincog_offset,int(o))
return t
class TestSuiteRefTX(TestSuiteMain,TestSuiteBase):
'create a reference transaction file (administrative command)'
segwit_opts_ok = False
passthru_opts = ('coin','testnet')
tmpdir_nums = [31,32,33,34]
cmd_group = (
('ref_tx_addrgen1', (31,'address generation (legacy)', [[[],1]])),
('ref_tx_addrgen2', (32,'address generation (compressed)', [[[],1]])),
('ref_tx_addrgen3', (33,'address generation (segwit)', [[[],1]])),
('ref_tx_addrgen4', (34,'address generation (bech32)', [[[],1]])),
('ref_tx_txcreate', (31,'transaction creation',
([['addrs'],31],[['addrs'],32],[['addrs'],33],[['addrs'],34]))),
)
def __init__(self,trunner,cfgs,spawn):
for n in self.tmpdir_nums:
cfgs[str(n)].update({ 'addr_idx_list': '1-2',
'segwit': n in (33,34),
'dep_generators': { 'addrs':'ref_tx_addrgen'+str(n)[-1] }})
return TestSuiteMain.__init__(self,trunner,cfgs,spawn)
def ref_tx_addrgen(self,atype):
if atype not in g.proto.mmtypes: return
t = self.spawn('mmgen-addrgen',['--outdir='+self.tmpdir,'--type='+atype,dfl_words_file,'1-2'])
t.read()
return t
def ref_tx_addrgen1(self): return self.ref_tx_addrgen(atype='L')
def ref_tx_addrgen2(self): return self.ref_tx_addrgen(atype='C')
def ref_tx_addrgen3(self): return self.ref_tx_addrgen(atype='S')
def ref_tx_addrgen4(self): return self.ref_tx_addrgen(atype='B')
def ref_tx_txcreate(self,f1,f2,f3,f4):
sources = ['31','32']
if 'S' in g.proto.mmtypes: sources += ['33']
if 'B' in g.proto.mmtypes: sources += ['34']
return self.txcreate_common(
addrs_per_wallet = 2,
sources = sources,
add_args = ['--locktime=1320969600'],
do_label = True )

165
test/test_py_d/ts_ref.py Executable file
View file

@ -0,0 +1,165 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_ref.py: Reference file tests for the test.py test suite
"""
import os
from mmgen.globalvars import g
from mmgen.opts import opt
from test.common import *
from test.test_py_d.common import *
from test.test_py_d.ts_base import *
from test.test_py_d.ts_shared import *
wpasswd = 'reference password'
class TestSuiteRef(TestSuiteBase,TestSuiteShared):
'saved reference files'
tmpdir_nums = [8]
networks = ('btc','btc_tn','ltc','ltc_tn')
passthru_opts = ('coin','testnet')
sources = {
'ref_addrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.addrs',
'ref_segwitaddrfile':'98831F3A{}-S[1,31-33,500-501,1010-1011]{}.addrs',
'ref_bech32addrfile':'98831F3A{}-B[1,31-33,500-501,1010-1011]{}.addrs',
'ref_keyaddrfile': '98831F3A{}[1,31-33,500-501,1010-1011]{}.akeys.mmenc',
'ref_passwdfile': '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
'ref_tx_file': { # data shared with ref_altcoin, autosign
'btc': ('0B8D5A[15.31789,14,tl=1320969600].rawtx',
'0C7115[15.86255,14,tl=1320969600].testnet.rawtx'),
'ltc': ('AF3CDF-LTC[620.76194,1453,tl=1320969600].rawtx',
'A5A1E0-LTC[1454.64322,1453,tl=1320969600].testnet.rawtx'),
'bch': ('460D4D-BCH[10.19764,tl=1320969600].rawtx',
'359FD5-BCH[6.68868,tl=1320969600].testnet.rawtx'),
'eth': ('88FEFD-ETH[23.45495,40000].rawtx',
'B472BD-ETH[23.45495,40000].testnet.rawtx'),
'mm1': ('5881D2-MM1[1.23456,50000].rawtx',
'6BDB25-MM1[1.23456,50000].testnet.rawtx'),
'etc': ('ED3848-ETC[1.2345,40000].rawtx','')
},
}
chk_data = {
'ref_addrfile_chksum': {
'btc': ('6FEF 6FB9 7B13 5D91','424E 4326 CFFE 5F51'),
'ltc': ('AD52 C3FE 8924 AAF0','4EBE 2E85 E969 1B30'),
},
'ref_segwitaddrfile_chksum': {
'btc': ('06C1 9C87 F25C 4EE6','072C 8B07 2730 CB7A'),
'ltc': ('63DF E42A 0827 21C3','5DD1 D186 DBE1 59F2'),
},
'ref_bech32addrfile_chksum': {
'btc': ('9D2A D4B6 5117 F02E','0527 9C39 6C1B E39A'),
'ltc': ('FF1C 7939 5967 AB82','ED3D 8AA4 BED4 0B40'),
},
'ref_keyaddrfile_chksum': {
'btc': ('9F2D D781 1812 8BAD','88CC 5120 9A91 22C2'),
'ltc': ('B804 978A 8796 3ED4','98B5 AC35 F334 0398'),
},
'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB',
}
cmd_group = ( # TODO: move to tooltest2
('ref_addrfile_chk', 'saved reference address file'),
('ref_segwitaddrfile_chk','saved reference address file (segwit)'),
('ref_bech32addrfile_chk','saved reference address file (bech32)'),
('ref_keyaddrfile_chk','saved reference key-address file'),
('ref_passwdfile_chk', 'saved reference password file'),
# Create the fake inputs:
# ('txcreate8', 'transaction creation (8)'),
('ref_tx_chk', 'signing saved reference tx file'),
('ref_brain_chk_spc3', 'saved brainwallet (non-standard spacing)'),
('ref_tool_decrypt', 'decryption of saved MMGen-encrypted file'),
)
def _get_ref_subdir_by_coin(self,coin):
return {'btc': '',
'bch': '',
'ltc': 'litecoin',
'eth': 'ethereum',
'etc': 'ethereum_classic',
'xmr': 'monero',
'zec': 'zcash',
'dash': 'dash' }[coin.lower()]
@property
def ref_subdir(self):
return self._get_ref_subdir_by_coin(g.coin)
def ref_addrfile_chk(self,ftype='addr',coin=None,subdir=None,pfx=None,mmtype=None,add_args=[]):
af_key = 'ref_{}file'.format(ftype)
af_fn = TestSuiteRef.sources[af_key].format(pfx or self.altcoin_pfx,'' if coin else self.tn_ext)
af = joinpath(ref_dir,(subdir or self.ref_subdir,'')[ftype=='passwd'],af_fn)
coin_arg = [] if coin == None else ['--coin='+coin]
tool_cmd = ftype.replace('segwit','').replace('bech32','')+'file_chksum'
t = self.spawn('mmgen-tool',coin_arg+[tool_cmd,af]+add_args)
if ftype == 'keyaddr':
t.do_decrypt_ka_data(hp=ref_kafile_hash_preset,pw=ref_kafile_pass)
rc = self.chk_data[ 'ref_' + ftype + 'file_chksum' +
('_'+coin.lower() if coin else '') +
('_'+mmtype if mmtype else '')]
ref_chksum = rc if (ftype == 'passwd' or coin) else rc[g.proto.base_coin.lower()][g.testnet]
t.expect(chksum_pat,regex=True)
m = t.p.match.group(0)
t.read()
cmp_or_die(ref_chksum,m)
return t
def ref_segwitaddrfile_chk(self):
if not 'S' in g.proto.mmtypes:
return skip('not supported')
else:
return self.ref_addrfile_chk(ftype='segwitaddr')
def ref_bech32addrfile_chk(self):
if not 'B' in g.proto.mmtypes:
return skip('not supported')
else:
return self.ref_addrfile_chk(ftype='bech32addr')
def ref_keyaddrfile_chk(self):
return self.ref_addrfile_chk(ftype='keyaddr')
def ref_passwdfile_chk(self):
return self.ref_addrfile_chk(ftype='passwd')
def ref_tx_chk(self):
fn = self.sources['ref_tx_file'][g.coin.lower()][bool(self.tn_ext)]
if not fn: return
tf = joinpath(ref_dir,self.ref_subdir,fn)
wf = dfl_words_file
self.write_to_tmpfile(pwfile,wpasswd)
pf = joinpath(self.tmpdir,pwfile)
return self.txsign(tf,wf,pf,save=False,has_label=True,do_passwd=False)
def ref_brain_chk_spc3(self):
return self.ref_brain_chk(bw_file=ref_bw_file_spc)
def ref_tool_decrypt(self):
f = joinpath(ref_dir,ref_enc_fn)
disable_debug()
dec_file = joinpath(self.tmpdir,'famous.txt')
t = self.spawn('mmgen-tool', ['-q','decrypt',f,'outfile='+dec_file,'hash_preset=1'])
restore_debug()
t.passphrase('user data',tool_enc_passwd)
t.written_to_file('Decrypted data')
dec_txt = read_from_file(dec_file)
imsg_r(dec_txt)
cmp_or_die(sample_text,dec_txt)
return t

274
test/test_py_d/ts_ref_3seed.py Executable file
View file

@ -0,0 +1,274 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_ref_3seed.py: Saved and generated reference file tests for 128, 192 and
256-bit seeds for the test.py test suite
"""
from mmgen.globalvars import g
from mmgen.opts import opt
from test.common import *
from test.test_py_d.common import *
from test.test_py_d.ts_base import *
from test.test_py_d.ts_shared import *
from test.test_py_d.ts_wallet import TestSuiteWalletConv
class TestSuiteRef3Seed(TestSuiteBase,TestSuiteShared):
'saved and generated reference data for 128-, 192- and 256-bit seeds'
networks = ('btc','btc_tn','ltc','ltc_tn')
passthru_opts = ('coin','testnet')
mmtypes = (None,)
tmpdir_nums = [6,7,8]
addr_idx_list_in = '1010,500-501,31-33,1,33,500,1011'
pass_idx_list_in = '1,4,9-11,1100'
chk_data = {
'refaddrgen_legacy_1': {
'btc': ('B230 7526 638F 38CB','A9DC 5A13 12CB 1317'),
'ltc': ('2B23 5E97 848A B961','AEC3 E774 0B21 0202'),
},
'refaddrgen_segwit_1': {
'btc': ('9914 6D10 2307 F348','83C8 A6B6 ADA8 25B2'),
'ltc': ('CC09 A190 B7DF B7CD','0425 7893 C6F1 ECA3'),
},
'refaddrgen_bech32_1': {
'btc': ('C529 D686 31AA ACD4','21D0 26AD 3A22 5465'),
'ltc': ('3DFB CFCC E180 DC9D','8C72 D5C2 07E0 5F7B'),
},
'refaddrgen_compressed_1': {
'btc': ('95EB 8CC0 7B3B 7856','16E6 6170 154D 2202'),
'ltc': ('35D5 8ECA 9A42 46C3','15B3 5492 D3D3 6854'),
},
'refkeyaddrgen_legacy_1': {
'btc': ('CF83 32FB 8A8B 08E2','1F67 B73A FF8C 5D15'),
'ltc': ('1896 A26C 7F14 2D01','FA0E CD4E ADAF DBF4'),
},
'refkeyaddrgen_compressed_1': {
'btc': ('E43A FA46 5751 720A','FDEE 8E45 1C0A 02AD'),
'ltc': ('7603 2FE3 2145 FFAD','3FE0 5A8E 5FBE FF3E'),
},
'refkeyaddrgen_segwit_1': {
'btc': ('C13B F717 D4E8 CF59','BB71 175C 5416 19D8'),
'ltc': ('054B 9794 55B4 5D82','DE85 3CF3 9636 FE2E'),
},
'refkeyaddrgen_bech32_1': {
'btc': ('934F 1C33 6C06 B18C','A283 5BAB 7AF3 3EA4'),
'ltc': ('A6AD DF53 5968 7B6A','9572 43E0 A4DC 0B2E'),
},
'refpasswdgen_1': 'EB29 DC4F 924B 289F',
'ref_b32passwdgen_1': '37B6 C218 2ABC 7508',
'ref_hexpasswdgen_1': '523A F547 0E69 8323',
'refaddrgen_legacy_2': {
'btc': ('8C17 A5FA 0470 6E89','764C 66F9 7502 AAEA'),
'ltc': ('2B77 A009 D5D0 22AD','51D1 979D 0A35 F24B'),
},
'refaddrgen_compressed_2': {
'btc': ('2615 8401 2E98 7ECA','A386 EE07 A356 906D'),
'ltc': ('197C C48C 3C37 AB0F','8DDC 5FE3 BFF9 1226'),
},
'refaddrgen_segwit_2': {
'btc': ('91C4 0414 89E4 2089','BF9F C67F ED22 A47B'),
'ltc': ('8F12 FA7B 9F12 594C','2609 8494 A23C F836'),
},
'refaddrgen_bech32_2': {
'btc': ('2AA3 78DF B965 82EB','027B 1C1F 7FB2 D859'),
'ltc': ('951C 8FB2 FCA5 87D1','4A5D 67E0 8210 FEF2'),
},
'refkeyaddrgen_legacy_2': {
'btc': ('9648 5132 B98E 3AD9','1BD3 5A36 D51C 256D'),
'ltc': ('DBD4 FAB6 7E46 CD07','8822 3FDF FEC0 6A8C'),
},
'refkeyaddrgen_compressed_2': {
'btc': ('6D6D 3D35 04FD B9C3','94BF 4BCF 10B2 394B'),
'ltc': ('F5DA 9D60 6798 C4E9','7918 88DE 9096 DD7A'),
},
'refkeyaddrgen_segwit_2': {
'btc': ('C98B DF08 A3D5 204B','7E7F DF50 FE04 6F68'),
'ltc': ('1829 7FE7 2567 CB91','BE92 D19C 7589 EF30'),
},
'refkeyaddrgen_bech32_2': {
'btc': ('4A6B 3762 DF30 9368','12DD 1888 36BA 85F7'),
'ltc': ('5C12 FDD4 17AB F179','E195 B28C 59C4 C5EC'),
},
'refpasswdgen_2': 'ADEA 0083 094D 489A',
'ref_b32passwdgen_2': '2A28 C5C7 36EC 217A',
'ref_hexpasswdgen_2': 'B11C AC6A 1464 608D',
'refaddrgen_legacy_3': {
'btc': ('6FEF 6FB9 7B13 5D91','424E 4326 CFFE 5F51'),
'ltc': ('AD52 C3FE 8924 AAF0','4EBE 2E85 E969 1B30'),
},
'refaddrgen_compressed_3': {
'btc': ('A33C 4FDE F515 F5BC','6C48 AA57 2056 C8C8'),
'ltc': ('3FC0 8F03 C2D6 BD19','4C0A 49B6 2DD1 1BE0'),
},
'refaddrgen_segwit_3': {
'btc': ('06C1 9C87 F25C 4EE6','072C 8B07 2730 CB7A'),
'ltc': ('63DF E42A 0827 21C3','5DD1 D186 DBE1 59F2'),
},
'refaddrgen_bech32_3': {
'btc': ('9D2A D4B6 5117 F02E','0527 9C39 6C1B E39A'),
'ltc': ('FF1C 7939 5967 AB82','ED3D 8AA4 BED4 0B40'),
},
'refkeyaddrgen_legacy_3': {
'btc': ('9F2D D781 1812 8BAD','88CC 5120 9A91 22C2'),
'ltc': ('B804 978A 8796 3ED4','98B5 AC35 F334 0398'),
},
'refkeyaddrgen_compressed_3': {
'btc': ('420A 8EB5 A9E2 7814','F43A CB4A 81F3 F735'),
'ltc': ('8D1C 781F EB7F 44BC','05F3 5C68 FD31 FCEF'),
},
'refkeyaddrgen_segwit_3': {
'btc': ('A447 12C2 DD14 5A9B','C770 7391 C415 21F9'),
'ltc': ('E8A3 9F6E E164 A521','D3D5 BFDD F5D5 20BD'),
},
'refkeyaddrgen_bech32_3': {
'btc': ('D0DD BDE3 87BE 15AE','7552 D70C AAB8 DEAA'),
'ltc': ('74A0 7DD5 963B 6326','2CDA A007 4B9F E9A5'),
},
'refpasswdgen_3': '2D6D 8FBA 422E 1315',
'ref_b32passwdgen_3': 'F6C1 CDFB 97D9 FCAE',
'ref_hexpasswdgen_3': 'BD4F A0AC 8628 4BE4',
}
cmd_group = (
# reading
('ref_wallet_chk', ([],'saved reference wallet')),
('ref_seed_chk', ([],'saved seed file')),
('ref_hex_chk', ([],'saved mmhex file')),
('ref_mn_chk', ([],'saved mnemonic file')),
('ref_hincog_chk', ([],'saved hidden incog reference wallet')),
('ref_brain_chk', ([],'saved brainwallet')), # in ts_shared
# generating new reference ('abc' brainwallet) files:
('refwalletgen', ([],'gen new refwallet')),
('refaddrgen_legacy', (['mmdat',pwfile],'new refwallet addr chksum (uncompressed)')),
('refaddrgen_compressed', (['mmdat',pwfile],'new refwallet addr chksum (compressed)')),
('refaddrgen_segwit', (['mmdat',pwfile],'new refwallet addr chksum (segwit)')),
('refaddrgen_bech32', (['mmdat',pwfile],'new refwallet addr chksum (bech32)')),
('refkeyaddrgen_legacy', (['mmdat',pwfile],'new refwallet key-addr chksum (uncompressed)')),
('refkeyaddrgen_compressed', (['mmdat',pwfile],'new refwallet key-addr chksum (compressed)')),
('refkeyaddrgen_segwit', (['mmdat',pwfile],'new refwallet key-addr chksum (segwit)')),
('refkeyaddrgen_bech32', (['mmdat',pwfile],'new refwallet key-addr chksum (bech32)')),
('refpasswdgen', (['mmdat',pwfile],'new refwallet passwd file chksum')),
('ref_b32passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
('ref_hexpasswdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
)
def __init__(self,trunner,cfgs,spawn):
for k,j in self.cmd_group:
for n in (1,2,3): # 128,192,256 bits
setattr(self,'{}_{}'.format(k,n),getattr(self,k))
for n in self.tmpdir_nums:
cfgs[str(n)]['addr_idx_list'] = self.addr_idx_list_in
cfgs[str(n)]['pass_idx_list'] = self.pass_idx_list_in
return TestSuiteBase.__init__(self,trunner,cfgs,spawn)
def ref_wallet_chk(self):
wf = joinpath(ref_dir,TestSuiteWalletConv.sources[str(self.seed_len)]['ref_wallet'])
return self.walletchk(wf,pf=None,pw=True,sid=self.seed_id)
def ref_ss_chk(self,ss=None):
wf = joinpath(ref_dir,'{}.{}'.format(self.seed_id,ss.ext))
return self.walletchk(wf,pf=None,desc=ss.desc,sid=self.seed_id)
def ref_seed_chk(self):
from mmgen.seed import SeedFile
return self.ref_ss_chk(ss=SeedFile)
def ref_hex_chk(self):
from mmgen.seed import HexSeedFile
return self.ref_ss_chk(ss=HexSeedFile)
def ref_mn_chk(self):
from mmgen.seed import Mnemonic
return self.ref_ss_chk(ss=Mnemonic)
def ref_hincog_chk(self,desc='hidden incognito data'):
source = TestSuiteWalletConv.sources[str(self.seed_len)]
for wtype,edesc,of_arg in ('hic_wallet','',[]), \
('hic_wallet_old','(old format)',['-O']):
ic_arg = ['-H{},{}'.format(joinpath(ref_dir,source[wtype]),ref_wallet_incog_offset)]
slarg = ['-l{} '.format(self.seed_len)]
hparg = ['-p1']
if wtype == 'hic_wallet_old' and opt.profile: msg('')
t = self.spawn('mmgen-walletchk',
slarg + hparg + of_arg + ic_arg,
extra_desc=edesc)
t.passphrase(desc,self.wpasswd)
if wtype == 'hic_wallet_old':
t.expect('Is the Seed ID correct? (Y/n): ','\n')
chk = t.expect_getend('Seed ID: ')
t.close()
cmp_or_die(self.seed_id,chk)
ok_msg()
t.skip_ok = True
return t
def brainwalletgen_ref(self):
sl_arg = '-l{}'.format(self.seed_len)
hp_arg = '-p{}'.format(ref_wallet_hash_preset)
label = "test.py ref. wallet (pw '{}', seed len {}) α".format(ref_wallet_brainpass,self.seed_len)
bf = 'ref.mmbrain'
args = ['-d',self.tmpdir,hp_arg,sl_arg,'-ib','-L',label]
self.write_to_tmpfile(bf,ref_wallet_brainpass)
self.write_to_tmpfile(pwfile,self.wpasswd)
t = self.spawn('mmgen-walletconv', args + [self.usr_rand_arg])
t.license()
t.expect('Enter brainwallet: ', ref_wallet_brainpass+'\n')
t.passphrase_new('new MMGen wallet',self.wpasswd)
t.usr_rand(self.usr_rand_chars)
sid = os.path.basename(t.written_to_file('MMGen wallet')).split('-')[0]
cmp_or_die(sid,self.seed_id,desc='Seed ID')
return t
def refwalletgen(self):
return self.brainwalletgen_ref()
def refaddrgen_legacy(self,wf,pf):
return self.addrgen(wf,pf=pf,check_ref=True,mmtype='legacy')
def refaddrgen_compressed(self,wf,pf):
return self.addrgen(wf,pf=pf,check_ref=True,mmtype='compressed')
def refaddrgen_segwit(self,wf,pf):
return self.addrgen(wf,pf=pf,check_ref=True,mmtype='segwit')
def refaddrgen_bech32(self,wf,pf):
return self.addrgen(wf,pf=pf,check_ref=True,mmtype='bech32')
def refkeyaddrgen_legacy(self,wf,pf,mmtype='legacy'):
return self.keyaddrgen(wf,pf,check_ref=True)
def refkeyaddrgen_compressed(self,wf,pf):
return self.keyaddrgen(wf,pf=pf,check_ref=True,mmtype='compressed')
def refkeyaddrgen_segwit(self,wf,pf):
return self.keyaddrgen(wf,pf=pf,check_ref=True,mmtype='segwit')
def refkeyaddrgen_bech32(self,wf,pf):
return self.keyaddrgen(wf,pf=pf,check_ref=True,mmtype='bech32')
def refpasswdgen(self,wf,pf):
return self.addrgen(wf,pf,check_ref=True,ftype='pass',id_str='alice@crypto.org')
def ref_b32passwdgen(self,wf,pf):
ea = ['--base32','--passwd-len','17']
return self.addrgen(wf,pf,check_ref=True,ftype='pass32',id_str='фубар@crypto.org',extra_args=ea)
def ref_hexpasswdgen(self,wf,pf):
ea = ['--hex']
return self.addrgen(wf,pf,check_ref=True,ftype='passhex',id_str='фубар@crypto.org',extra_args=ea)

205
test/test_py_d/ts_ref_altcoin.py Executable file
View file

@ -0,0 +1,205 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_ref_altcoin.py: Altcoin reference file tests for the test.py test suite
"""
import os
from mmgen.globalvars import g
from mmgen.opts import opt
from test.test_py_d.common import *
from test.test_py_d.ts_ref import *
from test.test_py_d.ts_base import *
class TestSuiteRefAltcoin(TestSuiteRef,TestSuiteBase):
'saved and generated altcoin reference files'
tmpdir_nums = [8]
networks = ('btc',)
chk_data = {
'ref_addrfile_chksum_zec': '903E 7225 DD86 6E01',
'ref_addrfile_chksum_zec_z': '9C7A 72DC 3D4A B3AF',
'ref_addrfile_chksum_xmr': '4369 0253 AC2C 0E38',
'ref_addrfile_chksum_dash':'FBC1 6B6A 0988 4403',
'ref_addrfile_chksum_eth': 'E554 076E 7AF6 66A3',
'ref_addrfile_chksum_etc': 'E97A D796 B495 E8BC',
'ref_keyaddrfile_chksum_zec': 'F05A 5A5C 0C8E 2617',
'ref_keyaddrfile_chksum_zec_z': '6B87 9B2D 0D8D 8D1E',
'ref_keyaddrfile_chksum_xmr': 'E0D7 9612 3D67 404A',
'ref_keyaddrfile_chksum_dash': 'E83D 2C63 FEA2 4142',
'ref_keyaddrfile_chksum_eth': 'E400 70D9 0AE3 C7C2',
'ref_keyaddrfile_chksum_etc': 'EF49 967D BD6C FE45',
}
cmd_group = (
('ref_altcoin_tx_chk', 'signing saved reference tx files'),
('ref_addrfile_gen_eth', 'generate address file (ETH)'),
('ref_addrfile_gen_etc', 'generate address file (ETC)'),
('ref_addrfile_gen_dash', 'generate address file (DASH)'),
('ref_addrfile_gen_zec', 'generate address file (ZEC-T)'),
('ref_addrfile_gen_zec_z','generate address file (ZEC-Z)'),
('ref_addrfile_gen_xmr', 'generate address file (XMR)'),
# we test the old ed25519 library in test-release.sh, so skip this
# ('ref_addrfile_gen_xmr_old','generate address file (XMR - old (slow) ed25519 library)'),
('ref_keyaddrfile_gen_eth', 'generate key-address file (ETH)'),
('ref_keyaddrfile_gen_etc', 'generate key-address file (ETC)'),
('ref_keyaddrfile_gen_dash', 'generate key-address file (DASH)'),
('ref_keyaddrfile_gen_zec', 'generate key-address file (ZEC-T)'),
('ref_keyaddrfile_gen_zec_z','generate key-address file (ZEC-Z)'),
('ref_keyaddrfile_gen_xmr', 'generate key-address file (XMR)'),
('ref_addrfile_chk_eth', 'reference address file (ETH)'),
('ref_addrfile_chk_etc', 'reference address file (ETC)'),
('ref_addrfile_chk_dash','reference address file (DASH)'),
('ref_addrfile_chk_zec', 'reference address file (ZEC-T)'),
('ref_addrfile_chk_zec_z','reference address file (ZEC-Z)'),
('ref_addrfile_chk_xmr', 'reference address file (XMR)'),
('ref_keyaddrfile_chk_eth', 'reference key-address file (ETH)'),
('ref_keyaddrfile_chk_etc', 'reference key-address file (ETC)'),
('ref_keyaddrfile_chk_dash','reference key-address file (DASH)'),
('ref_keyaddrfile_chk_zec', 'reference key-address file (ZEC-T)'),
('ref_keyaddrfile_chk_zec_z','reference key-address file (ZEC-Z)'),
('ref_keyaddrfile_chk_xmr', 'reference key-address file (XMR)'),
)
# Check saved transaction files for *all* configured altcoins
# Though this basically duplicates the autosign test, here we do everything
# via the command line, so it's worth doing
def ref_altcoin_tx_chk(self):
self.write_to_tmpfile(pwfile,dfl_wpasswd)
pf = joinpath(self.tmpdir,pwfile)
from mmgen.protocol import init_coin
for k in ('bch','eth','mm1','etc'):
coin,token = ('eth','mm1') if k == 'mm1' else (k,None)
ref_subdir = self._get_ref_subdir_by_coin(coin)
for tn in (False,True):
if tn and coin == 'etc': continue
g.testnet = tn
init_coin(coin)
fn = TestSuiteRef.sources['ref_tx_file'][token or coin][bool(tn)]
tf = joinpath(ref_dir,ref_subdir,fn)
wf = dfl_words_file
e = ['--coin='+coin,'--testnet='+('0','1')[tn]]
if token: e += ['--token='+token]
t = self.txsign(tf, wf, pf,
save = False,
has_label = True,
do_passwd = False,
extra_desc = '({}{})'.format(token or coin,' testnet' if tn else ''),
extra_opts = e )
ok_msg()
g.testnet = False
init_coin('btc')
t.skip_ok = True
return t
def ref_altcoin_addrgen(self,coin,mmtype,gen_what='addr',coin_suf='',add_args=[]):
wf = dfl_words_file
t = self.spawn('mmgen-{}gen'.format(gen_what),
['-Sq','--coin='+coin] +
(['--type='+mmtype] if mmtype else []) +
add_args +
[wf,dfl_addr_idx_list])
if gen_what == 'key':
t.expect('Encrypt key list? (y/N): ','N')
chk = t.expect_getend(r'.* data checksum for \S*: ',regex=True)
chk_ref = self.chk_data['ref_{}addrfile_chksum_{}{}'.format(('','key')[gen_what=='key'],coin.lower(),coin_suf)]
t.read()
cmp_or_die(chk,chk_ref,desc='{}list data checksum'.format(gen_what))
return t
def ref_addrfile_gen_eth(self):
return self.ref_altcoin_addrgen(coin='ETH',mmtype='ethereum')
def ref_addrfile_gen_etc(self):
return self.ref_altcoin_addrgen(coin='ETC',mmtype='ethereum')
def ref_addrfile_gen_dash(self):
return self.ref_altcoin_addrgen(coin='DASH',mmtype='compressed')
def ref_addrfile_gen_zec(self):
return self.ref_altcoin_addrgen(coin='ZEC',mmtype='compressed')
def ref_addrfile_gen_zec_z(self):
return self.ref_altcoin_addrgen(coin='ZEC',mmtype='zcash_z',coin_suf='_z')
def ref_addrfile_gen_xmr(self):
return self.ref_altcoin_addrgen(coin='XMR',mmtype='monero')
def ref_addrfile_gen_xmr_old(self):
return self.ref_altcoin_addrgen(coin='XMR',mmtype='monero',add_args=['--use-old-ed25519'])
def ref_keyaddrfile_gen_eth(self):
return self.ref_altcoin_addrgen(coin='ETH',mmtype='ethereum',gen_what='key')
def ref_keyaddrfile_gen_etc(self):
return self.ref_altcoin_addrgen(coin='ETC',mmtype='ethereum',gen_what='key')
def ref_keyaddrfile_gen_dash(self):
return self.ref_altcoin_addrgen(coin='DASH',mmtype='compressed',gen_what='key')
def ref_keyaddrfile_gen_zec(self):
return self.ref_altcoin_addrgen(coin='ZEC',mmtype='compressed',gen_what='key')
def ref_keyaddrfile_gen_zec_z(self):
return self.ref_altcoin_addrgen(coin='ZEC',mmtype='zcash_z',coin_suf='_z',gen_what='key')
def ref_keyaddrfile_gen_xmr(self):
return self.ref_altcoin_addrgen(coin='XMR',mmtype='monero',gen_what='key')
def ref_addrfile_chk_eth(self):
return self.ref_addrfile_chk(ftype='addr',coin='ETH',subdir='ethereum',pfx='-ETH')
def ref_addrfile_chk_etc(self):
return self.ref_addrfile_chk(ftype='addr',coin='ETC',subdir='ethereum_classic',pfx='-ETC')
def ref_addrfile_chk_dash(self):
return self.ref_addrfile_chk(ftype='addr',coin='DASH',subdir='dash',pfx='-DASH-C')
def ref_addrfile_chk_zec(self):
return self.ref_addrfile_chk(ftype='addr',coin='ZEC',subdir='zcash',pfx='-ZEC-C')
def ref_addrfile_chk_zec_z(self):
if self.skip_for_win(): return 'skip'
return self.ref_addrfile_chk(ftype='addr',coin='ZEC',subdir='zcash',pfx='-ZEC-Z',mmtype='z')
def ref_addrfile_chk_xmr(self):
return self.ref_addrfile_chk(ftype='addr',coin='XMR',subdir='monero',pfx='-XMR-M')
def ref_keyaddrfile_chk_eth(self):
return self.ref_addrfile_chk(ftype='keyaddr',coin='ETH',subdir='ethereum',pfx='-ETH')
def ref_keyaddrfile_chk_etc(self):
return self.ref_addrfile_chk(ftype='keyaddr',coin='ETC',subdir='ethereum_classic',pfx='-ETC')
def ref_keyaddrfile_chk_dash(self):
return self.ref_addrfile_chk(ftype='keyaddr',coin='DASH',subdir='dash',pfx='-DASH-C')
def ref_keyaddrfile_chk_zec(self):
return self.ref_addrfile_chk(ftype='keyaddr',coin='ZEC',subdir='zcash',pfx='-ZEC-C')
def ref_keyaddrfile_chk_zec_z(self):
if self.skip_for_win(): return 'skip'
return self.ref_addrfile_chk(ftype='keyaddr',coin='ZEC',subdir='zcash',pfx='-ZEC-Z',mmtype='z')
def ref_keyaddrfile_chk_xmr(self):
return self.ref_addrfile_chk(ftype='keyaddr',coin='XMR',subdir='monero',pfx='-XMR-M')

598
test/test_py_d/ts_regtest.py Executable file
View file

@ -0,0 +1,598 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_regtest.py: Regtest tests for the test.py test suite
"""
import os,subprocess
from decimal import Decimal
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.util import die,gmsg,write_data_to_file
from mmgen.protocol import CoinProtocol
from mmgen.addr import AddrList
from test.common import *
from test.test_py_d.common import *
rt_pw = 'abc-α'
rt_data = {
'tx_fee': {'btc':'0.0001','bch':'0.001','ltc':'0.01'},
'rtFundAmt': {'btc':'500','bch':'500','ltc':'5500'},
'rtFee': {
'btc': ('20s','10s','60s','31s','10s','20s'),
'bch': ('20s','10s','60s','0.0001','10s','20s'),
'ltc': ('1000s','500s','1500s','0.05','400s','1000s')
},
'rtBals': {
'btc': ('499.9999488','399.9998282','399.9998147','399.9996877',
'52.99990000','946.99933647','999.99923647','52.9999',
'946.99933647'),
'bch': ('499.9999484','399.9999194','399.9998972','399.9997692',
'46.78900000','953.20966920','999.99866920','46.789',
'953.2096692'),
'ltc': ('5499.99744','5399.994425','5399.993885','5399.987535',
'52.99000000','10946.93753500','10999.92753500','52.99',
'10946.937535'),
},
'rtBals_gb': {
'btc': ('116.77629233','283.22339537'),
'bch': ('116.77637483','283.22339437'),
'ltc': ('5116.77036263','283.21717237')
},
'rtBobOp3': {'btc':'S:2','bch':'L:3','ltc':'S:2'},
'rtAmts': {
'btc': ('500',),
'bch': ('500',),
'ltc': ('5500',)
}
}
from test.test_py_d.ts_base import *
from test.test_py_d.ts_shared import *
class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
'transacting and tracking wallet operations via regtest mode'
networks = ('btc','ltc','bch')
passthru_opts = ('coin',)
tmpdir_nums = [17]
cmd_group = (
('setup', 'regtest (Bob and Alice) mode setup'),
('walletgen_bob', 'wallet generation (Bob)'),
('walletgen_alice', 'wallet generation (Alice)'),
('addrgen_bob', 'address generation (Bob)'),
('addrgen_alice', 'address generation (Alice)'),
('addrimport_bob', "importing Bob's addresses"),
('addrimport_alice', "importing Alice's addresses"),
('fund_bob', "funding Bob's wallet"),
('fund_alice', "funding Alice's wallet"),
('bob_bal1', "Bob's balance"),
('bob_add_label', "adding a 40-character UTF-8 encoded label"),
('bob_twview', "viewing Bob's tracking wallet"),
('bob_split1', "splitting Bob's funds"),
('generate', 'mining a block'),
('bob_bal2', "Bob's balance"),
('bob_bal2a', "Bob's balance (age_fmt=confs)"),
('bob_bal2b', "Bob's balance (showempty=1)"),
('bob_bal2c', "Bob's balance (showempty=1 minconf=2 age_fmt=days)"),
('bob_bal2d', "Bob's balance (minconf=2)"),
('bob_bal2e', "Bob's balance (showempty=1 sort=age)"),
('bob_bal2f', "Bob's balance (showempty=1 sort=age,reverse)"),
('bob_rbf_send', 'sending funds to Alice (RBF)'),
('get_mempool1', 'mempool (before RBF bump)'),
('bob_rbf_bump', 'bumping RBF transaction'),
('get_mempool2', 'mempool (after RBF bump)'),
('generate', 'mining a block'),
('bob_bal3', "Bob's balance"),
('bob_pre_import', 'sending to non-imported address'),
('generate', 'mining a block'),
('bob_import_addr', 'importing non-MMGen address with --rescan'),
('bob_bal4', "Bob's balance (after import with rescan)"),
('bob_import_list', 'importing flat address list'),
('bob_split2', "splitting Bob's funds"),
('generate', 'mining a block'),
('bob_bal5', "Bob's balance"),
('bob_bal5_getbalance', "Bob's balance"),
('bob_send_non_mmgen', 'sending funds to Alice (from non-MMGen addrs)'),
('generate', 'mining a block'),
('alice_add_label1', 'adding a label'),
('alice_chk_label1', 'the label'),
('alice_add_label2', 'adding a label'),
('alice_chk_label2', 'the label'),
('alice_edit_label1', 'editing a label'),
('alice_chk_label3', 'the label'),
('alice_remove_label1', 'removing a label'),
('alice_chk_label4', 'the label'),
('alice_add_label_coinaddr', 'adding a label using the coin address'),
('alice_chk_label_coinaddr', 'the label'),
('alice_add_label_badaddr1', 'adding a label with invalid address'),
('alice_add_label_badaddr2', 'adding a label with invalid address for this chain'),
('alice_add_label_badaddr3', 'adding a label with wrong MMGen address'),
('alice_add_label_badaddr4', 'adding a label with wrong coin address'),
('alice_bal_rpcfail', 'RPC failure code'),
('alice_send_estimatefee', 'tx creation with no fee on command line'),
('generate', 'mining a block'),
('bob_bal6', "Bob's balance"),
('bob_alice_bal', "Bob and Alice's balances"),
('alice_bal2', "Alice's balance"),
('stop', 'stopping regtest daemon'),
)
def __init__(self,trunner,cfgs,spawn):
coin = g.coin.lower()
for k in rt_data:
globals()[k] = rt_data[k][coin] if coin in rt_data[k] else None
return TestSuiteBase.__init__(self,trunner,cfgs,spawn)
def _add_comments_to_addr_file(self,addrfile,outfile,use_labels=False):
silence()
gmsg("Adding comments to address file '{}'".format(addrfile))
a = AddrList(addrfile)
for n,idx in enumerate(a.idxs(),1):
if use_labels:
a.set_comment(idx,get_label())
else:
if n % 2: a.set_comment(idx,'Test address {}'.format(n))
a.format(enable_comments=True)
write_data_to_file(outfile,a.fmt_data,silent=True,ignore_opt_outdir=True)
end_silence()
def setup(self):
os.environ['MMGEN_BOGUS_WALLET_DATA'] = ''
if g.testnet:
die(2,'--testnet option incompatible with regtest test suite')
try: shutil.rmtree(joinpath(self.tr.data_dir,'regtest'))
except: pass
os.environ['MMGEN_TEST_SUITE'] = '' # mnemonic is piped to stdin, so stop being a terminal
t = self.spawn('mmgen-regtest',['-n','setup'])
os.environ['MMGEN_TEST_SUITE'] = '1'
for s in ('Starting setup','Creating','Mined','Creating','Creating','Setup complete'):
t.expect(s)
return t
def walletgen(self,user):
t = self.spawn('mmgen-walletgen',['-q','-r0','-p1','--'+user])
t.passphrase_new('new MMGen wallet',rt_pw)
t.label()
t.expect('move it to the data directory? (Y/n): ','y')
t.written_to_file('MMGen wallet')
return t
def walletgen_bob(self): return self.walletgen('bob')
def walletgen_alice(self): return self.walletgen('alice')
def _user_dir(self,user,coin=None):
return joinpath(self.tr.data_dir,'regtest',coin or g.coin.lower(),user)
def _user_sid(self,user):
return os.path.basename(get_file_with_ext(self._user_dir(user),'mmdat'))[:8]
def addrgen(self,user,wf=None,addr_range='1-5'):
from mmgen.addr import MMGenAddrType
for mmtype in g.proto.mmtypes:
t = self.spawn('mmgen-addrgen',
['--quiet','--'+user,'--type='+mmtype,'--outdir={}'.format(self._user_dir(user))] +
([],[wf])[bool(wf)] + [addr_range],
extra_desc='({})'.format(MMGenAddrType.mmtypes[mmtype]['name']))
t.passphrase('MMGen wallet',rt_pw)
t.written_to_file('Addresses')
ok_msg()
t.skip_ok = True
return t
def addrgen_bob(self): return self.addrgen('bob')
def addrgen_alice(self): return self.addrgen('alice')
def addrimport(self,user,sid=None,addr_range='1-5',num_addrs=5):
id_strs = { 'legacy':'', 'compressed':'-C', 'segwit':'-S', 'bech32':'-B' }
if not sid: sid = self._user_sid(user)
from mmgen.addr import MMGenAddrType
for mmtype in g.proto.mmtypes:
desc = MMGenAddrType.mmtypes[mmtype]['name']
addrfile = joinpath(self._user_dir(user),
'{}{}{}[{}]{x}.testnet.addrs'.format(
sid,self.altcoin_pfx,id_strs[desc],addr_range,
x='' if g.debug_utf8 else ''))
if mmtype == g.proto.mmtypes[0] and user == 'bob':
psave = g.proto
g.proto = CoinProtocol(g.coin,True)
self._add_comments_to_addr_file(addrfile,addrfile,use_labels=True)
g.proto = psave
t = self.spawn( 'mmgen-addrimport',
['--quiet', '--'+user, '--batch', addrfile],
extra_desc='({})'.format(desc))
if g.debug:
t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
t.expect('Importing')
t.expect('{} addresses imported'.format(num_addrs))
ok_msg()
t.skip_ok = True
return t
def addrimport_bob(self): return self.addrimport('bob')
def addrimport_alice(self): return self.addrimport('alice')
def fund_wallet(self,user,mmtype,amt,sid=None,addr_range='1-5'):
if not sid: sid = self._user_sid(user)
addr = self.get_addr_from_addrlist(user,sid,mmtype,0,addr_range=addr_range)
t = self.spawn('mmgen-regtest', ['send',str(addr),str(amt)])
t.expect('Sending {} {}'.format(amt,g.coin))
t.expect('Mined 1 block')
return t
def fund_bob(self): return self.fund_wallet('bob','C',rtFundAmt)
def fund_alice(self): return self.fund_wallet('alice',('L','S')[g.proto.cap('segwit')],rtFundAmt)
def user_twview(self,user):
t = self.spawn('mmgen-tool',['--'+user,'twview'])
t.expect(r'1\).*\b{}\b'.format(rtAmts[0]),regex=True)
t.read()
return t
def bob_twview(self):
return self.user_twview('bob')
def user_bal(self,user,bal,args=['showempty=1'],skip_check=False,exit_val=0):
t = self.spawn('mmgen-tool',['--'+user,'listaddresses'] + args)
if skip_check:
t.read()
else:
total = t.expect_getend('TOTAL: ')
cmp_or_die('{} {}'.format(bal,g.coin),total)
t.req_exit_val = exit_val
return t
def alice_bal1(self):
return self.user_bal('alice',rtFundAmt)
def alice_bal2(self):
return self.user_bal('alice',rtBals[8])
def bob_bal1(self):
return self.user_bal('bob',rtFundAmt)
def bob_bal2(self):
return self.user_bal('bob',rtBals[0])
def bob_bal2a(self):
return self.user_bal('bob',rtBals[0],args=['showempty=1','age_fmt=confs'])
def bob_bal2b(self):
return self.user_bal('bob',rtBals[0],args=['showempty=1'])
def bob_bal2c(self):
return self.user_bal('bob',rtBals[0],args=['showempty=1','minconf=2','age_fmt=days'],skip_check=True)
def bob_bal2d(self):
return self.user_bal('bob',rtBals[0],args=['minconf=2'],skip_check=True)
def bob_bal2e(self):
return self.user_bal('bob',rtBals[0],args=['showempty=1','sort=age'])
def bob_bal2f(self):
return self.user_bal('bob',rtBals[0],args=['showempty=1','sort=age,reverse'])
def bob_bal3(self):
return self.user_bal('bob',rtBals[1])
def bob_bal4(self):
return self.user_bal('bob',rtBals[2])
def bob_bal5(self):
return self.user_bal('bob',rtBals[3])
def bob_bal6(self):
return self.user_bal('bob',rtBals[7])
def bob_bal5_getbalance(self):
t_ext,t_mmgen = rtBals_gb[0],rtBals_gb[1]
assert Decimal(t_ext) + Decimal(t_mmgen) == Decimal(rtBals[3])
t = self.spawn('mmgen-tool',['--bob','getbalance'])
t.expect(r'\n[0-9A-F]{8}: .* '+t_mmgen,regex=True)
t.expect(r'\nNon-MMGen: .* '+t_ext,regex=True)
t.expect(r'\nTOTAL: .* '+rtBals[3],regex=True)
t.read()
return t
def bob_alice_bal(self):
t = self.spawn('mmgen-regtest',['get_balances'])
t.expect('Switching')
ret = t.expect_getend("Bob's balance:").strip()
cmp_or_die(rtBals[4],ret)
ret = t.expect_getend("Alice's balance:").strip()
cmp_or_die(rtBals[5],ret)
ret = t.expect_getend("Total balance:").strip()
cmp_or_die(rtBals[6],ret)
return t
def user_txdo( self, user, fee, outputs_cl, outputs_list,
extra_args = [],
wf = None,
do_label = False,
bad_locktime = False,
full_tx_view = False ):
os.environ['MMGEN_BOGUS_SEND'] = ''
t = self.spawn('mmgen-txdo',
['-d',self.tmpdir,'-B','--'+user] +
(['--tx-fee='+fee] if fee else []) +
extra_args + ([],[wf])[bool(wf)] + outputs_cl)
os.environ['MMGEN_BOGUS_SEND'] = '1'
self.txcreate_ui_common(t,'txdo',
menu = ['M'],
inputs = outputs_list,
file_desc = 'Signed transaction',
interactive_fee = (tx_fee,'')[bool(fee)],
add_comment = ref_tx_label_jp,
view = 't',save=True)
t.passphrase('MMGen wallet',rt_pw)
t.written_to_file('Signed transaction')
self._do_confirm_send(t)
s,exit_val = (('Transaction sent',0),("can't be included",1))[bad_locktime]
t.expect(s)
t.req_exit_val = exit_val
return t
def bob_split1(self):
sid = self._user_sid('bob')
outputs_cl = [sid+':C:1,100', sid+':L:2,200',sid+':'+rtBobOp3]
return self.user_txdo('bob',rtFee[0],outputs_cl,'1',do_label=True,full_tx_view=True)
def get_addr_from_addrlist(self,user,sid,mmtype,idx,addr_range='1-5'):
id_str = { 'L':'', 'S':'-S', 'C':'-C', 'B':'-B' }[mmtype]
ext = '{}{}{}[{}]{x}.testnet.addrs'.format(
sid,self.altcoin_pfx,id_str,addr_range,x='' if g.debug_utf8 else '')
addrfile = get_file_with_ext(self._user_dir(user),ext,no_dot=True)
psave = g.proto
g.proto = CoinProtocol(g.coin,True)
if hasattr(g.proto,'bech32_hrp_rt'):
g.proto.bech32_hrp = g.proto.bech32_hrp_rt
silence()
addr = AddrList(addrfile).data[idx].addr
end_silence()
g.proto = psave
return addr
def _create_tx_outputs(self,user,data):
sid = self._user_sid(user)
return [self.get_addr_from_addrlist(user,sid,mmtype,idx-1)+amt_str for mmtype,idx,amt_str in data]
def bob_rbf_send(self):
outputs_cl = self._create_tx_outputs('alice',(('L',1,',60'),('C',1,',40'))) # alice_sid:L:1, alice_sid:C:1
outputs_cl += [self._user_sid('bob')+':'+rtBobOp3]
return self.user_txdo('bob',rtFee[1],outputs_cl,'3',
extra_args=([],['--rbf'])[g.proto.cap('rbf')])
def bob_send_non_mmgen(self):
outputs_cl = self._create_tx_outputs('alice',(
(('L','S')[g.proto.cap('segwit')],2,',10'),
(('L','S')[g.proto.cap('segwit')],3,'')
)) # alice_sid:S:2, alice_sid:S:3
keyfile = joinpath(self.tmpdir,'non-mmgen.keys')
return self.user_txdo('bob',rtFee[3],outputs_cl,'1,4-10',
extra_args=['--keys-from-file='+keyfile,'--vsize-adj=1.02'])
def alice_send_estimatefee(self):
outputs_cl = self._create_tx_outputs('bob',(('L',1,''),)) # bob_sid:L:1
return self.user_txdo('alice',None,outputs_cl,'1') # fee=None
def user_txbump(self,user,txfile,fee,red_op):
if not g.proto.cap('rbf'):
msg('Skipping RBF'); return 'skip'
os.environ['MMGEN_BOGUS_SEND'] = ''
t = self.spawn('mmgen-txbump',
['-d',self.tmpdir,'--send','--'+user,'--tx-fee='+fee,'--output-to-reduce='+red_op] + [txfile])
os.environ['MMGEN_BOGUS_SEND'] = '1'
t.expect('OK? (Y/n): ','y') # output OK?
t.expect('OK? (Y/n): ','y') # fee OK?
t.do_comment(False,has_label=True)
t.passphrase('MMGen wallet',rt_pw)
t.written_to_file('Signed transaction')
self.txsend_ui_common(t,'txdo',bogus_send=False,file_desc='Signed transaction')
t.read()
return t
def bob_rbf_bump(self):
ext = ',{}]{x}.testnet.sigtx'.format(rtFee[1][:-1],x='' if g.debug_utf8 else '')
txfile = self.get_file_with_ext(ext,delete=False,no_dot=True)
return self.user_txbump('bob',txfile,rtFee[2],'c')
def generate(self,coin=None,num_blocks=1):
int(num_blocks)
if coin: opt.coin = coin
t = self.spawn('mmgen-regtest',['generate',str(num_blocks)])
t.expect('Mined {} block'.format(num_blocks))
return t
def _get_mempool(self):
disable_debug()
ret = self.spawn('mmgen-regtest',['show_mempool']).read()
restore_debug()
from ast import literal_eval
return literal_eval(ret.split('\n')[0]) # allow for extra output by handler at end
def get_mempool1(self):
mp = self._get_mempool()
if len(mp) != 1:
rdie(2,'Mempool has more or less than one TX!')
self.write_to_tmpfile('rbf_txid',mp[0]+'\n')
return 'ok'
def get_mempool2(self):
if not g.proto.cap('rbf'):
msg('Skipping post-RBF mempool check'); return 'skip'
mp = self._get_mempool()
if len(mp) != 1:
rdie(2,'Mempool has more or less than one TX!')
chk = self.read_from_tmpfile('rbf_txid')
if chk.strip() == mp[0]:
rdie(2,'TX in mempool has not changed! RBF bump failed')
return 'ok'
@staticmethod
def _gen_pairs(n):
disable_debug()
ret = [subprocess.check_output(
['python3',joinpath('cmds','mmgen-tool'),'--testnet=1'] +
(['--type=compressed'],[])[i==0] +
['-r0','randpair']
).decode().split() for i in range(n)]
restore_debug()
return ret
def bob_pre_import(self):
pairs = self._gen_pairs(5)
self.write_to_tmpfile('non-mmgen.keys','\n'.join([a[0] for a in pairs])+'\n')
self.write_to_tmpfile('non-mmgen.addrs','\n'.join([a[1] for a in pairs])+'\n')
return self.user_txdo('bob',rtFee[4],[pairs[0][1]],'3')
def user_import(self,user,args):
t = self.spawn('mmgen-addrimport',['--quiet','--'+user]+args)
if g.debug:
t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
t.expect('Importing')
t.expect('OK')
return t
def bob_import_addr(self):
addr = self.read_from_tmpfile('non-mmgen.addrs').split()[0]
return self.user_import('bob',['--rescan','--address='+addr])
def bob_import_list(self):
addrfile = joinpath(self.tmpdir,'non-mmgen.addrs')
return self.user_import('bob',['--addrlist',addrfile])
def bob_split2(self):
addrs = self.read_from_tmpfile('non-mmgen.addrs').split()
amts = (1.12345678,2.87654321,3.33443344,4.00990099,5.43214321)
outputs1 = list(map('{},{}'.format,addrs,amts))
sid = self._user_sid('bob')
l1,l2 = (':S',':B') if 'B' in g.proto.mmtypes else (':S',':S') if g.proto.cap('segwit') else (':L',':L')
outputs2 = [sid+':C:2,6.333', sid+':L:3,6.667',sid+l1+':4,0.123',sid+l2+':5']
return self.user_txdo('bob',rtFee[5],outputs1+outputs2,'1-2')
def user_add_label(self,user,addr,label):
t = self.spawn('mmgen-tool',['--'+user,'add_label',addr,label])
t.expect('Added label.*in tracking wallet',regex=True)
return t
def user_remove_label(self,user,addr):
t = self.spawn('mmgen-tool',['--'+user,'remove_label',addr])
t.expect('Removed label.*in tracking wallet',regex=True)
return t
def bob_add_label(self):
sid = self._user_sid('bob')
return self.user_add_label('bob',sid+':C:1',utf8_label)
def alice_add_label1(self):
sid = self._user_sid('alice')
return self.user_add_label('alice',sid+':C:1','Original Label - 月へ')
def alice_add_label2(self):
sid = self._user_sid('alice')
return self.user_add_label('alice',sid+':C:1','Replacement Label')
def alice_add_label_coinaddr(self):
mmaddr = self._user_sid('alice') + ':C:2'
t = self.spawn('mmgen-tool',['--alice','listaddress',mmaddr],no_msg=True)
btcaddr = [i for i in t.read().splitlines() if i.lstrip()[0:len(mmaddr)] == mmaddr][0].split()[1]
return self.user_add_label('alice',btcaddr,'Label added using coin address')
def alice_chk_label_coinaddr(self):
sid = self._user_sid('alice')
return self.user_chk_label('alice',sid+':C:2','Label added using coin address')
def alice_add_label_badaddr(self,addr,reply):
t = self.spawn('mmgen-tool',['--alice','add_label',addr,'(none)'])
t.expect(reply,regex=True)
return t
def alice_add_label_badaddr1(self):
return self.alice_add_label_badaddr(rt_pw,'Invalid coin address for this chain: '+rt_pw)
def alice_add_label_badaddr2(self):
addr = g.proto.pubhash2addr(b'00'*20,False) # mainnet zero address
return self.alice_add_label_badaddr(addr,'Invalid coin address for this chain: '+addr)
def alice_add_label_badaddr3(self):
addr = self._user_sid('alice') + ':C:123'
return self.alice_add_label_badaddr(addr,
"MMGen address '{}' not found in tracking wallet".format(addr))
def alice_add_label_badaddr4(self):
addr = CoinProtocol(g.coin,True).pubhash2addr(b'00'*20,False) # testnet zero address
return self.alice_add_label_badaddr(addr,
"Address '{}' not found in tracking wallet".format(addr))
def alice_bal_rpcfail(self):
addr = self._user_sid('alice') + ':C:2'
os.environ['MMGEN_RPC_FAIL_ON_COMMAND'] = 'listunspent'
t = self.spawn('mmgen-tool',['--alice','getbalance'])
os.environ['MMGEN_RPC_FAIL_ON_COMMAND'] = ''
t.expect('Method not found')
t.read()
t.req_exit_val = 3
return t
def alice_remove_label1(self):
sid = self._user_sid('alice')
return self.user_remove_label('alice',sid+':C:1')
def user_chk_label(self,user,addr,label,label_pat=None):
t = self.spawn('mmgen-tool',['--'+user,'listaddresses','all_labels=1'])
t.expect(r'{}\s+\S{{30}}\S+\s+{}\s+'.format(addr,(label_pat or label)),regex=True)
return t
def alice_chk_label1(self):
sid = self._user_sid('alice')
return self.user_chk_label('alice',sid+':C:1','Original Label - 月へ')
def alice_chk_label2(self):
sid = self._user_sid('alice')
return self.user_chk_label('alice',sid+':C:1','Replacement Label')
def alice_edit_label1(self):
return self.user_edit_label('alice','1',utf8_label)
def alice_chk_label3(self):
sid = self._user_sid('alice')
return self.user_chk_label('alice',sid+':C:1',utf8_label,label_pat=utf8_label_pat)
def alice_chk_label4(self):
sid = self._user_sid('alice')
return self.user_chk_label('alice',sid+':C:1','-')
def user_edit_label(self,user,output,label):
t = self.spawn('mmgen-txcreate',['-B','--'+user,'-i'])
t.expect(r'add \[l\]abel:.','M',regex=True)
t.expect(r'add \[l\]abel:.','l',regex=True)
t.expect(r"Enter unspent.*return to main menu\):.",output+'\n',regex=True)
t.expect(r"Enter label text.*return to main menu\):.",label+'\n',regex=True)
t.expect(r'\[q\]uit view, .*?:.','q',regex=True)
return t
def stop(self):
if opt.no_daemon_stop:
self.spawn('',msg_only=True)
msg_r('(leaving daemon running by user request)')
return 'ok'
else:
return self.spawn('mmgen-regtest',['stop'])

223
test/test_py_d/ts_shared.py Executable file
View file

@ -0,0 +1,223 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_shared.py: Shared methods for the test.py test suite
"""
import os
from mmgen.globalvars import g
from mmgen.opts import opt
from mmgen.util import ymsg
from test.test_py_d.common import *
from test.common import *
class TestSuiteShared(object):
'shared methods for the test.py test suite'
def txcreate_ui_common(self,t,caller,
menu=[],inputs='1',
file_desc='Transaction',
input_sels_prompt='to spend',
bad_input_sels=False,non_mmgen_inputs=0,
interactive_fee='',
fee_desc='transaction fee',fee_res=None,eth_fee_res=None,
add_comment='',view='t',save=True):
for choice in menu + ['q']:
t.expect(r'\[q\]uit view, .*?:.',choice,regex=True)
if bad_input_sels:
for r in ('x','3-1','9999'):
t.expect(input_sels_prompt+': ',r+'\n')
t.expect(input_sels_prompt+': ',inputs+'\n')
if not caller[:4] == 'txdo':
for i in range(non_mmgen_inputs):
t.expect('Accept? (y/N): ','y')
have_est_fee = t.expect([fee_desc+': ','OK? (Y/n): ']) == 1
if have_est_fee and not interactive_fee:
t.send('y')
else:
if have_est_fee: t.send('n')
if eth_fee_res:
t.expect('or gas price: ',interactive_fee+'\n')
else:
t.send(interactive_fee+'\n')
if fee_res: t.expect(fee_res)
t.expect('OK? (Y/n): ','y')
t.expect('(Y/n): ','\n') # chg amt OK?
t.do_comment(add_comment)
t.view_tx(view)
if not caller[:4] == 'txdo':
t.expect('(y/N): ',('n','y')[save])
t.written_to_file(file_desc)
return t
def txsign_ui_common( self,t,caller,
view = 't',
add_comment = '',
file_desc = 'Signed transaction',
ni = False,
save = True,
do_passwd = False,
has_label = False ):
txdo = caller[:4] == 'txdo'
if do_passwd:
t.passphrase('MMGen wallet',self.wpasswd)
if not ni and not txdo:
t.view_tx(view)
t.do_comment(add_comment,has_label=has_label)
t.expect('(Y/n): ',('n','y')[save])
t.written_to_file(file_desc)
return t
def txsend_ui_common( self,t,caller,
view = 'n',
add_comment = '',
file_desc = 'Sent transaction',
confirm_send = True,
bogus_send = True,
quiet = False,
has_label = False ):
txdo = caller[:4] == 'txdo'
if not txdo:
t.license() # MMGEN_NO_LICENSE is set, so does nothing
t.view_tx(view)
t.do_comment(add_comment,has_label=has_label)
self._do_confirm_send(t,quiet=quiet,confirm_send=confirm_send)
if bogus_send:
txid = ''
t.expect('BOGUS transaction NOT sent')
else:
txid = t.expect_getend('Transaction sent: ')
assert len(txid) == 64,"'{}': Incorrect txid length!".format(txid)
t.written_to_file(file_desc)
return txid
def txsign_end(self,t,tnum=None,has_label=False):
t.expect('Signing transaction')
t.do_comment(False,has_label=has_label)
t.expect('Save signed transaction.*?\? \(Y/n\): ','y',regex=True)
t.written_to_file('Signed transaction' + (' #' + tnum if tnum else ''), oo=True)
return t
def txsign( self, txfile, wf,
pf = '',
bumpf = '',
save = True,
has_label = False,
do_passwd = True,
extra_opts = [],
extra_desc = '' ):
opts = extra_opts + ['-d',self.tmpdir,txfile] + ([wf] if wf else [])
t = self.spawn('mmgen-txsign', opts, extra_desc)
t.license()
t.view_tx('n')
if do_passwd: t.passphrase('MMGen wallet',self.wpasswd)
if save:
self.txsign_end(t,has_label=has_label)
else:
t.do_comment(False,has_label=has_label)
t.expect('Save signed transaction? (Y/n): ','n')
t.req_exit_val = 1
return t
def ref_brain_chk(self,bw_file=ref_bw_file):
wf = joinpath(ref_dir,bw_file)
add_args = ['-l{}'.format(self.seed_len), '-p'+ref_bw_hash_preset]
return self.walletchk(wf,pf=None,add_args=add_args,
desc='brainwallet',sid=self.ref_bw_seed_id)
def walletchk(self,wf,pf,desc='MMGen wallet',add_args=[],sid=None,pw=False,extra_desc=''):
args = []
hp = self.hash_preset if hasattr(self,'hash_preset') else '1'
wf_arg = [wf] if wf else []
t = self.spawn('mmgen-walletchk',
add_args+args+['-p',hp]+wf_arg,
extra_desc=extra_desc)
if desc != 'hidden incognito data':
t.expect("Getting {} from file '".format(desc))
if pw:
t.passphrase(desc,self.wpasswd)
t.expect(['Passphrase is OK', 'Passphrase.* are correct'],regex=True)
chk = t.expect_getend('Valid {} for Seed ID '.format(desc))[:8]
if sid: cmp_or_die(chk,sid)
return t
def addrgen(self,wf,pf=None,check_ref=False,ftype='addr',id_str=None,extra_args=[],mmtype=None):
if not mmtype and ftype[:4] != 'pass':
mmtype = self.segwit_mmtype
cmd_pfx = (ftype,'pass')[ftype[:4]=='pass']
t = self.spawn('mmgen-{}gen'.format(cmd_pfx),
['-d',self.tmpdir] + extra_args +
([],['--type='+str(mmtype)])[bool(mmtype)] +
([],[wf])[bool(wf)] +
([],[id_str])[bool(id_str)] +
[getattr(self,'{}_idx_list'.format(cmd_pfx))],
extra_desc='({})'.format(mmtype) if mmtype in ('segwit','bech32') else '')
t.license()
t.passphrase('MMGen wallet',self.wpasswd)
t.expect('Passphrase is OK')
desc = ('address','password')[ftype[:4]=='pass']
chk = t.expect_getend(r'Checksum for {} data .*?: '.format(desc),regex=True)
if ftype[:4] == 'pass':
t.expect('Encrypt password list? (y/N): ','\n')
t.written_to_file('Password list',oo=True)
else:
t.written_to_file('Addresses',oo=True)
if check_ref:
chk_ref = (self.chk_data[self.test_name] if ftype[:4] == 'pass' else
self.chk_data[self.test_name][self.fork][g.testnet])
cmp_or_die(chk,chk_ref,desc='{}list data checksum'.format(ftype))
return t
def keyaddrgen(self,wf,pf=None,check_ref=False,mmtype=None):
if not mmtype:
mmtype = self.segwit_mmtype
args = ['-d',self.tmpdir,self.usr_rand_arg,wf,self.addr_idx_list]
t = self.spawn('mmgen-keygen',
([],['--type='+str(mmtype)])[bool(mmtype)] + args,
extra_desc='({})'.format(mmtype) if mmtype in ('segwit','bech32') else '')
t.license()
t.passphrase('MMGen wallet',self.wpasswd)
chk = t.expect_getend(r'Checksum for key-address data .*?: ',regex=True)
if check_ref:
chk_ref = self.chk_data[self.test_name][self.fork][g.testnet]
cmp_or_die(chk,chk_ref,desc='key-address list data checksum')
t.expect('Encrypt key list? (y/N): ','y')
t.usr_rand(self.usr_rand_chars)
t.hash_preset('new key list','1')
t.passphrase_new('new key list',self.kapasswd)
t.written_to_file('Encrypted secret keys',oo=True)
return t
def _do_confirm_send(self,t,quiet=False,confirm_send=True):
t.expect('Are you sure you want to broadcast this')
m = ('YES, I REALLY WANT TO DO THIS','YES')[quiet]
t.expect("'{}' to confirm: ".format(m),('',m)[confirm_send]+'\n')

227
test/test_py_d/ts_wallet.py Executable file
View file

@ -0,0 +1,227 @@
#!/usr/bin/env python3
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2019 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/>.
"""
ts_wallet.py: Wallet conversion tests for the test.py test suite
"""
import os
from mmgen.opts import opt
from test.test_py_d.common import *
from test.test_py_d.ts_base import *
from test.test_py_d.ts_shared import *
class TestSuiteWalletConv(TestSuiteBase,TestSuiteShared):
'wallet conversion to and from reference data'
networks = ('btc',)
tmpdir_nums = [11,12,13]
sources = { '128': {
'ref_wallet': 'FE3C6545-D782B529[128,1].mmdat',
'ic_wallet': 'FE3C6545-E29303EA-5E229E30[128,1].mmincog',
'ic_wallet_hex': 'FE3C6545-BC4BE3F2-32586837[128,1].mmincox',
'hic_wallet': 'FE3C6545-161E495F-BEB7548E[128,1].incog-offset123',
'hic_wallet_old': 'FE3C6545-161E495F-9860A85B[128,1].incog-old.offset123',
},
'192': {
'ref_wallet': '1378FC64-6F0F9BB4[192,1].mmdat',
'ic_wallet': '1378FC64-2907DE97-F980D21F[192,1].mmincog',
'ic_wallet_hex': '1378FC64-4DCB5174-872806A7[192,1].mmincox',
'hic_wallet': '1378FC64-B55E9958-77256FC1[192,1].incog.offset123',
'hic_wallet_old': '1378FC64-B55E9958-D85FF20C[192,1].incog-old.offset123',
},
'256': {
'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]),
'ic_wallet': '98831F3A-5482381C-18460FB1[256,1].mmincog',
'ic_wallet_hex': '98831F3A-1630A9F2-870376A9[256,1].mmincox',
'hic_wallet': '98831F3A-F59B07A0-559CEF19[256,1].incog.offset123',
'hic_wallet_old': '98831F3A-F59B07A0-848535F3[256,1].incog-old.offset123',
},
}
cmd_group = (
# reading
('ref_wallet_conv', 'conversion of saved reference wallet'),
('ref_mn_conv', 'conversion of saved mnemonic'),
('ref_seed_conv', 'conversion of saved seed file'),
('ref_hex_conv', 'conversion of saved hexadecimal seed file'),
('ref_brain_conv', 'conversion of ref brainwallet'),
('ref_incog_conv', 'conversion of saved incog wallet'),
('ref_incox_conv', 'conversion of saved hex incog wallet'),
('ref_hincog_conv', 'conversion of saved hidden incog wallet'),
('ref_hincog_conv_old','conversion of saved hidden incog wallet (old format)'),
# writing
('ref_wallet_conv_out', 'ref seed conversion to wallet'),
('ref_mn_conv_out', 'ref seed conversion to mnemonic'),
('ref_hex_conv_out', 'ref seed conversion to hex seed'),
('ref_seed_conv_out', 'ref seed conversion to seed'),
('ref_incog_conv_out', 'ref seed conversion to incog data'),
('ref_incox_conv_out', 'ref seed conversion to hex incog data'),
('ref_hincog_conv_out', 'ref seed conversion to hidden incog data'),
('ref_hincog_blkdev_conv_out', 'ref seed conversion to hidden incog data on block device')
)
def __init__(self,trunner,cfgs,spawn):
for k,j in self.cmd_group:
for n in (1,2,3):
setattr(self,'{}_{}'.format(k,n),getattr(self,k))
return TestSuiteBase.__init__(self,trunner,cfgs,spawn)
def ref_wallet_conv(self):
wf = joinpath(ref_dir,self.sources[str(self.seed_len)]['ref_wallet'])
return self.walletconv_in(wf,'MMGen wallet',pw=True,oo=True)
def ref_mn_conv(self,ext='mmwords',desc='Mnemonic data'):
wf = joinpath(ref_dir,self.seed_id+'.'+ext)
return self.walletconv_in(wf,desc,oo=True)
def ref_seed_conv(self):
return self.ref_mn_conv(ext='mmseed',desc='Seed data')
def ref_hex_conv(self):
return self.ref_mn_conv(ext='mmhex',desc='Hexadecimal seed data')
def ref_brain_conv(self):
uopts = ['-i','b','-p','1','-l',str(self.seed_len)]
return self.walletconv_in(None,'brainwallet',uopts,oo=True)
def ref_incog_conv(self,wfk='ic_wallet',in_fmt='i',desc='incognito data'):
uopts = ['-i',in_fmt,'-p','1','-l',str(self.seed_len)]
wf = joinpath(ref_dir,self.sources[str(self.seed_len)][wfk])
return self.walletconv_in(wf,desc,uopts,oo=True,pw=True)
def ref_incox_conv(self):
return self.ref_incog_conv(in_fmt='xi',wfk='ic_wallet_hex',desc='hex incognito data')
def ref_hincog_conv(self,wfk='hic_wallet',add_uopts=[]):
ic_f = joinpath(ref_dir,self.sources[str(self.seed_len)][wfk])
uopts = ['-i','hi','-p','1','-l',str(self.seed_len)] + add_uopts
hi_opt = ['-H','{},{}'.format(ic_f,ref_wallet_incog_offset)]
return self.walletconv_in(None,'hidden incognito data',uopts+hi_opt,oo=True,pw=True)
def ref_hincog_conv_old(self):
return self.ref_hincog_conv(wfk='hic_wallet_old',add_uopts=['-O'])
def ref_wallet_conv_out(self):
return self.walletconv_out('MMGen wallet','w',pw=True)
def ref_mn_conv_out(self):
return self.walletconv_out('mnemonic data','mn')
def ref_seed_conv_out(self):
return self.walletconv_out('seed data','seed')
def ref_hex_conv_out(self):
return self.walletconv_out('hexadecimal seed data','hexseed')
def ref_incog_conv_out(self):
return self.walletconv_out('incognito data',out_fmt='i',pw=True)
def ref_incox_conv_out(self):
return self.walletconv_out('hex incognito data',out_fmt='xi',pw=True)
def ref_hincog_conv_out(self,ic_f=None):
if not ic_f: ic_f = joinpath(self.tmpdir,hincog_fn)
hi_parms = '{},{}'.format(ic_f,ref_wallet_incog_offset)
sl_parm = '-l' + str(self.seed_len)
return self.walletconv_out( 'hidden incognito data','hi',
uopts = ['-J',hi_parms,sl_parm],
uopts_chk = ['-H',hi_parms,sl_parm],
pw = True )
def ref_hincog_blkdev_conv_out(self):
imsg('Creating block device image file')
ic_img = joinpath(self.tmpdir,'hincog_blkdev_img')
subprocess.check_output(['dd','if=/dev/zero','of='+ic_img,'bs=1K','count=1'],stderr=subprocess.PIPE)
ic_dev = subprocess.check_output(['losetup','-f']).strip().decode()
ic_dev_mode_orig = '{:o}'.format(os.stat(ic_dev).st_mode & 0xfff)
ic_dev_mode = '0666'
imsg("Changing permissions on loop device to '{}'".format(ic_dev_mode))
subprocess.check_output(['sudo','chmod',ic_dev_mode,ic_dev],stderr=subprocess.PIPE)
imsg("Attaching loop device '{}'".format(ic_dev))
subprocess.check_output(['losetup',ic_dev,ic_img])
self.ref_hincog_conv_out(ic_f=ic_dev)
imsg("Detaching loop device '{}'".format(ic_dev))
subprocess.check_output(['losetup','-d',ic_dev])
imsg("Resetting permissions on loop device to '{}'".format(ic_dev_mode_orig))
subprocess.check_output(['sudo','chmod',ic_dev_mode_orig,ic_dev],stderr=subprocess.PIPE)
return 'ok'
# wallet conversion tests
def walletconv_in(self,infile,desc,uopts=[],pw=False,oo=False):
opts = ['-d',self.tmpdir,'-o','words',self.usr_rand_arg]
if_arg = [infile] if infile else []
d = '(convert)'
t = self.spawn('mmgen-walletconv',opts+uopts+if_arg,extra_desc=d)
t.license()
if desc == 'brainwallet':
t.expect('Enter brainwallet: ',ref_wallet_brainpass+'\n')
if pw:
t.passphrase(desc,self.wpasswd)
if self.test_name[:19] == 'ref_hincog_conv_old':
t.expect('Is the Seed ID correct? (Y/n): ','\n')
else:
t.expect(['Passphrase is OK',' are correct'])
# Output
wf = t.written_to_file('Mnemonic data',oo=oo)
t.p.wait()
# back check of result
msg('' if opt.profile else ' OK')
return self.walletchk( wf,
pf = None,
extra_desc = '(check)',
desc = 'mnemonic data',
sid = self.seed_id )
def walletconv_out(self,desc,out_fmt='w',uopts=[],uopts_chk=[],pw=False):
opts = ['-d',self.tmpdir,'-p1','-o',out_fmt] + uopts
infile = joinpath(ref_dir,self.seed_id+'.mmwords')
t = self.spawn('mmgen-walletconv',[self.usr_rand_arg]+opts+[infile],extra_desc='(convert)')
add_args = ['-l{}'.format(self.seed_len)]
t.license()
if pw:
t.passphrase_new('new '+desc,self.wpasswd)
t.usr_rand(self.usr_rand_chars)
if ' '.join(desc.split()[-2:]) == 'incognito data':
for i in (1,2,3):
t.expect('Generating encryption key from OS random data ')
if desc == 'hidden incognito data':
ret = t.expect(['Create? (Y/n): ',"'YES' to confirm: "])
if ret == 0:
t.send('\n')
t.expect('Enter file size: ',str(hincog_bytes)+'\n')
else:
t.send('YES\n')
if out_fmt == 'w': t.label()
wf = t.written_to_file(capfirst(desc),oo=True)
pf = None
if desc == 'hidden incognito data':
add_args += uopts_chk
wf = None
msg('' if opt.profile else ' OK')
return self.walletchk( wf,
pf = pf,
pw = pw,
desc = desc,
extra_desc = '(check)',
sid = self.seed_id,
add_args = add_args )

View file

@ -29,7 +29,7 @@ os.environ['MMGEN_TEST_SUITE'] = '1'
# Import this _after_ local path's been added to sys.path # Import this _after_ local path's been added to sys.path
from mmgen.common import * from mmgen.common import *
from mmgen.test import * from test.common import *
opts_data = lambda: { opts_data = lambda: {
'desc': "Test suite for the 'mmgen-tool' utility", 'desc': "Test suite for the 'mmgen-tool' utility",
@ -295,7 +295,7 @@ class MMGenToolTestUtils(object):
s = os.urandom(128) s = os.urandom(128)
fn = name+'.in' fn = name+'.in'
write_to_tmpfile(cfg,fn,s,binary=True) write_to_tmpfile(cfg,fn,s,binary=True)
ret = self.run_cmd(name,[get_tmpfile_fn(cfg,fn)],strip=strip,add_opts=add_opts) ret = self.run_cmd(name,[get_tmpfile(cfg,fn)],strip=strip,add_opts=add_opts)
fn = name+'.out' fn = name+'.out'
write_to_tmpfile(cfg,fn,ret+'\n') write_to_tmpfile(cfg,fn,ret+'\n')
ok() ok()
@ -303,6 +303,14 @@ class MMGenToolTestUtils(object):
tu = MMGenToolTestUtils() tu = MMGenToolTestUtils()
def ok_or_die(val,chk_func,s,skip_ok=False):
try: ret = chk_func(val)
except: ret = False
if ret:
if not skip_ok: ok()
else:
rdie(3,"Returned value '{}' is not a {}".format((val,s)))
class MMGenToolTestCmds(object): class MMGenToolTestCmds(object):
# Util # Util
@ -327,6 +335,7 @@ class MMGenToolTestCmds(object):
ret2 = tu.run_cmd(name,[s2],extra_msg='spaced input') ret2 = tu.run_cmd(name,[s2],extra_msg='spaced input')
cmp_or_die(ret1,ret2) cmp_or_die(ret1,ret2)
vmsg('Returned: {}'.format(ret1)) vmsg('Returned: {}'.format(ret1))
ok()
def hash160(self,name): tu.run_cmd_out(name,getrandhex(16)) def hash160(self,name): tu.run_cmd_out(name,getrandhex(16))
def hash256(self,name): tu.run_cmd_out(name,getrandstr(16)) def hash256(self,name): tu.run_cmd_out(name,getrandstr(16))
def hexreverse(self,name): tu.run_cmd_out(name,getrandhex(24)) def hexreverse(self,name): tu.run_cmd_out(name,getrandhex(24))
@ -336,12 +345,14 @@ class MMGenToolTestCmds(object):
ret = tu.run_cmd(name,[fn2],strip=False,binary=True) ret = tu.run_cmd(name,[fn2],strip=False,binary=True)
orig = read_from_file(fn1,binary=True) orig = read_from_file(fn1,binary=True)
cmp_or_die(orig,ret) cmp_or_die(orig,ret)
ok()
def rand2file(self,name): def rand2file(self,name):
of = name + '.out' of = name + '.out'
dlen = 1024 dlen = 1024
tu.run_cmd(name,[of,str(1024),'threads=4','silent=1'],strip=False) tu.run_cmd(name,[of,str(1024),'threads=4','silent=1'],strip=False)
d = read_from_tmpfile(cfg,of,binary=True) d = read_from_tmpfile(cfg,of,binary=True)
cmp_or_die(dlen,len(d)) cmp_or_die(dlen,len(d))
ok()
# Cryptocoin # Cryptocoin
def randwif(self,name): def randwif(self,name):
@ -382,6 +393,7 @@ class MMGenToolTestCmds(object):
iaddr = read_from_tmpfile(cfg,'randpair{}.out'.format(n+1)).split()[-1] iaddr = read_from_tmpfile(cfg,'randpair{}.out'.format(n+1)).split()[-1]
vmsg('Out: {}'.format(ret)) vmsg('Out: {}'.format(ret))
cmp_or_die(iaddr,ret) cmp_or_die(iaddr,ret)
ok()
def hex2wif(self,name,f1,f2,f3,f4): def hex2wif(self,name,f1,f2,f3,f4):
for n,fi,fo,k in ((1,f1,f2,''),(2,f3,f4,maybe_compressed)): for n,fi,fo,k in ((1,f1,f2,''),(2,f3,f4,maybe_compressed)):
ao = ['--type='+k] if k else [] ao = ['--type='+k] if k else []
@ -412,11 +424,13 @@ class MMGenToolTestCmds(object):
addr1 = read_from_tmpfile(cfg,'pubhex2addr3.out').strip() addr1 = read_from_tmpfile(cfg,'pubhex2addr3.out').strip()
addr2 = read_from_tmpfile(cfg,'randpair3.out').split()[1] addr2 = read_from_tmpfile(cfg,'randpair3.out').split()[1]
cmp_or_die(addr1,addr2) cmp_or_die(addr1,addr2)
ok()
def wif2redeem_script(self,name,f1,f2,f3): # compare output with above def wif2redeem_script(self,name,f1,f2,f3): # compare output with above
wif = read_from_file(f3).split()[0] wif = read_from_file(f3).split()[0]
ret1 = tu.run_cmd_out(name,wif,add_opts=maybe_type_segwit,fn_idx=3,Return=True) ret1 = tu.run_cmd_out(name,wif,add_opts=maybe_type_segwit,fn_idx=3,Return=True)
ret2 = read_from_tmpfile(cfg,'pubhex2redeem_script3.out').strip() ret2 = read_from_tmpfile(cfg,'pubhex2redeem_script3.out').strip()
cmp_or_die(ret1,ret2) cmp_or_die(ret1,ret2)
ok()
def wif2segwit_pair(self,name,f1,f2): # does its own checking, so just run def wif2segwit_pair(self,name,f1,f2): # does its own checking, so just run
wif = read_from_file(f2).split()[0] wif = read_from_file(f2).split()[0]
tu.run_cmd_out(name,wif,add_opts=maybe_type_segwit,fn_idx=2) tu.run_cmd_out(name,wif,add_opts=maybe_type_segwit,fn_idx=2)
@ -440,6 +454,7 @@ class MMGenToolTestCmds(object):
res = p.stdout.read().decode().strip() res = p.stdout.read().decode().strip()
addr = read_from_tmpfile(cfg,'wif2addr3.out').strip() addr = read_from_tmpfile(cfg,'wif2addr3.out').strip()
cmp_or_die(res,addr) cmp_or_die(res,addr)
ok()
# Mnemonic # Mnemonic
def hex2mn(self,name): def hex2mn(self,name):
@ -488,12 +503,12 @@ try:
msg('Running tests for {}:'.format(cmd_data[cmd]['desc'])) msg('Running tests for {}:'.format(cmd_data[cmd]['desc']))
do_cmds(cmd) do_cmds(cmd)
elif cmd == 'clean': elif cmd == 'clean':
cleandir(cfg['tmpdir']) cleandir(cfg['tmpdir'],do_msg=True)
sys.exit(0) sys.exit(0)
else: else:
die(1,"'{}': unrecognized command".format(cmd)) die(1,"'{}': unrecognized command".format(cmd))
else: else:
cleandir(cfg['tmpdir']) cleandir(cfg['tmpdir'],do_msg=True)
for cmd in cmd_data: for cmd in cmd_data:
msg('Running tests for {}:'.format(cmd_data[cmd]['desc'])) msg('Running tests for {}:'.format(cmd_data[cmd]['desc']))
do_cmds(cmd) do_cmds(cmd)

View file

@ -34,7 +34,7 @@ os.environ['MMGEN_TEST_SUITE'] = '1'
# Import these _after_ prepending repo_root to sys.path # Import these _after_ prepending repo_root to sys.path
from mmgen.common import * from mmgen.common import *
from mmgen.test import * from test.common import *
from mmgen.obj import is_wif,is_coin_addr from mmgen.obj import is_wif,is_coin_addr
opts_data = lambda: { opts_data = lambda: {