treplace tx.input, tx.output by methods, so that deserialize calls are encapsulated - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit d200b236ae8505f9cfbfec03628943f5109ce790
 (DIR) parent 321ab1074232e91594aa8564d9b6a18170e11c3d
 (HTM) Author: ThomasV <thomasv@electrum.org>
       Date:   Sun, 17 Jan 2016 14:12:57 +0100
       
       replace tx.input, tx.output by methods, so that deserialize calls are encapsulated
       
       Diffstat:
         M gui/qt/transaction_dialog.py        |       6 +++---
         M lib/coinchooser.py                  |      14 +++++++-------
         M lib/commands.py                     |       4 +---
         M lib/transaction.py                  |      72 ++++++++++++++++++-------------
         M lib/wallet.py                       |      12 +++++-------
         M plugins/ledger/ledger.py            |       4 ++--
         M plugins/trezor/plugin.py            |       6 ++----
       
       7 files changed, 63 insertions(+), 55 deletions(-)
       ---
 (DIR) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py
       t@@ -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()
       t@@ -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:
       t@@ -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)
 (DIR) diff --git a/lib/coinchooser.py b/lib/coinchooser.py
       t@@ -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
       t@@ -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
       t@@ -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
 (DIR) diff --git a/lib/commands.py b/lib/commands.py
       t@@ -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})
       t@@ -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):
 (DIR) diff --git a/lib/transaction.py b/lib/transaction.py
       t@@ -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:
       t@@ -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()
       t@@ -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
        
       t@@ -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):
       t@@ -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):
       t@@ -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',[]))
       t@@ -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
       t@@ -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'])
       t@@ -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)
       t@@ -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()
        
       t@@ -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:
       t@@ -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:
       t@@ -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
 (DIR) diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -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:
       t@@ -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
       t@@ -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']
       t@@ -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:
       t@@ -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.
       t@@ -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
 (DIR) diff --git a/plugins/ledger/ledger.py b/plugins/ledger/ledger.py
       t@@ -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)
 (DIR) diff --git a/plugins/trezor/plugin.py b/plugins/trezor/plugin.py
       t@@ -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
       t@@ -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):
       t@@ -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