Browse Source

obj.py,eth/obj.py,protocol.py: exception handling cleanups

MMGen 5 years ago
parent
commit
338cb77199
4 changed files with 99 additions and 93 deletions
  1. 4 4
      mmgen/altcoins/eth/obj.py
  2. 75 86
      mmgen/obj.py
  3. 3 2
      mmgen/protocol.py
  4. 17 1
      test/objtest.py

+ 4 - 4
mmgen/altcoins/eth/obj.py

@@ -51,11 +51,11 @@ class ETHNonce(int,Hilite,InitErrors): # WIP
 		from mmgen.util import is_int
 		try:
 			assert is_int(n),"'{}': value is not an integer".format(n)
-			me = int.__new__(cls,n)
-			return me
+			n = int(n)
+			assert n >= 0,"'{}': value is negative".format(n)
+			return int.__new__(cls,n)
 		except Exception as e:
-			m = "{!r}: value cannot be converted to ETH nonce ({})"
-			return cls.init_fail(m.format(n,e.args[0]),on_fail)
+			return cls.init_fail(e,n)
 
 	@classmethod
 	def colorize(cls,s,color=True):

+ 75 - 86
mmgen/obj.py

@@ -116,21 +116,34 @@ class AddrListList(list,MMGenObject): pass
 
 class InitErrors(object):
 
+	on_fail='die'
+
 	@classmethod
 	def arg_chk(cls,on_fail):
+		cls.on_fail = on_fail
 		assert on_fail in ('die','return','silent','raise'),(
 			"'{}': invalid value for 'on_fail' in class {}".format(on_fail,cls.__name__) )
 
-	@staticmethod
-	def init_fail(m,on_fail):
+	@classmethod
+	def init_fail(cls,e,m,e2=None,m2=None,objname=None,preformat=False):
+
+		if preformat:
+			errmsg = m
+		else:
+			fs = "{!r}: value cannot be converted to {} {}({})"
+			e2_fmt = '({}) '.format(e2.args[0]) if e2 else ''
+			errmsg = fs.format(m,objname or cls.__name__,e2_fmt,e.args[0])
+
+		if m2: errmsg = '{!r}\n{}'.format(m2,errmsg)
+
 		from mmgen.globalvars import g
-		if g.traceback: on_fail == 'raise'
+		if g.traceback: cls.on_fail == 'raise'
 		from mmgen.util import die,msg
-		if   on_fail == 'silent': return None # TODO: return False instead?
-		elif on_fail == 'raise':  raise ValueError(m)
-		elif on_fail == 'die':    die(1,m)
-		elif on_fail == 'return':
-			if m: msg(m)
+		if   cls.on_fail == 'silent': return None # TODO: return False instead?
+		elif cls.on_fail == 'raise':  raise ValueError(errmsg)
+		elif cls.on_fail == 'die':    die(1,errmsg)
+		elif cls.on_fail == 'return':
+			if errmsg: msg(errmsg)
 			return None                       # TODO: here too?
 
 class Hilite(object):
@@ -189,6 +202,9 @@ class Hilite(object):
 	def __str__(self):
 		return self.colorize(self,color=False)
 
+class Str(str,Hilite): pass
+class Int(int,Hilite): pass
+
 # For attrs that are always present in the data instance
 # Reassignment and deletion forbidden
 class MMGenImmutableAttr(object): # Descriptor
@@ -284,8 +300,7 @@ class AddrIdx(int,InitErrors):
 			assert me > 0,'is less than one'
 			return me
 		except Exception as e:
-			m = "{!r}: value cannot be converted to address index ({})"
-			return cls.init_fail(m.format(num,e.args[0]),on_fail)
+			return cls.init_fail(e,num)
 
 class AddrIdxList(list,InitErrors,MMGenObject):
 	max_len = 1000000
@@ -314,8 +329,7 @@ class AddrIdxList(list,InitErrors,MMGenObject):
 					return list.__init__(self,sorted(set(ret))) # fell off end of loop - success
 				raise ValueError("{!r}: invalid range".format(i))
 		except Exception as e:
-			m = "{!r}: value cannot be converted to AddrIdxList ({})"
-			return type(self).init_fail(m.format(idx_list or fmt_str,e.args[0]),on_fail)
+			return type(self).init_fail(e,idx_list or fmt_str)
 
 class MMGenRange(tuple,InitErrors,MMGenObject):
 
@@ -343,8 +357,7 @@ class MMGenRange(tuple,InitErrors,MMGenObject):
 				assert last <= cls.max_idx, 'end of range > {:,}'.format(cls.max_idx)
 			return tuple.__new__(cls,(first,last))
 		except Exception as e:
-			m = "{!r}: value cannot be converted to {} ({})"
-			return cls.init_fail(m.format(s,cls.__name__,e.args[0]),on_fail)
+			return cls.init_fail(e,s)
 
 	@property
 	def first(self):
@@ -396,8 +409,7 @@ class BTCAmt(Decimal,Hilite,InitErrors):
 			assert me >= 0,'coin amount cannot be negative'
 			return me
 		except Exception as e:
-			m = "{!r}: value cannot be converted to {} ({})"
-			return cls.init_fail(m.format(num,cls.__name__,e.args[0]),on_fail)
+			return cls.init_fail(e,num)
 
 	def toSatoshi(self):
 		return int(Decimal(self) // self.satoshi)
@@ -479,8 +491,7 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject):
 			me.hex = va['hex']
 			return me
 		except Exception as e:
-			m = "{!r}: value cannot be converted to {} address ({})"
-			return cls.init_fail(m.format(s,g.proto.__name__,e.args[0]),on_fail)
+			return cls.init_fail(e,s,objname='{} address'.format(g.proto.__name__))
 
 	@classmethod
 	def fmtc(cls,s,**kwargs):
@@ -548,8 +559,7 @@ class SeedID(str,Hilite,InitErrors):
 				return str.__new__(cls,sid)
 			raise ValueError('no arguments provided')
 		except Exception as e:
-			m = "{!r}: value cannot be converted to SeedID ({})"
-			return cls.init_fail(m.format(seed or sid,e.args[0]),on_fail)
+			return cls.init_fail(e,seed or sid)
 
 class SubSeedIdx(str,Hilite,InitErrors):
 	color = 'red'
@@ -572,8 +582,7 @@ class SubSeedIdx(str,Hilite,InitErrors):
 			me.type = sstype
 			return me
 		except Exception as e:
-			m = "{!r}: value cannot be converted to {} ({})"
-			return cls.init_fail(m.format(s,cls.__name__,e.args[0]),on_fail)
+			return cls.init_fail(e,s)
 
 class MMGenID(str,Hilite,InitErrors,MMGenObject):
 	color = 'orange'
@@ -595,8 +604,7 @@ class MMGenID(str,Hilite,InitErrors,MMGenObject):
 			me.sort_key = '{}:{}:{:0{w}}'.format(me.sid,me.mmtype,me.idx,w=me.idx.max_digits)
 			return me
 		except Exception as e:
-			m = "{}\n{!r}: value cannot be converted to MMGenID"
-			return cls.init_fail(m.format(e.args[0],s),on_fail)
+			return cls.init_fail(e,s)
 
 class TwMMGenID(str,Hilite,InitErrors,MMGenObject):
 	color = 'orange'
@@ -617,9 +625,8 @@ class TwMMGenID(str,Hilite,InitErrors,MMGenObject):
 				assert set(s[4:]) <= set(ascii_letters+digits),'contains non-alphanumeric characters'
 				assert len(s) > 4,'not more that four characters long'
 				ret,sort_key,idtype = str(s),'z_'+s,'non-mmgen'
-			except Exception as f:
-				m = "{}\nValue is {}\n{!r}: value cannot be converted to TwMMGenID"
-				return cls.init_fail(m.format(e.args[0],f.args[0],s),on_fail)
+			except Exception as e2:
+				return cls.init_fail(e,s,e2=e2)
 
 		me = str.__new__(cls,ret)
 		me.obj = ret
@@ -641,48 +648,33 @@ class TwLabel(str,InitErrors,MMGenObject):
 			me.comment = comment
 			return me
 		except Exception as e:
-			m = "{}\n{!r}: value cannot be converted to TwLabel"
-			return cls.init_fail(m.format(e.args[0],s),on_fail)
+			return cls.init_fail(e,s)
 
 class HexStr(str,Hilite,InitErrors):
 	color = 'red'
+	width = None
+	hexcase = 'lower'
 	trunc_ok = False
 	dtype = str
-	def __new__(cls,s,on_fail='die',case='lower'):
+	def __new__(cls,s,on_fail='die',case=None):
 		if type(s) == cls: return s
-		assert case in ('upper','lower')
 		cls.arg_chk(on_fail)
+		if case == None: case = cls.hexcase
 		try:
 			assert issubclass(type(s),str),'not a string or string subclass'
+			assert case in ('upper','lower'),"'{}' incorrect case specifier".format(case)
 			assert set(s) <= set(getattr(hexdigits,case)()),'not {}case hexadecimal symbols'.format(case)
 			assert not len(s) % 2,'odd-length string'
+			if cls.width:
+				assert len(s) == cls.width,'Value is not {} characters wide'.format(cls.width)
 			return cls.dtype.__new__(cls,s)
 		except Exception as e:
-			m = "{!r}: value cannot be converted to {} (value is {})"
-			return cls.init_fail(m.format(s,cls.__name__,e.args[0]),on_fail)
+			return cls.init_fail(e,s)
 
-class Str(str,Hilite): pass
-class Int(int,Hilite): pass
-
-class HexStrWithWidth(HexStr):
-	color = 'nocolor'
-	hexcase = 'lower'
-	width = None
-	parent_cls = HexStr
-	def __new__(cls,s,on_fail='die'):
-		cls.arg_chk(on_fail)
-		try:
-			ret = cls.parent_cls.__new__(cls,s,case=cls.hexcase,on_fail='raise')
-			assert len(s) == cls.width,'Value is not {} characters wide'.format(cls.width)
-			return ret
-		except Exception as e:
-			m = "{}\n{!r}: value cannot be converted to {}"
-			return cls.init_fail(m.format(e.args[0],s,cls.__name__),on_fail)
-
-class CoinTxID(HexStrWithWidth):       color,width,hexcase = 'purple',64,'lower'
-class WalletPassword(HexStrWithWidth): color,width,hexcase = 'blue',32,'lower'
-class MoneroViewKey(HexStrWithWidth):  color,width,hexcase = 'cyan',64,'lower'
-class MMGenTxID(HexStrWithWidth):      color,width,hexcase = 'red',6,'upper'
+class CoinTxID(HexStr):       color,width,hexcase = 'purple',64,'lower'
+class WalletPassword(HexStr): color,width,hexcase = 'blue',32,'lower'
+class MoneroViewKey(HexStr):  color,width,hexcase = 'cyan',64,'lower'
+class MMGenTxID(HexStr):      color,width,hexcase = 'red',6,'upper'
 
 class WifKey(str,Hilite,InitErrors):
 	width = 53
@@ -696,19 +688,18 @@ class WifKey(str,Hilite,InitErrors):
 			g.proto.wif2hex(s) # raises exception on error
 			return str.__new__(cls,s)
 		except Exception as e:
-			m = '{!r}: invalid value for WIF key ({})'.format(s,e.args[0])
-			return cls.init_fail(m,on_fail)
+			return cls.init_fail(e,s)
 
 class PubKey(HexStr,MMGenObject): # TODO: add some real checks
 	def __new__(cls,s,compressed,on_fail='die'):
 		try:
 			assert type(compressed) == bool,"'compressed' must be of type bool"
-			me = HexStr.__new__(cls,s,case='lower',on_fail='raise')
+		except Exception as e:
+			return cls.init_fail(e,s)
+		me = HexStr.__new__(cls,s,case='lower',on_fail=on_fail)
+		if me:
 			me.compressed = compressed
 			return me
-		except Exception as e:
-			m = '{!r}: invalid value for pubkey ({})'.format(s,e.args[0])
-			return cls.init_fail(m,on_fail)
 
 class PrivKey(str,Hilite,InitErrors,MMGenObject):
 
@@ -728,7 +719,7 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
 
 		if wif:
 			try:
-				assert s == None
+				assert s == None,"'wif' and key hex args are mutually exclusive"
 				assert set(wif) <= set(ascii_letters+digits),'not an ascii alphanumeric string'
 				w2h = g.proto.wif2hex(wif) # raises exception on error
 				me = str.__new__(cls,w2h['hex'])
@@ -738,25 +729,25 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject):
 				me.orig_hex = None
 				return me
 			except Exception as e:
-				fs = "Value {!r} cannot be converted to {} WIF key ({})"
-				return cls.init_fail(fs.format(wif,g.coin,e.args[0]),on_fail)
-
-		try:
-			assert s and type(compressed) == bool and pubkey_type,'Incorrect args for PrivKey()'
-			assert len(s) == cls.width // 2,'Key length must be {}'.format(cls.width//2)
-			if pubkey_type == 'password': # skip WIF creation and pre-processing for passwds
-				me = str.__new__(cls,s.hex())
-			else:
-				me = str.__new__(cls,g.proto.preprocess_key(s.hex(),pubkey_type))
-				me.wif = WifKey(g.proto.hex2wif(me,pubkey_type,compressed),on_fail='raise')
-			me.compressed = compressed
-			me.pubkey_type = pubkey_type
-			me.orig_hex = s.hex() # save the non-preprocessed key
-			return me
-		except Exception as e:
-			fs = "Key={!r}\nCompressed={}\nValue pair cannot be converted to PrivKey\n({})"
-			return cls.init_fail(fs.format(s,compressed,e.args[0]),on_fail)
-
+				return cls.init_fail(e,s,objname='{} WIF key'.format(g.coin))
+		else:
+			try:
+				assert s,'private key hex data missing'
+				assert compressed is not None, "'compressed' arg missing"
+				assert pubkey_type is not None,"'pubkey_type' arg missing"
+				assert type(compressed) == bool,"{!r}: 'compressed' not of type 'bool'".format(compressed)
+				assert len(s) == cls.width // 2,'key length must be {}'.format(cls.width // 2)
+				if pubkey_type == 'password': # skip WIF creation and pre-processing for passwds
+					me = str.__new__(cls,s.hex())
+				else:
+					me = str.__new__(cls,g.proto.preprocess_key(s.hex(),pubkey_type))
+					me.wif = WifKey(g.proto.hex2wif(me,pubkey_type,compressed),on_fail='raise')
+				me.compressed = compressed
+				me.pubkey_type = pubkey_type
+				me.orig_hex = s.hex() # save the non-preprocessed key
+				return me
+			except Exception as e:
+				return cls.init_fail(e,s)
 
 class AddrListID(str,Hilite,InitErrors,MMGenObject):
 	width = 10
@@ -773,8 +764,7 @@ class AddrListID(str,Hilite,InitErrors,MMGenObject):
 			me.mmtype = mmtype
 			return me
 		except Exception as e:
-			m = "Cannot create AddrListID ({})".format(e.args[0])
-			return cls.init_fail(m,on_fail)
+			return cls.init_fail(e,'sid={}, mmtype={}'.format(sid,mmtype))
 
 class MMGenLabel(str,Hilite,InitErrors):
 	color = 'pink'
@@ -808,8 +798,7 @@ class MMGenLabel(str,Hilite,InitErrors):
 				"contains one of these forbidden symbols: '{}'".format("', '".join(cls.forbidden))
 			return str.__new__(cls,s)
 		except Exception as e:
-			m = "{!r}: value cannot be converted to {} ({})"
-			return cls.init_fail(m.format(s,cls.__name__,e.args[0]),on_fail)
+			return cls.init_fail(e,s)
 
 class MMGenWalletLabel(MMGenLabel):
 	max_len = 48
@@ -902,7 +891,7 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject):
 		except Exception as e:
 			m = '{}{!r}: invalid value for {} ({})'.format(
 				('{!r}\n'.format(errmsg) if errmsg else ''),s,cls.__name__,e.args[0])
-			return cls.init_fail(m,on_fail)
+			return cls.init_fail(e,m,preformat=True)
 
 	@classmethod
 	def get_names(cls):

+ 3 - 2
mmgen/protocol.py

@@ -56,8 +56,8 @@ def _b58chk_decode(s):
 				sum(_b58a.index(ch) * 58**n for n,ch in enumerate(s[::-1])) )
 	if len(hexstr) % 2: hexstr = '0' + hexstr
 	if hexstr[-8:] != hash256(hexstr[:-8])[:8]:
-		raise ValueError('_b58chk_decode(): {}: incorrect checksum for {}, expected {}'.format(
-							hexstr[-8:],hexstr[:-8],hash256(hexstr[:-8])[:8]))
+		fs = '_b58chk_decode(): {}: incorrect checksum for {!r}, expected {}'
+		raise ValueError(fs.format(hexstr[-8:],hexstr[:-8],hash256(hexstr[:-8])[:8]))
 	return hexstr[:-8]
 
 # chainparams.cpp
@@ -125,6 +125,7 @@ class BitcoinProtocol(MMGenObject):
 	@classmethod
 	def hex2wif(cls,hexpriv,pubkey_type,compressed): # PrivKey
 		assert len(hexpriv) == cls.privkey_len*2, '{} bytes: incorrect private key length!'.format(len(hexpriv)//2)
+		assert pubkey_type in cls.wif_ver_num, '{!r}: invalid pubkey_type'.format(pubkey_type)
 		return _b58chk_encode(cls.wif_ver_num[pubkey_type] + hexpriv + ('','01')[bool(compressed)])
 
 	@classmethod

+ 17 - 1
test/objtest.py

@@ -28,6 +28,7 @@ sys.path.__setitem__(0,os.path.abspath(os.curdir))
 # Import these _after_ local path's been added to sys.path
 from mmgen.common import *
 from mmgen.obj import *
+from mmgen.altcoins.eth.obj import *
 from mmgen.seed import *
 
 opts_data = {
@@ -203,7 +204,18 @@ tests = OrderedDict([
 		'good': ({'arg':'deadbeef','compressed':True},) # TODO: add real pubkeys
 	}),
 	('PrivKey', {
-		'bad':  ({'wif':1},),
+		'bad': ( # TODO: add LTC, testnet
+			{'wif':1},
+			{'wif':'1'},
+			{'wif':'cMsqcmDYZP1LdKgqRh9L4ZRU9br28yvdmTPwW2YQwVSN9aQiMAoR'},
+			{'s':r32,'wif':'5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb'},
+			{'pubkey_type':'std'},
+			{'s':r32},
+			{'s':r32,'compressed':'yes'},
+			{'s':r32,'compressed':'yes','pubkey_type':'std'},
+			{'s':r32,'compressed':True,'pubkey_type':'nonstd'},
+			{'s':r32+b'x','compressed':True,'pubkey_type':'std'}
+		),
 		'good': ({
 			'btc': (({'wif':'5KXEpVzjWreTcQoG5hX357s1969MUKNLuSfcszF6yu84kpsNZKb',
 					'ret':'e0aef965b905a2fedf907151df8e0a6bac832aa697801c51f58bd2ecb4fd381c'},
@@ -269,6 +281,10 @@ tests = OrderedDict([
 		{'s':'password','ret':'P'},
 		{'s':'P','ret':'P'},
 	)}),
+	('ETHNonce', {
+		'bad': ('U','z','я',-1),
+		'good':  (('0',0),('1',1),('100',100),1,100)
+	}),
 ])
 
 def do_loop():