From d200b236ae8505f9cfbfec03628943f5109ce790 Mon Sep 17 00:00:00 2001 From: ThomasV Date: Sun, 17 Jan 2016 14:12:57 +0100 Subject: [PATCH] replace tx.input, tx.output by methods, so that deserialize calls are encapsulated --- gui/qt/transaction_dialog.py | 6 +-- lib/coinchooser.py | 14 +++---- lib/commands.py | 4 +- lib/transaction.py | 72 +++++++++++++++++++++--------------- lib/wallet.py | 12 +++--- plugins/ledger/ledger.py | 4 +- plugins/trezor/plugin.py | 6 +-- 7 files changed, 63 insertions(+), 55 deletions(-) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py index 16f346e15..ec920c4c1 100644 --- a/gui/qt/transaction_dialog.py +++ b/gui/qt/transaction_dialog.py @@ -235,7 +235,7 @@ class TxDialog(QDialog, MessageBoxMixin): if self.tx.locktime > 0: 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() rec = QTextCharFormat() @@ -258,7 +258,7 @@ class TxDialog(QDialog, MessageBoxMixin): i_text.setReadOnly(True) i_text.setMaximumHeight(100) cursor = i_text.textCursor() - for x in self.tx.inputs: + for x in self.tx.inputs(): if x.get('is_coinbase'): cursor.insertText('coinbase') else: @@ -279,7 +279,7 @@ class TxDialog(QDialog, MessageBoxMixin): cursor.insertBlock() 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.setFont(QFont(MONOSPACE_FONT)) o_text.setReadOnly(True) diff --git a/lib/coinchooser.py b/lib/coinchooser.py index 51e4a497b..7171a00d6 100644 --- a/lib/coinchooser.py +++ b/lib/coinchooser.py @@ -99,7 +99,7 @@ class CoinChooserBase(PrintError): def change_amounts(self, tx, count, fee_estimator, dust_threshold): # 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) # Use N change outputs @@ -187,16 +187,16 @@ class CoinChooserBase(PrintError): buckets = self.choose_buckets(buckets, sufficient_funds, 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) # This takes a count of change outputs and returns a tx fee; # each pay-to-bitcoin-address output serializes as 34 bytes fee = lambda count: fee_estimator(tx_size + count * 34) 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]) return tx @@ -282,9 +282,9 @@ class CoinChooserPrivacy(CoinChooserRandom): raise NotImplementedError def penalty_func(self, tx): - min_change = min(o[2] for o in tx.outputs) * 0.75 - max_change = max(o[2] for o in tx.outputs) * 1.33 - spent_amount = sum(o[2] for o in tx.outputs) + min_change = min(o[2] for o in tx.outputs()) * 0.75 + max_change = max(o[2] for o in tx.outputs()) * 1.33 + spent_amount = sum(o[2] for o in tx.outputs()) def penalty(buckets): badness = len(buckets) - 1 diff --git a/lib/commands.py b/lib/commands.py index c05361ab9..ede18566f 100644 --- a/lib/commands.py +++ b/lib/commands.py @@ -210,7 +210,6 @@ class Commands: def signtransaction(self, tx, privkey=None): """Sign a transaction. The wallet keys will be used unless a private key is provided.""" t = Transaction(tx) - t.deserialize() if privkey: pubkey = bitcoin.public_key_from_private_key(privkey) t.sign({pubkey:privkey}) @@ -221,8 +220,7 @@ class Commands: @command('') def deserialize(self, tx): """Deserialize a serialized transaction""" - t = Transaction(tx) - return t.deserialize() + return Transaction(tx).deserialize() @command('n') def broadcast(self, tx): diff --git a/lib/transaction.py b/lib/transaction.py index 9a1d43c64..17c0cff2b 100644 --- a/lib/transaction.py +++ b/lib/transaction.py @@ -479,17 +479,27 @@ class Transaction: self.raw = raw['hex'] else: raise BaseException("cannot initialize transaction", raw) - self.inputs = None + self._inputs = None def update(self, raw): self.raw = raw - self.inputs = None + self._inputs = None 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): """Add new signatures to a transaction""" d = deserialize(raw) - for i, txin in enumerate(self.inputs): + for i, txin in enumerate(self.inputs()): sigs1 = txin.get('signatures') sigs2 = d['inputs'][i].get('signatures') for sig in sigs2: @@ -509,8 +519,8 @@ class Transaction: public_key.verify_digest(sig_string, for_sig, sigdecode = ecdsa.util.sigdecode_string) j = pubkeys.index(pubkey) print_error("adding sig", i, j, pubkey, sig) - self.inputs[i]['signatures'][j] = sig - self.inputs[i]['x_pubkeys'][j] = pubkey + self._inputs[i]['signatures'][j] = sig + self._inputs[i]['x_pubkeys'][j] = pubkey break # redo raw self.raw = self.serialize() @@ -519,19 +529,19 @@ class Transaction: def deserialize(self): if self.raw is None: self.raw = self.serialize() - if self.inputs is not None: + if self._inputs is not None: return d = deserialize(self.raw) - self.inputs = d['inputs'] - self.outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']] + self._inputs = d['inputs'] + self._outputs = [(x['type'], x['address'], x['value']) for x in d['outputs']] self.locktime = d['lockTime'] return d @classmethod def from_io(klass, inputs, outputs, locktime=0): self = klass(None) - self.inputs = inputs - self.outputs = outputs + self._inputs = inputs + self._outputs = outputs self.locktime = locktime return self @@ -657,12 +667,12 @@ class Transaction: def BIP_LI01_sort(self): # 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.outputs.sort(key = lambda o: (o[2], self.pay_script(o[0], o[1]))) + 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]))) def serialize(self, for_sig=None): - inputs = self.inputs - outputs = self.outputs + inputs = self.inputs() + outputs = self.outputs() s = int_to_hex(1,4) # version s += var_int( len(inputs) ) # number of inputs for i, txin in enumerate(inputs): @@ -685,21 +695,25 @@ class Transaction: def hash(self): return Hash(self.raw.decode('hex') )[::-1].encode('hex') - def add_input(self, input): - self.inputs.append(input) + def add_inputs(self, inputs): + self._inputs.extend(inputs) + self.raw = None + + def add_outputs(self, outputs): + self._outputs.extend(outputs) self.raw = None 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): - return sum( val for tp,addr,val in self.outputs) + return sum( val for tp,addr,val in self.outputs()) def get_fee(self): return self.input_value() - self.output_value() 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 def fee_for_size(self, relay_fee, fee_per_kb, size): @@ -727,7 +741,7 @@ class Transaction: def signature_count(self): r = 0 s = 0 - for txin in self.inputs: + for txin in self.inputs(): if txin.get('is_coinbase'): continue signatures = filter(None, txin.get('signatures',[])) @@ -741,14 +755,14 @@ class Transaction: def inputs_without_script(self): out = set() - for i, txin in enumerate(self.inputs): + for i, txin in enumerate(self.inputs()): if txin.get('scriptSig') == '': out.add(i) return out def inputs_to_sign(self): out = set() - for txin in self.inputs: + for txin in self.inputs(): num_sig = txin.get('num_sig') if num_sig is None: continue @@ -765,7 +779,7 @@ class Transaction: return out def sign(self, keypairs): - for i, txin in enumerate(self.inputs): + for i, txin in enumerate(self.inputs()): num = txin['num_sig'] for x_pubkey in txin['x_pubkeys']: signatures = filter(None, txin['signatures']) @@ -775,14 +789,14 @@ class Transaction: if x_pubkey in keypairs.keys(): print_error("adding signature for", x_pubkey) # add pubkey to txin - txin = self.inputs[i] + txin = self._inputs[i] x_pubkeys = txin['x_pubkeys'] ii = x_pubkeys.index(x_pubkey) sec = keypairs[x_pubkey] pubkey = public_key_from_private_key(sec) txin['x_pubkeys'][ii] = pubkey txin['pubkeys'][ii] = pubkey - self.inputs[i] = txin + self._inputs[i] = txin # add signature for_sig = Hash(self.tx_for_sig(i).decode('hex')) 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 ) assert public_key.verify_digest( sig, for_sig, sigdecode = ecdsa.util.sigdecode_der) txin['signatures'][ii] = sig.encode('hex') - self.inputs[i] = txin + self._inputs[i] = txin print_error("is_complete", self.is_complete()) self.raw = self.serialize() @@ -800,7 +814,7 @@ class Transaction: def get_outputs(self): """convert pubkeys to addresses""" o = [] - for type, x, v in self.outputs: + for type, x, v in self.outputs(): if type == TYPE_ADDRESS: addr = x elif type == TYPE_PUBKEY: @@ -815,7 +829,7 @@ class Transaction: 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): if self.raw is None: @@ -842,7 +856,7 @@ class Transaction: # priority must be large enough for free tx threshold = 57600000 weight = 0 - for txin in self.inputs: + for txin in self.inputs(): age = wallet.get_confirmations(txin["prevout_hash"])[0] weight += txin["value"] * age priority = weight / size diff --git a/lib/wallet.py b/lib/wallet.py index 79edc04d4..ac714855d 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -274,7 +274,6 @@ class Abstract_Wallet(PrintError): continue tx = self.transactions.get(tx_hash) if tx is not None: - tx.deserialize() self.add_transaction(tx_hash, tx) save = True if save: @@ -541,7 +540,7 @@ class Abstract_Wallet(PrintError): is_pruned = False is_partial = False v_in = v_out = v_out_mine = 0 - for item in tx.inputs: + for item in tx.inputs(): addr = item.get('address') if addr in addresses: is_send = True @@ -722,11 +721,11 @@ class Abstract_Wallet(PrintError): return addr 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: # add inputs self.txi[tx_hash] = d = {} - for txi in tx.inputs: + for txi in tx.inputs(): addr = txi.get('address') if not txi.get('is_coinbase'): prevout_hash = txi['prevout_hash'] @@ -748,7 +747,7 @@ class Abstract_Wallet(PrintError): # add outputs self.txo[tx_hash] = d = {} - for n, txo in enumerate(tx.outputs): + for n, txo in enumerate(tx.outputs()): ser = tx_hash + ':%d'%n _type, x, v = txo if _type == TYPE_ADDRESS: @@ -827,7 +826,6 @@ class Abstract_Wallet(PrintError): # if addr is new, we have to recompute txi and txo 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: - tx.deserialize() self.add_transaction(tx_hash, tx) # Write updated TXI, TXO etc. @@ -1010,7 +1008,7 @@ class Abstract_Wallet(PrintError): self.check_password(password) # Add derivation for utxo in wallets for i, addr in self.utxo_can_sign(tx): - txin = tx.inputs[i] + txin = tx.inputs()[i] txin['address'] = addr self.add_input_info(txin) # Add private keys diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py index e33025d3e..ce9112be4 100644 --- a/plugins/ledger/ledger.py +++ b/plugins/ledger/ledger.py @@ -200,9 +200,9 @@ class BTChipWallet(BIP44_Wallet): pubKeys.append(self.get_public_keys(address)) # 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") - for type, address, amount in tx.outputs: + for type, address, amount in tx.outputs(): assert type == TYPE_ADDRESS if self.is_change(address): changePath = self.address_id(address) diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py index f8da75358..9b58d9b73 100644 --- a/plugins/trezor/plugin.py +++ b/plugins/trezor/plugin.py @@ -365,7 +365,7 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob): def tx_inputs(self, tx, for_sig=False): inputs = [] - for txin in tx.inputs: + for txin in tx.inputs(): txinputtype = self.types.TxInputType() if txin.get('is_coinbase'): prev_hash = "\0"*32 @@ -426,8 +426,7 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob): def tx_outputs(self, wallet, tx): outputs = [] - - for type, address, amount in tx.outputs: + for type, address, amount in tx.outputs(): assert type == TYPE_ADDRESS txoutputtype = self.types.TxOutputType() if wallet.is_change(address): @@ -464,7 +463,6 @@ class TrezorCompatiblePlugin(BasePlugin, ThreadJob): # This function is called from the trezor libraries (via tx_api) def get_tx(self, tx_hash): tx = self.prev_tx[tx_hash] - tx.deserialize() return self.electrum_tx_to_txtype(tx) @staticmethod