From abbdda0dcfc1b43970816ddebef7dd9a8a8560f8 Mon Sep 17 00:00:00 2001 From: MMGen Date: Wed, 24 Oct 2018 14:09:51 +0000 Subject: [PATCH] Add return codes to exception classes, handle on exit --- mmgen/altcoins/eth/contract.py | 2 +- mmgen/altcoins/eth/tw.py | 2 +- mmgen/exception.py | 25 ++++++++++++++++--------- mmgen/main.py | 17 ++++++++++------- mmgen/main_addrimport.py | 13 ++++++------- mmgen/rpc.py | 15 +++++++-------- mmgen/tx.py | 10 ++++++---- test/test.py | 19 ++++++++++++++++--- 8 files changed, 63 insertions(+), 40 deletions(-) diff --git a/mmgen/altcoins/eth/contract.py b/mmgen/altcoins/eth/contract.py index 92938434..cf380957 100755 --- a/mmgen/altcoins/eth/contract.py +++ b/mmgen/altcoins/eth/contract.py @@ -44,7 +44,7 @@ class Token(MMGenObject): # ERC20 if decimals is None: ret_hex = self.do_call('decimals()') try: decimals = int(ret_hex,16) - except: raise TokenNotInBlockchain,"token '{}' not in blockchain".format(addr) + except: raise TokenNotInBlockchain,"Token '{}' not in blockchain".format(addr) self.base_unit = Decimal(10) ** -decimals def transferdata2amt(self,data): # online diff --git a/mmgen/altcoins/eth/tw.py b/mmgen/altcoins/eth/tw.py index 00143004..5471125c 100755 --- a/mmgen/altcoins/eth/tw.py +++ b/mmgen/altcoins/eth/tw.py @@ -145,7 +145,7 @@ class EthereumTrackingWallet(TrackingWallet): d['comment'] = lbl.comment self.write() return None - else: # emulate RPC library + else: # emulate on_fail='return' of RPC library m = "Address '{}' not found in '{}' section of tracking wallet" return ('rpcfail',(None,2,m.format(coinaddr,self.data_root_desc()))) diff --git a/mmgen/exception.py b/mmgen/exception.py index 427bfe1e..cc2980f4 100755 --- a/mmgen/exception.py +++ b/mmgen/exception.py @@ -21,12 +21,19 @@ mmgen.exception: Exception classes for the MMGen suite """ -class UnrecognizedTokenSymbol(Exception): pass -class TokenNotInBlockchain(Exception): pass -class UserNonConfirmation(Exception): pass -class BadMMGenTxID(Exception): pass -class BadTxSizeEstimate(Exception): pass -class IllegalWitnessFlagValue(Exception): pass -class WitnessSizeMismatch(Exception): pass -class TxHexMismatch(Exception): pass -class RPCFailure(Exception): pass +# 1: no hl, message only +class UserNonConfirmation(Exception): mmcode = 1 + +# 2: yellow hl, message only +class UnrecognizedTokenSymbol(Exception): mmcode = 2 +class TokenNotInBlockchain(Exception): mmcode = 2 + +# 3: yellow hl, 'MMGen Error' + exception + message +class RPCFailure(Exception): mmcode = 3 +class BadTxSizeEstimate(Exception): mmcode = 3 + +# 4: red hl, 'MMGen Fatal Error' + exception + message +class BadMMGenTxID(Exception): mmcode = 4 +class IllegalWitnessFlagValue(Exception): mmcode = 4 +class WitnessSizeMismatch(Exception): mmcode = 4 +class TxHexMismatch(Exception): mmcode = 4 diff --git a/mmgen/main.py b/mmgen/main.py index 5685f420..d9082b56 100755 --- a/mmgen/main.py +++ b/mmgen/main.py @@ -56,11 +56,14 @@ def launch(what): raise else: try: m = u'{}'.format(e.message) - except: - try: m = e.message.decode('utf8') - except: m = repr(e.message) + except: m = repr(e.message) - from mmgen.util import die,ydie - if type(e).__name__ == 'UserNonConfirmation': die(1,m) - if type(e).__name__ == 'RPCFailure': ydie(2,m) - ydie(2,u'\nERROR: ' + m) + from mmgen.util import die,ydie,rdie + d = [ (ydie,2,u'\nMMGen Unhandled Exception ({n}): {m}'), + (die, 1,u'{m}'), + (ydie,2,u'{m}'), + (ydie,3,u'\nMMGen Error ({n}): {m}'), + (rdie,4,u'\nMMGen Fatal Error ({n}): {m}') + ][e.mmcode if hasattr(e,'mmcode') else 0] + + d[0](d[1],d[2].format(n=type(e).__name__,m=m)) diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index 1ea691d2..b22125d0 100755 --- a/mmgen/main_addrimport.py +++ b/mmgen/main_addrimport.py @@ -79,7 +79,11 @@ def import_mmgen_list(infile): rdie(2,'Segwit is not active on this chain. Cannot import Segwit addresses') return al -rpc_init() +try: + rpc_init() +except UnrecognizedTokenSymbol as e: + m = "When importing addresses for a new token, the token must be specified by address, not symbol." + raise type(e),'{}\n{}'.format(e.message,m) if len(cmd_args) == 1: infile = cmd_args[0] @@ -103,12 +107,7 @@ qmsg('OK. {} addresses{}'.format(al.num_addrs,m)) err_msg = None from mmgen.tw import TrackingWallet -try: - tw = TrackingWallet(mode='w') -except UnrecognizedTokenSymbolError as e: - m1 = "Note: when importing addresses for a new token, the token must be specified" - m2 = "by address, not symbol." - die(1,'{}\n{}\n{}'.format(e.message,m1,m2)) +tw = TrackingWallet(mode='w') if opt.rescan and not 'rescan' in tw.caps: msg("'--rescan' ignored: not supported by {}".format(type(tw).__name__)) diff --git a/mmgen/rpc.py b/mmgen/rpc.py index fdfe3dc8..b7ec4cb9 100755 --- a/mmgen/rpc.py +++ b/mmgen/rpc.py @@ -76,7 +76,7 @@ class CoinDaemonRPCConnection(object): # kwargs are for local use and are not passed to server # By default, raises RPCFailure exception with an error msg on all errors and exceptions - # on_fail is one of 'raise' (default), 'return', 'silent' or 'die' + # on_fail is one of 'raise' (default), 'return' or 'silent' # With on_fail='return', returns 'rpcfail',(resp_object,(die_args)) def request(self,cmd,*args,**kwargs): @@ -85,6 +85,9 @@ class CoinDaemonRPCConnection(object): cf = { 'timeout':g.http_timeout, 'batch':False, 'on_fail':'raise' } + if cf['on_fail'] not in ('raise','return','silent'): + raise ValueError, "request(): {}: illegal value for 'on_fail'".format(cf['on_fail']) + for k in cf: if k in kwargs and kwargs[k]: cf[k] = kwargs[k] @@ -96,16 +99,12 @@ class CoinDaemonRPCConnection(object): p = {'method':cmd,'params':args,'id':1,'jsonrpc':'2.0'} def do_fail(*args): - if cf['on_fail'] in ('return','silent'): - return 'rpcfail',args + if cf['on_fail'] in ('return','silent'): return 'rpcfail',args try: s = u'{}'.format(args[2]) except: s = repr(args[2]) - if cf['on_fail'] == 'raise': - raise RPCFailure,s - elif cf['on_fail'] == 'die': - die(args[1],yellow(s)) + raise RPCFailure,s dmsg_rpc('=== request() debug ===') dmsg_rpc(' RPC POST data ==> {}\n'.format(p)) @@ -155,7 +154,7 @@ class CoinDaemonRPCConnection(object): dmsg_rpc(u' RPC REPLY data ==> {}\n'.format(r2)) if not r2: - return do_fail(r,2,'Error: empty reply') + return do_fail(r,2,'Empty reply') r3 = json.loads(r2,parse_float=Decimal) ret = [] diff --git a/mmgen/tx.py b/mmgen/tx.py index 8acab925..c2f9040a 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -402,7 +402,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam d = g.rpch.decoderawtransaction(self.hex) vsize = d['vsize'] if 'vsize' in d else d['size'] vmsg('\nSize: {}, Vsize: {} (true) {} (estimated)'.format(d['size'],vsize,est_vsize)) - m1 = '\nERROR: Estimated transaction vsize is {:1.2f} times the true vsize\n' + m1 = 'Estimated transaction vsize is {:1.2f} times the true vsize\n' m2 = 'Your transaction fee estimates will be inaccurate\n' m3 = 'Please re-create and re-sign the transaction using the option --vsize-adj={:1.2f}' # allow for 5% error @@ -740,7 +740,9 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam msg('OK') return True except Exception as e: - msg(yellow(repr(e.message))) + try: m = u'{}'.format(e.message) + except: m = repr(e.message) + msg('\n'+yellow(m)) return False def mark_raw(self): @@ -758,11 +760,11 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam # check that a malicious, compromised or malfunctioning coin daemon hasn't altered hex tx data: # does not check witness or signature data def check_hex_tx_matches_mmgen_tx(self,deserial_tx): - m = 'Fatal error: a malicious or malfunctioning coin daemon or other program may have altered your data!' + m = 'A malicious or malfunctioning coin daemon or other program may have altered your data!' lt = deserial_tx['lock_time'] if lt != int(self.locktime or 0): - m2 = '\nTransaction hex locktime ({}) does not match MMGen transaction locktime ({})\n{}' + m2 = 'Transaction hex locktime ({}) does not match MMGen transaction locktime ({})\n{}' raise TxHexMismatch,m2.format(lt,self.locktime,m) def check_equal(desc,hexio,mmio): diff --git a/test/test.py b/test/test.py index 9786dafc..5f1373fe 100755 --- a/test/test.py +++ b/test/test.py @@ -985,6 +985,8 @@ cmd_group['ethdev'] = ( ('ethdev_token_transfer_funds','transferring token funds from dev to user'), ('ethdev_token_addrgen', 'generating token addresses'), + ('ethdev_token_addrimport_badaddr1','importing token addresses (no token address)'), + ('ethdev_token_addrimport_badaddr2','importing token addresses (bad token address)'), ('ethdev_token_addrimport', 'importing token addresses'), ('ethdev_bal7', 'the {} balance'.format(g.coin)), @@ -2345,12 +2347,15 @@ class MMGenTestSuite(object): t.view_tx('n') t.passphrase('MMGen wallet',cfgs['20']['wpasswd']) if bad_vsize: - t.expect('ERROR: Estimated transaction vsize is') + t.expect('Estimated transaction vsize') + t.expect('1 transaction could not be signed') + exit_val = 2 else: t.do_comment(False) t.expect('Save signed transaction? (Y/n): ','y') + exit_val = 0 t.read() - t.ok(exit_val=(0,2)[bad_vsize]) + t.ok(exit_val=exit_val) def walletgen6(self,name,del_dw_run='dummy'): self.walletgen(name) @@ -3321,10 +3326,12 @@ class MMGenTestSuite(object): t.read() t.ok() - def ethdev_addrimport(self,name,ext=u'21-23]{}.addrs',expect='9/9',add_args=[]): + def ethdev_addrimport(self,name,ext=u'21-23]{}.addrs',expect='9/9',add_args=[],bad_input=False): ext = ext.format(u'-α' if g.debug_utf8 else '') fn = get_file_with_ext(ext,cfg['tmpdir'],no_dot=True,delete=False) t = MMGenExpect(name,'mmgen-addrimport', eth_args()[1:] + add_args + [fn]) + if bad_input: + t.read(); t.ok(2); return if g.debug: t.expect("Type uppercase 'YES' to confirm: ",'YES\n') t.expect('Importing') t.expect(expect) @@ -3576,6 +3583,12 @@ class MMGenTestSuite(object): self.ethdev_addrgen(name,addrs='11-13') self.ethdev_addrgen(name,addrs='21-23') + def ethdev_token_addrimport_badaddr1(self,name): + self.ethdev_addrimport(name,ext=u'[11-13]{}.addrs',add_args=['--token=abc'],bad_input=True) + + def ethdev_token_addrimport_badaddr2(self,name): + self.ethdev_addrimport(name,ext=u'[11-13]{}.addrs',add_args=['--token='+'00deadbeef'*4],bad_input=True) + def ethdev_token_addrimport(self,name): for n,r in ('1','11-13'),('2','21-23'): tk_addr = read_from_tmpfile(cfg,'token_addr'+n).strip()