Browse Source

new MMGenSystemExit exception; remove rdie(), ydie()

The MMGen Project 3 years ago
parent
commit
41376eb515

+ 1 - 1
mmgen/base_proto/bitcoin/tx/status.py

@@ -82,7 +82,7 @@ class Status(TxBase.Status):
 		elif await is_in_wallet():
 		elif await is_in_wallet():
 			die(0,f'Transaction has {r.confs} confirmation{suf(r.confs)}')
 			die(0,f'Transaction has {r.confs} confirmation{suf(r.confs)}')
 		elif await is_in_utxos():
 		elif await is_in_utxos():
-			rdie(2,'ERROR: transaction is in the blockchain (but not in the tracking wallet)!')
+			die(4,'ERROR: transaction is in the blockchain (but not in the tracking wallet)!')
 		elif await is_replaced():
 		elif await is_replaced():
 			msg('Transaction has been replaced')
 			msg('Transaction has been replaced')
 			msg('Replacement transaction ' + (
 			msg('Replacement transaction ' + (

+ 3 - 3
mmgen/devtools.py

@@ -135,7 +135,7 @@ if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN
 		def immutable_attr_init_check(self):
 		def immutable_attr_init_check(self):
 			from .globalvars import g
 			from .globalvars import g
 			if g.test_suite:
 			if g.test_suite:
-				from .util import rdie
+				from .util import die
 				cls = type(self)
 				cls = type(self)
 				for attrname in sorted({a for a in self.valid_attrs if a[0] != '_'}):
 				for attrname in sorted({a for a in self.valid_attrs if a[0] != '_'}):
 					for o in (cls,cls.__bases__[0]): # assume there's only one base class
 					for o in (cls,cls.__bases__[0]): # assume there's only one base class
@@ -143,10 +143,10 @@ if os.getenv('MMGEN_DEBUG') or os.getenv('MMGEN_TEST_SUITE') or os.getenv('MMGEN
 							attr = o.__dict__[attrname]
 							attr = o.__dict__[attrname]
 							break
 							break
 					else:
 					else:
-						rdie(3,f'unable to find descriptor {cls.__name__}.{attrname}')
+						die(4,f'unable to find descriptor {cls.__name__}.{attrname}')
 					if type(attr).__name__ == 'ImmutableAttr':
 					if type(attr).__name__ == 'ImmutableAttr':
 						if attrname not in self.__dict__:
 						if attrname not in self.__dict__:
-							rdie(3,
+							die(4,
 						f'attribute {attrname!r} of {cls.__name__} has not been initialized in constructor!')
 						f'attribute {attrname!r} of {cls.__name__} has not been initialized in constructor!')
 
 
 	def print_diff(a,b,from_file='',to_file='',from_json=True):
 	def print_diff(a,b,from_file='',to_file='',from_json=True):

+ 13 - 0
mmgen/exception.py

@@ -20,6 +20,19 @@
 mmgen.exception: Exception classes for the MMGen suite
 mmgen.exception: Exception classes for the MMGen suite
 """
 """
 
 
+class MMGenError(Exception):
+
+	def __init__(self,errno,strerror,stdout):
+		self.mmcode = errno
+		self.stdout = stdout
+		super().__init__(strerror)
+
+	def __repr__(self):
+		return f'{type(self).__name__}({self.mmcode}): {self}'
+
+class MMGenSystemExit(MMGenError):
+	pass
+
 # 1: no hl, message only
 # 1: no hl, message only
 class UserNonConfirmation(Exception):     mmcode = 1
 class UserNonConfirmation(Exception):     mmcode = 1
 class BadAgeFormat(Exception):            mmcode = 1
 class BadAgeFormat(Exception):            mmcode = 1

+ 1 - 1
mmgen/fileutil.py

@@ -57,7 +57,7 @@ def check_binary(args):
 	try:
 	try:
 		run(args,stdout=DEVNULL,stderr=DEVNULL,check=True)
 		run(args,stdout=DEVNULL,stderr=DEVNULL,check=True)
 	except:
 	except:
-		rdie(2,f'{args[0]!r} binary missing, not in path, or not executable')
+		die(2,f'{args[0]!r} binary missing, not in path, or not executable')
 
 
 def shred_file(fn,verbose=False):
 def shred_file(fn,verbose=False):
 	check_binary(['shred','--version'])
 	check_binary(['shred','--version'])

+ 1 - 1
mmgen/led.py

@@ -89,7 +89,7 @@ class LEDControl:
 					fp.write(f'{init_val}\n')
 					fp.write(f'{init_val}\n')
 				return True
 				return True
 			except PermissionError:
 			except PermissionError:
-				ydie(1,'\n'+fmt(f"""
+				die(2,'\n'+fmt(f"""
 					You do not have access to the {desc} file
 					You do not have access to the {desc} file
 					To allow access, run the following command:
 					To allow access, run the following command:
 
 

+ 24 - 10
mmgen/main.py

@@ -43,21 +43,35 @@ def launch(mod):
 	except EOFError:
 	except EOFError:
 		sys.stderr.write('\nEnd of file\n')
 		sys.stderr.write('\nEnd of file\n')
 	except Exception as e:
 	except Exception as e:
+
 		if os.getenv('MMGEN_EXEC_WRAPPER'):
 		if os.getenv('MMGEN_EXEC_WRAPPER'):
 			raise
 			raise
 		else:
 		else:
-			try: m = '{}'.format(e.args[0])
-			except: m = repr(e.args[0])
+			try:
+				errmsg = '{}'.format(e.args[0])
+			except:
+				errmsg = repr(e.args[0])
+
+			from collections import namedtuple
+			from mmgen.color import nocolor,yellow,red
+
+			_o = namedtuple('exit_data',['color','exit_val','fs'])
+			d = {
+				1:   _o(nocolor, 1, '{message}'),
+				2:   _o(yellow,  2, '{message}'),
+				3:   _o(yellow,  3, '\nMMGen Error ({name}): {message}'),
+				4:   _o(red,     4, '\nMMGen Fatal Error ({name}): {message}'),
+				'x': _o(yellow,  5, '\nMMGen Unhandled Exception ({name}): {message}'),
+			}[getattr(e,'mmcode','x')]
+
+			(sys.stdout if getattr(e,'stdout',None) else sys.stderr).write(
+				d.color(d.fs.format(
+					name = type(e).__name__,
+					message = errmsg ))
+				+ '\n' )
 
 
-			from .util import die,ydie,rdie
-			d = [   (ydie,2,'\nMMGen Unhandled Exception ({n}): {m}'),
-					(die, 1,'{m}'),
-					(ydie,2,'{m}'),
-					(ydie,3,'\nMMGen Error ({n}): {m}'),
-					(rdie,4,'\nMMGen Fatal Error ({n}): {m}')
-				][e.mmcode if hasattr(e,'mmcode') else 0]
+			sys.exit(d.exit_val)
 
 
-			d[0](d[1],d[2].format(n=type(e).__name__,m=m))
 	except SystemExit as e:
 	except SystemExit as e:
 		if os.getenv('MMGEN_EXEC_WRAPPER') and e.code != 0:
 		if os.getenv('MMGEN_EXEC_WRAPPER') and e.code != 0:
 			from mmgen.color import red
 			from mmgen.color import red

+ 1 - 1
mmgen/main_addrimport.py

@@ -78,7 +78,7 @@ def parse_cmd_args(rpc,cmd_args):
 		al = (AddrList,KeyAddrList)[bool(opt.keyaddr_file)](proto,infile)
 		al = (AddrList,KeyAddrList)[bool(opt.keyaddr_file)](proto,infile)
 		if al.al_id.mmtype in ('S','B'):
 		if al.al_id.mmtype in ('S','B'):
 			if not rpc.info('segwit_is_active'):
 			if not rpc.info('segwit_is_active'):
-				rdie(2,'Segwit is not active on this chain. Cannot import Segwit addresses')
+				die(2,'Segwit is not active on this chain. Cannot import Segwit addresses')
 		return al
 		return al
 
 
 	if len(cmd_args) == 1:
 	if len(cmd_args) == 1:

+ 1 - 1
mmgen/main_autosign.py

@@ -164,7 +164,7 @@ async def check_daemons_running():
 			try:
 			try:
 				await rpc_init(proto)
 				await rpc_init(proto)
 			except SocketError as e:
 			except SocketError as e:
-				ydie(1,f'{coin} daemon not running or not listening on port {proto.rpc_port}')
+				die(2,f'{coin} daemon not running or not listening on port {proto.rpc_port}')
 
 
 def get_wallet_files():
 def get_wallet_files():
 	try:
 	try:

+ 3 - 3
mmgen/main_tool.py

@@ -281,7 +281,7 @@ def process_args(cmd,cmd_args,cls):
 	return ( args, kwargs )
 	return ( args, kwargs )
 
 
 def process_result(ret,pager=False,print_result=False):
 def process_result(ret,pager=False,print_result=False):
-	from .util import Msg,ydie,parse_bytespec
+	from .util import Msg,die,parse_bytespec
 	"""
 	"""
 	Convert result to something suitable for output to screen and return it.
 	Convert result to something suitable for output to screen and return it.
 	If result is bytes and not convertible to utf8, output as binary using os.write().
 	If result is bytes and not convertible to utf8, output as binary using os.write().
@@ -294,7 +294,7 @@ def process_result(ret,pager=False,print_result=False):
 	if ret == True:
 	if ret == True:
 		return True
 		return True
 	elif ret in (False,None):
 	elif ret in (False,None):
-		ydie(1,f'tool command returned {ret!r}')
+		die(2,f'tool command returned {ret!r}')
 	elif isinstance(ret,str):
 	elif isinstance(ret,str):
 		return triage_result(ret)
 		return triage_result(ret)
 	elif isinstance(ret,int):
 	elif isinstance(ret,int):
@@ -312,7 +312,7 @@ def process_result(ret,pager=False,print_result=False):
 			else:
 			else:
 				return ret
 				return ret
 	else:
 	else:
-		ydie(1,f'tool.py: can’t handle return value of type {type(ret).__name__!r}')
+		die(2,f'tool.py: can’t handle return value of type {type(ret).__name__!r}')
 
 
 def get_cmd_cls(cmd):
 def get_cmd_cls(cmd):
 	for modname,cmdlist in mods.items():
 	for modname,cmdlist in mods.items():

+ 1 - 1
mmgen/main_txsign.py

@@ -156,6 +156,6 @@ async def main():
 			bad_tx_count += 1
 			bad_tx_count += 1
 
 
 	if bad_tx_count:
 	if bad_tx_count:
-		ydie(2,f'{bad_tx_count} transaction{suf(bad_tx_count)} could not be signed')
+		die(2,f'{bad_tx_count} transaction{suf(bad_tx_count)} could not be signed')
 
 
 run_session(main())
 run_session(main())

+ 4 - 5
mmgen/protocol.py

@@ -69,7 +69,7 @@ class CoinProtocol(MMGenObject):
 
 
 			if 'tx' not in self.mmcaps and g.is_txprog:
 			if 'tx' not in self.mmcaps and g.is_txprog:
 				from .util import die
 				from .util import die
-				die(1,f'Command {g.prog_name!r} not supported for coin {self.coin}')
+				die(2,f'Command {g.prog_name!r} not supported for coin {self.coin}')
 
 
 			if hasattr(self,'chain_names'):
 			if hasattr(self,'chain_names'):
 				self.chain_name = self.chain_names[0] # first chain name is default
 				self.chain_name = self.chain_names[0] # first chain name is default
@@ -171,15 +171,14 @@ class CoinProtocol(MMGenObject):
 			if 0 < int.from_bytes(sec,'big') < self.secp256k1_ge:
 			if 0 < int.from_bytes(sec,'big') < self.secp256k1_ge:
 				return sec
 				return sec
 			else: # chance of this is less than 1 in 2^127
 			else: # chance of this is less than 1 in 2^127
-				from .util import ydie
+				from .util import die,ymsg
 				pk = int.from_bytes(sec,'big')
 				pk = int.from_bytes(sec,'big')
 				if pk == 0: # chance of this is 1 in 2^256
 				if pk == 0: # chance of this is 1 in 2^256
-					ydie(3,'Private key is zero!')
+					die(4,'Private key is zero!')
 				elif pk == self.secp256k1_ge: # ditto
 				elif pk == self.secp256k1_ge: # ditto
-					ydie(3,'Private key == secp256k1_ge!')
+					die(4,'Private key == secp256k1_ge!')
 				else:
 				else:
 					if not g.test_suite:
 					if not g.test_suite:
-						from .util import ymsg
 						ymsg(f'Warning: private key is greater than secp256k1 group order!:\n  {hexpriv}')
 						ymsg(f'Warning: private key is greater than secp256k1 group order!:\n  {hexpriv}')
 					return (pk % self.secp256k1_ge).to_bytes(self.privkey_len,'big')
 					return (pk % self.secp256k1_ge).to_bytes(self.privkey_len,'big')
 
 

+ 1 - 1
mmgen/regtest.py

@@ -102,7 +102,7 @@ class MMGenRegtest(MMGenObject):
 		out = await self.rpc_call(*cmd_args,wallet='miner')
 		out = await self.rpc_call(*cmd_args,wallet='miner')
 
 
 		if len(out) != blocks:
 		if len(out) != blocks:
-			rdie(1,'Error generating blocks')
+			die(4,'Error generating blocks')
 
 
 		gmsg(f'Mined {blocks} block{suf(blocks)}')
 		gmsg(f'Mined {blocks} block{suf(blocks)}')
 
 

+ 3 - 3
mmgen/rpc.py

@@ -537,7 +537,7 @@ class BitcoinRPCClient(RPCClient,metaclass=AsyncInit):
 				await self.icall('createwallet',wallet_name=wname)
 				await self.icall('createwallet',wallet_name=wname)
 				ymsg(f'Created {self.daemon.coind_name} wallet {wname!r}')
 				ymsg(f'Created {self.daemon.coind_name} wallet {wname!r}')
 			elif len(wallets) > 1: # support only one loaded wallet for now
 			elif len(wallets) > 1: # support only one loaded wallet for now
-				rdie(2,f'ERROR: more than one {self.daemon.coind_name} wallet loaded: {wallets}')
+				die(4,f'ERROR: more than one {self.daemon.coind_name} wallet loaded: {wallets}')
 			wallet_checked.append(True)
 			wallet_checked.append(True)
 
 
 	def get_daemon_cfg_fn(self):
 	def get_daemon_cfg_fn(self):
@@ -665,7 +665,7 @@ class EthereumRPCClient(RPCClient,metaclass=AsyncInit):
 		import re
 		import re
 		vip = re.match(self.daemon.version_pat,vi,re.ASCII)
 		vip = re.match(self.daemon.version_pat,vi,re.ASCII)
 		if not vip:
 		if not vip:
-			ydie(1,fmt(f"""
+			die(2,fmt(f"""
 			Aborting on daemon mismatch:
 			Aborting on daemon mismatch:
 			  Requested daemon: {self.daemon.id}
 			  Requested daemon: {self.daemon.id}
 			  Running daemon:   {vi}
 			  Running daemon:   {vi}
@@ -821,7 +821,7 @@ def handle_unsupported_daemon_version(rpc,name,warn_only):
 		daemon_warning('version',div=name,fmt_args=[rpc.daemon.coind_name])
 		daemon_warning('version',div=name,fmt_args=[rpc.daemon.coind_name])
 	else:
 	else:
 		name = rpc.daemon.coind_name
 		name = rpc.daemon.coind_name
-		rdie(1,'\n'+fmt(f"""
+		die(2,'\n'+fmt(f"""
 			The running {name} daemon has version {rpc.daemon_version_str}.
 			The running {name} daemon has version {rpc.daemon_version_str}.
 			This version of MMGen is tested only on {name} v{rpc.daemon.coind_version_str} and below.
 			This version of MMGen is tested only on {name} v{rpc.daemon.coind_version_str} and below.
 
 

+ 4 - 2
mmgen/twaddrs.py

@@ -43,7 +43,8 @@ class TwAddrList(MMGenDict,TwCommon,metaclass=AsyncInit):
 					err = True
 					err = True
 					msg(f'Duplicate MMGen ID ({mmid}) discovered in tracking wallet!\n')
 					msg(f'Duplicate MMGen ID ({mmid}) discovered in tracking wallet!\n')
 				mmid_prev = mmid
 				mmid_prev = mmid
-			if err: rdie(3,'Tracking wallet is corrupted!')
+			if err:
+				die(4,'Tracking wallet is corrupted!')
 
 
 		def check_addr_array_lens(acct_pairs):
 		def check_addr_array_lens(acct_pairs):
 			err = False
 			err = False
@@ -55,7 +56,8 @@ class TwAddrList(MMGenDict,TwCommon,metaclass=AsyncInit):
 						msg(f'Label {label!r}: has no associated address!')
 						msg(f'Label {label!r}: has no associated address!')
 					else:
 					else:
 						msg(f'{addrs!r}: more than one {proto.coin} address in account!')
 						msg(f'{addrs!r}: more than one {proto.coin} address in account!')
-			if err: rdie(3,'Tracking wallet is corrupted!')
+			if err:
+				die(4,'Tracking wallet is corrupted!')
 
 
 		self.rpc   = await rpc_init(proto)
 		self.rpc   = await rpc_init(proto)
 		self.total = proto.coin_amt('0')
 		self.total = proto.coin_amt('0')

+ 4 - 4
mmgen/tx/new.py

@@ -17,7 +17,7 @@ from ..opts import opt
 from .base import Base
 from .base import Base
 from ..color import pink
 from ..color import pink
 from ..obj import get_obj,HexStr
 from ..obj import get_obj,HexStr
-from ..util import msg,qmsg,fmt,suf,remove_dups,get_extension,keypress_confirm,do_license_msg,line_input
+from ..util import msg,qmsg,fmt,die,suf,remove_dups,get_extension,keypress_confirm,do_license_msg,line_input
 from ..addr import is_mmgen_id,CoinAddr,is_coin_addr
 from ..addr import is_mmgen_id,CoinAddr,is_coin_addr
 
 
 def mmaddr2coinaddr(mmaddr,ad_w,ad_f,proto):
 def mmaddr2coinaddr(mmaddr,ad_w,ad_f,proto):
@@ -53,9 +53,9 @@ def mmaddr2coinaddr(mmaddr,ad_w,ad_f,proto):
 				if not (opt.yes or keypress_confirm('Continue anyway?')):
 				if not (opt.yes or keypress_confirm('Continue anyway?')):
 					sys.exit(1)
 					sys.exit(1)
 			else:
 			else:
-				ydie(2,wmsg('addr_not_found'))
+				die(2,wmsg('addr_not_found'))
 		else:
 		else:
-			ydie(2,wmsg('addr_not_found_no_addrfile'))
+			die(2,wmsg('addr_not_found_no_addrfile'))
 
 
 	return CoinAddr(proto,coin_addr)
 	return CoinAddr(proto,coin_addr)
 
 
@@ -208,7 +208,7 @@ class New(Base):
 				'ERROR: No change output specified' ))
 				'ERROR: No change output specified' ))
 
 
 		if self.has_segwit_outputs() and not self.rpc.info('segwit_is_active'):
 		if self.has_segwit_outputs() and not self.rpc.info('segwit_is_active'):
-			rdie(2,f'{g.proj_name} Segwit address requested on the command line, '
+			die(2,f'{g.proj_name} Segwit address requested on the command line, '
 					+ 'but Segwit is not active on this chain')
 					+ 'but Segwit is not active on this chain')
 
 
 		if not self.outputs:
 		if not self.outputs:

+ 8 - 16
mmgen/util.py

@@ -120,11 +120,13 @@ def mdie(*args):
 	mmsg(*args)
 	mmsg(*args)
 	sys.exit(0)
 	sys.exit(0)
 
 
-def die(ev=0,s=''):
+def die(ev,s='',stdout=False):
 	assert isinstance(ev,int)
 	assert isinstance(ev,int)
-	if s:
-		msg(s)
-	sys.exit(ev)
+	from .exception import MMGenSystemExit,MMGenError
+	if ev <= 2:
+		raise MMGenSystemExit(ev,s,stdout)
+	else:
+		raise MMGenError(ev,s,stdout)
 
 
 def die_wait(delay,ev=0,s=''):
 def die_wait(delay,ev=0,s=''):
 	assert isinstance(delay,int)
 	assert isinstance(delay,int)
@@ -142,16 +144,7 @@ def die_pause(ev=0,s=''):
 	sys.exit(ev)
 	sys.exit(ev)
 
 
 def Die(ev=0,s=''):
 def Die(ev=0,s=''):
-	assert isinstance(ev,int)
-	if s:
-		Msg(s)
-	sys.exit(ev)
-
-def rdie(ev=0,s=''):
-	die(ev,red(s))
-
-def ydie(ev=0,s=''):
-	die(ev,yellow(s))
+	die(ev=ev,s=s,stdout=True)
 
 
 def pp_fmt(d):
 def pp_fmt(d):
 	import pprint
 	import pprint
@@ -206,8 +199,7 @@ def remove_dups(iterable,edesc='element',desc='list',quiet=False,hide=False):
 
 
 def exit_if_mswin(feature):
 def exit_if_mswin(feature):
 	if g.platform == 'win':
 	if g.platform == 'win':
-		m = capfirst(feature) + ' not supported on the MSWin / MSYS2 platform'
-		ydie(1,m)
+		die(2, capfirst(feature) + ' not supported on the MSWin / MSYS2 platform' )
 
 
 def get_keccak():
 def get_keccak():
 
 

+ 1 - 1
scripts/create-token.py

@@ -243,7 +243,7 @@ def compile_code(code):
 	if cp.returncode != 0:
 	if cp.returncode != 0:
 		rmsg('Solidity compiler produced the following error:')
 		rmsg('Solidity compiler produced the following error:')
 		msg(err)
 		msg(err)
-		rdie(2,f'Solidity compiler exited with error (return val: {cp.returncode})')
+		die(4,f'Solidity compiler exited with error (return val: {cp.returncode})')
 	if err:
 	if err:
 		ymsg('Solidity compiler produced the following warning:')
 		ymsg('Solidity compiler produced the following warning:')
 		msg(err)
 		msg(err)

+ 8 - 4
scripts/exec_wrapper.py

@@ -32,7 +32,7 @@ def exec_wrapper_init(): # don't change: name is used to test if script is runni
 		except:
 		except:
 			pass
 			pass
 
 
-def exec_wrapper_write_traceback():
+def exec_wrapper_write_traceback(e):
 	import traceback,re
 	import traceback,re
 	lines = traceback.format_exception(*sys.exc_info()) # returns a list
 	lines = traceback.format_exception(*sys.exc_info()) # returns a list
 
 
@@ -45,7 +45,11 @@ def exec_wrapper_write_traceback():
 		lines.pop()
 		lines.pop()
 
 
 	c = exec_wrapper_get_colors()
 	c = exec_wrapper_get_colors()
-	sys.stdout.write('{}{}'.format(c.yellow(''.join(lines)),c.red(exc)))
+	message = ( repr(e) if type(e).__name__ in ('MMGenError','MMGenSystemExit') else exc )
+	sys.stdout.write('{}{}'.format(
+		c.yellow( ''.join(lines) ),
+		c.red(message) )
+	+ '\n' )
 
 
 	with open('my.err','w') as fp:
 	with open('my.err','w') as fp:
 		fp.write(''.join(lines+[exc]))
 		fp.write(''.join(lines+[exc]))
@@ -96,13 +100,13 @@ try:
 		exec(fp.read())
 		exec(fp.read())
 except SystemExit as e:
 except SystemExit as e:
 	if e.code != 0 and not os.getenv('EXEC_WRAPPER_NO_TRACEBACK'):
 	if e.code != 0 and not os.getenv('EXEC_WRAPPER_NO_TRACEBACK'):
-		exec_wrapper_write_traceback()
+		exec_wrapper_write_traceback(e)
 	else:
 	else:
 		exec_wrapper_tracemalloc_log()
 		exec_wrapper_tracemalloc_log()
 		exec_wrapper_end_msg()
 		exec_wrapper_end_msg()
 	sys.exit(e.code)
 	sys.exit(e.code)
 except Exception as e:
 except Exception as e:
-	exec_wrapper_write_traceback()
+	exec_wrapper_write_traceback(e)
 	retval = e.mmcode if hasattr(e,'mmcode') else e.code if hasattr(e,'code') else 1
 	retval = e.mmcode if hasattr(e,'mmcode') else e.code if hasattr(e,'code') else 1
 	sys.exit(retval)
 	sys.exit(retval)
 
 

+ 1 - 1
test/gentest.py

@@ -318,7 +318,7 @@ def do_ab_test(proto,cfg,addr_type,gen1,kg2,ag,tool,cache_data):
 
 
 	kg1 = KeyGenerator( proto, addr_type.pubkey_type, gen1 )
 	kg1 = KeyGenerator( proto, addr_type.pubkey_type, gen1 )
 	if type(kg1) == type(kg2):
 	if type(kg1) == type(kg2):
-		rdie(1,'Key generators are the same!')
+		die(4,'Key generators are the same!')
 
 
 	e = cinfo.get_entry(proto.coin,proto.network)
 	e = cinfo.get_entry(proto.coin,proto.network)
 	qmsg(green("Comparing address generators '{A}' and '{B}' for {N} {c} ({n}), addrtype {a!r}".format(
 	qmsg(green("Comparing address generators '{A}' and '{B}' for {N} {c} ({n}), addrtype {a!r}".format(

+ 6 - 5
test/include/pexpect.py

@@ -23,7 +23,7 @@ test/pexpect.py: pexpect implementation for MMGen test suites
 import sys,os,time
 import sys,os,time
 from mmgen.globalvars import g
 from mmgen.globalvars import g
 from mmgen.opts import opt
 from mmgen.opts import opt
-from mmgen.util import msg,msg_r,vmsg,vmsg_r,rmsg,red,yellow,green,cyan,die,rdie
+from mmgen.util import msg,msg_r,vmsg,vmsg_r,rmsg,red,yellow,green,cyan,die
 from .common import *
 from .common import *
 
 
 try:
 try:
@@ -184,11 +184,12 @@ class MMGenPexpect(object):
 				f = (self.p.expect_exact,self.p.expect)[bool(regex)]
 				f = (self.p.expect_exact,self.p.expect)[bool(regex)]
 				ret = f(s)
 				ret = f(s)
 		except pexpect.TIMEOUT:
 		except pexpect.TIMEOUT:
-			if opt.debug_pexpect: raise
-			m1 = red(f'\nERROR.  Expect {s!r} timed out.  Exiting\n')
+			if opt.debug_pexpect:
+				raise
+			m1 = f'\nERROR.  Expect {s!r} timed out.  Exiting\n'
 			m2 = f'before: [{self.p.before}]\n'
 			m2 = f'before: [{self.p.before}]\n'
 			m3 = f'sent value: [{self.sent_value}]' if self.sent_value != None else ''
 			m3 = f'sent value: [{self.sent_value}]' if self.sent_value != None else ''
-			rdie(1,m1+m2+m3)
+			die(2,m1+m2+m3)
 
 
 		debug_pexpect_msg(self.p)
 		debug_pexpect_msg(self.p)
 
 
@@ -196,7 +197,7 @@ class MMGenPexpect(object):
 			msg_r(f' ==> {ret} ')
 			msg_r(f' ==> {ret} ')
 
 
 		if ret == -1:
 		if ret == -1:
-			rdie(1,f'Error.  Expect returned {ret}')
+			die(4,f'Error.  Expect returned {ret}')
 		else:
 		else:
 			if t == '':
 			if t == '':
 				if not nonl and not silent: vmsg('')
 				if not nonl and not silent: vmsg('')

+ 6 - 6
test/objattrtest.py

@@ -69,7 +69,7 @@ def get_descriptor_obj(objclass,attrname):
 	for o in (objclass,objclass.__bases__[0]): # assume there's only one base class
 	for o in (objclass,objclass.__bases__[0]): # assume there's only one base class
 		if attrname in o.__dict__:
 		if attrname in o.__dict__:
 			return o.__dict__[attrname]
 			return o.__dict__[attrname]
-	rdie(3,f'unable to find descriptor {objclass.__name__}.{attrname}')
+	die(4,f'unable to find descriptor {objclass.__name__}.{attrname}')
 
 
 def test_attr_perm(obj,attrname,perm_name,perm_value,dobj,attrval_type):
 def test_attr_perm(obj,attrname,perm_name,perm_value,dobj,attrval_type):
 
 
@@ -93,15 +93,15 @@ def test_attr_perm(obj,attrname,perm_name,perm_value,dobj,attrval_type):
 		elif perm_name == 'delete_ok':
 		elif perm_name == 'delete_ok':
 			delattr(obj,attrname)
 			delattr(obj,attrname)
 	except SampleObjError as e:
 	except SampleObjError as e:
-		rdie(2,f'Test script error ({e})')
+		die(4,f'Test script error ({e})')
 	except Exception as e:
 	except Exception as e:
 		if perm_value == True:
 		if perm_value == True:
 			fs = '{!r}: unable to {} attribute {!r}, though {}ing is allowed ({})'
 			fs = '{!r}: unable to {} attribute {!r}, though {}ing is allowed ({})'
-			rdie(2,fs.format(type(obj).__name__,pname,attrname,pstem,e))
+			die(4,fs.format(type(obj).__name__,pname,attrname,pstem,e))
 	else:
 	else:
 		if perm_value == False:
 		if perm_value == False:
 			fs = '{!r}: attribute {!r} is {n}able, though {n}ing is forbidden'
 			fs = '{!r}: attribute {!r} is {n}able, though {n}ing is forbidden'
-			rdie(2,fs.format(type(obj).__name__,attrname,n=pstem))
+			die(4,fs.format(type(obj).__name__,attrname,n=pstem))
 
 
 def test_attr(data,obj,attrname,dobj,bits,attrval_type):
 def test_attr(data,obj,attrname,dobj,bits,attrval_type):
 	if hasattr(obj,attrname): # TODO
 	if hasattr(obj,attrname): # TODO
@@ -109,7 +109,7 @@ def test_attr(data,obj,attrname,dobj,bits,attrval_type):
 
 
 		if attrval_type not in (td_attrval_type,type(None)):
 		if attrval_type not in (td_attrval_type,type(None)):
 			fs = '\nattribute {!r} of {!r} instance has incorrect type {!r} (should be {!r})'
 			fs = '\nattribute {!r} of {!r} instance has incorrect type {!r} (should be {!r})'
-			rdie(2,fs.format(attrname,type(obj).__name__,attrval_type.__name__,td_attrval_type.__name__))
+			die(4,fs.format(attrname,type(obj).__name__,attrval_type.__name__,td_attrval_type.__name__))
 
 
 	if hasattr(dobj,'__dict__'):
 	if hasattr(dobj,'__dict__'):
 		d = dobj.__dict__
 		d = dobj.__dict__
@@ -118,7 +118,7 @@ def test_attr(data,obj,attrname,dobj,bits,attrval_type):
 			if k in d:
 			if k in d:
 				if d[k] != bits[k]:
 				if d[k] != bits[k]:
 					fs = 'init value {iv}={a} for attr {n!r} does not match test data ({iv}={b})'
 					fs = 'init value {iv}={a} for attr {n!r} does not match test data ({iv}={b})'
-					rdie(2,fs.format(iv=k,n=attrname,a=d[k],b=bits[k]))
+					die(4,fs.format(iv=k,n=attrname,a=d[k],b=bits[k]))
 				if opt.verbose and d[k] == True:
 				if opt.verbose and d[k] == True:
 					msg_r(f' {k}={d[k]!r}')
 					msg_r(f' {k}={d[k]!r}')
 
 

+ 2 - 2
test/scrambletest.py

@@ -97,7 +97,7 @@ cmd_base = f'python3{cvr_opts} cmds/mmgen-{{}}gen -qS'
 def get_cmd_output(cmd):
 def get_cmd_output(cmd):
 	cp = run(cmd.split(),stdout=PIPE,stderr=PIPE)
 	cp = run(cmd.split(),stdout=PIPE,stderr=PIPE)
 	if cp.returncode != 0:
 	if cp.returncode != 0:
-		ydie(2,f'\nSpawned program exited with error code {cp.returncode}:\n{cp.stderr.decode()}')
+		die(2,f'\nSpawned program exited with error code {cp.returncode}:\n{cp.stderr.decode()}')
 	return cp.stdout.decode().splitlines()
 	return cp.stdout.decode().splitlines()
 
 
 def do_test(cmd,tdata,msg_str,addr_desc):
 def do_test(cmd,tdata,msg_str,addr_desc):
@@ -115,7 +115,7 @@ def do_test(cmd,tdata,msg_str,addr_desc):
 			s = k.replace('seed','seed[:8]').replace('addr',addr_desc)
 			s = k.replace('seed','seed[:8]').replace('addr',addr_desc)
 			vmsg(f'  {s:9}: {cmd_out[k]}')
 			vmsg(f'  {s:9}: {cmd_out[k]}')
 		else:
 		else:
-			rdie(1,f'\nError: sc_{k} value {cmd_out[k]} does not match reference value {ref_data[k]}')
+			die(4,f'\nError: sc_{k} value {cmd_out[k]} does not match reference value {ref_data[k]}')
 	msg('OK')
 	msg('OK')
 
 
 def do_coin_tests():
 def do_coin_tests():

+ 4 - 4
test/test.py

@@ -40,7 +40,7 @@ def create_shm_dir(data_dir,trash_dir):
 					try:
 					try:
 						run(['python3',os.path.join('cmds','mmgen-regtest'),'stop'],check=True)
 						run(['python3',os.path.join('cmds','mmgen-regtest'),'stop'],check=True)
 					except:
 					except:
-						rdie(1,f'Unable to remove {tdir!r}!')
+						die(4,f'Unable to remove {tdir!r}!')
 					else:
 					else:
 						time.sleep(2)
 						time.sleep(2)
 						shutil.rmtree(tdir)
 						shutil.rmtree(tdir)
@@ -977,7 +977,7 @@ class TestSuiteRunner(object):
 			self.skipped_warnings.append(
 			self.skipped_warnings.append(
 				'Test {!r} was skipped:\n  {}'.format(cmd,'\n  '.join(ret[1].split('\n'))))
 				'Test {!r} was skipped:\n  {}'.format(cmd,'\n  '.join(ret[1].split('\n'))))
 		else:
 		else:
-			rdie(1,f'{cmd!r} returned {ret}')
+			die(2,f'{cmd!r} returned {ret}')
 
 
 	def check_deps(self,cmds): # TODO: broken
 	def check_deps(self,cmds): # TODO: broken
 		if len(cmds) != 1:
 		if len(cmds) != 1:
@@ -1057,9 +1057,9 @@ except KeyboardInterrupt:
 	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:
-	ydie(1,e.args[0])
+	die(2,e.args[0])
 except TestSuiteFatalException as e:
 except TestSuiteFatalException as e:
-	rdie(1,e.args[0])
+	die(4,e.args[0])
 except Exception:
 except Exception:
 	if 'exec_wrapper_init' in globals(): # test.py itself is running under exec_wrapper
 	if 'exec_wrapper_init' in globals(): # test.py itself is running under exec_wrapper
 		import traceback
 		import traceback

+ 3 - 3
test/test_py_d/ts_autosign.py

@@ -244,18 +244,18 @@ class TestSuiteAutosign(TestSuiteBase):
 					run(['mount',mountpoint],check=True)
 					run(['mount',mountpoint],check=True)
 					imsg(f'Mounted {mountpoint}')
 					imsg(f'Mounted {mountpoint}')
 				except:
 				except:
-					ydie(1,f'Could not mount {mountpoint}!  Exiting')
+					die(2,f'Could not mount {mountpoint}!  Exiting')
 
 
 			txdir = joinpath(mountpoint,'tx')
 			txdir = joinpath(mountpoint,'tx')
 			if not os.path.isdir(txdir):
 			if not os.path.isdir(txdir):
-				ydie(1,f'Directory {txdir} does not exist!  Exiting')
+				die(2,f'Directory {txdir} does not exist!  Exiting')
 
 
 		def init_led():
 		def init_led():
 			try:
 			try:
 				cf = LEDControl(enabled=True,simulate=simulate)
 				cf = LEDControl(enabled=True,simulate=simulate)
 			except Exception as e:
 			except Exception as e:
 				msg(str(e))
 				msg(str(e))
-				ydie(2,'LEDControl initialization failed')
+				die(2,'LEDControl initialization failed')
 			for fn in (cf.board.status,cf.board.trigger):
 			for fn in (cf.board.status,cf.board.trigger):
 				if fn:
 				if fn:
 					run(['sudo','chmod','0666',fn],check=True)
 					run(['sudo','chmod','0666',fn],check=True)

+ 2 - 2
test/test_py_d/ts_ethdev.py

@@ -59,7 +59,7 @@ def check_solc_ver():
 	try:
 	try:
 		cp = run(cmd.split(),check=False,stdout=PIPE)
 		cp = run(cmd.split(),check=False,stdout=PIPE)
 	except Exception as e:
 	except Exception as e:
-		rdie(2,f'Unable to execute {cmd!r}: {e}')
+		die(4,f'Unable to execute {cmd!r}: {e}')
 	res = cp.stdout.decode().strip()
 	res = cp.stdout.decode().strip()
 	if cp.returncode == 0:
 	if cp.returncode == 0:
 		omsg(
 		omsg(
@@ -750,7 +750,7 @@ class TestSuiteEthdev(TestSuiteBase,TestSuiteShared):
 		cp = run(cmd,stdout=DEVNULL,stderr=PIPE)
 		cp = run(cmd,stdout=DEVNULL,stderr=PIPE)
 		if cp.returncode != 0:
 		if cp.returncode != 0:
 			rmsg('solc failed with the following output:')
 			rmsg('solc failed with the following output:')
-			ydie(2,cp.stderr.decode())
+			die(2,cp.stderr.decode())
 		imsg('ERC20 token {!r} compiled'.format( token_data['symbol'] ))
 		imsg('ERC20 token {!r} compiled'.format( token_data['symbol'] ))
 		return 'ok'
 		return 'ok'
 
 

+ 1 - 1
test/test_py_d/ts_misc.py

@@ -59,7 +59,7 @@ class TestSuiteHelp(TestSuiteBase):
 	def usage(self):
 	def usage(self):
 		t = self.spawn(f'mmgen-walletgen',['foo'])
 		t = self.spawn(f'mmgen-walletgen',['foo'])
 		t.expect('USAGE: mmgen-walletgen')
 		t.expect('USAGE: mmgen-walletgen')
-		t.expect('SystemExit: 1')
+		t.expect('MMGenSystemExit(1)')
 		t.req_exit_val = 1
 		t.req_exit_val = 1
 		return t
 		return t
 
 

+ 3 - 3
test/test_py_d/ts_regtest.py

@@ -741,7 +741,7 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 	def get_mempool1(self):
 	def get_mempool1(self):
 		mp = self._get_mempool()
 		mp = self._get_mempool()
 		if len(mp) != 1:
 		if len(mp) != 1:
-			rdie(2,'Mempool has more or less than one TX!')
+			die(4,'Mempool has more or less than one TX!')
 		self.write_to_tmpfile('rbf_txid',mp[0]+'\n')
 		self.write_to_tmpfile('rbf_txid',mp[0]+'\n')
 		return 'ok'
 		return 'ok'
 
 
@@ -762,10 +762,10 @@ class TestSuiteRegtest(TestSuiteBase,TestSuiteShared):
 			return 'skip'
 			return 'skip'
 		mp = self._get_mempool()
 		mp = self._get_mempool()
 		if len(mp) != 1:
 		if len(mp) != 1:
-			rdie(2,'Mempool has more or less than one TX!')
+			die(4,'Mempool has more or less than one TX!')
 		chk = self.read_from_tmpfile('rbf_txid')
 		chk = self.read_from_tmpfile('rbf_txid')
 		if chk.strip() == mp[0]:
 		if chk.strip() == mp[0]:
-			rdie(2,'TX in mempool has not changed!  RBF bump failed')
+			die(4,'TX in mempool has not changed!  RBF bump failed')
 		self.write_to_tmpfile('rbf_txid2',mp[0]+'\n')
 		self.write_to_tmpfile('rbf_txid2',mp[0]+'\n')
 		return 'ok'
 		return 'ok'
 
 

+ 5 - 5
test/test_py_d/ts_seedsplit.py

@@ -226,11 +226,11 @@ class TestSuiteSeedSplit(TestSuiteBase):
 
 
 	def ss_bad_invocation1(self):
 	def ss_bad_invocation1(self):
 		return self.ss_bad_invocation(
 		return self.ss_bad_invocation(
-			'mmgen-seedsplit',[],1,'SystemExit: 1')
+			'mmgen-seedsplit',[],1,'MMGenSystemExit(1)')
 
 
 	def ss_bad_invocation2(self):
 	def ss_bad_invocation2(self):
 		return self.ss_bad_invocation(
 		return self.ss_bad_invocation(
-			'mmgen-seedsplit',['-M1','1:9'],1,'SystemExit: 1')
+			'mmgen-seedsplit',['-M1','1:9'],1,'MMGenSystemExit(1)')
 
 
 	def ss_bad_invocation3(self):
 	def ss_bad_invocation3(self):
 		return self.ss_bad_invocation(
 		return self.ss_bad_invocation(
@@ -242,11 +242,11 @@ class TestSuiteSeedSplit(TestSuiteBase):
 
 
 	def ss_bad_invocation5(self):
 	def ss_bad_invocation5(self):
 		return self.ss_bad_invocation(
 		return self.ss_bad_invocation(
-			'mmgen-seedjoin',[],1,'SystemExit: 1')
+			'mmgen-seedjoin',[],1,'MMGenSystemExit(1)')
 
 
 	def ss_bad_invocation6(self):
 	def ss_bad_invocation6(self):
 		return self.ss_bad_invocation(
 		return self.ss_bad_invocation(
-			'mmgen-seedjoin',[self.tmpdir+'/a'],1,'SystemExit: 1')
+			'mmgen-seedjoin',[self.tmpdir+'/a'],1,'MMGenSystemExit(1)')
 
 
 	def ss_bad_invocation7(self):
 	def ss_bad_invocation7(self):
 		return self.ss_bad_invocation(
 		return self.ss_bad_invocation(
@@ -258,7 +258,7 @@ class TestSuiteSeedSplit(TestSuiteBase):
 
 
 	def ss_bad_invocation9(self):
 	def ss_bad_invocation9(self):
 		return self.ss_bad_invocation(
 		return self.ss_bad_invocation(
-			'mmgen-seedsplit',['x'],1,'SystemExit: 1')
+			'mmgen-seedsplit',['x'],1,'MMGenSystemExit(1)')
 
 
 	def ss_bad_invocation10(self):
 	def ss_bad_invocation10(self):
 		return self.ss_bad_invocation(
 		return self.ss_bad_invocation(

+ 4 - 5
test/tooltest.py

@@ -238,7 +238,7 @@ class MMGenToolTestUtils(object):
 				red('FAILED'),
 				red('FAILED'),
 				yellow('Command stderr output:'),
 				yellow('Command stderr output:'),
 				err.decode() ))
 				err.decode() ))
-			rdie(1,f'Called process returned with an error (retcode {cp.returncode})')
+			die(2,f'Called process returned with an error (retcode {cp.returncode})')
 		return (out,out.rstrip())[bool(strip)]
 		return (out,out.rstrip())[bool(strip)]
 
 
 	def run_cmd_chk(self,name,f1,f2,kwargs='',extra_msg='',strip_hex=False,add_opts=[]):
 	def run_cmd_chk(self,name,f1,f2,kwargs='',extra_msg='',strip_hex=False,add_opts=[]):
@@ -251,8 +251,7 @@ class MMGenToolTestUtils(object):
 			return (a.lstrip('0') == b.lstrip('0')) if strip_hex else (a == b)
 			return (a.lstrip('0') == b.lstrip('0')) if strip_hex else (a == b)
 		if cmp_equal(ret,idata): ok()
 		if cmp_equal(ret,idata): ok()
 		else:
 		else:
-			fs = "Error: values don't match:\nIn:  {!r}\nOut: {!r}"
-			rdie(3,fs.format(idata,ret))
+			die(4, "Error: values don't match:\nIn:  {!r}\nOut: {!r}".format(idata,ret))
 		return ret
 		return ret
 
 
 	def run_cmd_nochk(self,name,f1,kwargs='',add_opts=[]):
 	def run_cmd_nochk(self,name,f1,kwargs='',add_opts=[]):
@@ -278,7 +277,7 @@ class MMGenToolTestUtils(object):
 			else:
 			else:
 				if not hush: ok()
 				if not hush: ok()
 		else:
 		else:
-			rdie(3,f'Error for command {name!r}')
+			die(4,f'Error for command {name!r}')
 
 
 	def run_cmd_randinput(self,name,strip=True,add_opts=[]):
 	def run_cmd_randinput(self,name,strip=True,add_opts=[]):
 		s = getrand(128)
 		s = getrand(128)
@@ -298,7 +297,7 @@ def ok_or_die(val,chk_func,s,skip_ok=False):
 	if ret:
 	if ret:
 		if not skip_ok: ok()
 		if not skip_ok: ok()
 	else:
 	else:
-		rdie(3,f'Returned value {val!r} is not a {s}')
+		die(4,f'Returned value {val!r} is not a {s}')
 
 
 class MMGenToolTestCmds(object):
 class MMGenToolTestCmds(object):
 
 

+ 1 - 1
test/tooltest2.py

@@ -782,7 +782,7 @@ def fork_cmd(cmd_name,args,out,opts,stdin_input):
 		if m:
 		if m:
 			return { b'None': None, b'False': False }[m.group(1)]
 			return { b'None': None, b'False': False }[m.group(1)]
 		else:
 		else:
-			ydie(1,f'Spawned program exited with error: {cp.stderr}')
+			die(2,f'Spawned program exited with error: {cp.stderr}')
 
 
 	return cmd_out.strip()
 	return cmd_out.strip()
 
 

+ 3 - 3
test/unit_tests.py

@@ -95,7 +95,7 @@ class UnitTestHelpers(object):
 				assert exc == exc_chk, m_exc.format(exc,exc_chk)
 				assert exc == exc_chk, m_exc.format(exc,exc_chk)
 				assert re.search(emsg_chk,emsg), m_err.format(emsg,emsg_chk)
 				assert re.search(emsg_chk,emsg), m_err.format(emsg,emsg_chk)
 			else:
 			else:
-				rdie(3,m_noraise.format(desc,exc_chk))
+				die(4,m_noraise.format(desc,exc_chk))
 
 
 tests_seen = []
 tests_seen = []
 
 
@@ -110,7 +110,7 @@ def run_test(test,subtest=None):
 		if type(ret).__name__ == 'coroutine':
 		if type(ret).__name__ == 'coroutine':
 			ret = run_session(ret)
 			ret = run_session(ret)
 		if not ret:
 		if not ret:
-			rdie(1,f'Unit subtest {subtest!r} failed')
+			die(4,f'Unit subtest {subtest!r} failed')
 		pass
 		pass
 
 
 	if test not in tests_seen:
 	if test not in tests_seen:
@@ -139,7 +139,7 @@ def run_test(test,subtest=None):
 				run_subtest(subtest)
 				run_subtest(subtest)
 		else:
 		else:
 			if not mod.unit_test().run_test(test,UnitTestHelpers):
 			if not mod.unit_test().run_test(test,UnitTestHelpers):
-				rdie(1,'Unit test {test!r} failed')
+				die(4,'Unit test {test!r} failed')
 
 
 try:
 try:
 	for test in (cmd_args or all_tests):
 	for test in (cmd_args or all_tests):

+ 2 - 2
test/unit_tests_d/ut_daemon.py

@@ -77,9 +77,9 @@ def test_cmds(op):
 						try:
 						try:
 							cp = run([d.exec_fn,'--help'],stdout=PIPE,stderr=PIPE)
 							cp = run([d.exec_fn,'--help'],stdout=PIPE,stderr=PIPE)
 						except:
 						except:
-							ydie(1,f'Unable to execute {d.exec_fn}')
+							die(2,f'Unable to execute {d.exec_fn}')
 						if cp.returncode:
 						if cp.returncode:
-							ydie(1,f'Unable to execute {d.exec_fn}')
+							die(2,f'Unable to execute {d.exec_fn}')
 						else:
 						else:
 							vmsg('{:16} {}'.format(
 							vmsg('{:16} {}'.format(
 								d.exec_fn+':',
 								d.exec_fn+':',

+ 1 - 1
test/unit_tests_d/ut_indexed_dict.py

@@ -21,7 +21,7 @@ class unit_test(object):
 		def bad4(): d.clear()
 		def bad4(): d.clear()
 		def bad5(): d.update(d)
 		def bad5(): d.update(d)
 
 
-		def odie(n): rdie(3,f'\nillegal action {bad_msg[n]!r} failed to raise exception')
+		def odie(n): die(4,f'\nillegal action {bad_msg[n]!r} failed to raise exception')
 		def omsg(e): vmsg(' - ' + e.args[0])
 		def omsg(e): vmsg(' - ' + e.args[0])
 
 
 		msg_r('Testing class IndexedDict...')
 		msg_r('Testing class IndexedDict...')