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

This commit is contained in:
The MMGen Project 2018-10-23 16:12:33 +00:00
commit 67c94010f6
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
8 changed files with 59 additions and 51 deletions

View file

@ -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!')

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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')

View file

@ -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)))

View file

@ -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)

View file

@ -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