Browse Source

txsign(): improve error handling, always return control to caller

MMGen 6 years ago
parent
commit
67c94010f6
8 changed files with 59 additions and 51 deletions
  1. 1 1
      mmgen/altcoins/eth/tx.py
  2. 5 0
      mmgen/exception.py
  3. 5 3
      mmgen/main_autosign.py
  4. 6 4
      mmgen/main_txbump.py
  5. 8 10
      mmgen/main_txdo.py
  6. 12 7
      mmgen/main_txsign.py
  7. 21 22
      mmgen/tx.py
  8. 1 4
      mmgen/txsign.py

+ 1 - 1
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!')

+ 5 - 0
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

+ 5 - 3
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

+ 6 - 4
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)

+ 8 - 10
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')

+ 12 - 7
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 not opt.yes:
-		tx.add_comment()   # edits an existing comment
+	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
 
-	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)))

+ 21 - 22
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 not ret['complete']:
+			msg('failed\n{} returned the following errors:'.format(g.proto.daemon_name.capitalize()))
+			msg(repr(ret['errors']))
 			return False
 
-		if ret['complete']:
+		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)

+ 1 - 4
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