Browse Source

replace tx.input, tx.output by methods, so that deserialize calls are encapsulated

283
ThomasV 9 years ago
parent
commit
d200b236ae
  1. 6
      gui/qt/transaction_dialog.py
  2. 14
      lib/coinchooser.py
  3. 4
      lib/commands.py
  4. 72
      lib/transaction.py
  5. 12
      lib/wallet.py
  6. 4
      plugins/ledger/ledger.py
  7. 6
      plugins/trezor/plugin.py

6
gui/qt/transaction_dialog.py

@ -235,7 +235,7 @@ class TxDialog(QDialog, MessageBoxMixin):
if self.tx.locktime > 0: if self.tx.locktime > 0:
vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime)) vbox.addWidget(QLabel("LockTime: %d\n" % self.tx.locktime))
vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs))) vbox.addWidget(QLabel(_("Inputs") + ' (%d)'%len(self.tx.inputs())))
ext = QTextCharFormat() ext = QTextCharFormat()
rec = QTextCharFormat() rec = QTextCharFormat()
@ -258,7 +258,7 @@ class TxDialog(QDialog, MessageBoxMixin):
i_text.setReadOnly(True) i_text.setReadOnly(True)
i_text.setMaximumHeight(100) i_text.setMaximumHeight(100)
cursor = i_text.textCursor() cursor = i_text.textCursor()
for x in self.tx.inputs: for x in self.tx.inputs():
if x.get('is_coinbase'): if x.get('is_coinbase'):
cursor.insertText('coinbase') cursor.insertText('coinbase')
else: else:
@ -279,7 +279,7 @@ class TxDialog(QDialog, MessageBoxMixin):
cursor.insertBlock() cursor.insertBlock()
vbox.addWidget(i_text) vbox.addWidget(i_text)
vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs))) vbox.addWidget(QLabel(_("Outputs") + ' (%d)'%len(self.tx.outputs())))
o_text = QTextEdit() o_text = QTextEdit()
o_text.setFont(QFont(MONOSPACE_FONT)) o_text.setFont(QFont(MONOSPACE_FONT))
o_text.setReadOnly(True) o_text.setReadOnly(True)

14
lib/coinchooser.py

@ -99,7 +99,7 @@ class CoinChooserBase(PrintError):
def change_amounts(self, tx, count, fee_estimator, dust_threshold): def change_amounts(self, tx, count, fee_estimator, dust_threshold):
# Break change up if bigger than max_change # Break change up if bigger than max_change
output_amounts = [o[2] for o in tx.outputs] output_amounts = [o[2] for o in tx.outputs()]
max_change = max(max(output_amounts) * 1.25, dust_threshold * 10) max_change = max(max(output_amounts) * 1.25, dust_threshold * 10)
# Use N change outputs # Use N change outputs
@ -187,16 +187,16 @@ class CoinChooserBase(PrintError):
buckets = self.choose_buckets(buckets, sufficient_funds, buckets = self.choose_buckets(buckets, sufficient_funds,
self.penalty_func(tx)) self.penalty_func(tx))
tx.inputs = [coin for b in buckets for coin in b.coins] tx.add_inputs([coin for b in buckets for coin in b.coins])
tx_size = base_size + sum(bucket.size for bucket in buckets) tx_size = base_size + sum(bucket.size for bucket in buckets)
# This takes a count of change outputs and returns a tx fee; # This takes a count of change outputs and returns a tx fee;
# each pay-to-bitcoin-address output serializes as 34 bytes # each pay-to-bitcoin-address output serializes as 34 bytes
fee = lambda count: fee_estimator(tx_size + count * 34) fee = lambda count: fee_estimator(tx_size + count * 34)
change = self.change_outputs(tx, change_addrs, fee, dust_threshold) change = self.change_outputs(tx, change_addrs, fee, dust_threshold)
tx.outputs.extend(change) tx.add_outputs(change)
self.print_error("using %d inputs" % len(tx.inputs)) self.print_error("using %d inputs" % len(tx.inputs()))
self.print_error("using buckets:", [bucket.desc for bucket in buckets]) self.print_error("using buckets:", [bucket.desc for bucket in buckets])
return tx return tx
@ -282,9 +282,9 @@ class CoinChooserPrivacy(CoinChooserRandom):
raise NotImplementedError raise NotImplementedError
def penalty_func(self, tx): def penalty_func(self, tx):
min_change = min(o[2] for o in tx.outputs) * 0.75 min_change = min(o[2] for o in tx.outputs()) * 0.75
max_change = max(o[2] for o in tx.outputs) * 1.33 max_change = max(o[2] for o in tx.outputs()) * 1.33
spent_amount = sum(o[2] for o in tx.outputs) spent_amount = sum(o[2] for o in tx.outputs())
def penalty(buckets): def penalty(buckets):
badness = len(buckets) - 1 badness = len(buckets) - 1

4
lib/commands.py

@ -210,7 +210,6 @@ class Commands:
def signtransaction(self, tx, privkey=None): def signtransaction(self, tx, privkey=None):
"""Sign a transaction. The wallet keys will be used unless a private key is provided.""" """Sign a transaction. The wallet keys will be used unless a private key is provided."""
t = Transaction(tx) t = Transaction(tx)
t.deserialize()
if privkey: if privkey:
pubkey = bitcoin.public_key_from_private_key(privkey) pubkey = bitcoin.public_key_from_private_key(privkey)
t.sign({pubkey:privkey}) t.sign({pubkey:privkey})
@ -221,8 +220,7 @@ class Commands:
@command('') @command('')
def deserialize(self, tx): def deserialize(self, tx):
"""Deserialize a serialized transaction""" """Deserialize a serialized transaction"""
t = Transaction(tx) return Transaction(tx).deserialize()
return t.deserialize()
@command('n') @command('n')
def broadcast(self, tx): def broadcast(self, tx):

72
lib/transaction.py

@ -479,17 +479,27 @@ class Transaction:
self.raw = raw['hex'] self.raw = raw['hex']
else: else:
raise BaseException("cannot initialize transaction", raw) raise BaseException("cannot initialize transaction", raw)
self.inputs = None self._inputs = None
def update(self, raw): def update(self, raw):
self.raw = raw self.raw = raw
self.inputs = None self._inputs = None
self.deserialize() self.deserialize()
def inputs(self):
if self._inputs is None:
self.deserialize()
return self._inputs
def outputs(self):
if self._outputs is None:
self.deserialize()
return self._outputs
def update_signatures(self, raw): def update_signatures(self, raw):
"""Add new signatures to a transaction""" """Add new signatures to a transaction"""
d = deserialize(raw) d = deserialize(raw)
for i, txin in enumerate(self.inputs): for i, txin in enumerate(self.inputs()):
sigs1 = txin.get('signatures') sigs1 = txin.get('signatures')
sigs2 = d['inputs'][i].get('signatures') sigs2 = d['inputs'][i].get('signatures')
for sig in sigs2: for sig in sigs2:
@ -509,8 +519,8 @@ class Transaction:
public_key.verify_digest(sig_string, for_sig, sigdecode = ecdsa.util.sigdecode_string) public_key.verify_digest(sig_string, for_sig, sigdecode = ecdsa.util.sigdecode_string)
j = pubkeys.index(pubkey) j = pubkeys.index(pubkey)
print_error("adding sig", i, j, pubkey, sig) print_error("adding sig", i, j, pubkey, sig)
self.inputs[i]['signatures'][j] = sig self._inputs[i]['signatures'][j] = sig
self.inputs[i]['x_pubkeys'][j] = pubkey self._inputs[i]['x_pubkeys'][j] = pubkey
break break
# redo raw # redo raw
self.raw = self.serialize() self.raw = self.serialize()
@ -519,19 +529,19 @@ class Transaction:
def deserialize(self): def deserialize(self):
if self.raw is None: if self.raw is None:
self.raw = self.serialize() self.raw = self.serialize()
if self.inputs is not None: if self._inputs is not None:
return return
d = deserialize(self.raw) d = deserialize(self.raw)
self.inputs = d['inputs'] self._inputs = d['inputs']
self.outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']] self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']]
self.locktime = d['lockTime'] self.locktime = d['lockTime']
return d return d
@classmethod @classmethod
def from_io(klass, inputs, outputs, locktime=0): def from_io(klass, inputs, outputs, locktime=0):
self = klass(None) self = klass(None)
self.inputs = inputs self._inputs = inputs
self.outputs = outputs self._outputs = outputs
self.locktime = locktime self.locktime = locktime
return self return self
@ -657,12 +667,12 @@ class Transaction:
def BIP_LI01_sort(self): def BIP_LI01_sort(self):
# See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki # See https://github.com/kristovatlas/rfc/blob/master/bips/bip-li01.mediawiki
self.inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n'])) self._inputs.sort(key = lambda i: (i['prevout_hash'], i['prevout_n']))
self.outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1]))) self._outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1])))
def serialize(self, for_sig=None): def serialize(self, for_sig=None):
inputs = self.inputs inputs = self.inputs()
outputs = self.outputs outputs = self.outputs()
s = int_to_hex(1,4) # version s = int_to_hex(1,4) # version
s += var_int( len(inputs) ) # number of inputs s += var_int( len(inputs) ) # number of inputs
for i, txin in enumerate(inputs): for i, txin in enumerate(inputs):
@ -685,21 +695,25 @@ class Transaction:
def hash(self): def hash(self):
return Hash(self.raw.decode('hex') )[::-1].encode('hex') return Hash(self.raw.decode('hex') )[::-1].encode('hex')
def add_input(self, input): def add_inputs(self, inputs):
self.inputs.append(input) self._inputs.extend(inputs)
self.raw = None
def add_outputs(self, outputs):
self._outputs.extend(outputs)
self.raw = None self.raw = None
def input_value(self): def input_value(self):
return sum(x['value'] for x in self.inputs) return sum(x['value'] for x in self.inputs())
def output_value(self): def output_value(self):
return sum( val for tp,addr,val in self.outputs) return sum( val for tp,addr,val in self.outputs())
def get_fee(self): def get_fee(self):
return self.input_value() - self.output_value() return self.input_value() - self.output_value()
def is_final(self): def is_final(self):
return not any([x.get('sequence') < 0xffffffff - 1 for x in self.inputs]) return not any([x.get('sequence') < 0xffffffff - 1 for x in self.inputs()])
@classmethod @classmethod
def fee_for_size(self, relay_fee, fee_per_kb, size): def fee_for_size(self, relay_fee, fee_per_kb, size):
@ -727,7 +741,7 @@ class Transaction:
def signature_count(self): def signature_count(self):
r = 0 r = 0
s = 0 s = 0
for txin in self.inputs: for txin in self.inputs():
if txin.get('is_coinbase'): if txin.get('is_coinbase'):
continue continue
signatures = filter(None, txin.get('signatures',[])) signatures = filter(None, txin.get('signatures',[]))
@ -741,14 +755,14 @@ class Transaction:
def inputs_without_script(self): def inputs_without_script(self):
out = set() out = set()
for i, txin in enumerate(self.inputs): for i, txin in enumerate(self.inputs()):
if txin.get('scriptSig') == '': if txin.get('scriptSig') == '':
out.add(i) out.add(i)
return out return out
def inputs_to_sign(self): def inputs_to_sign(self):
out = set() out = set()
for txin in self.inputs: for txin in self.inputs():
num_sig = txin.get('num_sig') num_sig = txin.get('num_sig')
if num_sig is None: if num_sig is None:
continue continue
@ -765,7 +779,7 @@ class Transaction:
return out return out
def sign(self, keypairs): def sign(self, keypairs):
for i, txin in enumerate(self.inputs): for i, txin in enumerate(self.inputs()):
num = txin['num_sig'] num = txin['num_sig']
for x_pubkey in txin['x_pubkeys']: for x_pubkey in txin['x_pubkeys']:
signatures = filter(None, txin['signatures']) signatures = filter(None, txin['signatures'])
@ -775,14 +789,14 @@ class Transaction:
if x_pubkey in keypairs.keys(): if x_pubkey in keypairs.keys():
print_error("adding signature for", x_pubkey) print_error("adding signature for", x_pubkey)
# add pubkey to txin # add pubkey to txin
txin = self.inputs[i] txin = self._inputs[i]
x_pubkeys = txin['x_pubkeys'] x_pubkeys = txin['x_pubkeys']
ii = x_pubkeys.index(x_pubkey) ii = x_pubkeys.index(x_pubkey)
sec = keypairs[x_pubkey] sec = keypairs[x_pubkey]
pubkey = public_key_from_private_key(sec) pubkey = public_key_from_private_key(sec)
txin['x_pubkeys'][ii] = pubkey txin['x_pubkeys'][ii] = pubkey
txin['pubkeys'][ii] = pubkey txin['pubkeys'][ii] = pubkey
self.inputs[i] = txin self._inputs[i] = txin
# add signature # add signature
for_sig = Hash(self.tx_for_sig(i).decode('hex')) for_sig = Hash(self.tx_for_sig(i).decode('hex'))
pkey = regenerate_key(sec) pkey = regenerate_key(sec)
@ -792,7 +806,7 @@ class Transaction:
sig = private_key.sign_digest_deterministic( for_sig, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der ) sig = private_key.sign_digest_deterministic( for_sig, hashfunc=hashlib.sha256, sigencode = ecdsa.util.sigencode_der )
assert public_key.verify_digest( sig, for_sig, sigdecode = ecdsa.util.sigdecode_der) assert public_key.verify_digest( sig, for_sig, sigdecode = ecdsa.util.sigdecode_der)
txin['signatures'][ii] = sig.encode('hex') txin['signatures'][ii] = sig.encode('hex')
self.inputs[i] = txin self._inputs[i] = txin
print_error("is_complete", self.is_complete()) print_error("is_complete", self.is_complete())
self.raw = self.serialize() self.raw = self.serialize()
@ -800,7 +814,7 @@ class Transaction:
def get_outputs(self): def get_outputs(self):
"""convert pubkeys to addresses""" """convert pubkeys to addresses"""
o = [] o = []
for type, x, v in self.outputs: for type, x, v in self.outputs():
if type == TYPE_ADDRESS: if type == TYPE_ADDRESS:
addr = x addr = x
elif type == TYPE_PUBKEY: elif type == TYPE_PUBKEY:
@ -815,7 +829,7 @@ class Transaction:
def has_address(self, addr): def has_address(self, addr):
return (addr in self.get_output_addresses()) or (addr in (tx.get("address") for tx in self.inputs)) return (addr in self.get_output_addresses()) or (addr in (tx.get("address") for tx in self.inputs()))
def as_dict(self): def as_dict(self):
if self.raw is None: if self.raw is None:
@ -842,7 +856,7 @@ class Transaction:
# priority must be large enough for free tx # priority must be large enough for free tx
threshold = 57600000 threshold = 57600000
weight = 0 weight = 0
for txin in self.inputs: for txin in self.inputs():
age = wallet.get_confirmations(txin["prevout_hash"])[0] age = wallet.get_confirmations(txin["prevout_hash"])[0]
weight += txin["value"] * age weight += txin["value"] * age
priority = weight / size priority = weight / size

12
lib/wallet.py

@ -274,7 +274,6 @@ class Abstract_Wallet(PrintError):
continue continue
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
if tx is not None: if tx is not None:
tx.deserialize()
self.add_transaction(tx_hash, tx) self.add_transaction(tx_hash, tx)
save = True save = True
if save: if save:
@ -541,7 +540,7 @@ class Abstract_Wallet(PrintError):
is_pruned = False is_pruned = False
is_partial = False is_partial = False
v_in = v_out = v_out_mine = 0 v_in = v_out = v_out_mine = 0
for item in tx.inputs: for item in tx.inputs():
addr = item.get('address') addr = item.get('address')
if addr in addresses: if addr in addresses:
is_send = True is_send = True
@ -722,11 +721,11 @@ class Abstract_Wallet(PrintError):
return addr return addr
def add_transaction(self, tx_hash, tx): def add_transaction(self, tx_hash, tx):
is_coinbase = tx.inputs[0].get('is_coinbase') == True is_coinbase = tx.inputs()[0].get('is_coinbase') == True
with self.transaction_lock: with self.transaction_lock:
# add inputs # add inputs
self.txi[tx_hash] = d = {} self.txi[tx_hash] = d = {}
for txi in tx.inputs: for txi in tx.inputs():
addr = txi.get('address') addr = txi.get('address')
if not txi.get('is_coinbase'): if not txi.get('is_coinbase'):
prevout_hash = txi['prevout_hash'] prevout_hash = txi['prevout_hash']
@ -748,7 +747,7 @@ class Abstract_Wallet(PrintError):
# add outputs # add outputs
self.txo[tx_hash] = d = {} self.txo[tx_hash] = d = {}
for n, txo in enumerate(tx.outputs): for n, txo in enumerate(tx.outputs()):
ser = tx_hash + ':%d'%n ser = tx_hash + ':%d'%n
_type, x, v = txo _type, x, v = txo
if _type == TYPE_ADDRESS: if _type == TYPE_ADDRESS:
@ -827,7 +826,6 @@ class Abstract_Wallet(PrintError):
# if addr is new, we have to recompute txi and txo # if addr is new, we have to recompute txi and txo
tx = self.transactions.get(tx_hash) tx = self.transactions.get(tx_hash)
if tx is not None and self.txi.get(tx_hash, {}).get(addr) is None and self.txo.get(tx_hash, {}).get(addr) is None: if tx is not None and self.txi.get(tx_hash, {}).get(addr) is None and self.txo.get(tx_hash, {}).get(addr) is None:
tx.deserialize()
self.add_transaction(tx_hash, tx) self.add_transaction(tx_hash, tx)
# Write updated TXI, TXO etc. # Write updated TXI, TXO etc.
@ -1010,7 +1008,7 @@ class Abstract_Wallet(PrintError):
self.check_password(password) self.check_password(password)
# Add derivation for utxo in wallets # Add derivation for utxo in wallets
for i, addr in self.utxo_can_sign(tx): for i, addr in self.utxo_can_sign(tx):
txin = tx.inputs[i] txin = tx.inputs()[i]
txin['address'] = addr txin['address'] = addr
self.add_input_info(txin) self.add_input_info(txin)
# Add private keys # Add private keys

4
plugins/ledger/ledger.py

@ -200,9 +200,9 @@ class BTChipWallet(BIP44_Wallet):
pubKeys.append(self.get_public_keys(address)) pubKeys.append(self.get_public_keys(address))
# Recognize outputs - only one output and one change is authorized # Recognize outputs - only one output and one change is authorized
if len(tx.outputs) > 2: # should never happen if len(tx.outputs()) > 2: # should never happen
self.give_error("Transaction with more than 2 outputs not supported") self.give_error("Transaction with more than 2 outputs not supported")
for type, address, amount in tx.outputs: for type, address, amount in tx.outputs():
assert type == TYPE_ADDRESS assert type == TYPE_ADDRESS
if self.is_change(address): if self.is_change(address):
changePath = self.address_id(address) changePath = self.address_id(address)

6
plugins/trezor/plugin.py

@ -365,7 +365,7 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
def tx_inputs(self, tx, for_sig=False): def tx_inputs(self, tx, for_sig=False):
inputs = [] inputs = []
for txin in tx.inputs: for txin in tx.inputs():
txinputtype = self.types.TxInputType() txinputtype = self.types.TxInputType()
if txin.get('is_coinbase'): if txin.get('is_coinbase'):
prev_hash = "\0"*32 prev_hash = "\0"*32
@ -426,8 +426,7 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
def tx_outputs(self, wallet, tx): def tx_outputs(self, wallet, tx):
outputs = [] outputs = []
for type, address, amount in tx.outputs():
for type, address, amount in tx.outputs:
assert type == TYPE_ADDRESS assert type == TYPE_ADDRESS
txoutputtype = self.types.TxOutputType() txoutputtype = self.types.TxOutputType()
if wallet.is_change(address): if wallet.is_change(address):
@ -464,7 +463,6 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob):
# This function is called from the trezor libraries (via tx_api) # This function is called from the trezor libraries (via tx_api)
def get_tx(self, tx_hash): def get_tx(self, tx_hash):
tx = self.prev_tx[tx_hash] tx = self.prev_tx[tx_hash]
tx.deserialize()
return self.electrum_tx_to_txtype(tx) return self.electrum_tx_to_txtype(tx)
@staticmethod @staticmethod

Loading…
Cancel
Save