From 67c94010f6a021d69575c1fc6f331f0db97f19f7 Mon Sep 17 00:00:00 2001 From: MMGen Date: Tue, 23 Oct 2018 16:12:33 +0000 Subject: [PATCH] txsign(): improve error handling, always return control to caller --- mmgen/altcoins/eth/tx.py | 2 +- mmgen/exception.py | 5 +++++ mmgen/main_autosign.py | 8 +++++--- mmgen/main_txbump.py | 10 ++++++---- mmgen/main_txdo.py | 18 ++++++++--------- mmgen/main_txsign.py | 19 +++++++++++------- mmgen/tx.py | 43 ++++++++++++++++++++-------------------- mmgen/txsign.py | 5 +---- 8 files changed, 59 insertions(+), 51 deletions(-) diff --git a/mmgen/altcoins/eth/tx.py b/mmgen/altcoins/eth/tx.py index c79abd3f..9b6562f5 100755 --- a/mmgen/altcoins/eth/tx.py +++ b/mmgen/altcoins/eth/tx.py @@ -304,7 +304,7 @@ class EthereumMMGenTX(MMGenTX): return self.check_sigs() - def sign(self,tx_num_str,keys): # return true or false; don't exit + def sign(self,tx_num_str,keys): # return True or False; don't exit or raise exception if self.marked_signed(): msg('Transaction is already signed!') diff --git a/mmgen/exception.py b/mmgen/exception.py index e98d0bea..427bfe1e 100755 --- a/mmgen/exception.py +++ b/mmgen/exception.py @@ -24,4 +24,9 @@ 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 diff --git a/mmgen/main_autosign.py b/mmgen/main_autosign.py index dc76e5ce..e9b5aca4 100755 --- a/mmgen/main_autosign.py +++ b/mmgen/main_autosign.py @@ -190,9 +190,11 @@ def sign_tx_file(txfile): if g.proto.sign_mode == 'daemon': rpc_init(reinit=True) - txsign(tx,wfs,None,None) - tx.write_to_file(ask_write=False) - return True + if txsign(tx,wfs,None,None): + tx.write_to_file(ask_write=False) + return True + else: + return False except Exception as e: msg(u'An error occurred: {}'.format(e.message)) return False diff --git a/mmgen/main_txbump.py b/mmgen/main_txbump.py index f494dca4..400d2e99 100755 --- a/mmgen/main_txbump.py +++ b/mmgen/main_txbump.py @@ -131,9 +131,11 @@ if not silent: msg_r(tx.format_view(terse=True)) if seed_files or kl or kal: - txsign(tx,seed_files,kl,kal) - tx.write_to_file(ask_write=False) - tx.send(exit_on_fail=True) - tx.write_to_file(ask_write=False) + if txsign(tx,seed_files,kl,kal): + tx.write_to_file(ask_write=False) + tx.send(exit_on_fail=True) + tx.write_to_file(ask_write=False) + else: + die(2,'Transaction could not be signed') else: tx.write_to_file(ask_write=not opt.yes,ask_write_default_yes=False,ask_overwrite=not opt.yes) diff --git a/mmgen/main_txdo.py b/mmgen/main_txdo.py index 3407b869..e9aaf0ad 100755 --- a/mmgen/main_txdo.py +++ b/mmgen/main_txdo.py @@ -102,13 +102,11 @@ tx = MMGenTX(caller='txdo') tx.create(cmd_args,int(opt.locktime or 0)) -txsign(tx,seed_files,kl,kal) - -tx.write_to_file(ask_write=False) - -tx.send(exit_on_fail=True) - -tx.write_to_file(ask_overwrite=False,ask_write=False) - -if hasattr(tx,'token_addr'): - msg('Contract address: {}'.format(tx.token_addr.hl())) +if txsign(tx,seed_files,kl,kal): + tx.write_to_file(ask_write=False) + tx.send(exit_on_fail=True) + tx.write_to_file(ask_overwrite=False,ask_write=False) + if hasattr(tx,'token_addr'): + msg('Contract address: {}'.format(tx.token_addr.hl())) +else: + die(2,'Transaction could not be signed') diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index 5d4e2999..2d236734 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -90,7 +90,7 @@ kal = get_keyaddrlist(opt) kl = get_keylist(opt) if kl and kal: kl.remove_dup_keys(kal) -tx_num_str = '' +tx_num_str,bad_tx_count = '',0 for tx_num,tx_file in enumerate(tx_files,1): if len(tx_files) > 1: msg('\nTransaction #{} of {}:'.format(tx_num,len(tx_files))) @@ -98,7 +98,8 @@ for tx_num,tx_file in enumerate(tx_files,1): tx = MMGenTX(tx_file) if tx.marked_signed(): - die(1,'Transaction is already signed!') + msg('Transaction is already signed!'); continue + vmsg(u"Successfully opened transaction file '{}'".format(tx_file)) if opt.tx_id: @@ -110,9 +111,13 @@ for tx_num,tx_file in enumerate(tx_files,1): if not opt.yes: tx.view_with_prompt('View data for transaction{}?'.format(tx_num_str)) - txsign(tx,seed_files,kl,kal,tx_num_str) + if txsign(tx,seed_files,kl,kal,tx_num_str): + if not opt.yes: + tx.add_comment() # edits an existing comment + tx.write_to_file(ask_write=not opt.yes,ask_write_default_yes=True,add_desc=tx_num_str) + else: + ymsg('Transaction could not be signed') + bad_tx_count += 1 - if not opt.yes: - tx.add_comment() # edits an existing comment - - tx.write_to_file(ask_write=not opt.yes,ask_write_default_yes=True,add_desc=tx_num_str) +if bad_tx_count: + ydie(2,'{} transaction{} could not be signed'.format(bad_tx_count,suf(bad_tx_count))) diff --git a/mmgen/tx.py b/mmgen/tx.py index a91f256b..8acab925 100755 --- a/mmgen/tx.py +++ b/mmgen/tx.py @@ -145,7 +145,7 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types if has_witness: u = hshift(tx,2,skip=True)[2:] if u != '01': - die(2,"'{}': Illegal value for flag in transaction!".format(u)) + raise IllegalWitnessFlagValue,"'{}': Illegal value for flag in transaction!".format(u) del tx_copy[-len(tx)-2:-len(tx)] d['num_txins'] = readVInt(tx) @@ -181,7 +181,7 @@ class DeserializedTX(OrderedDict,MMGenObject): # need to add MMGen types hshift(wd,readVInt(wd,skip=True),skip=True) for item in range(readVInt(wd,skip=True)) ] if wd: - die(3,'More witness data than inputs with witnesses!') + raise WitnessSizeMismatch,'More witness data than inputs with witnesses!' d['lock_time'] = bytes2int(hshift(tx,4)) d['txid'] = hexlify(sha256(sha256(''.join(tx_copy)).digest()).digest()[::-1]) @@ -408,7 +408,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam # allow for 5% error ratio = float(est_vsize) / vsize if not (0.95 < ratio < 1.05): - die(2,(m1+m2+m3).format(ratio,1/ratio)) + raise BadTxSizeEstimate,(m1+m2+m3).format(ratio,1/ratio) # https://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending # 180: uncompressed, 148: compressed @@ -679,8 +679,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam def get_non_mmaddrs(self,desc): return list(set(i.addr for i in getattr(self,desc) if not i.mmid)) - # return true or false; don't exit - def sign(self,tx_num_str,keys): + def sign(self,tx_num_str,keys): # return True or False; don't exit or raise exception if self.marked_signed(): msg('Transaction is already signed!') @@ -720,28 +719,28 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam if 'sign_with_key' in g.rpch.caps else \ g.rpch.signrawtransaction(self.hex,sig_data,wifs,g.proto.sighash_type) except Exception as e: - if 'Invalid sighash param' in e.message: - m = 'This is not the BCH chain.' - m += "\nRe-run the script without the --coin=bch option." - else: - m = e.message - msg(yellow(m)) + msg(yellow('This is not the BCH chain.\nRe-run the script without the --coin=bch option.' + if 'Invalid sighash param' in e.message else e.message)) return False - if ret['complete']: + if not ret['complete']: + msg('failed\n{} returned the following errors:'.format(g.proto.daemon_name.capitalize())) + msg(repr(ret['errors'])) + return False + + try: self.hex = ret['hex'] self.compare_size_and_estimated_size() dt = DeserializedTX(self.hex) self.check_hex_tx_matches_mmgen_tx(dt) - self.coin_txid = CoinTxID(dt['txid'],on_fail='return') + self.coin_txid = CoinTxID(dt['txid'],on_fail='raise') self.check_sigs(dt) - assert self.coin_txid == g.rpch.decoderawtransaction(self.hex)['txid'],( - 'txid mismatch (after signing)') + if not self.coin_txid == g.rpch.decoderawtransaction(self.hex)['txid']: + raise BadMMGenTxID,'txid mismatch (after signing)' msg('OK') return True - else: - msg('failed\n{} returned the following errors:'.format(g.proto.daemon_name.capitalize())) - msg(repr(ret['errors'])) + except Exception as e: + msg(yellow(repr(e.message))) return False def mark_raw(self): @@ -764,14 +763,14 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam lt = deserial_tx['lock_time'] if lt != int(self.locktime or 0): m2 = '\nTransaction hex locktime ({}) does not match MMGen transaction locktime ({})\n{}' - rdie(3,m2.format(lt,self.locktime,m)) + raise TxHexMismatch,m2.format(lt,self.locktime,m) def check_equal(desc,hexio,mmio): if mmio != hexio: msg('\nMMGen {}:\n{}'.format(desc,pformat(mmio))) msg('Hex {}:\n{}'.format(desc,pformat(hexio))) m2 = '{} in hex transaction data from coin daemon do not match those in MMGen transaction!\n' - rdie(3,(m2+m).format(desc.capitalize())) + raise TxHexMismatch,(m2+m).format(desc.capitalize()) seq_hex = map(lambda i: int(i['nSeq'],16),deserial_tx['txins']) seq_mmgen = map(lambda i: i.sequence or g.max_int,self.inputs) @@ -787,7 +786,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam uh = deserial_tx['unsigned_hex'] if str(self.txid) != make_chksum_6(unhexlify(uh)).upper(): - rdie(3,'MMGen TxID ({}) does not match hex transaction data!\n{}'.format(self.txid,m)) + raise TxHexMismatch,'MMGen TxID ({}) does not match hex transaction data!\n{}'.format(self.txid,m) def check_pubkey_scripts(self): for n,i in enumerate(self.inputs,1): @@ -802,7 +801,7 @@ Selected non-{pnm} inputs: {{}}""".strip().format(pnm=g.proj_name,pnl=g.proj_nam 'scriptPubKey->address:',addr )) # check signature and witness data - def check_sigs(self,deserial_tx=None): # return False if no sigs, die on error + def check_sigs(self,deserial_tx=None): # return False if no sigs, raise exception on error txins = (deserial_tx or DeserializedTX(self.hex))['txins'] has_ss = any(ti['scriptSig'] for ti in txins) has_witness = any('witness' in ti and ti['witness'] for ti in txins) diff --git a/mmgen/txsign.py b/mmgen/txsign.py index a2e8165b..5d546443 100755 --- a/mmgen/txsign.py +++ b/mmgen/txsign.py @@ -160,7 +160,4 @@ def txsign(tx,seed_files,kl,kal,tx_num_str=''): if extra_sids: msg('Unused Seed ID{}: {}'.format(suf(extra_sids,'s'),' '.join(extra_sids))) - if tx.sign(tx_num_str,keys): - return tx - else: - die(3,red('Transaction {}could not be signed.'.format(tx_num_str))) + return tx.sign(tx_num_str,keys) # returns True or False