minor changes and fixes throughout
This commit is contained in:
parent
ef6e03d78b
commit
57f12cd1cb
26 changed files with 312 additions and 197 deletions
|
|
@ -2,6 +2,7 @@ include README.md SIGNING_KEYS.pub LICENSE INSTALL
|
|||
include doc/wiki/using-mmgen/*
|
||||
|
||||
include test/*.py
|
||||
include test/include/*.py
|
||||
include test/test_py_d/*.py
|
||||
include test/objtest_py_d/*.py
|
||||
include test/objattrtest_py_d/*.py
|
||||
|
|
|
|||
|
|
@ -67,7 +67,10 @@ class Token(MMGenObject): # ERC20
|
|||
if g.debug:
|
||||
msg('ETH_CALL {}: {}'.format(method_sig,'\n '.join(parse_abi(data))))
|
||||
ret = g.rpch.eth_call({ 'to': '0x'+self.addr, 'data': '0x'+data })
|
||||
return int(ret,16) * self.base_unit if toUnit else ret
|
||||
if toUnit:
|
||||
return int(ret,16) * self.base_unit
|
||||
else:
|
||||
return ret
|
||||
|
||||
def balance(self,acct_addr):
|
||||
return ETHAmt(self.do_call('balanceOf(address)',acct_addr.rjust(64,'0'),toUnit=True))
|
||||
|
|
|
|||
|
|
@ -95,9 +95,9 @@ class EthereumTrackingWallet(TrackingWallet):
|
|||
r = self.data_root
|
||||
if addr in r:
|
||||
if not r[addr]['mmid'] and label.mmid:
|
||||
msg("Warning: MMGen ID '{}' was missing in tracking wallet!".format(label.mmid))
|
||||
msg(f'Warning: MMGen ID {label.mmid!r} was missing in tracking wallet!')
|
||||
elif r[addr]['mmid'] != label.mmid:
|
||||
die(3,"MMGen ID '{}' does not match tracking wallet!".format(label.mmid))
|
||||
die(3,'MMGen ID {label.mmid!r} does not match tracking wallet!')
|
||||
r[addr] = { 'mmid': label.mmid, 'comment': label.comment }
|
||||
|
||||
@write_mode
|
||||
|
|
@ -153,13 +153,14 @@ class EthereumTrackingWallet(TrackingWallet):
|
|||
if self.data['tokens'][addr]['params'].get('symbol') == sym.upper():
|
||||
return addr
|
||||
|
||||
if no_rpc: return None
|
||||
if no_rpc:
|
||||
return None
|
||||
|
||||
for addr in self.data['tokens']:
|
||||
if Token(addr).symbol().upper() == sym.upper():
|
||||
self.force_set_token_param(addr,'symbol',sym.upper())
|
||||
return addr
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_token_param(self,token,param):
|
||||
|
|
@ -180,6 +181,7 @@ class EthereumTrackingWallet(TrackingWallet):
|
|||
|
||||
class EthereumTokenTrackingWallet(EthereumTrackingWallet):
|
||||
|
||||
desc = 'Ethereum token tracking wallet'
|
||||
decimals = None
|
||||
symbol = None
|
||||
cur_eth_balances = {}
|
||||
|
|
@ -315,7 +317,8 @@ class EthereumTokenTwUnspentOutputs(EthereumTwUnspentOutputs):
|
|||
prompt_fs = 'Total to spend: {} {}\n\n'
|
||||
col_adj = 37
|
||||
|
||||
def get_display_precision(self): return 10 # truncate precision for narrow display
|
||||
def get_display_precision(self):
|
||||
return 10 # truncate precision for narrow display
|
||||
|
||||
def get_unspent_data(self):
|
||||
super().get_unspent_data()
|
||||
|
|
@ -336,11 +339,12 @@ class EthereumTwAddrList(TwAddrList):
|
|||
for mmid,d in list(tw_dict.items()):
|
||||
# if d['confirmations'] < minconf: continue # cannot get confirmations for eth account
|
||||
label = TwLabel(mmid+' '+d['comment'],on_fail='raise')
|
||||
if usr_addr_list and (label.mmid not in usr_addr_list): continue
|
||||
if usr_addr_list and (label.mmid not in usr_addr_list):
|
||||
continue
|
||||
bal = self.wallet.get_balance(d['addr'])
|
||||
if bal == 0 and not showempty:
|
||||
if not label.comment: continue
|
||||
if not all_labels: continue
|
||||
if not label.comment or not all_labels:
|
||||
continue
|
||||
self[label.mmid] = {'amt': g.proto.coin_amt('0'), 'lbl': label }
|
||||
if showbtcaddrs:
|
||||
self[label.mmid]['addr'] = CoinAddr(d['addr'])
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ class EthereumMMGenTX(MMGenTX):
|
|||
self.tx_gas = o['startGas'] # approximate, but better than nothing
|
||||
self.fee = self.fee_rel2abs(o['gasPrice'].toWei())
|
||||
self.txobj = o
|
||||
return d # 'token_addr','decimals' required by subclass
|
||||
return d # 'token_addr','decimals' required by Token subclass
|
||||
|
||||
def get_nonce(self):
|
||||
return ETHNonce(int(g.rpch.parity_nextNonce('0x'+self.inputs[0].addr),16))
|
||||
|
|
@ -149,7 +149,8 @@ class EthereumMMGenTX(MMGenTX):
|
|||
self.hex = json.dumps(odict)
|
||||
self.update_txid()
|
||||
|
||||
def del_output(self,idx): pass
|
||||
def del_output(self,idx):
|
||||
pass
|
||||
|
||||
def update_txid(self):
|
||||
assert not is_hex_str(self.hex),'update_txid() must be called only when self.hex is not hex data'
|
||||
|
|
@ -160,12 +161,14 @@ class EthereumMMGenTX(MMGenTX):
|
|||
|
||||
def process_cmd_args(self,cmd_args,ad_f,ad_w):
|
||||
lc = len(cmd_args)
|
||||
if lc == 0 and self.usr_contract_data and not 'Token' in type(self).__name__: return
|
||||
if lc == 0 and self.usr_contract_data and not 'Token' in type(self).__name__:
|
||||
return
|
||||
if lc != 1:
|
||||
fs = '{} output{} specified, but Ethereum transactions must have exactly one'
|
||||
die(1,fs.format(lc,suf(lc)))
|
||||
|
||||
for a in cmd_args: self.process_cmd_arg(a,ad_f,ad_w)
|
||||
for a in cmd_args:
|
||||
self.process_cmd_arg(a,ad_f,ad_w)
|
||||
|
||||
def select_unspent(self,unspent):
|
||||
prompt = 'Enter an account to spend from: '
|
||||
|
|
@ -366,7 +369,8 @@ class EthereumMMGenTX(MMGenTX):
|
|||
|
||||
self.get_status()
|
||||
|
||||
if prompt_user: self.confirm_send()
|
||||
if prompt_user:
|
||||
self.confirm_send()
|
||||
|
||||
ret = None if g.bogus_send else g.rpch.eth_sendRawTransaction('0x'+self.hex,on_fail='return')
|
||||
|
||||
|
|
@ -374,11 +378,14 @@ class EthereumMMGenTX(MMGenTX):
|
|||
if rpc_error(ret):
|
||||
msg(yellow(rpc_errmsg(ret)))
|
||||
msg(red('Send of MMGen transaction {} failed'.format(self.txid)))
|
||||
if exit_on_fail: sys.exit(1)
|
||||
if exit_on_fail:
|
||||
sys.exit(1)
|
||||
return False
|
||||
else:
|
||||
m = 'BOGUS transaction NOT sent: {}' if g.bogus_send else 'Transaction sent: {}'
|
||||
if not g.bogus_send:
|
||||
if g.bogus_send:
|
||||
m = 'BOGUS transaction NOT sent: {}'
|
||||
else:
|
||||
m = 'Transaction sent: {}'
|
||||
assert ret == '0x'+self.coin_txid,'txid mismatch (after sending)'
|
||||
self.desc = 'sent transaction'
|
||||
msg(m.format(self.coin_txid.hl()))
|
||||
|
|
|
|||
|
|
@ -56,6 +56,8 @@ class Daemon(MMGenObject):
|
|||
|
||||
def exec_cmd(self,cmd,check):
|
||||
cp = run(cmd,check=False,stdout=PIPE,stderr=PIPE)
|
||||
if self.debug:
|
||||
print(cp)
|
||||
if check and cp.returncode != 0:
|
||||
raise MMGenCalledProcessError(cp)
|
||||
return cp
|
||||
|
|
@ -172,8 +174,8 @@ class Daemon(MMGenObject):
|
|||
return True
|
||||
time.sleep(0.2)
|
||||
else:
|
||||
m = 'Wait for state {!r} timeout exceeded for daemon {} {}'
|
||||
die(2,m.format(req_state,self.daemon_id.upper(),self.network))
|
||||
m = 'Wait for state {!r} timeout exceeded for daemon {} {} (port {})'
|
||||
die(2,m.format(req_state,self.daemon_id.upper(),self.network,self.rpc_port))
|
||||
|
||||
@classmethod
|
||||
def check_implement(cls):
|
||||
|
|
@ -202,6 +204,13 @@ class Daemon(MMGenObject):
|
|||
die(1,'Flag {!r} not set, so cannot be removed'.format(val))
|
||||
self._flags.remove(val)
|
||||
|
||||
def remove_datadir(self):
|
||||
if self.state == 'stopped':
|
||||
import shutil
|
||||
shutil.rmtree(self.datadir,ignore_errors=True)
|
||||
else:
|
||||
msg(f'Cannot remove {self.datadir!r} - daemon is not stopped')
|
||||
|
||||
class MoneroWalletDaemon(Daemon):
|
||||
|
||||
desc = 'RPC daemon'
|
||||
|
|
|
|||
|
|
@ -40,8 +40,11 @@ if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN
|
|||
class MMGenObject(object):
|
||||
|
||||
# Pretty-print any object subclassed from MMGenObject, recursing into sub-objects - WIP
|
||||
def pmsg(self): print(self.pfmt())
|
||||
def pdie(self): print(self.pfmt()); sys.exit(0)
|
||||
def pmsg(self):
|
||||
print(self.pfmt())
|
||||
def pdie(self):
|
||||
print(self.pfmt())
|
||||
sys.exit(1)
|
||||
def pfmt(self,lvl=0,id_list=[]):
|
||||
scalars = (str,int,float,Decimal)
|
||||
def do_list(out,e,lvl=0,is_dict=False):
|
||||
|
|
|
|||
|
|
@ -37,6 +37,13 @@ class g(object):
|
|||
if s: sys.stderr.write(s+'\n')
|
||||
sys.exit(ev)
|
||||
|
||||
for k in ('linux','win','msys'):
|
||||
if sys.platform[:len(k)] == k:
|
||||
platform = { 'linux':'linux', 'win':'win', 'msys':'win' }[k]
|
||||
break
|
||||
else:
|
||||
die(1,"'{}': platform not supported by {}\n".format(sys.platform,proj_name))
|
||||
|
||||
# Constants:
|
||||
|
||||
version = '0.12.099'
|
||||
|
|
@ -89,7 +96,7 @@ class g(object):
|
|||
use_internal_keccak_module = False
|
||||
|
||||
chain = None # set by first call to rpc_init()
|
||||
chains = 'mainnet','testnet','regtest'
|
||||
chains = ('mainnet','testnet','regtest')
|
||||
|
||||
# rpc:
|
||||
rpc_host = ''
|
||||
|
|
@ -118,13 +125,6 @@ class g(object):
|
|||
|
||||
mnemonic_entry_modes = {}
|
||||
|
||||
for k in ('linux','win','msys'):
|
||||
if sys.platform[:len(k)] == k:
|
||||
platform = { 'linux':'linux', 'win':'win', 'msys':'win' }[k]
|
||||
break
|
||||
else:
|
||||
die(1,"'{}': platform not supported by {}\n".format(sys.platform,proj_name))
|
||||
|
||||
color = sys.stdout.isatty()
|
||||
|
||||
if os.getenv('HOME'): # Linux or MSYS
|
||||
|
|
@ -154,7 +154,8 @@ class g(object):
|
|||
|
||||
# 'long' opts - opt sets global var
|
||||
common_opts = (
|
||||
'color','no_license','rpc_host','rpc_port','testnet','rpc_user','rpc_password',
|
||||
'color','no_license','testnet',
|
||||
'rpc_host','rpc_port','rpc_user','rpc_password',
|
||||
'monero_wallet_rpc_host','monero_wallet_rpc_user','monero_wallet_rpc_password',
|
||||
'daemon_data_dir','force_256_color','regtest','coin','bob','alice',
|
||||
'accept_defaults','token'
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ key_fn = 'autosign.key'
|
|||
from .common import *
|
||||
prog_name = os.path.basename(sys.argv[0])
|
||||
opts_data = {
|
||||
'sets': [('stealth_led', True, 'led', True)],
|
||||
'text': {
|
||||
'desc': 'Auto-sign MMGen transactions',
|
||||
'usage':'[opts] [command]',
|
||||
|
|
@ -43,6 +44,7 @@ opts_data = {
|
|||
-I, --no-insert-check Don't check for device insertion
|
||||
-l, --led Use status LED to signal standby, busy and error
|
||||
-m, --mountpoint=m Specify an alternate mountpoint (default: '{mp}')
|
||||
-n, --no-summary Don't print a transaction summary
|
||||
-s, --stealth-led Stealth LED mode - signal busy and error only, and only
|
||||
after successful authorization.
|
||||
-S, --full-summary Print a full summary of each signed transaction after
|
||||
|
|
@ -116,9 +118,9 @@ from .protocol import CoinProtocol,init_coin
|
|||
if g.test_suite:
|
||||
from .daemon import CoinDaemon
|
||||
|
||||
if opt.stealth_led: opt.led = True
|
||||
if opt.mountpoint:
|
||||
mountpoint = opt.mountpoint # TODO: make global
|
||||
|
||||
if opt.mountpoint: mountpoint = opt.mountpoint # TODO: make global
|
||||
opt.outdir = tx_dir = os.path.join(mountpoint,'tx')
|
||||
|
||||
def check_daemons_running():
|
||||
|
|
@ -132,47 +134,44 @@ def check_daemons_running():
|
|||
|
||||
for coin in coins:
|
||||
g.proto = CoinProtocol(coin,g.testnet)
|
||||
if g.proto.sign_mode != 'daemon':
|
||||
continue
|
||||
if g.proto.sign_mode == 'daemon':
|
||||
if g.test_suite:
|
||||
g.proto.daemon_data_dir = 'test/daemons/' + coin.lower()
|
||||
g.rpc_port = CoinDaemon(get_network_id(coin,g.testnet),test_suite=True).rpc_port
|
||||
vmsg('Checking {} daemon'.format(coin))
|
||||
vmsg(f'Checking {coin} daemon')
|
||||
try:
|
||||
rpc_init(reinit=True)
|
||||
g.rpch.getblockcount()
|
||||
except SystemExit as e:
|
||||
if e.code != 0:
|
||||
fs = '{} daemon not running or not listening on port {}'
|
||||
ydie(1,fs.format(coin,g.proto.rpc_port))
|
||||
ydie(1,f'{coin} daemon not running or not listening on port {g.proto.rpc_port}')
|
||||
|
||||
def get_wallet_files():
|
||||
m = "Cannot open wallet directory '{}'. Did you run 'mmgen-autosign setup'?"
|
||||
try: dlist = os.listdir(wallet_dir)
|
||||
except: die(1,m.format(wallet_dir))
|
||||
try:
|
||||
dlist = os.listdir(wallet_dir)
|
||||
except:
|
||||
die(1,f"Cannot open wallet directory {wallet_dir!r}. Did you run 'mmgen-autosign setup'?")
|
||||
|
||||
wfs = [x for x in dlist if x[-6:] == '.mmdat']
|
||||
if not wfs:
|
||||
fns = [x for x in dlist if x.endswith('.mmdat')]
|
||||
if fns:
|
||||
return [os.path.join(wallet_dir,w) for w in fns]
|
||||
else:
|
||||
die(1,'No wallet files present!')
|
||||
return [os.path.join(wallet_dir,w) for w in wfs]
|
||||
|
||||
def do_mount():
|
||||
if not os.path.ismount(mountpoint):
|
||||
if run(['mount',mountpoint],stderr=DEVNULL,stdout=DEVNULL).returncode == 0:
|
||||
msg('Mounting '+mountpoint)
|
||||
msg(f'Mounting {mountpoint}')
|
||||
try:
|
||||
ds = os.stat(tx_dir)
|
||||
m1 = "'{}' is not a directory!"
|
||||
m2 = "'{}' is not read/write for this user!"
|
||||
assert S_ISDIR(ds.st_mode),m1.format(tx_dir)
|
||||
assert ds.st_mode & S_IWUSR|S_IRUSR == S_IWUSR|S_IRUSR,m2.format(tx_dir)
|
||||
assert S_ISDIR(ds.st_mode), f'{tx_dir!r} is not a directory!'
|
||||
assert ds.st_mode & S_IWUSR|S_IRUSR == S_IWUSR|S_IRUSR,f'{tx_dir!r} is not read/write for this user!'
|
||||
except:
|
||||
die(1,'{} missing, or not read/writable by user!'.format(tx_dir))
|
||||
die(1,'{tx_dir!r} missing, or not read/writable by user!')
|
||||
|
||||
def do_umount():
|
||||
if os.path.ismount(mountpoint):
|
||||
run(['sync'],check=True)
|
||||
msg('Unmounting '+mountpoint)
|
||||
msg(f'Unmounting {mountpoint}')
|
||||
run(['umount',mountpoint],check=True)
|
||||
|
||||
def sign_tx_file(txfile,signed_txs):
|
||||
|
|
@ -187,8 +186,8 @@ def sign_tx_file(txfile,signed_txs):
|
|||
init_coin(tmp_tx.coin,testnet=True)
|
||||
|
||||
if hasattr(g.proto,'chain_name'):
|
||||
m = 'Chains do not match! tx file: {}, proto: {}'
|
||||
assert tmp_tx.chain == g.proto.chain_name,m.format(tmp_tx.chain,g.proto.chain_name)
|
||||
if tmp_tx.chain != g.proto.chain_name:
|
||||
die(2, f'Chains do not match! tx file: {tmp_tx.chain}, proto: {g.proto.chain_name}')
|
||||
|
||||
g.chain = tmp_tx.chain
|
||||
g.token = tmp_tx.dcoin
|
||||
|
|
@ -209,18 +208,17 @@ def sign_tx_file(txfile,signed_txs):
|
|||
else:
|
||||
return False
|
||||
except Exception as e:
|
||||
msg('An error occurred: {}'.format(e.args[0]))
|
||||
msg(f'An error occurred: {e.args[0]}')
|
||||
if g.debug or g.traceback:
|
||||
print_stack_trace('AUTOSIGN {}'.format(txfile))
|
||||
print_stack_trace(f'AUTOSIGN {txfile}')
|
||||
return False
|
||||
except:
|
||||
return False
|
||||
|
||||
def sign():
|
||||
dirlist = os.listdir(tx_dir)
|
||||
raw = [f for f in dirlist if f[-6:] == '.rawtx']
|
||||
signed = [f[:-6] for f in dirlist if f[-6:] == '.sigtx']
|
||||
unsigned = [os.path.join(tx_dir,f) for f in raw if f[:-6] not in signed]
|
||||
raw,signed = [set(f[:-6] for f in dirlist if f.endswith(ext)) for ext in ('.rawtx','.sigtx')]
|
||||
unsigned = [os.path.join(tx_dir,f+'.rawtx') for f in raw - signed]
|
||||
|
||||
if unsigned:
|
||||
signed_txs,fails = [],[]
|
||||
|
|
@ -233,10 +231,10 @@ def sign():
|
|||
msg('{} transaction{} signed'.format(len(signed_txs),suf(signed_txs)))
|
||||
if fails:
|
||||
rmsg('{} transaction{} failed to sign'.format(len(fails),suf(fails)))
|
||||
if signed_txs:
|
||||
if signed_txs and not opt.no_summary:
|
||||
print_summary(signed_txs)
|
||||
if fails:
|
||||
rmsg('{}Failed transactions:'.format('' if opt.full_summary else '\n'))
|
||||
rmsg('\nFailed transactions:')
|
||||
rmsg(' ' + '\n '.join(sorted(fails)) + '\n')
|
||||
return False if fails else True
|
||||
else:
|
||||
|
|
@ -248,7 +246,6 @@ def decrypt_wallets():
|
|||
opt.hash_preset = '1'
|
||||
opt.set_by_user = ['hash_preset']
|
||||
opt.passwd_file = os.path.join(tx_dir,key_fn)
|
||||
# opt.passwd_file = '/tmp/key'
|
||||
from .wallet import Wallet
|
||||
msg("Unlocking wallet{} with key from '{}'".format(suf(wfs),opt.passwd_file))
|
||||
fails = 0
|
||||
|
|
@ -261,43 +258,52 @@ def decrypt_wallets():
|
|||
|
||||
return False if fails else True
|
||||
|
||||
|
||||
def print_summary(signed_txs):
|
||||
|
||||
if opt.full_summary:
|
||||
bmsg('\nAutosign summary:\n')
|
||||
def gen():
|
||||
for tx in signed_txs:
|
||||
init_coin(tx.coin,tx.chain == 'testnet')
|
||||
msg_r(tx.format_view(terse=True))
|
||||
yield tx.format_view(terse=True)
|
||||
msg_r(''.join(gen()))
|
||||
return
|
||||
|
||||
body = []
|
||||
def gen():
|
||||
for tx in signed_txs:
|
||||
non_mmgen = [o for o in tx.outputs if not o.mmid]
|
||||
if non_mmgen:
|
||||
body.append((tx,non_mmgen))
|
||||
yield (tx,non_mmgen)
|
||||
|
||||
body = list(gen())
|
||||
|
||||
if body:
|
||||
bmsg('\nAutosign summary:')
|
||||
fs = '{} {} {}'
|
||||
t_wid,a_wid = 6,44
|
||||
msg(fs.format('TX ID ','Non-MMGen outputs'+' '*(a_wid-17),'Amount'))
|
||||
msg(fs.format('-'*t_wid, '-'*a_wid, '-'*7))
|
||||
|
||||
def gen():
|
||||
yield fs.format('TX ID ','Non-MMGen outputs'+' '*(a_wid-17),'Amount')
|
||||
yield fs.format('-'*t_wid, '-'*a_wid, '-'*7)
|
||||
for tx,non_mmgen in body:
|
||||
for nm in non_mmgen:
|
||||
msg(fs.format(
|
||||
yield fs.format(
|
||||
tx.txid.fmt(width=t_wid,color=True) if nm is non_mmgen[0] else ' '*t_wid,
|
||||
nm.addr.fmt(width=a_wid,color=True),
|
||||
nm.amt.hl() + ' ' + yellow(tx.coin)))
|
||||
nm.amt.hl() + ' ' + yellow(tx.coin))
|
||||
|
||||
msg('\n'.join(gen()))
|
||||
else:
|
||||
msg('No non-MMGen outputs')
|
||||
|
||||
def do_sign():
|
||||
if not opt.stealth_led: set_led('busy')
|
||||
if not opt.stealth_led:
|
||||
set_led('busy')
|
||||
do_mount()
|
||||
key_ok = decrypt_wallets()
|
||||
if key_ok:
|
||||
if opt.stealth_led: set_led('busy')
|
||||
if opt.stealth_led:
|
||||
set_led('busy')
|
||||
ret = sign()
|
||||
do_umount()
|
||||
set_led(('standby','off','error')[(not ret)*2 or bool(opt.stealth_led)])
|
||||
|
|
@ -305,7 +311,8 @@ def do_sign():
|
|||
else:
|
||||
msg('Password is incorrect!')
|
||||
do_umount()
|
||||
if not opt.stealth_led: set_led('error')
|
||||
if not opt.stealth_led:
|
||||
set_led('error')
|
||||
return False
|
||||
|
||||
def wipe_existing_key():
|
||||
|
|
@ -397,7 +404,8 @@ def set_led(cmd):
|
|||
led_thread.start()
|
||||
|
||||
def get_insert_status():
|
||||
if opt.no_insert_check: return True
|
||||
if opt.no_insert_check:
|
||||
return True
|
||||
try: os.stat(os.path.join('/dev/disk/by-label',part_label))
|
||||
except: return False
|
||||
else: return True
|
||||
|
|
@ -471,11 +479,12 @@ if len(cmd_args) not in (0,1):
|
|||
opts.usage()
|
||||
|
||||
if len(cmd_args) == 1:
|
||||
if cmd_args[0] in ('gen_key','setup'):
|
||||
globals()[cmd_args[0]]()
|
||||
cmd = cmd_args[0]
|
||||
if cmd in ('gen_key','setup'):
|
||||
globals()[cmd]()
|
||||
sys.exit(0)
|
||||
elif cmd_args[0] != 'wait':
|
||||
die(1,"'{}': unrecognized command".format(cmd_args[0]))
|
||||
elif cmd != 'wait':
|
||||
die(1,f'{cmd!r}: unrecognized command')
|
||||
|
||||
check_wipe_present()
|
||||
wfs = get_wallet_files()
|
||||
|
|
|
|||
|
|
@ -93,7 +93,9 @@ cmd_args = opts.init(opts_data,add_opts=['hidden_incog_input_params','in_fmt','u
|
|||
|
||||
g.use_cached_balances = opt.cached_balances
|
||||
|
||||
if len(cmd_args) < 1: opts.usage()
|
||||
if len(cmd_args) < 1:
|
||||
opts.usage()
|
||||
|
||||
cmd = cmd_args.pop(0)
|
||||
|
||||
import mmgen.tool as tool
|
||||
|
|
|
|||
|
|
@ -44,9 +44,11 @@ rpc_init()
|
|||
|
||||
if len(cmd_args) == 1:
|
||||
infile = cmd_args[0]; check_infile(infile)
|
||||
else: opts.usage()
|
||||
else:
|
||||
opts.usage()
|
||||
|
||||
if not opt.status: do_license_msg()
|
||||
if not opt.status:
|
||||
do_license_msg()
|
||||
|
||||
from .tx import *
|
||||
|
||||
|
|
|
|||
|
|
@ -91,8 +91,11 @@ column below:
|
|||
|
||||
infiles = opts.init(opts_data,add_opts=['b16'])
|
||||
|
||||
if not infiles: opts.usage()
|
||||
for i in infiles: check_infile(i)
|
||||
if not infiles:
|
||||
opts.usage()
|
||||
|
||||
for i in infiles:
|
||||
check_infile(i)
|
||||
|
||||
if g.proto.sign_mode == 'daemon':
|
||||
rpc_init()
|
||||
|
|
|
|||
|
|
@ -112,7 +112,8 @@ def override_globals_from_cfg_file(ucfg):
|
|||
|
||||
def override_globals_from_env():
|
||||
for name in g.env_opts:
|
||||
if name == 'MMGEN_DEBUG_ALL': continue
|
||||
if name == 'MMGEN_DEBUG_ALL':
|
||||
continue
|
||||
disable = name[:14] == 'MMGEN_DISABLE_'
|
||||
val = os.getenv(name) # os.getenv() returns None if env var is unset
|
||||
if val: # exclude empty string values; string value of '0' or 'false' sets variable to False
|
||||
|
|
@ -528,9 +529,9 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
|
|||
def check_and_set_autoset_opts(): # Raises exception if any check fails
|
||||
|
||||
def nocase_str(key,val,asd):
|
||||
if val.lower() in asd.choices:
|
||||
return True
|
||||
else:
|
||||
try:
|
||||
return asd.choices.index(val)
|
||||
except:
|
||||
return 'one of'
|
||||
|
||||
def nocase_pfx(key,val,asd):
|
||||
|
|
|
|||
|
|
@ -67,6 +67,8 @@ def _b58chk_decode(s):
|
|||
raise ValueError('_b58chk_decode(): incorrect checksum')
|
||||
return out[:-4]
|
||||
|
||||
finfo = namedtuple('fork_info',['height','hash','name','replayable'])
|
||||
|
||||
# chainparams.cpp
|
||||
class BitcoinProtocol(MMGenObject):
|
||||
name = 'bitcoin'
|
||||
|
|
@ -87,9 +89,9 @@ class BitcoinProtocol(MMGenObject):
|
|||
daemon_data_subdir = ''
|
||||
sighash_type = 'ALL'
|
||||
block0 = '000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f'
|
||||
forks = [ # height, hash, name, replayable
|
||||
(478559,'00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148','bch',False),
|
||||
(None,'','b2x',True)
|
||||
forks = [
|
||||
finfo(478559,'00000000000000000019f112ec0a9982926f1258cdcc558dd7c3b7e5dc7fa148','BCH',False),
|
||||
finfo(None,'','B2X',True),
|
||||
]
|
||||
caps = ('rbf','segwit')
|
||||
mmcaps = ('key','addr','rpc','tx')
|
||||
|
|
@ -241,7 +243,7 @@ class BitcoinCashProtocol(BitcoinProtocol):
|
|||
mmtypes = ('L','C')
|
||||
sighash_type = 'ALL|FORKID'
|
||||
forks = [
|
||||
(478559,'000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec','btc',False)
|
||||
finfo(478559,'000000000000000000651ef99cb9fcbe0dadde1d424bd9f15ff20136191a5eec','BTC',False)
|
||||
]
|
||||
caps = ()
|
||||
coin_amt = BCHAmt
|
||||
|
|
@ -267,7 +269,7 @@ class B2XProtocol(BitcoinProtocol):
|
|||
coin_amt = B2XAmt
|
||||
max_tx_fee = B2XAmt('0.1')
|
||||
forks = [
|
||||
(None,'','btc',True) # activation: 494784
|
||||
finfo(None,'','BTC',True) # activation: 494784
|
||||
]
|
||||
|
||||
class B2XTestnetProtocol(B2XProtocol):
|
||||
|
|
|
|||
|
|
@ -264,9 +264,11 @@ class MMGenToolCmdMeta(type):
|
|||
def __contains__(cls,val):
|
||||
return cls.methods.__contains__(val)
|
||||
|
||||
def classname(cls,cmd_name):
|
||||
return cls.methods[cmd_name].__qualname__.split('.')[0]
|
||||
|
||||
def call(cls,cmd_name,*args,**kwargs):
|
||||
subcls = cls.classes[cls.methods[cmd_name].__qualname__.split('.')[0]]
|
||||
return getattr(subcls(),cmd_name)(*args,**kwargs)
|
||||
return getattr(cls.classes[cls.classname(cmd_name)](),cmd_name)(*args,**kwargs)
|
||||
|
||||
@property
|
||||
def user_commands(cls):
|
||||
|
|
@ -909,7 +911,10 @@ class MMGenToolCmdRPC(MMGenToolCmds):
|
|||
twuo.do_sort(sort,reverse=reverse)
|
||||
twuo.age_fmt = age_fmt
|
||||
twuo.show_mmid = show_mmid
|
||||
ret = twuo.format_for_printing(color=True,show_confs=wide_show_confs) if wide else twuo.format_for_display()
|
||||
if wide:
|
||||
ret = twuo.format_for_printing(color=True,show_confs=wide_show_confs)
|
||||
else:
|
||||
ret = twuo.format_for_display()
|
||||
del twuo.wallet
|
||||
return ret
|
||||
|
||||
|
|
@ -940,7 +945,7 @@ class MMGenToolCmdMonero(MMGenToolCmds):
|
|||
|
||||
Note that the use of these commands requires private data to be exposed on
|
||||
a network-connected machine in order to unlock the Monero wallets. This is
|
||||
a violation of MMGen's security policy.
|
||||
a violation of good security practice.
|
||||
"""
|
||||
|
||||
_monero_chain_height = None
|
||||
|
|
@ -999,7 +1004,7 @@ class MMGenToolCmdMonero(MMGenToolCmds):
|
|||
restore_height = blockheight,
|
||||
language = 'English' )
|
||||
|
||||
pp_msg(ret) if opt.verbose else msg(' Address: {}'.format(ret['address']))
|
||||
pp_msg(ret) if opt.debug else msg(' Address: {}'.format(ret['address']))
|
||||
return True
|
||||
|
||||
def sync(n,d,fn,c,m):
|
||||
|
|
@ -1043,7 +1048,7 @@ class MMGenToolCmdMonero(MMGenToolCmds):
|
|||
from .obj import XMRAmt
|
||||
bals[fn] = tuple([XMRAmt(ret[k],from_unit='min_coin_unit') for k in ('balance','unlocked_balance')])
|
||||
|
||||
if opt.verbose:
|
||||
if opt.debug:
|
||||
pp_msg(ret)
|
||||
else:
|
||||
msg(' Balance: {} Unlocked balance: {}'.format(*[b.hl() for b in bals[fn]]))
|
||||
|
|
|
|||
32
mmgen/tw.py
32
mmgen/tw.py
|
|
@ -161,7 +161,8 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
tr_rpc = []
|
||||
lbl_id = ('account','label')['label_api' in g.rpch.caps]
|
||||
for o in us_rpc:
|
||||
if not lbl_id in o: continue # coinbase outputs have no account field
|
||||
if not lbl_id in o:
|
||||
continue # coinbase outputs have no account field
|
||||
l = get_tw_label(o[lbl_id])
|
||||
if l:
|
||||
o.update({
|
||||
|
|
@ -172,6 +173,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
'confs': o['confirmations']
|
||||
})
|
||||
tr_rpc.append(o)
|
||||
|
||||
self.unspent = self.MMGenTwOutputList(
|
||||
self.MMGenTwUnspentOutput(
|
||||
**{k:v for k,v in o.items() if k in dir(self.MMGenTwUnspentOutput)}
|
||||
|
|
@ -668,14 +670,19 @@ class TrackingWallet(MMGenObject):
|
|||
del tw
|
||||
atexit.register(del_tw,self)
|
||||
|
||||
# TrackingWallet instances must be explicitly destroyed with 'del tw', 'del twuo.wallet'
|
||||
# and the like to ensure the instance is deleted and wallet is written before global
|
||||
# vars are destroyed by interpreter at shutdown.
|
||||
# This is especially important, as exceptions are ignored within __del__():
|
||||
# /usr/share/doc/python3.6-doc/html/reference/datamodel.html#object.__del__
|
||||
# This code can only be debugged by examining the program output. Since no exceptions
|
||||
# are raised, errors will not be caught by the test suite.
|
||||
def __del__(self):
|
||||
"""
|
||||
TrackingWallet instances opened in write or import mode must be explicitly destroyed
|
||||
with 'del tw', 'del twuo.wallet' and the like to ensure the instance is deleted and
|
||||
wallet is written before global vars are destroyed by the interpreter at shutdown.
|
||||
|
||||
Not that this code can only be debugged by examining the program output, as exceptions
|
||||
are ignored within __del__():
|
||||
|
||||
/usr/share/doc/python3.6-doc/html/reference/datamodel.html#object.__del__
|
||||
|
||||
Since no exceptions are raised, errors will not be caught by the test suite.
|
||||
"""
|
||||
if g.debug:
|
||||
print_stack_trace('TW DEL {!r}'.format(self))
|
||||
|
||||
|
|
@ -684,7 +691,8 @@ class TrackingWallet(MMGenObject):
|
|||
elif g.debug:
|
||||
msg('read-only wallet, doing nothing')
|
||||
|
||||
def upgrade_wallet_maybe(self): pass
|
||||
def upgrade_wallet_maybe(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def conv_types(ad):
|
||||
|
|
@ -825,12 +833,14 @@ class TrackingWallet(MMGenObject):
|
|||
from .addr import AddrData
|
||||
mmaddr = AddrData(source='tw').coinaddr2mmaddr(coinaddr)
|
||||
|
||||
if not mmaddr: mmaddr = '{}:{}'.format(g.proto.base_coin.lower(),coinaddr)
|
||||
if not mmaddr:
|
||||
mmaddr = '{}:{}'.format(g.proto.base_coin.lower(),coinaddr)
|
||||
|
||||
mmaddr = TwMMGenID(mmaddr)
|
||||
|
||||
cmt = TwComment(label,on_fail=on_fail)
|
||||
if cmt in (False,None): return False
|
||||
if cmt in (False,None):
|
||||
return False
|
||||
|
||||
lbl = TwLabel(mmaddr + ('',' '+cmt)[bool(cmt)],on_fail=on_fail)
|
||||
|
||||
|
|
|
|||
20
mmgen/tx.py
20
mmgen/tx.py
|
|
@ -330,7 +330,8 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
|
||||
if filename:
|
||||
self.parse_tx_file(filename,metadata_only=metadata_only,quiet_open=quiet_open)
|
||||
if metadata_only: return
|
||||
if metadata_only:
|
||||
return
|
||||
self.check_pubkey_scripts()
|
||||
self.check_sigs() # marks the tx as signed
|
||||
|
||||
|
|
@ -538,8 +539,8 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
fee_per_kb = ret['feerate'] if 'feerate' in ret else -2
|
||||
fe_type = 'estimatesmartfee'
|
||||
except:
|
||||
fee_per_kb = g.rpch.estimatefee() if g.coin=='BCH' and g.rpch.daemon_version >= 190100 \
|
||||
else g.rpch.estimatefee(opt.tx_confs)
|
||||
args = () if g.coin=='BCH' and g.rpc.daemon_version >= 190100 else (opt.tx_confs,)
|
||||
fee_per_kb = await g.rpc.call('estimatefee',*args)
|
||||
fe_type = 'estimatefee'
|
||||
|
||||
return fee_per_kb,fe_type
|
||||
|
|
@ -982,7 +983,8 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
m = errmsg
|
||||
msg(yellow(m))
|
||||
msg(red('Send of MMGen transaction {} failed'.format(self.txid)))
|
||||
if exit_on_fail: sys.exit(1)
|
||||
if exit_on_fail:
|
||||
sys.exit(1)
|
||||
return False
|
||||
else:
|
||||
if g.bogus_send:
|
||||
|
|
@ -1267,7 +1269,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
desc = 'send amount in metadata'
|
||||
self.send_amt = g.proto.coin_amt(send_amt,on_fail='raise')
|
||||
|
||||
desc = 'transaction hex data'
|
||||
desc = 'transaction file hex data'
|
||||
self.check_txfile_hex_data()
|
||||
# the following ops will all fail if g.coin doesn't match self.coin
|
||||
desc = 'coin type in metadata'
|
||||
|
|
@ -1457,7 +1459,8 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam
|
|||
|
||||
self.twuo.display_total()
|
||||
|
||||
if do_info: sys.exit(0)
|
||||
if do_info:
|
||||
sys.exit(0)
|
||||
|
||||
self.send_amt = self.sum_outputs()
|
||||
|
||||
|
|
@ -1515,7 +1518,7 @@ class MMGenBumpTX(MMGenTX):
|
|||
if not self.is_replaceable():
|
||||
die(1,"Transaction '{}' is not replaceable".format(self.txid))
|
||||
|
||||
# If sending, require tx to have been signed
|
||||
# If sending, require tx to be signed
|
||||
if send:
|
||||
if not self.marked_signed():
|
||||
die(1,"File '{}' is not a signed {} transaction file".format(filename,g.proj_name))
|
||||
|
|
@ -1568,7 +1571,8 @@ class MMGenBumpTX(MMGenTX):
|
|||
p = 'Fee will be deducted from output {}{} ({} {})'.format(idx+1,cs,o_amt,g.coin)
|
||||
if check_sufficient_funds(o_amt):
|
||||
if opt.yes or keypress_confirm(p+'. OK?',default_yes=True):
|
||||
if opt.yes: msg(p)
|
||||
if opt.yes:
|
||||
msg(p)
|
||||
self.bump_output_idx = idx
|
||||
return idx
|
||||
|
||||
|
|
|
|||
|
|
@ -150,7 +150,8 @@ def txsign(tx,seed_files,kl,kal,tx_num_str=''):
|
|||
tmp = KeyAddrList(addrlist=non_mm_addrs)
|
||||
tmp.add_wifs(kl)
|
||||
m = tmp.list_missing('sec')
|
||||
if m: die(2,wmsg['missing_keys_error'].format(suf(m,'es'),'\n '.join(m)))
|
||||
if m:
|
||||
die(2,wmsg['missing_keys_error'].format(suf(m,'es'),'\n '.join(m)))
|
||||
keys += tmp.data
|
||||
|
||||
if opt.mmgen_keys_from_file:
|
||||
|
|
|
|||
|
|
@ -96,9 +96,9 @@ def pp_fmt(d):
|
|||
def pp_msg(d):
|
||||
msg(pp_fmt(d))
|
||||
|
||||
def fmt(s,indent=''):
|
||||
def fmt(s,indent='',strip_char=None):
|
||||
"de-indent multiple lines of text, or indent with specified string"
|
||||
return indent + ('\n'+indent).join([l.strip() for l in s.strip().splitlines()]) + '\n'
|
||||
return indent + ('\n'+indent).join([l.strip(strip_char) for l in s.strip().splitlines()]) + '\n'
|
||||
|
||||
def fmt_list(l,fmt='dfl',indent=''):
|
||||
"pretty-format a list"
|
||||
|
|
@ -608,6 +608,8 @@ def write_data_to_file( outfile,data,desc='data',
|
|||
d = ''
|
||||
finally:
|
||||
if d != cmp_data:
|
||||
if g.test_suite:
|
||||
print_diff(cmp_data,d)
|
||||
m = "{} in file '{}' has been altered by some other program! Aborting file write"
|
||||
die(3,m.format(desc,outfile))
|
||||
|
||||
|
|
|
|||
2
setup.py
2
setup.py
|
|
@ -20,7 +20,7 @@ import sys,os,subprocess
|
|||
from shutil import copy2
|
||||
|
||||
sys_ver = sys.version_info[:2]
|
||||
req_ver = (3,7)
|
||||
req_ver = (3,6)
|
||||
ver2f = lambda t: float('{}.{:03}'.format(*t))
|
||||
|
||||
if ver2f(sys_ver) < ver2f(req_ver):
|
||||
|
|
|
|||
|
|
@ -165,13 +165,11 @@ def iqmsg_r(s):
|
|||
if not opt.quiet: omsg_r(s)
|
||||
|
||||
def start_test_daemons(*network_ids):
|
||||
if hasattr(opt,'no_daemon_autostart') and opt.no_daemon_autostart:
|
||||
return
|
||||
if not opt.no_daemon_autostart:
|
||||
return test_daemons_ops(*network_ids,op='start')
|
||||
|
||||
def stop_test_daemons(*network_ids):
|
||||
if hasattr(opt,'no_daemon_stop') and opt.no_daemon_stop:
|
||||
return
|
||||
if not opt.no_daemon_stop:
|
||||
return test_daemons_ops(*network_ids,op='stop')
|
||||
|
||||
def restart_test_daemons(*network_ids):
|
||||
|
|
@ -179,11 +177,10 @@ def restart_test_daemons(*network_ids):
|
|||
return start_test_daemons(*network_ids)
|
||||
|
||||
def test_daemons_ops(*network_ids,op):
|
||||
if opt.no_daemon_autostart:
|
||||
return
|
||||
if not opt.no_daemon_autostart:
|
||||
from mmgen.daemon import CoinDaemon
|
||||
silent = not opt.verbose and not (hasattr(opt,'exact_output') and opt.exact_output)
|
||||
silent = not opt.verbose and not getattr(opt,'exact_output',False)
|
||||
for network_id in network_ids:
|
||||
if network_id not in CoinDaemon.network_ids: # silently ignore invalid IDs
|
||||
if network_id.lower() not in CoinDaemon.network_ids: # silently ignore invalid IDs
|
||||
continue
|
||||
CoinDaemon(network_id,test_suite=True).cmd(op,silent=silent)
|
||||
|
|
|
|||
61
test/test.py
61
test/test.py
|
|
@ -60,8 +60,8 @@ def create_shm_dir(data_dir,trash_dir):
|
|||
|
||||
dest = os.path.join(shm_dir,os.path.basename(trash_dir))
|
||||
os.mkdir(dest,0o755)
|
||||
try: os.unlink(trash_dir)
|
||||
except: pass
|
||||
|
||||
run(f'rm -rf {trash_dir}',shell=True,check=True)
|
||||
os.symlink(dest,trash_dir)
|
||||
|
||||
dest = os.path.join(shm_dir,os.path.basename(data_dir))
|
||||
|
|
@ -115,6 +115,7 @@ opts_data = {
|
|||
-P, --profile Record the execution time of each script
|
||||
-q, --quiet Produce minimal output. Suppress dependency info
|
||||
-r, --resume=c Resume at command 'c' after interrupted run
|
||||
-R, --resume-after=c Same, but resume at command following 'c'
|
||||
-s, --system Test scripts and modules installed on system rather
|
||||
than those in the repo root
|
||||
-S, --skip-deps Skip dependency checking for command
|
||||
|
|
@ -168,13 +169,19 @@ if not ('resume' in _uopts or 'skip_deps' in _uopts):
|
|||
|
||||
check_segwit_opts()
|
||||
|
||||
if opt.profile: opt.names = True
|
||||
if opt.resume: opt.skip_deps = True
|
||||
if opt.profile:
|
||||
opt.names = True
|
||||
|
||||
if opt.exact_output:
|
||||
def msg(s): pass
|
||||
qmsg = qmsg_r = vmsg = vmsg_r = msg_r = msg
|
||||
|
||||
if opt.resume or opt.resume_after:
|
||||
opt.skip_deps = True
|
||||
resume = opt.resume or opt.resume_after
|
||||
else:
|
||||
resume = False
|
||||
|
||||
cfgs = { # addr_idx_lists (except 31,32,33,34) must contain exactly 8 addresses
|
||||
'1': { 'wpasswd': 'Dorian-α',
|
||||
'kapasswd': 'Grok the blockchain',
|
||||
|
|
@ -514,7 +521,9 @@ class CmdGroupMgr(object):
|
|||
shared_deps are "implied" dependencies for all cmds in cmd_group that don't appear in
|
||||
the cmd_group data or cmds' argument lists. Supported only for 3seed tests at present.
|
||||
"""
|
||||
if not hasattr(cls,'shared_deps'): return []
|
||||
if not hasattr(cls,'shared_deps'):
|
||||
return []
|
||||
|
||||
return [k for k,v in cfgs[str(tmpdir_idx)]['dep_generators'].items()
|
||||
if k in cls.shared_deps and v != cmdname]
|
||||
|
||||
|
|
@ -543,6 +552,7 @@ class CmdGroupMgr(object):
|
|||
def gm_init_group(self,trunner,gname,spawn_prog):
|
||||
kwargs = self.cmd_groups[gname][1]
|
||||
cls = self.create_group(gname,**kwargs)
|
||||
cls.group_name = gname
|
||||
return cls(trunner,cfgs,spawn_prog)
|
||||
|
||||
def list_cmd_groups(self):
|
||||
|
|
@ -657,13 +667,15 @@ class TestSuiteRunner(object):
|
|||
else:
|
||||
omsg_r('Testing {}: '.format(desc))
|
||||
|
||||
if msg_only: return
|
||||
if msg_only:
|
||||
return
|
||||
|
||||
if opt.log:
|
||||
try:
|
||||
self.log_fd.write(cmd_disp+'\n')
|
||||
except:
|
||||
self.log_fd.write(ascii(cmd_disp)+'\n')
|
||||
self.log_fd.write('[{}][{}:{}] {}\n'.format(
|
||||
g.coin.lower(),
|
||||
self.ts.group_name,
|
||||
self.ts.test_name,
|
||||
cmd_disp))
|
||||
|
||||
from test.include.pexpect import MMGenPexpect
|
||||
return MMGenPexpect(args,no_output=no_output)
|
||||
|
|
@ -710,6 +722,11 @@ class TestSuiteRunner(object):
|
|||
|
||||
self.ts = self.gm.gm_init_group(self,gname,self.spawn_wrapper)
|
||||
|
||||
if opt.resume_after:
|
||||
global resume
|
||||
resume = self.gm.cmd_list[self.gm.cmd_list.index(resume)+1]
|
||||
omsg(f'INFO → Resuming at command {resume!r}')
|
||||
|
||||
if opt.exit_after and opt.exit_after not in self.gm.cmd_list:
|
||||
die(1,'{!r}: command not recognized'.format(opt.exit_after))
|
||||
|
||||
|
|
@ -721,7 +738,8 @@ class TestSuiteRunner(object):
|
|||
if usr_args:
|
||||
for arg in usr_args:
|
||||
if arg in self.gm.cmd_groups:
|
||||
if not self.init_group(arg): continue
|
||||
if not self.init_group(arg):
|
||||
continue
|
||||
clean(self.ts.tmpdir_nums)
|
||||
for cmd in self.gm.cmd_list:
|
||||
self.check_needs_rerun(cmd,build=True)
|
||||
|
|
@ -753,8 +771,10 @@ class TestSuiteRunner(object):
|
|||
if e not in self.gm.cmd_groups_dfl:
|
||||
die(1,'{!r}: group not recognized'.format(e))
|
||||
for gname in self.gm.cmd_groups_dfl:
|
||||
if opt.exclude_groups and gname in exclude: continue
|
||||
if not self.init_group(gname): continue
|
||||
if opt.exclude_groups and gname in exclude:
|
||||
continue
|
||||
if not self.init_group(gname):
|
||||
continue
|
||||
clean(self.ts.tmpdir_nums)
|
||||
for cmd in self.gm.cmd_list:
|
||||
self.check_needs_rerun(cmd,build=True)
|
||||
|
|
@ -823,13 +843,13 @@ class TestSuiteRunner(object):
|
|||
if hasattr(self.ts,'shared_deps'):
|
||||
arg_list = arg_list[:-len(self.ts.shared_deps)]
|
||||
|
||||
if opt.resume:
|
||||
if cmd == opt.resume:
|
||||
bmsg('Resuming at {!r}'.format(cmd))
|
||||
opt.resume = False
|
||||
opt.skip_deps = False
|
||||
else:
|
||||
global resume
|
||||
if resume:
|
||||
if cmd != resume:
|
||||
return
|
||||
bmsg('Resuming at {!r}'.format(cmd))
|
||||
resume = False
|
||||
opt.skip_deps = False
|
||||
|
||||
if opt.profile: start = time.time()
|
||||
|
||||
|
|
@ -938,14 +958,17 @@ if opt.pause:
|
|||
set_restore_term_at_exit()
|
||||
|
||||
set_environ_for_spawned_scripts()
|
||||
if network_id not in ('eth','etc'):
|
||||
start_test_daemons(network_id)
|
||||
|
||||
try:
|
||||
tr = TestSuiteRunner(data_dir,trash_dir)
|
||||
tr.run_tests(usr_args)
|
||||
tr.warn_skipped()
|
||||
if network_id not in ('eth','etc'):
|
||||
stop_test_daemons(network_id)
|
||||
except KeyboardInterrupt:
|
||||
if network_id not in ('eth','etc'):
|
||||
stop_test_daemons(network_id)
|
||||
tr.warn_skipped()
|
||||
die(1,'\ntest.py exiting at user request')
|
||||
|
|
|
|||
|
|
@ -157,12 +157,16 @@ class TestSuiteAutosign(TestSuiteBase):
|
|||
copy_files(mountpoint,remove_signed_only=True,include_bad_tx=not led_opts)
|
||||
do_unmount()
|
||||
do_loop()
|
||||
imsg(purple('\nKilling wait loop!'))
|
||||
t.kill(2) # 2 = SIGINT
|
||||
t.req_exit_val = 1
|
||||
return t
|
||||
|
||||
def do_autosign(opts,mountpoint):
|
||||
|
||||
if not opt.skip_deps:
|
||||
make_wallet(opts)
|
||||
|
||||
copy_files(mountpoint,include_bad_tx=True)
|
||||
|
||||
t = self.spawn('mmgen-autosign',opts+['--full-summary','wait'],extra_desc='(sign - full summary)')
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
|
||||
('txcreate1', 'creating a transaction (spend from dev address to address :1)'),
|
||||
('txsign1', 'signing the transaction'),
|
||||
('tx_status0', 'getting the transaction status'),
|
||||
('tx_status0_bad', 'getting the transaction status'),
|
||||
('txsign1_ni', 'signing the transaction (non-interactive)'),
|
||||
('txsend1', 'sending the transaction'),
|
||||
('bal1', 'the {} balance'.format(g.coin)),
|
||||
|
|
@ -425,7 +425,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
|
|||
return self.txcreate(args=args,menu=menu,acct='1',non_mmgen_inputs=1)
|
||||
|
||||
def txsign1(self): return self.txsign(add_args=['--use-internal-keccak-module'])
|
||||
def tx_status0(self):
|
||||
def tx_status0_bad(self):
|
||||
return self.tx_status(ext='{}.sigtx',expect_str='neither in mempool nor blockchain',exit_val=1)
|
||||
def txsign1_ni(self): return self.txsign(ni=True)
|
||||
def txsend1(self): return self.txsend()
|
||||
|
|
|
|||
|
|
@ -725,11 +725,12 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
|
|||
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)
|
||||
t = self.spawn('mmgen-addrimport',['--'+user]+args)
|
||||
if g.debug:
|
||||
t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
|
||||
t.expect('Importing')
|
||||
t.expect('OK')
|
||||
t.read()
|
||||
return t
|
||||
|
||||
def bob_import_addr(self):
|
||||
|
|
|
|||
|
|
@ -437,6 +437,7 @@ try:
|
|||
die(1,'Only one command may be specified')
|
||||
cmd = cmd_args[0]
|
||||
if cmd in cmd_data:
|
||||
cleandir(cfg['tmpdir'],do_msg=True)
|
||||
msg('Running tests for {}:'.format(cmd_data[cmd]['desc']))
|
||||
do_cmds(cmd)
|
||||
elif cmd == 'clean':
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from mmgen.common import *
|
|||
opts_data = {
|
||||
'text': {
|
||||
'desc': "Unit tests for the MMGen suite",
|
||||
'usage':'[options] [tests]',
|
||||
'usage':'[options] [tests | test [subtest]]',
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
-A, --no-daemon-autostart Don't start and stop daemons automatically
|
||||
|
|
@ -45,7 +45,7 @@ If no test is specified, all available tests are run
|
|||
}
|
||||
|
||||
sys.argv.insert(1,'--skip-cfg-file')
|
||||
cmd_args = opts.init(opts_data)
|
||||
cmd_args = opts.init(opts_data,add_opts=['no_daemon_stop'])
|
||||
|
||||
def exit_msg():
|
||||
t = int(time.time()) - start_time
|
||||
|
|
@ -81,20 +81,40 @@ class UnitTestHelpers(object):
|
|||
else:
|
||||
rdie(3,m_noraise.format(desc,exc_chk))
|
||||
|
||||
try:
|
||||
for test in cmd_args:
|
||||
if test not in all_tests:
|
||||
die(1,"'{}': test not recognized".format(test))
|
||||
|
||||
import importlib
|
||||
for test in (cmd_args or all_tests):
|
||||
def run_test(test,subtest=None):
|
||||
modname = 'test.unit_tests_d.ut_{}'.format(test)
|
||||
mod = importlib.import_module(modname)
|
||||
gmsg('Running unit test {}'.format(test))
|
||||
if not mod.unit_test().run_test(test,UnitTestHelpers):
|
||||
rdie(1,'Unit test {!r} failed'.format(test))
|
||||
del mod
|
||||
|
||||
def run_subtest(subtest):
|
||||
gmsg(f'Running unit subtest {test}.{subtest}')
|
||||
t = getattr(mod,'unit_tests')()
|
||||
if not getattr(t,subtest)(test,UnitTestHelpers):
|
||||
rdie(1,f'Unit subtest {subtest!r} failed')
|
||||
pass
|
||||
|
||||
if subtest:
|
||||
run_subtest(subtest)
|
||||
else:
|
||||
gmsg(f'Running unit test {test}')
|
||||
if hasattr(mod,'unit_tests'):
|
||||
t = getattr(mod,'unit_tests')
|
||||
subtests = [k for k,v in t.__dict__.items() if type(v).__name__ == 'function']
|
||||
for subtest in subtests:
|
||||
run_subtest(subtest)
|
||||
else:
|
||||
if not mod.unit_test().run_test(test,UnitTestHelpers):
|
||||
rdie(1,'Unit test {test!r} failed')
|
||||
|
||||
try:
|
||||
import importlib
|
||||
if len(cmd_args) == 2 and cmd_args[0] in all_tests and cmd_args[1] not in all_tests:
|
||||
run_test(*cmd_args) # assume 2nd arg is subtest
|
||||
else:
|
||||
for test in cmd_args:
|
||||
if test not in all_tests:
|
||||
die(1,f'{test!r}: test not recognized')
|
||||
for test in (cmd_args or all_tests):
|
||||
run_test(test)
|
||||
exit_msg()
|
||||
except KeyboardInterrupt:
|
||||
die(1,green('\nExiting at user request'))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue