Browse Source

handle MMGen exceptions via die()

- exception module is imported only after an error occurs
The MMGen Project 3 years ago
parent
commit
aad252a077
50 changed files with 143 additions and 169 deletions
  1. 4 5
      mmgen/base_proto/bitcoin/tx/base.py
  2. 2 2
      mmgen/base_proto/bitcoin/tx/new.py
  3. 2 3
      mmgen/base_proto/bitcoin/tx/signed.py
  4. 1 1
      mmgen/base_proto/bitcoin/tx/unsigned.py
  5. 1 1
      mmgen/base_proto/ethereum/contract.py
  6. 4 5
      mmgen/base_proto/ethereum/twctl.py
  7. 2 2
      mmgen/base_proto/ethereum/tx/new.py
  8. 7 8
      mmgen/baseconv.py
  9. 5 6
      mmgen/bip39.py
  10. 3 4
      mmgen/cfg.py
  11. 1 2
      mmgen/daemon.py
  12. 2 4
      mmgen/filename.py
  13. 3 4
      mmgen/fileutil.py
  14. 6 7
      mmgen/flags.py
  15. 3 3
      mmgen/key.py
  16. 3 2
      mmgen/keygen.py
  17. 3 2
      mmgen/led.py
  18. 1 0
      mmgen/main.py
  19. 1 1
      mmgen/main_tool.py
  20. 1 2
      mmgen/obj.py
  21. 5 4
      mmgen/objmethods.py
  22. 24 23
      mmgen/opts.py
  23. 2 3
      mmgen/passwdlist.py
  24. 6 7
      mmgen/rpc.py
  25. 3 6
      mmgen/seedsplit.py
  26. 2 3
      mmgen/subseed.py
  27. 6 7
      mmgen/tw.py
  28. 1 2
      mmgen/twaddrs.py
  29. 3 5
      mmgen/twctl.py
  30. 1 2
      mmgen/twuo.py
  31. 4 4
      mmgen/tx/base.py
  32. 2 4
      mmgen/txfile.py
  33. 12 9
      mmgen/util.py
  34. 1 1
      mmgen/wallet.py
  35. 4 7
      mmgen/xmrseed.py
  36. 1 1
      mmgen/xmrwallet.py
  37. 3 2
      test/include/common.py
  38. 1 1
      test/objattrtest.py
  39. 2 1
      test/test.py
  40. 0 1
      test/test_py_d/ts_ethdev.py
  41. 3 3
      test/test_py_d/ts_main.py
  42. 1 1
      test/test_py_d/ts_misc.py
  43. 1 1
      test/tooltest2.py
  44. 0 1
      test/unit_tests_d/ut_baseconv.py
  45. 0 1
      test/unit_tests_d/ut_bip39.py
  46. 0 1
      test/unit_tests_d/ut_daemon.py
  47. 0 1
      test/unit_tests_d/ut_flags.py
  48. 0 1
      test/unit_tests_d/ut_lockable.py
  49. 0 1
      test/unit_tests_d/ut_rpc.py
  50. 0 1
      test/unit_tests_d/ut_xmrseed.py

+ 4 - 5
mmgen/base_proto/bitcoin/tx/base.py

@@ -17,7 +17,7 @@ from collections import namedtuple
 import mmgen.tx.base as TxBase
 import mmgen.tx.base as TxBase
 from ....opts import opt
 from ....opts import opt
 from ....obj import MMGenObject,MMGenList,HexStr
 from ....obj import MMGenObject,MMGenList,HexStr
-from ....util import msg,dmsg,make_chksum_6
+from ....util import msg,dmsg,make_chksum_6,die
 
 
 def addr2pubhash(proto,addr):
 def addr2pubhash(proto,addr):
 	ap = proto.parse_addr(addr)
 	ap = proto.parse_addr(addr)
@@ -100,7 +100,7 @@ def DeserializeTX(proto,txhex):
 	if has_witness:
 	if has_witness:
 		u = bshift(2,skip=True).hex()
 		u = bshift(2,skip=True).hex()
 		if u != '0001':
 		if u != '0001':
-			raise IllegalWitnessFlagValue(f'{u!r}: Illegal value for flag in transaction!')
+			die( 'IllegalWitnessFlagValue', f'{u!r}: Illegal value for flag in transaction!' )
 
 
 	d['num_txins'] = readVInt()
 	d['num_txins'] = readVInt()
 
 
@@ -140,7 +140,7 @@ def DeserializeTX(proto,txhex):
 		d['witness_size'] = 0
 		d['witness_size'] = 0
 
 
 	if len(tx) - var.idx != 4:
 	if len(tx) - var.idx != 4:
-		raise TxHexParseError('TX hex has invalid length: {} extra bytes'.format(len(tx)-var.idx-4))
+		die( 'TxHexParseError', 'TX hex has invalid length: {} extra bytes'.format(len(tx)-var.idx-4) )
 
 
 	d['locktime'] = bytes2int(bshift(4))
 	d['locktime'] = bytes2int(bshift(4))
 	d['unsigned_hex'] = var.raw_tx.hex()
 	d['unsigned_hex'] = var.raw_tx.hex()
@@ -283,8 +283,7 @@ class Base(TxBase.Base):
 		"""
 		"""
 
 
 		def do_error(errmsg):
 		def do_error(errmsg):
-			from ....exception import TxHexMismatch
-			raise TxHexMismatch(errmsg+'\n'+hdr)
+			die( 'TxHexMismatch', errmsg+'\n'+hdr )
 
 
 		def check_equal(desc,hexio,mmio):
 		def check_equal(desc,hexio,mmio):
 			if mmio != hexio:
 			if mmio != hexio:

+ 2 - 2
mmgen/base_proto/bitcoin/tx/new.py

@@ -16,7 +16,7 @@ import mmgen.tx.new as TxBase
 from .base import Base
 from .base import Base
 from ....opts import opt
 from ....opts import opt
 from ....obj import HexStr,MMGenTxID
 from ....obj import HexStr,MMGenTxID
-from ....util import dmsg,vmsg,make_chksum_6
+from ....util import dmsg,vmsg,make_chksum_6,die
 
 
 class New(Base,TxBase.New):
 class New(Base,TxBase.New):
 	usr_fee_prompt = 'Enter transaction fee: '
 	usr_fee_prompt = 'Enter transaction fee: '
@@ -98,7 +98,7 @@ class New(Base,TxBase.New):
 		fee = self.sum_inputs() - self.sum_outputs()
 		fee = self.sum_inputs() - self.sum_outputs()
 		if fee > self.proto.max_tx_fee:
 		if fee > self.proto.max_tx_fee:
 			c = self.proto.coin
 			c = self.proto.coin
-			raise MaxFeeExceeded(f'Transaction fee of {fee} {c} too high! (> {self.proto.max_tx_fee} {c})')
+			die( 'MaxFeeExceeded', f'Transaction fee of {fee} {c} too high! (> {self.proto.max_tx_fee} {c})' )
 
 
 	def final_inputs_ok_msg(self,funds_left):
 	def final_inputs_ok_msg(self,funds_left):
 		return 'Transaction produces {} {} in change'.format(
 		return 'Transaction produces {} {} in change'.format(

+ 2 - 3
mmgen/base_proto/bitcoin/tx/signed.py

@@ -14,7 +14,7 @@ base_proto.bitcoin.tx.signed: Bitcoin signed transaction class
 
 
 import mmgen.tx.signed as TxBase
 import mmgen.tx.signed as TxBase
 from .completed import Completed
 from .completed import Completed
-from ....util import fmt,vmsg
+from ....util import fmt,vmsg,die
 
 
 class Signed(Completed,TxBase.Signed):
 class Signed(Completed,TxBase.Signed):
 
 
@@ -25,8 +25,7 @@ class Signed(Completed,TxBase.Signed):
 		vmsg(f'\nVsize: {vsize} (true) {est_vsize} (estimated)')
 		vmsg(f'\nVsize: {vsize} (true) {est_vsize} (estimated)')
 		ratio = float(est_vsize) / vsize
 		ratio = float(est_vsize) / vsize
 		if not (0.95 < ratio < 1.05): # allow for 5% error
 		if not (0.95 < ratio < 1.05): # allow for 5% error
-			from ....exception import BadTxSizeEstimate
-			raise BadTxSizeEstimate(fmt(f"""
+			die( 'BadTxSizeEstimate', fmt(f"""
 				Estimated transaction vsize is {ratio:1.2f} times the true vsize
 				Estimated transaction vsize is {ratio:1.2f} times the true vsize
 				Your transaction fee estimates will be inaccurate
 				Your transaction fee estimates will be inaccurate
 				Please re-create and re-sign the transaction using the option --vsize-adj={1/ratio:1.2f}
 				Please re-create and re-sign the transaction using the option --vsize-adj={1/ratio:1.2f}

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

@@ -73,7 +73,7 @@ class Unsigned(Completed,TxBase.Unsigned):
 			new.compare_size_and_estimated_size(tx_decoded)
 			new.compare_size_and_estimated_size(tx_decoded)
 			new.coin_txid = CoinTxID(self.deserialized.txid)
 			new.coin_txid = CoinTxID(self.deserialized.txid)
 			if not new.coin_txid == tx_decoded['txid']:
 			if not new.coin_txid == tx_decoded['txid']:
-				raise BadMMGenTxID('txid mismatch (after signing)')
+				die( 'BadMMGenTxID', 'txid mismatch (after signing)' )
 			msg('OK')
 			msg('OK')
 			return new
 			return new
 		except Exception as e:
 		except Exception as e:

+ 1 - 1
mmgen/base_proto/ethereum/contract.py

@@ -174,5 +174,5 @@ class TokenResolve(TokenCommon,metaclass=AsyncInit):
 		self.addr = TokenAddr(proto,addr)
 		self.addr = TokenAddr(proto,addr)
 		decimals = await self.get_decimals() # requires self.addr!
 		decimals = await self.get_decimals() # requires self.addr!
 		if not decimals:
 		if not decimals:
-			raise TokenNotInBlockchain(f'Token {addr!r} not in blockchain')
+			die( 'TokenNotInBlockchain', f'Token {addr!r} not in blockchain' )
 		Token.__init__(self,proto,addr,decimals,rpc)
 		Token.__init__(self,proto,addr,decimals,rpc)

+ 4 - 5
mmgen/base_proto/ethereum/twctl.py

@@ -20,7 +20,7 @@
 altcoins.base_proto.ethereum.twctl: Ethereum tracking wallet control class
 altcoins.base_proto.ethereum.twctl: Ethereum tracking wallet control class
 """
 """
 
 
-from ...util import msg,ymsg,write_mode
+from ...util import msg,ymsg,write_mode,die
 from ...twctl import TrackingWallet
 from ...twctl import TrackingWallet
 from ...addr import is_coin_addr,is_mmgen_id
 from ...addr import is_coin_addr,is_mmgen_id
 from ...amt import ETHAmt
 from ...amt import ETHAmt
@@ -160,13 +160,12 @@ class EthereumTokenTrackingWallet(EthereumTrackingWallet):
 
 
 		if self.importing and token_addr:
 		if self.importing and token_addr:
 			if not is_coin_addr(proto,token_addr):
 			if not is_coin_addr(proto,token_addr):
-				raise InvalidTokenAddress(f'{token_addr!r}: invalid token address')
+				die( 'InvalidTokenAddress', f'{token_addr!r}: invalid token address' )
 		else:
 		else:
 			assert token_addr == None,'EthereumTokenTrackingWallet_chk1'
 			assert token_addr == None,'EthereumTokenTrackingWallet_chk1'
 			token_addr = await self.sym2addr(proto.tokensym) # returns None on failure
 			token_addr = await self.sym2addr(proto.tokensym) # returns None on failure
 			if not is_coin_addr(proto,token_addr):
 			if not is_coin_addr(proto,token_addr):
-				from ...exception import UnrecognizedTokenSymbol
-				raise UnrecognizedTokenSymbol(f'Specified token {proto.tokensym!r} could not be resolved!')
+				die( 'UnrecognizedTokenSymbol', f'Specified token {proto.tokensym!r} could not be resolved!' )
 
 
 		from ...addr import TokenAddr
 		from ...addr import TokenAddr
 		self.token = TokenAddr(proto,token_addr)
 		self.token = TokenAddr(proto,token_addr)
@@ -175,7 +174,7 @@ class EthereumTokenTrackingWallet(EthereumTrackingWallet):
 			if self.importing:
 			if self.importing:
 				await self.import_token(self.token)
 				await self.import_token(self.token)
 			else:
 			else:
-				raise TokenNotInWallet(f'Specified token {self.token!r} not in wallet!')
+				die( 'TokenNotInWallet', f'Specified token {self.token!r} not in wallet!' )
 
 
 		self.decimals = self.get_param('decimals')
 		self.decimals = self.get_param('decimals')
 		self.symbol   = self.get_param('symbol')
 		self.symbol   = self.get_param('symbol')

+ 2 - 2
mmgen/base_proto/ethereum/tx/new.py

@@ -139,10 +139,10 @@ class New(Base,TxBase.New):
 							ret.append(waddr)
 							ret.append(waddr)
 							break
 							break
 					else:
 					else:
-						raise UserAddressNotInWallet(errmsg.format(addr))
+						die( 'UserAddressNotInWallet', errmsg.format(addr) )
 				elif is_coin_addr(self.proto,addr):
 				elif is_coin_addr(self.proto,addr):
 					if not addr in data_root:
 					if not addr in data_root:
-						raise UserAddressNotInWallet(errmsg.format(addr))
+						die( 'UserAddressNotInWallet', errmsg.format(addr) )
 					ret.append(addr)
 					ret.append(addr)
 				else:
 				else:
 					die(1,f'{addr!r}: not an MMGen ID or coin address')
 					die(1,f'{addr!r}: not an MMGen ID or coin address')

+ 7 - 8
mmgen/baseconv.py

@@ -22,7 +22,6 @@ baseconv.py:  base conversion class for the MMGen suite
 
 
 from collections import namedtuple
 from collections import namedtuple
 
 
-from .exception import BaseConversionError,BaseConversionPadError,HexadecimalStringError,SeedLengthError
 from .util import die
 from .util import die
 
 
 def is_b58_str(s):
 def is_b58_str(s):
@@ -123,7 +122,7 @@ class baseconv(object):
 		elif pad == 'seed':
 		elif pad == 'seed':
 			return seed_pad_func()
 			return seed_pad_func()
 		else:
 		else:
-			raise BaseConversionPadError(f"{pad!r}: illegal value for 'pad' (must be None,'seed' or int)")
+			die('BaseConversionPadError',f"{pad!r}: illegal value for 'pad' (must be None,'seed' or int)")
 
 
 	def tohex(self,words_arg,pad=None):
 	def tohex(self,words_arg,pad=None):
 		"convert string or list data of instance base to hex string"
 		"convert string or list data of instance base to hex string"
@@ -136,13 +135,13 @@ class baseconv(object):
 		desc = self.desc.short
 		desc = self.desc.short
 
 
 		if len(words) == 0:
 		if len(words) == 0:
-			raise BaseConversionError(f'empty {desc} data')
+			die('BaseConversionError',f'empty {desc} data')
 
 
 		def get_seed_pad():
 		def get_seed_pad():
 			assert hasattr(self,'seedlen_map_rev'), f'seed padding not supported for base {self.wl_id!r}'
 			assert hasattr(self,'seedlen_map_rev'), f'seed padding not supported for base {self.wl_id!r}'
 			d = self.seedlen_map_rev
 			d = self.seedlen_map_rev
 			if not len(words) in d:
 			if not len(words) in d:
-				raise BaseConversionError(
+				die( 'BaseConversionError',
 					f'{len(words)}: invalid length for seed-padded {desc} data in base conversion' )
 					f'{len(words)}: invalid length for seed-padded {desc} data in base conversion' )
 			return d[len(words)]
 			return d[len(words)]
 
 
@@ -151,7 +150,7 @@ class baseconv(object):
 		base = len(wl)
 		base = len(wl)
 
 
 		if not set(words) <= set(wl):
 		if not set(words) <= set(wl):
-			raise BaseConversionError(
+			die( 'BaseConversionError',
 				( 'seed data' if pad == 'seed' else f'{words_arg!r}:' ) +
 				( 'seed data' if pad == 'seed' else f'{words_arg!r}:' ) +
 				f' not in {desc} format' )
 				f' not in {desc} format' )
 
 
@@ -164,7 +163,7 @@ class baseconv(object):
 
 
 		from .util import is_hex_str
 		from .util import is_hex_str
 		if not is_hex_str(hexstr):
 		if not is_hex_str(hexstr):
-			raise HexadecimalStringError(
+			die( 'HexadecimalStringError',
 				( 'seed data' if pad == 'seed' else f'{hexstr!r}:' ) +
 				( 'seed data' if pad == 'seed' else f'{hexstr!r}:' ) +
 				' not a hexadecimal string' )
 				' not a hexadecimal string' )
 
 
@@ -174,13 +173,13 @@ class baseconv(object):
 		"convert byte string to list or string data of instance base"
 		"convert byte string to list or string data of instance base"
 
 
 		if not bytestr:
 		if not bytestr:
-			raise BaseConversionError('empty data not allowed in base conversion')
+			die( 'BaseConversionError', 'empty data not allowed in base conversion' )
 
 
 		def get_seed_pad():
 		def get_seed_pad():
 			assert hasattr(self,'seedlen_map'), f'seed padding not supported for base {self.wl_id!r}'
 			assert hasattr(self,'seedlen_map'), f'seed padding not supported for base {self.wl_id!r}'
 			d = self.seedlen_map
 			d = self.seedlen_map
 			if not len(bytestr) in d:
 			if not len(bytestr) in d:
-				raise SeedLengthError(
+				die( 'SeedLengthError',
 					f'{len(bytestr)}: invalid byte length for seed data in seed-padded base conversion' )
 					f'{len(bytestr)}: invalid byte length for seed data in seed-padded base conversion' )
 			return d[len(bytestr)]
 			return d[len(bytestr)]
 
 

+ 5 - 6
mmgen/bip39.py

@@ -22,9 +22,8 @@ bip39.py - Data and routines for BIP39 mnemonic seed phrases
 
 
 from hashlib import sha256
 from hashlib import sha256
 
 
-from .exception import MnemonicError
 from .baseconv import baseconv
 from .baseconv import baseconv
-from .util import is_hex_str
+from .util import is_hex_str,die
 
 
 def is_bip39_str(s):
 def is_bip39_str(s):
 	return bool( bip39().tohex(s.split()) )
 	return bool( bip39().tohex(s.split()) )
@@ -59,7 +58,7 @@ class bip39(baseconv):
 		for k,v in cls.constants.items():
 		for k,v in cls.constants.items():
 			if v.mn_len == nwords:
 			if v.mn_len == nwords:
 				return k//8 if in_bytes else k//4 if in_hex else k
 				return k//8 if in_bytes else k//4 if in_hex else k
-		raise MnemonicError(f'{nwords!r}: invalid word length for BIP39 mnemonic')
+		die( 'MnemonicError', f'{nwords!r}: invalid word length for BIP39 mnemonic' )
 
 
 	@classmethod
 	@classmethod
 	def seedlen2nwords(cls,seed_len,in_bytes=False,in_hex=False):
 	def seedlen2nwords(cls,seed_len,in_bytes=False,in_hex=False):
@@ -83,7 +82,7 @@ class bip39(baseconv):
 
 
 		for n,w in enumerate(words):
 		for n,w in enumerate(words):
 			if w not in wl:
 			if w not in wl:
-				raise MnemonicError(f'word #{n+1} is not in the BIP39 word list')
+				die( 'MnemonicError', f'word #{n+1} is not in the BIP39 word list' )
 
 
 		res = ''.join(['{:011b}'.format(wl.index(w)) for w in words])
 		res = ''.join(['{:011b}'.format(wl.index(w)) for w in words])
 
 
@@ -92,7 +91,7 @@ class bip39(baseconv):
 				bitlen = k
 				bitlen = k
 				break
 				break
 		else:
 		else:
-			raise MnemonicError(f'{len(words)}: invalid BIP39 seed phrase length')
+			die( 'MnemonicError', f'{len(words)}: invalid BIP39 seed phrase length' )
 
 
 		seed_bin = res[:bitlen]
 		seed_bin = res[:bitlen]
 		chk_bin = res[bitlen:]
 		chk_bin = res[bitlen:]
@@ -105,7 +104,7 @@ class bip39(baseconv):
 		chk_bin_chk = '{:0{w}b}'.format(int(chk_hex_chk,16),w=256)[:chk_len]
 		chk_bin_chk = '{:0{w}b}'.format(int(chk_hex_chk,16),w=256)[:chk_len]
 
 
 		if chk_bin != chk_bin_chk:
 		if chk_bin != chk_bin_chk:
-			raise MnemonicError('invalid BIP39 seed phrase checksum')
+			die( 'MnemonicError', 'invalid BIP39 seed phrase checksum' )
 
 
 		return seed_hex
 		return seed_hex
 
 

+ 3 - 4
mmgen/cfg.py

@@ -28,7 +28,6 @@ import sys,os,re
 from collections import namedtuple
 from collections import namedtuple
 
 
 from .globalvars import *
 from .globalvars import *
-from .exception import CfgFileParseError
 from .util import *
 from .util import *
 
 
 def cfg_file(id_str):
 def cfg_file(id_str):
@@ -92,7 +91,7 @@ class CfgFile(object):
 				if m:
 				if m:
 					yield self.line_data(m[1],m[3],lineno,None)
 					yield self.line_data(m[1],m[3],lineno,None)
 				else:
 				else:
-					raise CfgFileParseError(f'Parse error in file {self.fn!r}, line {lineno}')
+					die( 'CfgFileParseError', f'Parse error in file {self.fn!r}, line {lineno}' )
 		return gen_lines()
 		return gen_lines()
 
 
 	@classmethod
 	@classmethod
@@ -135,7 +134,7 @@ class CfgFileSample(CfgFile):
 			if m:
 			if m:
 				return self.line_data(m[2],m[4],lineno,chunk)
 				return self.line_data(m[2],m[4],lineno,chunk)
 			else:
 			else:
-				raise CfgFileParseError(f'Parse error in file {self.fn!r}, line {lineno}')
+				die( 'CfgFileParseError', f'Parse error in file {self.fn!r}, line {lineno}' )
 
 
 		def gen_chunks(lines):
 		def gen_chunks(lines):
 			hdr = True
 			hdr = True
@@ -163,7 +162,7 @@ class CfgFileSample(CfgFile):
 						chunk.append(line)
 						chunk.append(line)
 					last_nonblank = lineno
 					last_nonblank = lineno
 				else:
 				else:
-					raise CfgFileParseError(f'Parse error in file {self.fn!r}, line {lineno}')
+					die( 'CfgFileParseError', f'Parse error in file {self.fn!r}, line {lineno}' )
 
 
 			if chunk:
 			if chunk:
 				yield process_chunk(chunk,last_nonblank)
 				yield process_chunk(chunk,last_nonblank)

+ 1 - 2
mmgen/daemon.py

@@ -80,8 +80,7 @@ class Daemon(Lockable):
 		try:
 		try:
 			cp = run(cmd,check=False,stdout=out,stderr=out)
 			cp = run(cmd,check=False,stdout=out,stderr=out)
 		except Exception as e:
 		except Exception as e:
-			from .exception import MMGenCalledProcessError
-			raise MMGenCalledProcessError(f'Error starting executable: {type(e).__name__} [Errno {e.errno}]')
+			die( 'MMGenCalledProcessError', f'Error starting executable: {type(e).__name__} [Errno {e.errno}]' )
 		if self.debug:
 		if self.debug:
 			print(cp)
 			print(cp)
 		return cp
 		return cp

+ 2 - 4
mmgen/filename.py

@@ -54,16 +54,14 @@ class Filename(MMGenObject):
 		if base_class:
 		if base_class:
 			subclass = base_class.ext_to_type(self.ext,proto)
 			subclass = base_class.ext_to_type(self.ext,proto)
 			if not subclass:
 			if not subclass:
-				from .exception import BadFileExtension
-				raise BadFileExtension(f'{self.ext!r}: not a recognized file extension for {base_class}')
+				die( 'BadFileExtension', f'{self.ext!r}: not a recognized file extension for {base_class}' )
 
 
 		self.subclass = subclass
 		self.subclass = subclass
 
 
 		try:
 		try:
 			st = os.stat(fn)
 			st = os.stat(fn)
 		except:
 		except:
-			from .exception import FileNotFound
-			raise FileNotFound(f'{fn!r}: file not found')
+			die( 'FileNotFound', f'{fn!r}: file not found' )
 
 
 		import stat
 		import stat
 		if stat.S_ISBLK(st.st_mode):
 		if stat.S_ISBLK(st.st_mode):

+ 3 - 4
mmgen/fileutil.py

@@ -89,8 +89,7 @@ def _check_file_type_and_access(fname,ftype,blkdev_ok=False):
 	try:
 	try:
 		mode = os.stat(fname).st_mode
 		mode = os.stat(fname).st_mode
 	except:
 	except:
-		from .exception import FileNotFound
-		raise FileNotFound(f'Requested {ftype} {fname!r} not found')
+		die( 'FileNotFound', f'Requested {ftype} {fname!r} not found' )
 
 
 	for t in ok_types:
 	for t in ok_types:
 		if t[0](mode):
 		if t[0](mode):
@@ -289,8 +288,8 @@ def get_data_from_file(infile,desc='data',dash=False,silent=False,binary=False,q
 		data = data.decode()
 		data = data.decode()
 
 
 	if len(data) == g.max_input_size + 1:
 	if len(data) == g.max_input_size + 1:
-		from .exception import MaxInputSizeExceeded
-		raise MaxInputSizeExceeded(f'Too much input data!  Max input data size: {f.max_input_size} bytes')
+		die( 'MaxInputSizeExceeded',
+			f'Too much input data!  Max input data size: {f.max_input_size} bytes' )
 
 
 	return data
 	return data
 
 

+ 6 - 7
mmgen/flags.py

@@ -20,9 +20,8 @@
 flags.py:  Class flags and opts for the MMGen suite
 flags.py:  Class flags and opts for the MMGen suite
 """
 """
 
 
-from .exception import ClassFlagsError
 from .base_obj import AttrCtrl,Lockable
 from .base_obj import AttrCtrl,Lockable
-from .util import fmt_list
+from .util import fmt_list,die
 
 
 class ClassFlags(AttrCtrl):
 class ClassFlags(AttrCtrl):
 	_name = 'flags'
 	_name = 'flags'
@@ -35,10 +34,10 @@ class ClassFlags(AttrCtrl):
 
 
 		for a in self._available:
 		for a in self._available:
 			if a.startswith('_'):
 			if a.startswith('_'):
-				raise ClassFlagsError(f'{a!r}: {self._desc} cannot begin with an underscore')
+				die( 'ClassFlagsError', f'{a!r}: {self._desc} cannot begin with an underscore' )
 			for b in self.reserved_attrs:
 			for b in self.reserved_attrs:
 				if a == b:
 				if a == b:
-					raise ClassFlagsError(f'{a!r}: {b} is a reserved name for {self._desc}')
+					die( 'ClassFlagsError', f'{a!r}: {b} is a reserved name for {self._desc}' )
 
 
 		if arg:
 		if arg:
 			assert type(arg) in (list,tuple), f"{arg!r}: {self._name!r} must be list or tuple"
 			assert type(arg) in (list,tuple), f"{arg!r}: {self._name!r} must be list or tuple"
@@ -69,14 +68,14 @@ class ClassFlags(AttrCtrl):
 				assert type(val) is bool, f'{val!r} not boolean'
 				assert type(val) is bool, f'{val!r} not boolean'
 				old_val = getattr(self,name)
 				old_val = getattr(self,name)
 				if val and old_val:
 				if val and old_val:
-					raise ClassFlagsError(f'{self._desc} {name!r} already set')
+					die( 'ClassFlagsError', f'{self._desc} {name!r} already set' )
 				if not val and not old_val:
 				if not val and not old_val:
-					raise ClassFlagsError(f'{self._desc} {name!r} not set, so cannot be unset')
+					die( 'ClassFlagsError', f'{self._desc} {name!r} not set, so cannot be unset' )
 
 
 		super().__setattr__(name,val)
 		super().__setattr__(name,val)
 
 
 	def not_available_error(self,name):
 	def not_available_error(self,name):
-		raise ClassFlagsError('{!r}: unrecognized {} for {}: (available {}: {})'.format(
+		die( 'ClassFlagsError', '{!r}: unrecognized {} for {}: (available {}: {})'.format(
 			name,
 			name,
 			self._desc,
 			self._desc,
 			type(self._parent).__name__,
 			type(self._parent).__name__,

+ 3 - 3
mmgen/key.py

@@ -83,9 +83,9 @@ class PrivKey(bytes,Hilite,InitErrors,MMGenObject):
 				me.wif = str.__new__(WifKey,wif) # check has been done
 				me.wif = str.__new__(WifKey,wif) # check has been done
 				me.orig_bytes = None
 				me.orig_bytes = None
 				if k.sec != proto.preprocess_key(k.sec,k.pubkey_type):
 				if k.sec != proto.preprocess_key(k.sec,k.pubkey_type):
-					from .exception import PrivateKeyError
-					raise PrivateKeyError(
-						f'{proto.cls_name} WIF key {me.wif!r} encodes private key with invalid value {me}')
+					from .util import die
+					die( 'PrivateKeyError',
+						f'{proto.cls_name} WIF key {me.wif!r} encodes private key with invalid value {me}' )
 				me.proto = proto
 				me.proto = proto
 				return me
 				return me
 			except Exception as e:
 			except Exception as e:

+ 3 - 2
mmgen/keygen.py

@@ -64,8 +64,9 @@ class keygen_backend:
 				try:
 				try:
 					from .secp256k1 import priv2pub
 					from .secp256k1 import priv2pub
 					if not priv2pub(bytes.fromhex('deadbeef'*8),1):
 					if not priv2pub(bytes.fromhex('deadbeef'*8),1):
-						from .exception import ExtensionModuleError
-						raise ExtensionModuleError('Unable to execute priv2pub() from secp256k1 extension module')
+						from .util import die
+						die( 'ExtensionModuleError',
+							'Unable to execute priv2pub() from secp256k1 extension module' )
 					return True
 					return True
 				except Exception as e:
 				except Exception as e:
 					if not silent:
 					if not silent:

+ 3 - 2
mmgen/led.py

@@ -69,8 +69,8 @@ class LEDControl:
 			except: pass
 			except: pass
 			else: break
 			else: break
 		else:
 		else:
-			from mmgen.exception import NoLEDSupport
-			raise NoLEDSupport('Control files not found!  LED control not supported on this system')
+			from .util import die
+			die( 'NoLEDSupport', 'Control files not found!  LED control not supported on this system' )
 
 
 		msg(f'{board.name} board detected')
 		msg(f'{board.name} board detected')
 
 
@@ -89,6 +89,7 @@ class LEDControl:
 					fp.write(f'{init_val}\n')
 					fp.write(f'{init_val}\n')
 				return True
 				return True
 			except PermissionError:
 			except PermissionError:
+				from .util import die
 				die(2,'\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:

+ 1 - 0
mmgen/main.py

@@ -57,6 +57,7 @@ def launch(mod):
 
 
 			_o = namedtuple('exit_data',['color','exit_val','fs'])
 			_o = namedtuple('exit_data',['color','exit_val','fs'])
 			d = {
 			d = {
+				0:   _o(nocolor, 1, '{message}'),
 				1:   _o(nocolor, 1, '{message}'),
 				1:   _o(nocolor, 1, '{message}'),
 				2:   _o(yellow,  2, '{message}'),
 				2:   _o(yellow,  2, '{message}'),
 				3:   _o(yellow,  3, '\nMMGen Error ({name}): {message}'),
 				3:   _o(yellow,  3, '\nMMGen Error ({name}): {message}'),

+ 1 - 1
mmgen/main_tool.py

@@ -215,7 +215,7 @@ def process_args(cmd,cmd_args,cls):
 		# If we're reading from a pipe, replace '-' with output of previous command
 		# If we're reading from a pipe, replace '-' with output of previous command
 		if flag == 'STDIN_OK' and u_args and u_args[0] == '-':
 		if flag == 'STDIN_OK' and u_args and u_args[0] == '-':
 			if sys.stdin.isatty():
 			if sys.stdin.isatty():
-				raise BadFilename("Standard input is a TTY.  Can't use '-' as a filename")
+				die( 'BadFilename', "Standard input is a TTY.  Can't use '-' as a filename" )
 			else:
 			else:
 				max_dlen_spec = '10kB' # limit input to 10KB for now
 				max_dlen_spec = '10kB' # limit input to 10KB for now
 				max_dlen = parse_bytespec(max_dlen_spec)
 				max_dlen = parse_bytespec(max_dlen_spec)

+ 1 - 2
mmgen/obj.py

@@ -23,7 +23,6 @@ obj.py: MMGen native classes
 import sys,os,re,unicodedata
 import sys,os,re,unicodedata
 
 
 from .objmethods import *
 from .objmethods import *
-from .exception import BadTwComment
 
 
 def get_obj(objname,*args,**kwargs):
 def get_obj(objname,*args,**kwargs):
 	"""
 	"""
@@ -378,7 +377,7 @@ class MMGenWalletLabel(MMGenLabel):
 class TwComment(MMGenLabel):
 class TwComment(MMGenLabel):
 	max_screen_width = 80
 	max_screen_width = 80
 	desc = 'tracking wallet comment'
 	desc = 'tracking wallet comment'
-	exc = BadTwComment
+	exc = 'BadTwComment'
 
 
 class MMGenTxLabel(MMGenLabel):
 class MMGenTxLabel(MMGenLabel):
 	max_len = 72
 	max_len = 72

+ 5 - 4
mmgen/objmethods.py

@@ -115,13 +115,14 @@ class InitErrors:
 		if m2:
 		if m2:
 			errmsg = repr(m2) + '\n' + errmsg
 			errmsg = repr(m2) + '\n' + errmsg
 
 
-		if hasattr(cls,'passthru_excs') and type(e) in cls.passthru_excs:
+		from .util import die
+
+		if hasattr(cls,'passthru_excs') and type(e).__name__ in cls.passthru_excs:
 			raise
 			raise
 		elif hasattr(cls,'exc'):
 		elif hasattr(cls,'exc'):
-			raise cls.exc(errmsg)
+			die( cls.exc, errmsg )
 		else:
 		else:
-			from .exception import ObjectInitError
-			raise ObjectInitError(errmsg)
+			die( 'ObjectInitError', errmsg )
 
 
 	@classmethod
 	@classmethod
 	def method_not_implemented(cls):
 	def method_not_implemented(cls):

+ 24 - 23
mmgen/opts.py

@@ -21,7 +21,6 @@ opts.py:  MMGen-specific options processing after generic processing by share.Op
 """
 """
 import sys,os,stat
 import sys,os,stat
 
 
-from .exception import UserOptError,CfgFileParseError
 from .globalvars import g
 from .globalvars import g
 from .base_obj import Lockable
 from .base_obj import Lockable
 
 
@@ -163,11 +162,13 @@ def override_globals_from_cfg_file(ucfg,need_proto):
 			refval = getattr(cls,attr)
 			refval = getattr(cls,attr)
 			val = ucfg.parse_value(d.value,refval)
 			val = ucfg.parse_value(d.value,refval)
 			if not val:
 			if not val:
-				raise CfgFileParseError(f'Parse error in file {ucfg.fn!r}, line {d.lineno}')
+				from .util import die
+				die( 'CfgFileParseError', f'Parse error in file {ucfg.fn!r}, line {d.lineno}' )
 			val_conv = set_for_type(val,refval,attr,src=ucfg.fn)
 			val_conv = set_for_type(val,refval,attr,src=ucfg.fn)
 			setattr(cls,attr,val_conv)
 			setattr(cls,attr,val_conv)
 		else:
 		else:
-			raise CfgFileParseError(f'{d.name!r}: unrecognized option in {ucfg.fn!r}, line {d.lineno}')
+			from .util import die
+			die( 'CfgFileParseError', f'{d.name!r}: unrecognized option in {ucfg.fn!r}, line {d.lineno}' )
 
 
 def override_globals_and_set_opts_from_env(opt):
 def override_globals_and_set_opts_from_env(opt):
 	for name in g.env_opts:
 	for name in g.env_opts:
@@ -426,39 +427,39 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 		try:
 		try:
 			l = val.split(sep)
 			l = val.split(sep)
 		except:
 		except:
-			raise UserOptError(f'{val!r}: invalid {desc} (not {sepword}-separated list)')
+			die( 'UserOptError', f'{val!r}: invalid {desc} (not {sepword}-separated list)' )
 
 
 		if len(l) != n:
 		if len(l) != n:
-			raise UserOptError(f'{val!r}: invalid {desc} ({n} {sepword}-separated items required)')
+			die( 'UserOptError', f'{val!r}: invalid {desc} ({n} {sepword}-separated items required)' )
 
 
 	def opt_compares(val,op_str,target,desc,desc2=''):
 	def opt_compares(val,op_str,target,desc,desc2=''):
 		import operator as o
 		import operator as o
 		op_f = { '<':o.lt, '<=':o.le, '>':o.gt, '>=':o.ge, '=':o.eq }[op_str]
 		op_f = { '<':o.lt, '<=':o.le, '>':o.gt, '>=':o.ge, '=':o.eq }[op_str]
 		if not op_f(val,target):
 		if not op_f(val,target):
 			d2 = desc2 + ' ' if desc2 else ''
 			d2 = desc2 + ' ' if desc2 else ''
-			raise UserOptError(f'{val}: invalid {desc} ({d2}not {op_str} {target})')
+			die( 'UserOptError', f'{val}: invalid {desc} ({d2}not {op_str} {target})' )
 
 
 	def opt_is_int(val,desc):
 	def opt_is_int(val,desc):
 		if not is_int(val):
 		if not is_int(val):
-			raise UserOptError(f'{val!r}: invalid {desc} (not an integer)')
+			die( 'UserOptError', f'{val!r}: invalid {desc} (not an integer)' )
 
 
 	def opt_is_float(val,desc):
 	def opt_is_float(val,desc):
 		try:
 		try:
 			float(val)
 			float(val)
 		except:
 		except:
-			raise UserOptError(f'{val!r}: invalid {desc} (not a floating-point number)')
+			die( 'UserOptError', f'{val!r}: invalid {desc} (not a floating-point number)' )
 
 
 	def opt_is_in_list(val,tlist,desc):
 	def opt_is_in_list(val,tlist,desc):
 		if val not in tlist:
 		if val not in tlist:
 			q,sep = (('',','),("'","','"))[type(tlist[0]) == str]
 			q,sep = (('',','),("'","','"))[type(tlist[0]) == str]
-			raise UserOptError('{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}'.format(
+			die( 'UserOptError', '{q}{v}{q}: invalid {w}\nValid choices: {q}{o}{q}'.format(
 				v = val,
 				v = val,
 				w = desc,
 				w = desc,
 				q = q,
 				q = q,
 				o = sep.join(map(str,sorted(tlist))) ))
 				o = sep.join(map(str,sorted(tlist))) ))
 
 
 	def opt_unrecognized(key,val,desc='value'):
 	def opt_unrecognized(key,val,desc='value'):
-		raise UserOptError(f'{val!r}: unrecognized {desc} for option {fmt_opt(key)!r}')
+		die( 'UserOptError', f'{val!r}: unrecognized {desc} for option {fmt_opt(key)!r}' )
 
 
 	def opt_display(key,val='',beg='For selected',end=':\n'):
 	def opt_display(key,val='',beg='For selected',end=':\n'):
 		from .util import msg_r
 		from .util import msg_r
@@ -475,15 +476,15 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 		if key == 'out_fmt':
 		if key == 'out_fmt':
 			p = 'hidden_incog_output_params'
 			p = 'hidden_incog_output_params'
 			if sstype == IncogWalletHidden and not getattr(opt,p):
 			if sstype == IncogWalletHidden and not getattr(opt,p):
-				raise UserOptError(
+				die( 'UserOptError',
 					'Hidden incog format output requested.  ' +
 					'Hidden incog format output requested.  ' +
 					f'You must supply a file and offset with the {fmt_opt(p)!r} option' )
 					f'You must supply a file and offset with the {fmt_opt(p)!r} option' )
 			if issubclass(sstype,IncogWallet) and opt.old_incog_fmt:
 			if issubclass(sstype,IncogWallet) and opt.old_incog_fmt:
 				opt_display(key,val,beg='Selected',end=' ')
 				opt_display(key,val,beg='Selected',end=' ')
 				opt_display('old_incog_fmt',beg='conflicts with',end=':\n')
 				opt_display('old_incog_fmt',beg='conflicts with',end=':\n')
-				raise UserOptError('Export to old incog wallet format unsupported')
+				die( 'UserOptError', 'Export to old incog wallet format unsupported' )
 			elif issubclass(sstype,Brainwallet):
 			elif issubclass(sstype,Brainwallet):
-				raise UserOptError('Output to brainwallet format unsupported')
+				die( 'UserOptError', 'Output to brainwallet format unsupported' )
 
 
 	chk_out_fmt = chk_in_fmt
 	chk_out_fmt = chk_in_fmt
 
 
@@ -491,7 +492,7 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 		a = val.rsplit(',',1) # permit comma in filename
 		a = val.rsplit(',',1) # permit comma in filename
 		if len(a) != 2:
 		if len(a) != 2:
 			opt_display(key,val)
 			opt_display(key,val)
-			raise UserOptError('Option requires two comma-separated arguments')
+			die( 'UserOptError', 'Option requires two comma-separated arguments' )
 
 
 		fn,offset = a
 		fn,offset = a
 		opt_is_int(offset,desc)
 		opt_is_int(offset,desc)
@@ -514,7 +515,7 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 			val2 = getattr(opt,key2)
 			val2 = getattr(opt,key2)
 			from .wallet import IncogWalletHidden
 			from .wallet import IncogWalletHidden
 			if val2 and val2 not in IncogWalletHidden.fmt_codes:
 			if val2 and val2 not in IncogWalletHidden.fmt_codes:
-				raise UserOptError(f'Option conflict:\n  {fmt_opt(key)}, with\n  {fmt_opt(key2)}={val2}')
+				die( 'UserOptError', f'Option conflict:\n  {fmt_opt(key)}, with\n  {fmt_opt(key2)}={val2}' )
 
 
 	chk_hidden_incog_output_params = chk_hidden_incog_input_params
 	chk_hidden_incog_output_params = chk_hidden_incog_input_params
 
 
@@ -539,7 +540,7 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 		a = val.split(',')
 		a = val.split(',')
 		if len(a) != 2:
 		if len(a) != 2:
 			opt_display(key,val)
 			opt_display(key,val)
-			raise UserOptError('Option requires two comma-separated arguments')
+			die( 'UserOptError', 'Option requires two comma-separated arguments' )
 		opt_is_int(a[0],'seed length '+desc)
 		opt_is_int(a[0],'seed length '+desc)
 		opt_is_in_list(int(a[0]),Seed.lens,'seed length '+desc)
 		opt_is_in_list(int(a[0]),Seed.lens,'seed length '+desc)
 		opt_is_in_list(a[1],list(hash_presets.keys()),'hash preset '+desc)
 		opt_is_in_list(a[1],list(hash_presets.keys()),'hash preset '+desc)
@@ -567,14 +568,14 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 # TODO: move this check elsewhere
 # TODO: move this check elsewhere
 #	def chk_rbf(key,val,desc):
 #	def chk_rbf(key,val,desc):
 #		if not proto.cap('rbf'):
 #		if not proto.cap('rbf'):
-#			raise UserOptError(f'--rbf requested, but {proto.coin} does not support replace-by-fee transactions')
+#			die( 'UserOptError', f'--rbf requested, but {proto.coin} does not support replace-by-fee transactions' )
 
 
 #	def chk_bob(key,val,desc):
 #	def chk_bob(key,val,desc):
 #		from .regtest import MMGenRegtest
 #		from .regtest import MMGenRegtest
 #		try:
 #		try:
 #			os.stat(os.path.join(MMGenRegtest(g.coin).d.datadir,'regtest','debug.log'))
 #			os.stat(os.path.join(MMGenRegtest(g.coin).d.datadir,'regtest','debug.log'))
 #		except:
 #		except:
-#			raise UserOptError(
+#			die( 'UserOptError',
 #				'Regtest (Bob and Alice) mode not set up yet.  ' +
 #				'Regtest (Bob and Alice) mode not set up yet.  ' +
 #				f"Run '{g.proj_name.lower()}-regtest setup' to initialize." )
 #				f"Run '{g.proj_name.lower()}-regtest setup' to initialize." )
 #
 #
@@ -587,13 +588,13 @@ def check_usr_opts(usr_opts): # Raises an exception if any check fails
 # TODO: move this check elsewhere
 # TODO: move this check elsewhere
 #	def chk_token(key,val,desc):
 #	def chk_token(key,val,desc):
 #		if not 'token' in proto.caps:
 #		if not 'token' in proto.caps:
-#			raise UserOptError(f'Coin {tx.coin!r} does not support the --token option')
+#			die( 'UserOptError', f'Coin {tx.coin!r} does not support the --token option' )
 #		if len(val) == 40 and is_hex_str(val):
 #		if len(val) == 40 and is_hex_str(val):
 #			return
 #			return
 #		if len(val) > 20 or not all(s.isalnum() for s in val):
 #		if len(val) > 20 or not all(s.isalnum() for s in val):
-#			raise UserOptError(f'{val!r}: invalid parameter for --token option')
+#			die( 'UserOptError', f'{val!r}: invalid parameter for --token option' )
 
 
-	from .util import is_int
+	from .util import is_int,die
 
 
 	cfuncs = { k:v for k,v in locals().items() if k.startswith('chk_') }
 	cfuncs = { k:v for k,v in locals().items() if k.startswith('chk_') }
 
 
@@ -642,8 +643,8 @@ def check_and_set_autoset_opts(): # Raises exception if any check fails
 			else:
 			else:
 				ret = locals()[asd.type](key,val,asd)
 				ret = locals()[asd.type](key,val,asd)
 				if type(ret) is str:
 				if type(ret) is str:
-					from .util import fmt_list
-					raise UserOptError(
+					from .util import fmt_list,die
+					die( 'UserOptError',
 						'{!r}: invalid parameter for option --{} (not {}: {})'.format(
 						'{!r}: invalid parameter for option --{} (not {}: {})'.format(
 							val,
 							val,
 							key.replace('_','-'),
 							key.replace('_','-'),

+ 2 - 3
mmgen/passwdlist.py

@@ -22,7 +22,7 @@ passwdlist.py: Password list class for the MMGen suite
 
 
 from collections import namedtuple
 from collections import namedtuple
 
 
-from .util import ymsg,is_int,keypress_confirm
+from .util import ymsg,is_int,keypress_confirm,die
 from .obj import ImmutableAttr,ListItemAttr,MMGenPWIDString,TwComment
 from .obj import ImmutableAttr,ListItemAttr,MMGenPWIDString,TwComment
 from .key import PrivKey
 from .key import PrivKey
 from .addr import MMGenPasswordType,AddrIdx,AddrListID
 from .addr import MMGenPasswordType,AddrIdx,AddrListID
@@ -112,8 +112,7 @@ class PasswordList(AddrList):
 			self.pw_fmt = pw_fmt
 			self.pw_fmt = pw_fmt
 			self.pw_fmt_disp = pw_fmt
 			self.pw_fmt_disp = pw_fmt
 		if self.pw_fmt not in self.pw_info:
 		if self.pw_fmt not in self.pw_info:
-			from .exception import InvalidPasswdFormat
-			raise InvalidPasswdFormat(
+			die( 'InvalidPasswdFormat',
 				'{!r}: invalid password format.  Valid formats: {}'.format(
 				'{!r}: invalid password format.  Valid formats: {}'.format(
 					self.pw_fmt,
 					self.pw_fmt,
 					', '.join(self.pw_info) ))
 					', '.join(self.pw_info) ))

+ 6 - 7
mmgen/rpc.py

@@ -190,7 +190,7 @@ class RPCBackends:
 					headers = self.http_hdrs )
 					headers = self.http_hdrs )
 				r = s.getresponse() # => http.client.HTTPResponse instance
 				r = s.getresponse() # => http.client.HTTPResponse instance
 			except Exception as e:
 			except Exception as e:
-				raise RPCFailure(str(e))
+				die( 'RPCFailure', str(e) )
 
 
 			if timeout:
 			if timeout:
 				ret = ( r.read(), r.status )
 				ret = ( r.read(), r.status )
@@ -303,8 +303,7 @@ class RPCClient(MMGenObject):
 			try:
 			try:
 				socket.create_connection((host,port),timeout=1).close()
 				socket.create_connection((host,port),timeout=1).close()
 			except:
 			except:
-				from .exception import SocketError
-				raise SocketError(f'Unable to connect to {host}:{port}')
+				die( 'SocketError', f'Unable to connect to {host}:{port}' )
 
 
 		self.http_hdrs = { 'Content-Type': 'application/json' }
 		self.http_hdrs = { 'Content-Type': 'application/json' }
 		self.url = f'{self.network_proto}://{host}:{port}{self.host_path}'
 		self.url = f'{self.network_proto}://{host}:{port}{self.host_path}'
@@ -435,7 +434,7 @@ class RPCClient(MMGenObject):
 					except:
 					except:
 						try: m = t['error']
 						try: m = t['error']
 						except: m = t
 						except: m = t
-					raise RPCFailure(m)
+					die( 'RPCFailure', m )
 		else:
 		else:
 			import http
 			import http
 			m,s = ( '', http.HTTPStatus(status) )
 			m,s = ( '', http.HTTPStatus(status) )
@@ -445,7 +444,7 @@ class RPCClient(MMGenObject):
 				except:
 				except:
 					try: m = text.decode()
 					try: m = text.decode()
 					except: m = text
 					except: m = text
-			raise RPCFailure(f'{s.value} {s.name}: {m}')
+			die( 'RPCFailure', f'{s.value} {s.name}: {m}' )
 
 
 	async def stop_daemon(self,quiet=False,silent=False):
 	async def stop_daemon(self,quiet=False,silent=False):
 		if self.daemon.state == 'ready':
 		if self.daemon.state == 'ready':
@@ -852,10 +851,10 @@ async def rpc_init(proto,backend=None,daemon=None,ignore_daemon_version=False):
 			ignore_daemon_version or proto.ignore_daemon_version or g.ignore_daemon_version )
 			ignore_daemon_version or proto.ignore_daemon_version or g.ignore_daemon_version )
 
 
 	if rpc.chain not in proto.chain_names:
 	if rpc.chain not in proto.chain_names:
-		raise RPCChainMismatch('\n'+fmt(f"""
+		die( 'RPCChainMismatch', '\n' + fmt(f"""
 			Protocol:           {proto.cls_name}
 			Protocol:           {proto.cls_name}
 			Valid chain names:  {fmt_list(proto.chain_names,fmt='bare')}
 			Valid chain names:  {fmt_list(proto.chain_names,fmt='bare')}
 			RPC client chain:   {rpc.chain}
 			RPC client chain:   {rpc.chain}
-			""",indent='  ').rstrip())
+			""",indent='  ').rstrip() )
 
 
 	return rpc
 	return rpc

+ 3 - 6
mmgen/seedsplit.py

@@ -80,8 +80,7 @@ class SeedShareList(SubSeedList):
 						msg(f'master_share seed ID collision with parent seed, incrementing nonce to {nonce+1}')
 						msg(f'master_share seed ID collision with parent seed, incrementing nonce to {nonce+1}')
 				else:
 				else:
 					return ms
 					return ms
-			from .exception import SubSeedNonceRangeExceeded
-			raise SubSeedNonceRangeExceeded('nonce range exceeded')
+			die( 'SubSeedNonceRangeExceeded', 'nonce range exceeded' )
 
 
 		def last_share_debug(last_share):
 		def last_share_debug(last_share):
 			if not debug_last_share:
 			if not debug_last_share:
@@ -109,8 +108,7 @@ class SeedShareList(SubSeedList):
 				self.data['long'][ls.sid] = (count,nonce)
 				self.data['long'][ls.sid] = (count,nonce)
 				break
 				break
 		else:
 		else:
-			from .exception import SubSeedNonceRangeExceeded
-			raise SubSeedNonceRangeExceeded('nonce range exceeded')
+			die( 'SubSeedNonceRangeExceeded', 'nonce range exceeded' )
 
 
 		if g.debug_subseed:
 		if g.debug_subseed:
 			A = parent_seed.data
 			A = parent_seed.data
@@ -119,8 +117,7 @@ class SeedShareList(SubSeedList):
 
 
 	def get_share_by_idx(self,idx,base_seed=False):
 	def get_share_by_idx(self,idx,base_seed=False):
 		if idx < 1 or idx > self.count:
 		if idx < 1 or idx > self.count:
-			from .exception import RangeError
-			raise RangeError(f'{idx}: share index out of range')
+			die( 'RangeError', f'{idx}: share index out of range' )
 		elif idx == self.count:
 		elif idx == self.count:
 			return self.last_share
 			return self.last_share
 		elif self.master_share and idx == 1:
 		elif self.master_share and idx == 1:

+ 2 - 3
mmgen/subseed.py

@@ -21,7 +21,7 @@ subseed.py:  Subseed classes and methods for the MMGen suite
 """
 """
 
 
 from .color import green
 from .color import green
-from .util import msg_r,msg,qmsg
+from .util import msg_r,msg,qmsg,die
 from .obj import MMGenRange,IndexedDict
 from .obj import MMGenRange,IndexedDict
 from .seed import *
 from .seed import *
 from .crypto import scramble_seed
 from .crypto import scramble_seed
@@ -191,8 +191,7 @@ class SubSeedList(MMGenObject):
 					self.data[length][sid] = (idx,nonce)
 					self.data[length][sid] = (idx,nonce)
 					return last_sid == sid
 					return last_sid == sid
 			else: # must exit here, as this could leave self.data in inconsistent state
 			else: # must exit here, as this could leave self.data in inconsistent state
-				from .exception import SubSeedNonceRangeExceeded
-				raise SubSeedNonceRangeExceeded('add_subseed(): nonce range exceeded')
+				die( 'SubSeedNonceRangeExceeded', 'add_subseed(): nonce range exceeded' )
 
 
 		for idx in SubSeedIdxRange(first_idx,last_idx).iterate():
 		for idx in SubSeedIdxRange(first_idx,last_idx).iterate():
 			match1 = add_subseed(idx,'long')
 			match1 = add_subseed(idx,'long')

+ 6 - 7
mmgen/tw.py

@@ -22,7 +22,6 @@ tw: Tracking wallet dependency classes for the MMGen suite
 
 
 import time
 import time
 
 
-from .exception import BadTwLabel,BadTwComment
 from .objmethods import Hilite,InitErrors,MMGenObject
 from .objmethods import Hilite,InitErrors,MMGenObject
 from .obj import TwComment
 from .obj import TwComment
 from .addr import MMGenID
 from .addr import MMGenID
@@ -86,8 +85,8 @@ class TwMMGenID(str,Hilite,InitErrors,MMGenObject):
 
 
 # non-displaying container for TwMMGenID,TwComment
 # non-displaying container for TwMMGenID,TwComment
 class TwLabel(str,InitErrors,MMGenObject):
 class TwLabel(str,InitErrors,MMGenObject):
-	exc = BadTwLabel
-	passthru_excs = (BadTwComment,)
+	exc = 'BadTwLabel'
+	passthru_excs = ('BadTwComment',)
 	def __new__(cls,proto,text):
 	def __new__(cls,proto,text):
 		if type(text) == cls:
 		if type(text) == cls:
 			return text
 			return text
@@ -109,8 +108,8 @@ def get_tw_label(proto,s):
 	"""
 	"""
 	try:
 	try:
 		return TwLabel(proto,s)
 		return TwLabel(proto,s)
-	except BadTwComment:
-		raise
 	except Exception as e:
 	except Exception as e:
-#		print(e)
-		return None
+		if type(e).__name__ == 'BadTwComment': # do it this way to avoid importing .exception
+			raise
+		else:
+			return None

+ 1 - 2
mmgen/twaddrs.py

@@ -126,8 +126,7 @@ class TwAddrList(MMGenDict,TwCommon,metaclass=AsyncInit):
 		if not self.has_age:
 		if not self.has_age:
 			show_age = False
 			show_age = False
 		if age_fmt not in self.age_fmts:
 		if age_fmt not in self.age_fmts:
-			from .exception import BadAgeFormat
-			raise BadAgeFormat(f'{age_fmt!r}: invalid age format (must be one of {self.age_fmts!r})')
+			die( 'BadAgeFormat', f'{age_fmt!r}: invalid age format (must be one of {self.age_fmts!r})' )
 		fs = '{mid}' + ('',' {addr}')[showbtcaddrs] + ' {cmt} {amt}' + ('',' {age}')[show_age]
 		fs = '{mid}' + ('',' {addr}')[showbtcaddrs] + ' {cmt} {amt}' + ('',' {age}')[show_age]
 		mmaddrs = [k for k in self.keys() if k.type == 'mmgen']
 		mmaddrs = [k for k in self.keys() if k.type == 'mmgen']
 		max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10
 		max_mmid_len = max(len(k) for k in mmaddrs) + 2 if mmaddrs else 10

+ 3 - 5
mmgen/twctl.py

@@ -21,7 +21,7 @@ twctl: Tracking wallet control class for the MMGen suite
 """
 """
 
 
 from .globalvars import g
 from .globalvars import g
-from .util import msg,dmsg,write_mode,base_proto_subclass
+from .util import msg,dmsg,write_mode,base_proto_subclass,die
 from .base_obj import AsyncInit
 from .base_obj import AsyncInit
 from .objmethods import MMGenObject
 from .objmethods import MMGenObject
 from .obj import TwComment,get_obj
 from .obj import TwComment,get_obj
@@ -61,8 +61,7 @@ class TrackingWallet(MMGenObject,metaclass=AsyncInit):
 			self.init_empty()
 			self.init_empty()
 
 
 		if self.data['coin'] != self.proto.coin: # TODO remove?
 		if self.data['coin'] != self.proto.coin: # TODO remove?
-			from .exception import WalletFileError
-			raise WalletFileError(
+			die( 'WalletFileError',
 				'Tracking wallet coin ({}) does not match current coin ({})!'.format(
 				'Tracking wallet coin ({}) does not match current coin ({})!'.format(
 					self.data['coin'],
 					self.data['coin'],
 					self.proto.coin ))
 					self.proto.coin ))
@@ -98,8 +97,7 @@ class TrackingWallet(MMGenObject,metaclass=AsyncInit):
 				self.init_empty()
 				self.init_empty()
 				self.force_write()
 				self.force_write()
 			else:
 			else:
-				from .exception import WalletFileError
-				raise WalletFileError(f'File {self.tw_fn!r} exists but does not contain valid json data')
+				die( 'WalletFileError', f'File {self.tw_fn!r} exists but does not contain valid json data' )
 		else:
 		else:
 			self.upgrade_wallet_maybe()
 			self.upgrade_wallet_maybe()
 
 

+ 1 - 2
mmgen/twuo.py

@@ -128,8 +128,7 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, add [l]abel:
 	@age_fmt.setter
 	@age_fmt.setter
 	def age_fmt(self,val):
 	def age_fmt(self,val):
 		if val not in self.age_fmts:
 		if val not in self.age_fmts:
-			from .exception import BadAgeFormat
-			raise BadAgeFormat(f'{val!r}: invalid age format (must be one of {self.age_fmts!r})')
+			die( 'BadAgeFormat', f'{val!r}: invalid age format (must be one of {self.age_fmts!r})' )
 		self._age_fmt = val
 		self._age_fmt = val
 
 
 	def get_display_precision(self):
 	def get_display_precision(self):

+ 4 - 4
mmgen/tx/base.py

@@ -16,7 +16,7 @@ from ..globalvars import *
 from ..objmethods import MMGenObject
 from ..objmethods import MMGenObject
 from ..obj import ImmutableAttr,ListItemAttr,MMGenListItem,MMGenTxLabel,TwComment,CoinTxID,HexStr
 from ..obj import ImmutableAttr,ListItemAttr,MMGenListItem,MMGenTxLabel,TwComment,CoinTxID,HexStr
 from ..addr import MMGenID,CoinAddr
 from ..addr import MMGenID,CoinAddr
-from ..util import msg,ymsg,fmt,remove_dups,keypress_confirm,make_timestamp,line_input
+from ..util import msg,ymsg,fmt,remove_dups,keypress_confirm,make_timestamp,line_input,die
 from ..opts import opt
 from ..opts import opt
 
 
 class MMGenTxIO(MMGenListItem):
 class MMGenTxIO(MMGenListItem):
@@ -118,8 +118,8 @@ class Base(MMGenObject):
 	def check_correct_chain(self):
 	def check_correct_chain(self):
 		if hasattr(self,'rpc'):
 		if hasattr(self,'rpc'):
 			if self.chain != self.rpc.chain:
 			if self.chain != self.rpc.chain:
-				raise TransactionChainMismatch(
-					f'Transaction is for {self.chain}, but coin daemon chain is {self.rpc.chain}!')
+				die( 'TransactionChainMismatch',
+					f'Transaction is for {self.chain}, but coin daemon chain is {self.rpc.chain}!' )
 
 
 	def sum_inputs(self):
 	def sum_inputs(self):
 		return sum(e.amt for e in self.inputs)
 		return sum(e.amt for e in self.inputs)
@@ -177,7 +177,7 @@ class Base(MMGenObject):
 			m = fs.format('\n    '.join(non_mmaddrs))
 			m = fs.format('\n    '.join(non_mmaddrs))
 			if caller in ('txdo','txsign'):
 			if caller in ('txdo','txsign'):
 				if not opt.keys_from_file:
 				if not opt.keys_from_file:
-					raise UserOptError(f'\n{indent}ERROR: {m}\n')
+					die( 'UserOptError', f'\n{indent}ERROR: {m}\n' )
 			else:
 			else:
 				msg(f'\n{indent}WARNING: {m}\n')
 				msg(f'\n{indent}WARNING: {m}\n')
 				if not (opt.yes or keypress_confirm('Continue?',default_yes=True)):
 				if not (opt.yes or keypress_confirm('Continue?',default_yes=True)):

+ 2 - 4
mmgen/txfile.py

@@ -60,8 +60,7 @@ class MMGenTxFile(MMGenObject):
 		try:
 		try:
 			desc = 'data'
 			desc = 'data'
 			if len(tx_data) > g.max_tx_file_size:
 			if len(tx_data) > g.max_tx_file_size:
-				from .exception import MaxFileSizeExceeded
-				raise MaxFileSizeExceeded(f'Transaction file size exceeds limit ({g.max_tx_file_size} bytes)')
+				die( 'MaxFileSizeExceeded', f'Transaction file size exceeds limit ({g.max_tx_file_size} bytes)' )
 			tx_data = tx_data.splitlines()
 			tx_data = tx_data.splitlines()
 			assert len(tx_data) >= 5,'number of lines less than 5'
 			assert len(tx_data) >= 5,'number of lines less than 5'
 			assert len(tx_data[0]) == 6,'invalid length of first line'
 			assert len(tx_data[0]) == 6,'invalid length of first line'
@@ -187,8 +186,7 @@ class MMGenTxFile(MMGenObject):
 		self.chksum = make_chksum_6(' '.join(lines))
 		self.chksum = make_chksum_6(' '.join(lines))
 		fmt_data = '\n'.join([self.chksum] + lines) + '\n'
 		fmt_data = '\n'.join([self.chksum] + lines) + '\n'
 		if len(fmt_data) > g.max_tx_file_size:
 		if len(fmt_data) > g.max_tx_file_size:
-			from .exception import MaxFileSizeExceeded
-			raise MaxFileSizeExceeded(f'Transaction file size exceeds limit ({g.max_tx_file_size} bytes)')
+			die( 'MaxFileSizeExceeded', f'Transaction file size exceeds limit ({g.max_tx_file_size} bytes)' )
 		return fmt_data
 		return fmt_data
 
 
 	def write(self,
 	def write(self,

+ 12 - 9
mmgen/util.py

@@ -121,12 +121,17 @@ def mdie(*args):
 	sys.exit(0)
 	sys.exit(0)
 
 
 def die(ev,s='',stdout=False):
 def die(ev,s='',stdout=False):
-	assert isinstance(ev,int)
-	from .exception import MMGenSystemExit,MMGenError
-	if ev <= 2:
-		raise MMGenSystemExit(ev,s,stdout)
+	if isinstance(ev,int):
+		from .exception import MMGenSystemExit,MMGenError
+		if ev <= 2:
+			raise MMGenSystemExit(ev,s,stdout)
+		else:
+			raise MMGenError(ev,s,stdout)
+	elif isinstance(ev,str):
+		import mmgen.exception
+		raise getattr(mmgen.exception,ev)(s)
 	else:
 	else:
-		raise MMGenError(ev,s,stdout)
+		raise ValueError(f'{ev}: exit value must be string or int instance')
 
 
 def die_wait(delay,ev=0,s=''):
 def die_wait(delay,ev=0,s=''):
 	assert isinstance(delay,int)
 	assert isinstance(delay,int)
@@ -460,8 +465,7 @@ def compare_or_die(val1, desc1, val2, desc2, e='Error'):
 def check_wallet_extension(fn):
 def check_wallet_extension(fn):
 	from .wallet import Wallet
 	from .wallet import Wallet
 	if not Wallet.ext_to_type(get_extension(fn)):
 	if not Wallet.ext_to_type(get_extension(fn)):
-		from .exception import BadFileExtension
-		raise BadFileExtension(f'{fn!r}: unrecognized seed source file extension')
+		die( 'BadFileExtension', f'{fn!r}: unrecognized seed source file extension' )
 
 
 def make_full_path(outdir,outfile):
 def make_full_path(outdir,outfile):
 	return os.path.normpath(os.path.join(outdir, os.path.basename(outfile)))
 	return os.path.normpath(os.path.join(outdir, os.path.basename(outfile)))
@@ -472,8 +476,7 @@ def confirm_or_raise(message,q,expect='YES',exit_msg='Exiting at user request'):
 	a = f'{q}  ' if q[0].isupper() else f'Are you sure you want to {q}?\n'
 	a = f'{q}  ' if q[0].isupper() else f'Are you sure you want to {q}?\n'
 	b = f'Type uppercase {expect!r} to confirm: '
 	b = f'Type uppercase {expect!r} to confirm: '
 	if line_input(a+b).strip() != expect:
 	if line_input(a+b).strip() != expect:
-		from .exception import UserNonConfirmation
-		raise UserNonConfirmation(exit_msg)
+		die( 'UserNonConfirmation', exit_msg )
 
 
 def get_words_from_user(prompt):
 def get_words_from_user(prompt):
 	words = line_input(prompt, echo=opt.echo_passphrase).split()
 	words = line_input(prompt, echo=opt.echo_passphrase).split()

+ 1 - 1
mmgen/wallet.py

@@ -521,7 +521,7 @@ class DieRollSeedFile(WalletUnenc):
 		rmap = bc.seedlen_map_rev
 		rmap = bc.seedlen_map_rev
 
 
 		if not len(d) in rmap:
 		if not len(d) in rmap:
-			raise SeedLengthError('{!r}: invalid length for {} (must be one of {})'.format(
+			die( 'SeedLengthError', '{!r}: invalid length for {} (must be one of {})'.format(
 				len(d),
 				len(d),
 				self.desc,
 				self.desc,
 				list(rmap) ))
 				list(rmap) ))

+ 4 - 7
mmgen/xmrseed.py

@@ -54,17 +54,15 @@ class xmrseed(baseconv):
 		wl = self.digits
 		wl = self.digits
 		base = len(wl)
 		base = len(wl)
 
 
-		from .exception import MnemonicError
-
 		if not set(words) <= set(wl):
 		if not set(words) <= set(wl):
-			raise MnemonicError( f'{words!r}: not in {desc} format' )
+			die( 'MnemonicError',  f'{words!r}: not in {desc} format'  )
 
 
 		if len(words) not in self.seedlen_map_rev:
 		if len(words) not in self.seedlen_map_rev:
-			raise MnemonicError( f'{len(words)}: invalid seed phrase length for {desc}' )
+			die( 'MnemonicError',  f'{len(words)}: invalid seed phrase length for {desc}' )
 
 
 		z = self.monero_mn_checksum(words[:-1])
 		z = self.monero_mn_checksum(words[:-1])
 		if z != words[-1]:
 		if z != words[-1]:
-			raise MnemonicError(f'invalid {desc} checksum')
+			die( 'MnemonicError', f'invalid {desc} checksum' )
 
 
 		words = tuple(words[:-1])
 		words = tuple(words[:-1])
 
 
@@ -84,8 +82,7 @@ class xmrseed(baseconv):
 		base = len(wl)
 		base = len(wl)
 
 
 		if len(bytestr) not in self.seedlen_map:
 		if len(bytestr) not in self.seedlen_map:
-			from .exception import SeedLengthError
-			raise SeedLengthError(f'{len(bytestr)}: invalid seed byte length for {desc}')
+			die( 'SeedLengthError', f'{len(bytestr)}: invalid seed byte length for {desc}' )
 
 
 		def num2base_monero(num):
 		def num2base_monero(num):
 			w1 = num % base
 			w1 = num % base

+ 1 - 1
mmgen/xmrwallet.py

@@ -888,6 +888,6 @@ class MoneroWalletOps:
 						ymsg('Transaction not relayed')
 						ymsg('Transaction not relayed')
 					return True
 					return True
 				else:
 				else:
-					raise RPCFailure(repr(res))
+					die( 'RPCFailure', repr(res) )
 			else:
 			else:
 				die(1,'Exiting at user request')
 				die(1,'Exiting at user request')

+ 3 - 2
test/include/common.py

@@ -112,7 +112,8 @@ def cleandir(d,do_msg=False):
 def mk_tmpdir(d):
 def mk_tmpdir(d):
 	try: os.mkdir(d,0o755)
 	try: os.mkdir(d,0o755)
 	except OSError as e:
 	except OSError as e:
-		if e.errno != 17: raise
+		if e.errno != 17:
+			raise
 	else:
 	else:
 		vmsg(f'Created directory {d!r}')
 		vmsg(f'Created directory {d!r}')
 
 
@@ -149,7 +150,7 @@ def ok():
 
 
 def cmp_or_die(s,t,desc=None):
 def cmp_or_die(s,t,desc=None):
 	if s != t:
 	if s != t:
-		raise TestSuiteFatalException(
+		die( 'TestSuiteFatalException',
 			(f'For {desc}:\n' if desc else '') +
 			(f'For {desc}:\n' if desc else '') +
 			f'ERROR: recoded data:\n{t!r}\ndiffers from original data:\n{s!r}'
 			f'ERROR: recoded data:\n{t!r}\ndiffers from original data:\n{s!r}'
 		)
 		)

+ 1 - 1
test/objattrtest.py

@@ -85,7 +85,7 @@ def test_attr_perm(obj,attrname,perm_name,perm_value,dobj,attrval_type):
 			try:
 			try:
 				so = sample_objs[attrval_type.__name__]
 				so = sample_objs[attrval_type.__name__]
 			except:
 			except:
-				raise SampleObjError(f'unable to find sample object of type {attrval_type.__name__!r}')
+				die( 'SampleObjError', f'unable to find sample object of type {attrval_type.__name__!r}' )
 			# ListItemAttr allows setting an attribute if its value is None
 			# ListItemAttr allows setting an attribute if its value is None
 			if type(dobj) == ListItemAttr and getattr(obj,attrname) == None:
 			if type(dobj) == ListItemAttr and getattr(obj,attrname) == None:
 				setattr(obj,attrname,so)
 				setattr(obj,attrname,so)

+ 2 - 1
test/test.py

@@ -460,7 +460,8 @@ def create_tmp_dirs(shm_dir):
 			try:
 			try:
 				os.unlink(cfgs[cfg]['tmpdir'])
 				os.unlink(cfgs[cfg]['tmpdir'])
 			except OSError as e:
 			except OSError as e:
-				if e.errno != 2: raise
+				if e.errno != 2:
+					raise
 			finally:
 			finally:
 				os.symlink(src,cfgs[cfg]['tmpdir'])
 				os.symlink(src,cfgs[cfg]['tmpdir'])
 
 

+ 0 - 1
test/test_py_d/ts_ethdev.py

@@ -28,7 +28,6 @@ from subprocess import run,PIPE,DEVNULL
 from mmgen.globalvars import g
 from mmgen.globalvars import g
 from mmgen.opts import opt
 from mmgen.opts import opt
 from mmgen.util import die
 from mmgen.util import die
-from mmgen.exception import *
 from mmgen.amt import ETHAmt
 from mmgen.amt import ETHAmt
 from mmgen.protocol import CoinProtocol
 from mmgen.protocol import CoinProtocol
 from ..include.common import *
 from ..include.common import *

+ 3 - 3
test/test_py_d/ts_main.py

@@ -46,7 +46,7 @@ def make_brainwallet_file(fn):
 def verify_checksum_or_exit(checksum,chk):
 def verify_checksum_or_exit(checksum,chk):
 	chk = strip_ansi_escapes(chk)
 	chk = strip_ansi_escapes(chk)
 	if checksum != chk:
 	if checksum != chk:
-		raise TestSuiteFatalException(f'Checksum error: {chk}')
+		die( 'TestSuiteFatalException', f'Checksum error: {chk}' )
 	vmsg(green('Checksums match: ') + cyan(chk))
 	vmsg(green('Checksums match: ') + cyan(chk))
 
 
 addrs_per_wallet = 8
 addrs_per_wallet = 8
@@ -403,7 +403,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 			ad.add(al)
 			ad.add(al)
 			aix = AddrIdxList(fmt_str=self.cfgs[s]['addr_idx_list'])
 			aix = AddrIdxList(fmt_str=self.cfgs[s]['addr_idx_list'])
 			if len(aix) != addrs_per_wallet:
 			if len(aix) != addrs_per_wallet:
-				raise TestSuiteFatalException(f'Address index list length != {addrs_per_wallet}: {repr(aix)}')
+				die( 'TestSuiteFatalException', f'Address index list length != {addrs_per_wallet}: {repr(aix)}' )
 			tx_data[s] = {
 			tx_data[s] = {
 				'addrfile': afile,
 				'addrfile': afile,
 				'chk': al.chksum,
 				'chk': al.chksum,
@@ -497,7 +497,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared):
 			['-f',self.tx_fee,'-B'] + add_args + cmd_args + txdo_args)
 			['-f',self.tx_fee,'-B'] + add_args + cmd_args + txdo_args)
 
 
 		if t.expect([('Get','Unsigned transac')[cmdline_inputs],r'Unable to connect to \S+'],regex=True) == 1:
 		if t.expect([('Get','Unsigned transac')[cmdline_inputs],r'Unable to connect to \S+'],regex=True) == 1:
-			raise TestSuiteException('\n'+t.p.after)
+			die( 'TestSuiteException', '\n'+t.p.after )
 
 
 		if cmdline_inputs:
 		if cmdline_inputs:
 			t.written_to_file('tion')
 			t.written_to_file('tion')

+ 1 - 1
test/test_py_d/ts_misc.py

@@ -58,8 +58,8 @@ 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('MMGenSystemExit(1)')
 		t.expect('MMGenSystemExit(1)')
+		t.expect('USAGE: mmgen-walletgen')
 		t.req_exit_val = 1
 		t.req_exit_val = 1
 		return t
 		return t
 
 

+ 1 - 1
test/tooltest2.py

@@ -778,7 +778,7 @@ def fork_cmd(cmd_name,args,out,opts,stdin_input):
 		vmsg(cp.stderr.strip().decode())
 		vmsg(cp.stderr.strip().decode())
 	if cp.returncode != 0:
 	if cp.returncode != 0:
 		import re
 		import re
-		m = re.match(b'tool command returned (None|False)'+NL.encode(),cp.stderr)
+		m = re.search(b'tool command returned (None|False)',cp.stdout)
 		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:

+ 0 - 1
test/unit_tests_d/ut_baseconv.py

@@ -4,7 +4,6 @@ test/unit_tests_d/ut_baseconv.py: Base conversion unit test for the MMGen suite
 """
 """
 
 
 from mmgen.common import *
 from mmgen.common import *
-from mmgen.exception import *
 
 
 class unit_test(object):
 class unit_test(object):
 
 

+ 0 - 1
test/unit_tests_d/ut_bip39.py

@@ -4,7 +4,6 @@ test/unit_tests_d/ut_bip39: BIP39 unit test for the MMGen suite
 """
 """
 
 
 from mmgen.common import *
 from mmgen.common import *
-from mmgen.exception import *
 
 
 class unit_test(object):
 class unit_test(object):
 
 

+ 0 - 1
test/unit_tests_d/ut_daemon.py

@@ -5,7 +5,6 @@ test/unit_tests_d/ut_daemon.py: unit test for the MMGen suite's Daemon class
 
 
 from subprocess import run,DEVNULL
 from subprocess import run,DEVNULL
 from mmgen.common import *
 from mmgen.common import *
-from mmgen.exception import *
 from mmgen.daemon import *
 from mmgen.daemon import *
 from mmgen.protocol import init_proto
 from mmgen.protocol import init_proto
 
 

+ 0 - 1
test/unit_tests_d/ut_flags.py

@@ -4,7 +4,6 @@ test/unit_tests_d/ut_flags.py: unit test for the MMGen suite's ClassFlags class
 """
 """
 
 
 from mmgen.common import *
 from mmgen.common import *
-from mmgen.exception import *
 from mmgen.flags import *
 from mmgen.flags import *
 
 
 class unit_test(object):
 class unit_test(object):

+ 0 - 1
test/unit_tests_d/ut_lockable.py

@@ -4,7 +4,6 @@ test/unit_tests_d/ut_lockable.py: unit test for the MMGen suite's Lockable class
 """
 """
 
 
 from mmgen.common import *
 from mmgen.common import *
-from mmgen.exception import *
 
 
 class unit_test(object):
 class unit_test(object):
 
 

+ 0 - 1
test/unit_tests_d/ut_rpc.py

@@ -4,7 +4,6 @@ test.unit_tests_d.ut_rpc: RPC unit test for the MMGen suite
 """
 """
 
 
 from mmgen.common import *
 from mmgen.common import *
-from mmgen.exception import *
 
 
 from mmgen.protocol import init_proto
 from mmgen.protocol import init_proto
 from mmgen.rpc import rpc_init,MoneroWalletRPCClient
 from mmgen.rpc import rpc_init,MoneroWalletRPCClient

+ 0 - 1
test/unit_tests_d/ut_xmrseed.py

@@ -4,7 +4,6 @@ test/unit_tests_d/ut_xmrseed: Monero mnemonic unit test for the MMGen suite
 """
 """
 
 
 from mmgen.common import *
 from mmgen.common import *
-from mmgen.exception import *
 
 
 class unit_test(object):
 class unit_test(object):