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