diff --git a/mmgen/addr.py b/mmgen/addr.py index ae76d698..0e5f6ac9 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -1159,7 +1159,7 @@ class TwAddrData(AddrData,metaclass=aInitMeta): twd = await self.get_tw_data(wallet) out,i = {},0 for acct,addr_array in twd: - l = TwLabel(self.proto,acct,on_fail='silent') + l = get_obj(TwLabel,proto=self.proto,text=acct,silent=True) if l and l.mmid.type == 'mmgen': obj = l.mmid.obj if len(addr_array) != 1: diff --git a/mmgen/altcoins/eth/tw.py b/mmgen/altcoins/eth/tw.py index 7e05451f..8b21ee6d 100755 --- a/mmgen/altcoins/eth/tw.py +++ b/mmgen/altcoins/eth/tw.py @@ -257,7 +257,7 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, if self.addrs: wl = [d for d in wl if d['addr'] in self.addrs] return [{ - 'account': TwLabel(self.proto,d['mmid']+' '+d['comment'],on_fail='raise'), + 'account': TwLabel(self.proto,d['mmid']+' '+d['comment']), 'address': d['addr'], 'amount': await self.wallet.get_balance(d['addr']), 'confirmations': 0, # TODO @@ -298,7 +298,7 @@ class EthereumTwAddrList(TwAddrList): from mmgen.obj import CoinAddr for mmid,d in list(tw_dict.items()): # if d['confirmations'] < minconf: continue # cannot get confirmations for eth account - label = TwLabel(self.proto,mmid+' '+d['comment'],on_fail='raise') + label = TwLabel(self.proto,mmid+' '+d['comment']) if usr_addr_list and (label.mmid not in usr_addr_list): continue bal = await self.wallet.get_balance(d['addr']) diff --git a/mmgen/exception.py b/mmgen/exception.py index 0d7d14cf..2dee1fa0 100755 --- a/mmgen/exception.py +++ b/mmgen/exception.py @@ -40,10 +40,11 @@ class InvalidTokenAddress(Exception): mmcode = 2 class UnrecognizedTokenSymbol(Exception): mmcode = 2 class TokenNotInBlockchain(Exception): mmcode = 2 class TokenNotInWallet(Exception): mmcode = 2 -class BadTwComment(Exception): mmcode = 2 +class BadTwLabel(Exception): mmcode = 2 class BaseConversionError(Exception): mmcode = 2 class BaseConversionPadError(Exception): mmcode = 2 class TransactionChainMismatch(Exception):mmcode = 2 +class ObjectInitError(Exception): mmcode = 2 # 3: yellow hl, 'MMGen Error' + exception + message class RPCFailure(Exception): mmcode = 3 diff --git a/mmgen/main_split.py b/mmgen/main_split.py index a60ded14..d38d12c8 100755 --- a/mmgen/main_split.py +++ b/mmgen/main_split.py @@ -104,7 +104,7 @@ if len(cmd_args) != 2: from .obj import MMGenID try: - mmids = [MMGenID(a,on_fail='die') for a in cmd_args] + mmids = [MMGenID(a) for a in cmd_args] except: die(1,'Command line arguments must be valid MMGen IDs') diff --git a/mmgen/main_wallet.py b/mmgen/main_wallet.py index 24665243..d4be11e1 100755 --- a/mmgen/main_wallet.py +++ b/mmgen/main_wallet.py @@ -148,10 +148,10 @@ if invoked_as == 'subgen': from .obj import SubSeedIdx ss_idx = SubSeedIdx(cmd_args.pop()) elif invoked_as == 'seedsplit': - from .obj import SeedSplitSpecifier + from .obj import get_obj,SeedSplitSpecifier master_share = MasterShareIdx(opt.master_share) if opt.master_share else None if cmd_args: - sss = SeedSplitSpecifier(cmd_args.pop(),on_fail='silent') + sss = get_obj(SeedSplitSpecifier,s=cmd_args.pop(),silent=True) if master_share: if not sss: sss = SeedSplitSpecifier('1:2') diff --git a/mmgen/obj.py b/mmgen/obj.py index fab4c5f1..3430a69a 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -34,14 +34,42 @@ class aInitMeta(type): await instance.__ainit__(*args,**kwargs) return instance -def is_mmgen_seed_id(s): return SeedID(sid=s,on_fail='silent') -def is_mmgen_idx(s): return AddrIdx(s,on_fail='silent') -def is_addrlist_id(s): return AddrListID(s,on_fail='silent') -def is_seed_split_specifier(s): return SeedSplitSpecifier(s,on_fail='silent') +def get_obj(objname,*args,**kwargs): + """ + Wrapper for data objects + - If the object throws an exception on instantiation, return False, otherwise return the object. + - If silent is True, suppress display of the exception. + - If return_bool is True, return True instead of the object. + Only keyword args are accepted. + """ + assert args == (), 'get_obj_chk1' -def is_mmgen_id(proto,s): return MMGenID(proto,s,on_fail='silent') -def is_coin_addr(proto,s): return CoinAddr(proto,s,on_fail='silent') -def is_wif(proto,s): return WifKey(proto,s,on_fail='silent') + silent,return_bool = (False,False) + if 'silent' in kwargs: + silent = kwargs['silent'] + del kwargs['silent'] + if 'return_bool' in kwargs: + return_bool = kwargs['return_bool'] + del kwargs['return_bool'] + + try: + ret = objname(**kwargs) + except Exception as e: + if not silent: + from .util import msg + msg(f'{e!s}') + return False + else: + return True if return_bool else ret + +def is_mmgen_seed_id(s): return get_obj(SeedID, sid=s, silent=True,return_bool=True) +def is_mmgen_idx(s): return get_obj(AddrIdx, n=s, silent=True,return_bool=True) +def is_addrlist_id(s): return get_obj(AddrListID, sid=s, silent=True,return_bool=True) +def is_seed_split_specifier(s): return get_obj(SeedSplitSpecifier, s=s, silent=True,return_bool=True) + +def is_mmgen_id(proto,s): return get_obj(MMGenID, proto=proto, id_str=s, silent=True,return_bool=True) +def is_coin_addr(proto,s): return get_obj(CoinAddr, proto=proto, addr=s, silent=True,return_bool=True) +def is_wif(proto,s): return get_obj(WifKey, proto=proto, wif=s, silent=True,return_bool=True) def truncate_str(s,width): # width = screen width wide_count = 0 @@ -88,15 +116,7 @@ class MMGenList(list,MMGenObject): pass class MMGenDict(dict,MMGenObject): pass class AddrListData(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__) ) +class InitErrors: @classmethod def init_fail(cls,e,m,e2=None,m2=None,objname=None,preformat=False): @@ -104,27 +124,19 @@ class InitErrors(object): 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]) + errmsg = '{!r}: value cannot be converted to {} {}({!s})'.format( + m, + (objname or cls.__name__), + (f'({e2!s}) ' if e2 else ''), + e ) if m2: - errmsg = '{!r}\n{}'.format(m2,errmsg) + errmsg = repr(m2) + '\n' + errmsg - from .util import die,msg - if cls.on_fail == 'silent': - return None # TODO: return False instead? - elif cls.on_fail == 'return': - if errmsg: - msg(errmsg) - return None # TODO: return False instead? - elif g.traceback or cls.on_fail == 'raise': - if hasattr(cls,'exc'): - raise cls.exc(errmsg) - else: - raise - elif cls.on_fail == 'die': - die(1,errmsg) + if hasattr(cls,'exc'): + raise cls.exc(errmsg) + else: + raise ObjectInitError(errmsg) @classmethod def method_not_implemented(cls): @@ -201,10 +213,9 @@ class Int(int,Hilite,InitErrors): max_digits = None color = 'red' - def __new__(cls,n,base=10,on_fail='raise'): + def __new__(cls,n,base=10): if type(n) == cls: return n - cls.arg_chk(on_fail) try: me = int.__new__(cls,str(n),base) if cls.min_val != None: @@ -246,9 +257,9 @@ class ImmutableAttr: # Descriptor "convert this attribute's type" if type(dtype) == str: if include_proto: - self.conv = lambda instance,value: globals()[dtype](instance.proto,value,on_fail='raise') + self.conv = lambda instance,value: globals()[dtype](instance.proto,value) else: - self.conv = lambda instance,value: globals()[dtype](value,on_fail='raise') + self.conv = lambda instance,value: globals()[dtype](value) else: if set_none_ok: self.conv = lambda instance,value: None if value is None else dtype(value) @@ -359,28 +370,27 @@ class AddrIdx(MMGenIdx): max_digits = 7 class AddrIdxList(list,InitErrors,MMGenObject): max_len = 1000000 - def __init__(self,fmt_str=None,idx_list=None,on_fail='die',sep=','): - type(self).arg_chk(on_fail) + def __init__(self,fmt_str=None,idx_list=None,sep=','): try: if idx_list: - return list.__init__(self,sorted({AddrIdx(i,on_fail='raise') for i in idx_list})) + return list.__init__(self,sorted({AddrIdx(i) for i in idx_list})) elif fmt_str: ret = [] for i in (fmt_str.split(sep)): j = i.split('-') if len(j) == 1: - idx = AddrIdx(i,on_fail='raise') + idx = AddrIdx(i) if not idx: break ret.append(idx) elif len(j) == 2: - beg = AddrIdx(j[0],on_fail='raise') + beg = AddrIdx(j[0]) if not beg: break - end = AddrIdx(j[1],on_fail='raise') + end = AddrIdx(j[1]) if not beg or (end < beg): break - ret.extend([AddrIdx(x,on_fail='raise') for x in range(beg,end+1)]) + ret.extend([AddrIdx(x) for x in range(beg,end+1)]) else: break else: return list.__init__(self,sorted(set(ret))) # fell off end of loop - success @@ -393,8 +403,7 @@ class MMGenRange(tuple,InitErrors,MMGenObject): min_idx = None max_idx = None - def __new__(cls,*args,on_fail='die'): - cls.arg_chk(on_fail) + def __new__(cls,*args): try: if len(args) == 1: s = args[0] @@ -449,10 +458,9 @@ class BTCAmt(Decimal,Hilite,InitErrors): forbidden_types = (float,int) # NB: 'from_decimal' rounds down to precision of 'min_coin_unit' - def __new__(cls,num,from_unit=None,from_decimal=False,on_fail='die'): + def __new__(cls,num,from_unit=None,from_decimal=False): if type(num) == cls: return num - cls.arg_chk(on_fail) try: if from_unit: assert from_unit in cls.units,( @@ -546,10 +554,9 @@ class CoinAddr(str,Hilite,InitErrors,MMGenObject): hex_width = 40 width = 1 trunc_ok = False - def __new__(cls,proto,addr,on_fail='die'): + def __new__(cls,proto,addr): if type(addr) == cls: return addr - cls.arg_chk(on_fail) try: assert set(addr) <= set(ascii_letters+digits),'contains non-alphanumeric characters' me = str.__new__(cls,addr) @@ -571,11 +578,11 @@ class TokenAddr(CoinAddr): color = 'blue' class ViewKey(object): - def __new__(cls,proto,viewkey,on_fail='die'): + def __new__(cls,proto,viewkey): if proto.name == 'Zcash': - return ZcashViewKey.__new__(ZcashViewKey,proto,viewkey,on_fail) + return ZcashViewKey.__new__(ZcashViewKey,proto,viewkey) elif proto.name == 'Monero': - return MoneroViewKey.__new__(MoneroViewKey,viewkey,on_fail) + return MoneroViewKey.__new__(MoneroViewKey,viewkey) else: raise ValueError(f'{proto.name}: protocol does not support view keys') @@ -585,10 +592,9 @@ class SeedID(str,Hilite,InitErrors): color = 'blue' width = 8 trunc_ok = False - def __new__(cls,seed=None,sid=None,on_fail='die'): + def __new__(cls,seed=None,sid=None): if type(sid) == cls: return sid - cls.arg_chk(on_fail) try: if seed: from .seed import SeedBase @@ -606,10 +612,9 @@ class SeedID(str,Hilite,InitErrors): class SubSeedIdx(str,Hilite,InitErrors): color = 'red' trunc_ok = False - def __new__(cls,s,on_fail='die'): + def __new__(cls,s): if type(s) == cls: return s - cls.arg_chk(on_fail) try: assert isinstance(s,str),'not a string or string subclass' idx = s[:-1] if s[-1] in 'SsLl' else s @@ -631,15 +636,14 @@ class MMGenID(str,Hilite,InitErrors,MMGenObject): color = 'orange' width = 0 trunc_ok = False - def __new__(cls,proto,id_str,on_fail='die'): - cls.arg_chk(on_fail) + def __new__(cls,proto,id_str): try: ss = str(id_str).split(':') assert len(ss) in (2,3),'not 2 or 3 colon-separated items' - t = proto.addr_type((ss[1],proto.dfl_mmtype)[len(ss)==2],on_fail='raise') + t = proto.addr_type((ss[1],proto.dfl_mmtype)[len(ss)==2]) me = str.__new__(cls,'{}:{}:{}'.format(ss[0],t,ss[-1])) - me.sid = SeedID(sid=ss[0],on_fail='raise') - me.idx = AddrIdx(ss[-1],on_fail='raise') + me.sid = SeedID(sid=ss[0]) + me.idx = AddrIdx(ss[-1]) me.mmtype = t assert t in proto.mmtypes, f'{t}: invalid address type for {proto.cls_name}' me.al_id = str.__new__(AddrListID,me.sid+':'+me.mmtype) # checks already done @@ -653,13 +657,12 @@ class TwMMGenID(str,Hilite,InitErrors,MMGenObject): color = 'orange' width = 0 trunc_ok = False - def __new__(cls,proto,id_str,on_fail='die'): + def __new__(cls,proto,id_str): if type(id_str) == cls: return id_str - cls.arg_chk(on_fail) ret = None try: - ret = MMGenID(proto,id_str,on_fail='raise') + ret = MMGenID(proto,id_str) sort_key,idtype = ret.sort_key,'mmgen' except Exception as e: try: @@ -680,14 +683,14 @@ class TwMMGenID(str,Hilite,InitErrors,MMGenObject): # non-displaying container for TwMMGenID,TwComment class TwLabel(str,InitErrors,MMGenObject): - def __new__(cls,proto,text,on_fail='die'): + exc = BadTwLabel + def __new__(cls,proto,text): if type(text) == cls: return text - cls.arg_chk(on_fail) try: ts = text.split(None,1) - mmid = TwMMGenID(proto,ts[0],on_fail='raise') - comment = TwComment(ts[1] if len(ts) == 2 else '',on_fail='raise') + mmid = TwMMGenID(proto,ts[0]) + comment = TwComment(ts[1] if len(ts) == 2 else '') me = str.__new__( cls, mmid + (' ' + comment if comment else '') ) me.mmid = mmid me.comment = comment @@ -701,10 +704,9 @@ class HexStr(str,Hilite,InitErrors): width = None hexcase = 'lower' trunc_ok = False - def __new__(cls,s,on_fail='die',case=None): + def __new__(cls,s,case=None): if type(s) == cls: return s - cls.arg_chk(on_fail) if case == None: case = cls.hexcase try: @@ -730,10 +732,9 @@ class WifKey(str,Hilite,InitErrors): """ width = 53 color = 'blue' - def __new__(cls,proto,wif,on_fail='die'): + def __new__(cls,proto,wif): if type(wif) == cls: return wif - cls.arg_chk(on_fail) try: assert set(wif) <= set(ascii_letters+digits),'not an ascii alphanumeric string' proto.parse_wif(wif) # raises exception on error @@ -742,12 +743,12 @@ class WifKey(str,Hilite,InitErrors): return cls.init_fail(e,wif) class PubKey(HexStr,MMGenObject): # TODO: add some real checks - def __new__(cls,s,compressed,on_fail='die'): + def __new__(cls,s,compressed): try: assert type(compressed) == bool,"'compressed' must be of type bool" except Exception as e: return cls.init_fail(e,s) - me = HexStr.__new__(cls,s,case='lower',on_fail=on_fail) + me = HexStr.__new__(cls,s,case='lower') if me: me.compressed = compressed return me @@ -767,12 +768,9 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject): wif = ImmutableAttr(WifKey,typeconv=False) # initialize with (priv_bin,compressed), WIF or self - def __new__(cls,proto,s=None,compressed=None,wif=None,pubkey_type=None,on_fail='die'): - + def __new__(cls,proto,s=None,compressed=None,wif=None,pubkey_type=None): if type(s) == cls: return s - cls.arg_chk(on_fail) - if wif: try: assert s == None,"'wif' and key hex args are mutually exclusive" @@ -801,7 +799,7 @@ class PrivKey(str,Hilite,InitErrors,MMGenObject): assert compressed is not None, "'compressed' arg missing" assert type(compressed) == bool,"{!r}: 'compressed' not of type 'bool'".format(compressed) me = str.__new__(cls,proto.preprocess_key(s,pubkey_type).hex()) - me.wif = WifKey(proto,proto.hex2wif(me,pubkey_type,compressed),on_fail='raise') + me.wif = WifKey(proto,proto.hex2wif(me,pubkey_type,compressed)) me.compressed = compressed me.pubkey_type = pubkey_type me.orig_hex = s.hex() # save the non-preprocessed key @@ -814,8 +812,7 @@ class AddrListID(str,Hilite,InitErrors,MMGenObject): width = 10 trunc_ok = False color = 'yellow' - def __new__(cls,sid,mmtype,on_fail='die'): - cls.arg_chk(on_fail) + def __new__(cls,sid,mmtype): try: assert type(sid) == SeedID,"{!r} not a SeedID instance".format(sid) if not isinstance(mmtype,(MMGenAddrType,MMGenPasswordType)): @@ -835,10 +832,9 @@ class MMGenLabel(str,Hilite,InitErrors): min_len = 0 max_screen_width = 0 # if != 0, overrides max_len desc = 'label' - def __new__(cls,s,on_fail='die',msg=None): + def __new__(cls,s,msg=None): if type(s) == cls: return s - cls.arg_chk(on_fail) for k in cls.forbidden,cls.allowed: assert type(k) == list for ch in k: assert type(ch) == str and len(ch) == 1 @@ -874,7 +870,6 @@ class MMGenWalletLabel(MMGenLabel): class TwComment(MMGenLabel): max_screen_width = 80 desc = 'tracking wallet comment' - exc = BadTwComment class MMGenTxLabel(MMGenLabel): max_len = 72 @@ -889,18 +884,17 @@ class MMGenPWIDString(MMGenLabel): class SeedSplitSpecifier(str,Hilite,InitErrors,MMGenObject): color = 'red' - def __new__(cls,s,on_fail='raise'): + def __new__(cls,s): if type(s) == cls: return s - cls.arg_chk(on_fail) try: arr = s.split(':') assert len(arr) in (2,3), 'cannot be parsed' a,b,c = arr if len(arr) == 3 else ['default'] + arr me = str.__new__(cls,s) - me.id = SeedSplitIDString(a,on_fail=on_fail) - me.idx = SeedShareIdx(b,on_fail=on_fail) - me.count = SeedShareCount(c,on_fail=on_fail) + me.id = SeedSplitIDString(a) + me.idx = SeedShareIdx(b) + me.count = SeedShareCount(c) assert me.idx <= me.count, 'share index greater than share count' return me except Exception as e: @@ -936,10 +930,9 @@ class MMGenAddrType(str,Hilite,InitErrors,MMGenObject): 'Z': ati('zcash_z','zcash_z',False,'zcash_z', 'zcash_z', 'wif', ('viewkey',), 'Zcash z-address'), 'M': ati('monero', 'monero', False,'monero', 'monero', 'spendkey',('viewkey','wallet_passwd'),'Monero address'), } - def __new__(cls,proto,id_str,on_fail='die',errmsg=None): + def __new__(cls,proto,id_str,errmsg=None): if type(id_str) == cls: return id_str - cls.arg_chk(on_fail) try: for k,v in cls.mmtypes.items(): if id_str in (k,v.name): diff --git a/mmgen/protocol.py b/mmgen/protocol.py index e361de9d..3083b2a1 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -168,11 +168,11 @@ class CoinProtocol(MMGenObject): def coin_addr(self,addr): return CoinAddr( proto=self, addr=addr ) - def addr_type(self,id_str,on_fail='die'): - return MMGenAddrType(proto=self,id_str=id_str,on_fail=on_fail) + def addr_type(self,id_str): + return MMGenAddrType( proto=self, id_str=id_str ) - def priv_key(self,s,on_fail='die'): - return PrivKey(proto=self,s=s,on_fail=on_fail) + def priv_key(self,s): + return PrivKey( proto=self, s=s ) class Secp256k1(Base): """ diff --git a/mmgen/tw.py b/mmgen/tw.py index c06a84b5..e839da6c 100755 --- a/mmgen/tw.py +++ b/mmgen/tw.py @@ -33,8 +33,8 @@ def CUR_RIGHT(n): return '\033[{}C'.format(n) def get_tw_label(proto,s): try: - return TwLabel(proto,s,on_fail='raise') - except BadTwComment: + return TwLabel(proto,s) + except BadTwLabel: raise except: return None @@ -410,7 +410,7 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, add [l]abel: while True: ret = my_raw_input(f'Enter {self.item_desc} number (or RETURN to return to main menu): ') if ret == '': return (None,None) if action == 'a_lbl_add' else None - n = AddrIdx(ret,on_fail='silent') + n = get_obj(AddrIdx,n=ret,silent=True) if not n or n < 1 or n > len(self.unspent): msg(f'Choice must be a single number between 1 and {len(self.unspent)}') else: @@ -424,7 +424,7 @@ Actions: [q]uit view, [p]rint to file, pager [v]iew, [w]ide view, add [l]abel: f'Removing label for {self.item_desc} #{n}. Is this what you want?'): return n,s elif s: - if TwComment(s,on_fail='return'): + if get_obj(TwComment,s=s): return n,s else: if action == 'a_addr_delete': @@ -801,8 +801,8 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta): def conv_types(self,ad): for k,v in ad.items(): if k not in ('params','coin'): - v['mmid'] = TwMMGenID(self.proto,v['mmid'],on_fail='raise') - v['comment'] = TwComment(v['comment'],on_fail='raise') + v['mmid'] = TwMMGenID(self.proto,v['mmid']) + v['comment'] = TwComment(v['comment']) @property def data_root(self): @@ -918,9 +918,10 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta): # returns on failure @write_mode async def add_label(self,arg1,label='',addr=None,silent=False,on_fail='return'): + assert on_fail in ('return','raise'), 'add_label_chk1' mmaddr,coinaddr = None,None if is_coin_addr(self.proto,addr or arg1): - coinaddr = CoinAddr(self.proto,addr or arg1,on_fail='return') + coinaddr = get_obj(CoinAddr,proto=self.proto,addr=addr or arg1) if is_mmgen_id(self.proto,arg1): mmaddr = TwMMGenID(self.proto,arg1) @@ -948,11 +949,14 @@ class TrackingWallet(MMGenObject,metaclass=aInitMeta): mmaddr = TwMMGenID(self.proto,mmaddr) - cmt = TwComment(label,on_fail=on_fail) + cmt = TwComment(label) if on_fail=='raise' else get_obj(TwComment,s=label) if cmt in (False,None): return False - lbl = TwLabel(self.proto,mmaddr + ('',' '+cmt)[bool(cmt)],on_fail=on_fail) + lbl_txt = mmaddr + (' ' + cmt if cmt else '') + lbl = ( + TwLabel(self.proto,lbl_txt) if on_fail == 'raise' else + get_obj(TwLabel,proto=self.proto,text=lbl_txt) ) if await self.set_label(coinaddr,lbl) == False: if not silent: diff --git a/mmgen/tx.py b/mmgen/tx.py index 8dce9a57..ebc06644 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -616,8 +616,9 @@ class MMGenTX: # given tx size and absolute fee or fee spec, return absolute fee # relative fee is N+ def process_fee_spec(self,tx_fee,tx_size): - if self.proto.coin_amt(tx_fee,on_fail='silent'): - return self.proto.coin_amt(tx_fee) + fee = get_obj(self.proto.coin_amt,num=tx_fee,silent=True) + if fee: + return fee else: import re units = {u[0]:u for u in self.proto.coin_amt.units} @@ -733,7 +734,7 @@ class MMGenTX: while True: reply = my_raw_input(prompt).strip() if reply: - selected = AddrIdxList(fmt_str=','.join(reply.split()),on_fail='return') + selected = get_obj(AddrIdxList, fmt_str=','.join(reply.split()) ) if selected: if selected[-1] <= len(unspent): return selected @@ -1004,7 +1005,7 @@ class MMGenTX: return self.inputs[0].sequence == g.max_int - 2 def check_txfile_hex_data(self): - self.hex = HexStr(self.hex,on_fail='raise') + self.hex = HexStr(self.hex) def parse_txfile_hex_data(self): pass @@ -1242,7 +1243,7 @@ class MMGenTX: tx_decoded = await self.rpc.call('decoderawtransaction',ret['hex']) new.compare_size_and_estimated_size(tx_decoded) new.check_hex_tx_matches_mmgen_tx(dtx) - new.coin_txid = CoinTxID(dtx['txid'],on_fail='raise') + new.coin_txid = CoinTxID(dtx['txid']) if not new.coin_txid == tx_decoded['txid']: raise BadMMGenTxID('txid mismatch (after signing)') msg('OK') diff --git a/mmgen/txfile.py b/mmgen/txfile.py index 7cfb61fb..c14882b7 100755 --- a/mmgen/txfile.py +++ b/mmgen/txfile.py @@ -65,13 +65,13 @@ class MMGenTxFile: tx_data = tx_data.splitlines() assert len(tx_data) >= 5,'number of lines less than 5' assert len(tx_data[0]) == 6,'invalid length of first line' - self.chksum = HexStr(tx_data.pop(0),on_fail='raise') + self.chksum = HexStr(tx_data.pop(0)) assert self.chksum == make_chksum_6(' '.join(tx_data)),'file data does not match checksum' if len(tx_data) == 6: assert len(tx_data[-1]) == 64,'invalid coin TxID length' desc = f'coin TxID' - tx.coin_txid = CoinTxID(tx_data.pop(-1),on_fail='raise') + tx.coin_txid = CoinTxID(tx_data.pop(-1)) if len(tx_data) == 5: # rough check: allow for 4-byte utf8 characters + base58 (4 * 11 / 8 = 6 (rounded up)) @@ -83,7 +83,7 @@ class MMGenTxFile: comment = baseconv.tobytes(c,'b58').decode() assert comment != False,'invalid comment' desc = 'comment' - tx.label = MMGenTxLabel(comment,on_fail='raise') + tx.label = MMGenTxLabel(comment) desc = 'number of lines' # four required lines metadata,tx.hex,inputs_data,outputs_data = tx_data @@ -113,7 +113,7 @@ class MMGenTxFile: txid,send_amt,tx.timestamp,blockcount = metadata desc = 'TxID in metadata' - tx.txid = MMGenTxID(txid,on_fail='raise') + tx.txid = MMGenTxID(txid) desc = 'send amount in metadata' tx.send_amt = tx.proto.coin_amt(send_amt) desc = 'block count in metadata' diff --git a/mmgen/wallet.py b/mmgen/wallet.py index 4e6a2a36..f37a664a 100755 --- a/mmgen/wallet.py +++ b/mmgen/wallet.py @@ -724,7 +724,7 @@ class MMGenWallet(WalletEnc): msg_r(prompt) ret = my_raw_input('') if ret: - lbl = MMGenWalletLabel(ret,on_fail='return') + lbl = get_obj(MMGenWalletLabel,s=ret) if lbl: return lbl else: diff --git a/test/objtest.py b/test/objtest.py index 129da1c9..2e8e2887 100755 --- a/test/objtest.py +++ b/test/objtest.py @@ -41,6 +41,7 @@ opts_data = { 'options': """ -h, --help Print this help message --, --longhelp Print help message for long options (common options) +-g, --getobj Instantiate objects with get_obj() wrapper -q, --quiet Produce quieter output -s, --silent Silence output of tested objects -S, --super-silent Silence all output except for errors @@ -51,12 +52,11 @@ opts_data = { cmd_args = opts.init(opts_data) -def run_test(test,arg,input_data): +def run_test(test,arg,input_data,arg1,exc_name): arg_copy = arg - kwargs = {'on_fail':'silent'} if opt.silent else {'on_fail':'die'} + kwargs = {} ret_chk = arg ret_idx = None - exc_type = None if input_data == 'good' and type(arg) == tuple: arg,ret_chk = arg if type(arg) == dict: # pass one arg + kwargs to constructor @@ -76,28 +76,42 @@ def run_test(test,arg,input_data): ret_idx = arg['ret_idx'] del arg['ret_idx'] del arg_copy['ret_idx'] - if 'ExcType' in arg: - exc_type = arg['ExcType'] - del arg['ExcType'] - del arg_copy['ExcType'] kwargs.update(arg) elif type(arg) == tuple: args = arg else: args = [arg] + + if opt.getobj: + if args: + assert len(args) == 1, 'objtest_chk1: only one positional arg is allowed' + kwargs.update( { arg1: args[0] } ) + if opt.silent: + kwargs.update( { 'silent': True } ) + try: if not opt.super_silent: arg_disp = repr(arg_copy[0] if type(arg_copy) == tuple else arg_copy) msg_r((orange,green)[input_data=='good']('{:<22}'.format(arg_disp+':'))) cls = globals()[test] - ret = cls(*args,**kwargs) + + if opt.getobj: + ret = get_obj(globals()[test],**kwargs) + else: + ret = cls(*args,**kwargs) + bad_ret = list() if issubclass(cls,list) else None if isinstance(ret_chk,str): ret_chk = ret_chk.encode() if isinstance(ret,str): ret = ret.encode() - if (opt.silent and input_data=='bad' and ret!=bad_ret) or (not opt.silent and input_data=='bad'): - raise UserWarning("Non-'None' return value {} with bad input data".format(repr(ret))) + if opt.getobj: + if input_data == 'bad': + assert ret == False, 'non-False return on bad input data' + else: + if (opt.silent and input_data=='bad' and ret!=bad_ret) or (not opt.silent and input_data=='bad'): + raise UserWarning(f"Non-'None' return value {ret!r} with bad input data") + if opt.silent and input_data=='good' and ret==bad_ret: raise UserWarning("'None' returned with good input data") @@ -105,20 +119,31 @@ def run_test(test,arg,input_data): if ret_idx: ret_chk = arg[list(arg.keys())[ret_idx]].encode() if ret != ret_chk and repr(ret) != repr(ret_chk): - raise UserWarning("Return value ({!r}) doesn't match expected value ({!r})".format(ret,ret_chk)) - if not opt.super_silent: + raise UserWarning(f"Return value ({ret!r}) doesn't match expected value ({ret_chk!r})") + + if opt.super_silent: + return + + if opt.getobj and (not opt.silent and input_data == 'bad'): + pass + else: try: ret_disp = ret.decode() except: ret_disp = ret msg(f'==> {ret_disp!r}') if opt.verbose and issubclass(cls,MMGenObject): ret.pmsg() if hasattr(ret,'pmsg') else pmsg(ret) + except Exception as e: - if not type(e).__name__ == exc_type: + if not type(e).__name__ == exc_name: + msg(f'Incorrect exception: expected {exc_name} but got {type(e).__name__}') raise - if not opt.super_silent: - msg_r(' {}'.format(yellow(exc_type+':'))) - msg(e.args[0]) + if opt.super_silent: + pass + elif opt.silent: + msg(f'==> {exc_name}') + else: + msg( yellow(f' {exc_name}:') + str(e) ) except SystemExit as e: if input_data == 'good': raise ValueError('Error on good input data') @@ -137,10 +162,17 @@ def do_loop(): clr = None utests = cmd_args for test in test_data: + arg1 = test_data[test].get('arg1') if utests and test not in utests: continue nl = ('\n','')[bool(opt.super_silent) or clr == None] clr = (blue,nocolor)[bool(opt.super_silent)] - msg(clr('{}Testing {}'.format(nl,test))) + + if opt.getobj and arg1 is None: + msg(gray(f'{nl}Skipping {test}')) + continue + else: + msg(clr(f'{nl}Testing {test}')) + for k in ('bad','good'): if not opt.silent: msg(purple(capfirst(k)+' input:')) @@ -149,6 +181,8 @@ def do_loop(): test, arg, input_data = k, + arg1 = arg1, + exc_name = test_data[test].get('exc_name') or 'ObjectInitError', ) from mmgen.protocol import init_proto_from_opts diff --git a/test/objtest_py_d/ot_btc_mainnet.py b/test/objtest_py_d/ot_btc_mainnet.py index 0407781e..ee2a15a4 100755 --- a/test/objtest_py_d/ot_btc_mainnet.py +++ b/test/objtest_py_d/ot_btc_mainnet.py @@ -19,6 +19,7 @@ ssm = str(SeedShareCount.max_val) tests = { 'Int': { + 'arg1': 'n', 'bad': ('1L',0.0,'0.0','1.0',1.0,'s',1.1,'1.1'), 'good': ( ('0',0),('-1',-1),('7',7),-1,0,1,9999999, @@ -29,22 +30,27 @@ tests = { ) }, 'AddrIdx': { + 'arg1': 'n', 'bad': ('s',1.1,10000000,-1,0), 'good': (('7',7),(1,1),(9999999,9999999)) }, 'SeedShareIdx': { + 'arg1': 'n', 'bad': ('s',1.1,1025,-1,0), 'good': (('7',7),(1,1),(1024,1024)) }, 'SeedShareCount': { + 'arg1': 'n', 'bad': ('s',2.1,1025,-1,0,1), 'good': (('7',7),(2,2),(1024,1024)) }, 'MasterShareIdx': { + 'arg1': 'n', 'bad': ('s',1.1,1025,-1,0), 'good': (('7',7),(1,1),(1024,1024)) }, 'AddrIdxList': { + 'arg1': 'fmt_str', 'bad': ('x','5,9,1-2-3','8,-11','66,3-2'), 'good': ( ('3,2,2',[2,3]), @@ -63,6 +69,7 @@ tests = { ) }, 'BTCAmt': { + 'arg1': 'num', 'bad': ('-3.2','0.123456789',123,'123L','22000000',20999999.12345678, {'num':'1','from_decimal':True}, {'num':1,'from_decimal':True}, @@ -86,6 +93,7 @@ tests = { ) }, 'CoinAddr': { + 'arg1': 'addr', 'good': ( {'addr':'1MjjELEy6EJwk8fSNfpS8b5teFRo4X5fZr', 'proto':proto}, {'addr':'32GiSWo9zJQgkCmjAaLRrbPwXhKry2jHhj', 'proto':proto}, @@ -97,6 +105,7 @@ tests = { ), }, 'SeedID': { + 'arg1': 'sid', 'bad': ( {'sid':'я'}, {'sid':'F00F00'}, @@ -106,13 +115,19 @@ tests = { {'sid':'f00baa12'}, 'я',r32,'abc' ), - 'good': (({'sid':'F00BAA12'},'F00BAA12'),(Seed(r16),Seed(r16).sid)) + 'good': ( + {'sid':'F00BAA12'}, + {'seed': Seed(r16), 'ret': SeedID(seed=Seed(r16))}, + {'sid': Seed(r16).sid, 'ret': SeedID(seed=Seed(r16))} + ) }, 'SubSeedIdx': { + 'arg1': 's', 'bad': (33,'x','я','1x',200,'1ss','L','s','200LS','30ll','s100',str(SubSeedIdxRange.max_idx+1),'0'), 'good': (('1','1L'),('1s','1S'),'20S','30L',('300l','300L'),('200','200L'),str(SubSeedIdxRange.max_idx)+'S') }, 'MMGenID': { + 'arg1': 'id_str', 'bad': ( {'id_str':'x', 'proto':proto}, {'id_str':1, 'proto':proto}, @@ -129,6 +144,7 @@ tests = { ), }, 'TwMMGenID': { + 'arg1': 'id_str', 'bad': ( {'id_str':'x', 'proto':proto}, {'id_str':'я', 'proto':proto}, @@ -150,6 +166,8 @@ tests = { ), }, 'TwLabel': { + 'arg1': 'proto', + 'exc_name': 'BadTwLabel', 'bad': ( {'text':'x x', 'proto':proto}, {'text':'x я', 'proto':proto}, @@ -163,7 +181,7 @@ tests = { {'text':tw_pfx+' x', 'proto':proto}, {'text':tw_pfx+'я x', 'proto':proto}, {'text':utf8_ctrl[:40], 'proto':proto}, - {'text':'F00BAA12:S:1 '+ utf8_ctrl[:40], 'proto':proto, 'on_fail':'raise','ExcType':'BadTwComment'}, + {'text':'F00BAA12:S:1 '+ utf8_ctrl[:40], 'proto':proto, }, ), 'good': ( {'text':'F00BAA12:99 a comment', 'proto':proto, 'ret':'F00BAA12:L:99 a comment'}, @@ -174,14 +192,17 @@ tests = { ), }, 'MMGenTxID': { + 'arg1': 's', 'bad': (1,[],'\0','\1','я','g','gg','FF','f00','F00F0012'), 'good': ('DEADBE','F00BAA') }, 'CoinTxID':{ + 'arg1': 's', 'bad': (1,[],'\0','\1','я','g','gg','FF','f00','F00F0012',r16.hex(),r32.hex()+'ee'), 'good': (r32.hex(),) }, 'WifKey': { + 'arg1': 'proto', 'bad': ( {'proto':proto, 'wif':1}, {'proto':proto, 'wif':[]}, @@ -201,10 +222,12 @@ tests = { ) }, 'PubKey': { + 'arg1': 's', 'bad': ({'arg':1,'compressed':False},{'arg':'F00BAA12','compressed':False},), 'good': ({'arg':'deadbeef','compressed':True},) # TODO: add real pubkeys }, 'PrivKey': { + 'arg1': 'proto', 'bad': ( {'proto':proto, 'wif':1}, {'proto':proto, 'wif':'1'}, @@ -227,6 +250,7 @@ tests = { ) }, 'AddrListID': { # a rather pointless test, but do it anyway + 'arg1': 'sid', 'bad': ( {'sid':SeedID(sid='F00BAA12'),'mmtype':'Z','ret':'F00BAA12:Z'}, ), @@ -236,10 +260,12 @@ tests = { ) }, 'MMGenWalletLabel': { + 'arg1': 's', 'bad': (utf8_text[:49],utf8_combining[:48],utf8_ctrl[:48],gr_uc_w_ctrl), 'good': (utf8_text[:48],) }, 'TwComment': { + 'arg1': 's', 'bad': ( utf8_combining[:40], utf8_ctrl[:40], text_jp[:41], @@ -252,14 +278,17 @@ tests = { text_zh[:40] ) }, 'MMGenTxLabel':{ + 'arg1': 's', 'bad': (utf8_text[:73],utf8_combining[:72],utf8_ctrl[:72],gr_uc_w_ctrl), 'good': (utf8_text[:72],) }, 'MMGenPWIDString': { # forbidden = list(u' :/\\') + 'arg1': 's', 'bad': ('foo/','foo:','foo:\\'), 'good': ('qwerty@яяя',) }, 'MMGenAddrType': { + 'arg1': 'proto', 'bad': ( {'proto':proto, 'id_str':'U', 'ret':'L'}, {'proto':proto, 'id_str':'z', 'ret':'L'}, @@ -278,6 +307,7 @@ tests = { ) }, 'MMGenPasswordType': { + 'arg1': 'proto', 'bad': ( {'proto':proto, 'id_str':'U', 'ret':'L'}, {'proto':proto, 'id_str':'z', 'ret':'L'}, @@ -291,6 +321,7 @@ tests = { ) }, 'SeedSplitSpecifier': { + 'arg1': 's', 'bad': ('M','αβ:2',1,'0:1','1:1','2:1','3:2','1:2000','abc:0:2'), 'good': ( ('1:2','2:2','alice:2:2','αβ:2:2','1:'+ssm,ssm+':'+ssm) diff --git a/test/test-release.sh b/test/test-release.sh index bce62318..9387ef20 100755 --- a/test/test-release.sh +++ b/test/test-release.sh @@ -248,6 +248,7 @@ i_obj='Data object' s_obj='Testing data objects' t_obj=" $objtest_py --coin=btc + $objtest_py --getobj --coin=btc $objtest_py --coin=btc --testnet=1 $objtest_py --coin=ltc $objtest_py --coin=ltc --testnet=1