Improved command-line processing in 'mmgen-tool'

Added 'tool' argument to test/test.py (WIP)
This commit is contained in:
philemon 2015-01-07 07:41:25 +03:00
commit 7b44b67e0a
5 changed files with 187 additions and 65 deletions

View file

@ -20,10 +20,6 @@
mmgen-tool: Perform various Bitcoin-related operations.
Part of the MMGen suite
"""
import sys
print "MMGEN-TOOL was determined to be buggy and is in the process of maintenance."
print "Please check back soon!"
sys.exit(3)
from mmgen.main import launch
launch("tool")

View file

@ -62,9 +62,9 @@ if cmd_args and cmd_args[0] == '--help':
tool.tool_usage(g.prog_name, command)
sys.exit(0)
args = tool.process_args(g.prog_name, command, cmd_args)
args,kwargs = tool.process_args(g.prog_name, command, cmd_args)
#msgrepr(args,kwargs)
tool.opts = opts
#print command + "(" + ", ".join(args) + ")"
eval("tool." + command + "(" + ", ".join(args) + ")")
tool.__dict__[command](*args,**kwargs)

View file

@ -155,65 +155,53 @@ def tool_usage(prog_name, command):
print "USAGE: '%s %s%s'" % (prog_name, command,
(" "+" ".join(commands[command]) if commands[command] else ""))
def process_args(prog_name, command, uargs):
cargs = commands[command]
cargs_req = [[i.split(" [")[0],i.split(" [")[1][:-1]]
for i in cargs if "=" not in i]
cargs_nam = dict([[
def process_args(prog_name, command, cmd_args):
c_args = [[i.split(" [")[0],i.split(" [")[1][:-1]]
for i in commands[command] if "=" not in i]
c_kwargs = dict([[
i.split(" [")[0],
[i.split(" [")[1].split("=")[0], i.split(" [")[1].split("=")[1][:-1]]
] for i in cargs if "=" in i])
uargs_req = [i for i in uargs if "=" not in i]
uargs_nam = dict([i.split("=") for i in uargs if "=" in i])
] for i in commands[command] if "=" in i])
u_args = cmd_args[:len(c_args)]
u_kwargs = dict([i.split("=") for i in cmd_args[len(c_args):]])
# print cargs_req; print cargs_nam; print uargs_req; print uargs_nam; sys.exit()
# print c_args; print c_kwargs; print u_args; print u_kwargs; sys.exit()
n = len(cargs_req)
if len(uargs_req) != n:
if len(u_args) != len(c_args):
tool_usage(prog_name, command)
sys.exit(1)
for a in uargs_nam.keys():
if a not in cargs_nam.keys():
print "'%s' invalid named argument" % a
sys.exit(1)
if set(u_kwargs) > set(c_kwargs):
print "Invalid named argument"
sys.exit(1)
def test_type(arg_type,arg,name=""):
def convert_type(arg,arg_name,arg_type):
try:
t = type(eval(arg))
assert(t == eval(arg_type))
return __builtins__[arg_type](arg)
except:
print "'%s': Invalid argument for argument %s ('%s' required)" % \
(arg, name, arg_type)
(arg, arg_name, arg_type)
sys.exit(1)
return True
ret = []
def normalize_arg(arg, arg_type):
def convert_to_bool_maybe(arg, arg_type):
if arg_type == "bool":
if arg.lower() in ("true","yes","1","on"): return "True"
if arg.lower() in ("false","no","0","off"): return "False"
if arg.lower() in ("true","yes","1","on"): return True
if arg.lower() in ("false","no","0","off"): return False
return arg
for i in range(len(cargs_req)):
arg_type = cargs_req[i][1]
arg = normalize_arg(uargs_req[i], arg_type)
if arg_type == "str":
ret.append('"%s"' % (arg))
elif test_type(arg_type, arg, "#"+str(i+1)):
ret.append('%s' % (arg))
args = []
for i in range(len(c_args)):
arg_type = c_args[i][1]
arg = convert_to_bool_maybe(u_args[i], arg_type)
args.append(convert_type(arg,c_args[i][0],arg_type))
for k in uargs_nam.keys():
arg_type = cargs_nam[k][0]
arg = normalize_arg(uargs_nam[k], arg_type)
if arg_type == "str":
ret.append('%s="%s"' % (k, arg))
elif test_type(arg_type, arg, "'"+k+"'"):
ret.append('%s=%s' % (k, arg))
return ret
kwargs = {}
for k in u_kwargs:
arg_type = c_kwargs[k][0]
arg = convert_to_bool_maybe(u_kwargs[k], arg_type)
kwargs[k] = convert_type(arg,k,arg_type)
return args,kwargs
# Individual commands

View file

@ -316,7 +316,7 @@ def write_to_stdout(data, what, confirm=True):
sys.stdout.write(data)
def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=False,exit_on_error=True):
def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=False,exit_on_error=True,silent=False):
if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
@ -333,13 +333,13 @@ def write_to_file(outfile,data,opts,what="data",confirm_overwrite=False,verbose=
msg("Not overwriting file at user request")
return False
else:
msg("Overwriting file '%s'" % outfile)
if not silent: msg("Overwriting file '%s'" % outfile)
f = open_file_or_exit(outfile,'wb')
try:
f.write(data)
except:
msg("Failed to write %s to file '%s'" % (what,outfile))
if not silent: msg("Failed to write %s to file '%s'" % (what,outfile))
sys.exit(2)
f.close
@ -549,9 +549,10 @@ def get_lines_from_file(infile,what="",trim_comments=False):
return remove_comments(lines) if trim_comments else lines
def get_data_from_file(infile,what="data",dash=False):
def get_data_from_file(infile,what="data",dash=False,silent=False):
if dash and infile == "-": return sys.stdin.read()
qmsg("Getting %s from file '%s'" % (what,infile))
if not silent:
qmsg("Getting %s from file '%s'" % (what,infile))
f = open_file_or_exit(infile,'rb')
data = f.read()
f.close()

View file

@ -45,23 +45,55 @@ cmd_data = OrderedDict([
['keyaddrgen', (1,'key-address file generation', [[["mmdat"],1]])],
['txsign_keyaddr',(1,'transaction signing with key-address file', [[["akeys.mmenc","raw"],1]])],
['walletgen2',(2,'wallet generation (2), 128-bit seed (WIP)', [])],
['walletgen2',(2,'wallet generation (2)', [])],
# ['walletgen2',(2,'wallet generation (2), 128-bit seed (WIP)', [])],
['addrgen2', (2,'address generation (2)', [[["mmdat"],2]])],
['txcreate2', (2,'transaction creation (2)', [[["addrs"],2]])],
['txsign2', (2,'transaction signing, two transactions',[[["mmdat","raw"],1],[["mmdat","raw"],2]])],
['export_mnemonic2', (2,'seed export to mmwords format (2), 128-bit seed (WIP)',[[["mmdat"],2]])],
['export_mnemonic2', (2,'seed export to mmwords format (2)',[[["mmdat"],2]])],
# ['export_mnemonic2', (2,'seed export to mmwords format (2), 128-bit seed (WIP)',[[["mmdat"],2]])],
['walletgen3',(3,'wallet generation (3)', [])],
['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","raw"],3]])],
['walletgen4',(4,'wallet generation (4) (brainwallet, 192-bit seed (WIP))', [])],
['walletgen4',(4,'wallet generation (4) (brainwallet)', [])],
# ['walletgen4',(4,'wallet generation (4) (brainwallet, 192-bit seed (WIP))', [])],
['addrgen4', (4,'address generation (4)', [[["mmdat"],4]])],
['txcreate4', (4,'tx creation with inputs and outputs from four seed sources, plus non-MMGen inputs and outputs', [[["addrs"],1],[["addrs"],2],[["addrs"],3],[["addrs"],4]])],
['txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet and brainwallet, plus non-MMGen inputs and outputs', [[["mmincog"],1],[["mmwords"],2],[["mmdat"],3],[["mmbrain","raw"],4]])],
])
tool_cmd_data = OrderedDict([
['strtob58', (10, '', [])],
['b58tostr', (10, '', [[["strtob58.in","strtob58.out"],10]])],
['hextob58', (10, '', [])],
['b58tohex', (10, '', [[["hextob58.in","hextob58.out"],10]])],
# "b58randenc": [],
# "randhex": ['nbytes [int=32]'],
# "randwif": ['compressed [bool=False]'],
# "randpair": ['compressed [bool=False]'],
# "wif2hex": ['<wif> [str]', 'compressed [bool=False]'],
# "wif2addr": ['<wif> [str]', 'compressed [bool=False]'],
# "hex2wif": ['<private key in hex format> [str]', 'compressed [bool=False]'],
# "hexdump": ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]'],
# "unhexdump": ['<infile> [str]'],
# "hex2mn": ['<hexadecimal string> [str]','wordlist [str="electrum"]'],
# "mn2hex": ['<mnemonic> [str]', 'wordlist [str="electrum"]'],
# "b32tohex": ['<b32 num> [str]'],
# "hextob32": ['<hex num> [str]'],
# "mn_rand128": ['wordlist [str="electrum"]'],
# "mn_rand192": ['wordlist [str="electrum"]'],
# "mn_rand256": ['wordlist [str="electrum"]'],
# "mn_stats": ['wordlist [str="electrum"]'],
# "mn_printlist": ['wordlist [str="electrum"]'],
# "id8": ['<infile> [str]'],
# "id6": ['<infile> [str]'],
# "str2id6": ['<string (spaces are ignored)> [str]'],
])
utils = {
'check_deps': 'check dependencies for specified command',
'clean': 'clean specified tmp dir(s) 1, 2, 3 or 4 (no arg = all dirs)',
@ -69,6 +101,16 @@ utils = {
addrs_per_wallet = 8
cfgs = {
'10': {
'name': "test the tool utility",
'enc_passwd': "Ten Satoshis",
'tmpdir': "test/tmp10",
'dep_generators': {
'strtob58.out': "strtob58",
'strtob58.in': "strtob58",
},
},
'6': {
'name': "reference wallet check",
'bw_passwd': "abc",
@ -156,6 +198,7 @@ cfgs = {
from binascii import hexlify
def getrand(n): return int(hexlify(os.urandom(n)),16)
def getrandhex(n): return hexlify(os.urandom(n))
# total of two outputs must be < 10 BTC
for k in cfgs.keys():
@ -342,12 +385,17 @@ def verify_checksum_or_exit(checksum,chk):
sys.exit(1)
vmsg(green("Checksums match: %s") % (cyan(chk)))
def get_rand_printable_chars(num_chars,no_punc=False):
return [chr(ord(i)%94+33) for i in list(os.urandom(num_chars))]
class MMGenExpect(object):
def __init__(self,name,mmgen_cmd,cmd_args=[],env=env):
if not 'system' in opts:
mmgen_cmd = os.path.join(os.curdir,mmgen_cmd)
desc = cmd_data[name][1]
if not desc: desc = name
if verbose or exact_output:
sys.stderr.write(
green("Testing %s\nExecuting " % desc) +
@ -365,7 +413,7 @@ class MMGenExpect(object):
my_expect(self.p,p,'c')
def usr_rand(self,num_chars):
rand_chars = [chr(ord(i)%94+33) for i in list(os.urandom(num_chars))]
rand_chars = get_rand_printable_chars(num_chars)
my_expect(self.p,'symbols left: ','x')
try:
vmsg_r("SEND ")
@ -429,7 +477,10 @@ class MMGenExpect(object):
def readline(self):
return self.p.readline()
def read(self,n):
def readlines(self):
return [l.rstrip()+"\n" for l in self.p.readlines()]
def read(self,n=None):
return self.p.read(n)
@ -513,7 +564,7 @@ def do_cmd(ts,cmd):
global cfg
cfg = cfgs[str(cmd_data[cmd][0])]
MMGenTestSuite.__dict__[cmd](*([ts,cmd] + al))
ts.__class__.__dict__[cmd](*([ts,cmd] + al))
hincog_bytes = 1024*1024
@ -591,7 +642,6 @@ def ok():
sys.stderr.write(green("OK\n"))
else: msg(" OK")
class MMGenTestSuite(object):
def __init__(self):
@ -1004,10 +1054,85 @@ class MMGenTestSuite(object):
t.written_to_file("Signed transaction")
ok()
# main()
ts = MMGenTestSuite()
start_time = int(time.time())
def write_to_tmpfile(fn,data):
write_to_file(os.path.join(cfg['tmpdir'],fn),data,{},silent=True)
def read_from_tmpfile(fn):
from mmgen.util import get_data_from_file
return get_data_from_file(os.path.join(cfg['tmpdir'],fn),silent=True)
def read_from_file(fn):
from mmgen.util import get_data_from_file
return get_data_from_file(fn,silent=True)
class MMGenToolTestSuite(object):
def __init__(self):
global cmd_data,tool_cmd_data
cmd_data = tool_cmd_data
pass
def clean(self,name):
cleandir(cfgs['10']['tmpdir'])
def cmd(self,name,tool_args):
mk_tmpdir(cfg)
t = MMGenExpect(name,"mmgen-tool", ["-d",cfg['tmpdir']] + tool_args)
return t.read()
def cmd_to_tmpfile(self,name,tool_args,tmpfile):
ret = self.cmd(name,tool_args)
if ret:
write_to_tmpfile(tmpfile,ret)
ok()
def strtob58(self,name):
s = "".join(get_rand_printable_chars(15))
write_to_tmpfile('strtob58.in',s)
self.cmd_to_tmpfile(name,["strtob58",s],'strtob58.out')
def b58tostr(self,name,f1,f2):
idata = read_from_file(f1)
odata = read_from_file(f2)[:-2]
res = self.cmd(name,["b58tostr",odata])[:-2]
if res == idata: ok()
else: errmsg(red("Error"))
def hextob58(self,name):
hexnum = getrandhex(32)
write_to_tmpfile('hextob58.in',hexnum)
self.cmd_to_tmpfile(name,["hextob58",hexnum],'hextob58.out')
def b58tohex(self,name,f1,f2):
idata = read_from_file(f1)
odata = read_from_file(f2)[:-2]
res = self.cmd(name,["b58tohex",odata])[:-2]
if res == idata: ok()
else: errmsg(red("Error"))
# "b58randenc": [],
# "randhex": ['nbytes [int=32]'],
# "randwif": ['compressed [bool=False]'],
# "randpair": ['compressed [bool=False]'],
# "wif2hex": ['<wif> [str]', 'compressed [bool=False]'],
# "wif2addr": ['<wif> [str]', 'compressed [bool=False]'],
# "hex2wif": ['<private key in hex format> [str]', 'compressed [bool=False]'],
# "hexdump": ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]'],
# "unhexdump": ['<infile> [str]'],
# "hex2mn": ['<hexadecimal string> [str]','wordlist [str="electrum"]'],
# "mn2hex": ['<mnemonic> [str]', 'wordlist [str="electrum"]'],
# "b32tohex": ['<b32 num> [str]'],
# "hextob32": ['<hex num> [str]'],
# "mn_rand128": ['wordlist [str="electrum"]'],
# "mn_rand192": ['wordlist [str="electrum"]'],
# "mn_rand256": ['wordlist [str="electrum"]'],
# "mn_stats": ['wordlist [str="electrum"]'],
# "mn_printlist": ['wordlist [str="electrum"]'],
# "id8": ['<infile> [str]'],
# "id6": ['<infile> [str]'],
# "str2id6": ['<string (spaces are ignored)> [str]'],
# main()
if pause:
import termios,atexit
fd = sys.stdin.fileno()
@ -1016,20 +1141,24 @@ if pause:
termios.tcsetattr(fd, termios.TCSADRAIN, old)
atexit.register(at_exit)
start_time = int(time.time())
try:
if cmd_args:
if cmd_args and cmd_args[0] != "tool":
arg1 = cmd_args[0]
if arg1 in utils:
MMGenTestSuite.__dict__[arg1](ts,arg1,cmd_args[1:])
sys.exit()
elif arg1 in meta_cmds:
ts = MMGenTestSuite()
if len(cmd_args) == 1:
for cmd in meta_cmds[arg1][1]:
check_needs_rerun(cmd,build=True,force_delete=True)
else:
msg("Only one meta command may be specified")
sys.exit(1)
elif arg1 in cmd_data:
elif arg1 in cmd_data.keys() + tool_cmd_data.keys():
ts = MMGenTestSuite() if arg1 in cmd_data else MMGenToolTestSuite()
if len(cmd_args) == 1:
check_needs_rerun(arg1,build=True)
else:
@ -1039,6 +1168,14 @@ try:
errmsg("%s: unrecognized command" % arg1)
sys.exit(1)
else:
if cmd_args: # tool
if len(cmd_args) != 1:
msg("Only one command may be specified")
sys.exit(1)
ts = MMGenToolTestSuite()
else:
ts = MMGenTestSuite()
ts.clean("clean")
for cmd in cmd_data:
do_cmd(ts,cmd)