tMerge pull request #3915 from spesmilo/add_transaction - electrum - Electrum Bitcoin wallet
 (HTM) git clone https://git.parazyd.org/electrum
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) Submodules
       ---
 (DIR) commit c9ffe8d48a489028c4bed9f83e1374c84759b082
 (DIR) parent c4d31674abec59b8fddbbd480dfe9d96c9cca9e8
 (HTM) Author: ThomasV <thomasv@electrum.org>
       Date:   Sat, 17 Feb 2018 16:38:39 +0100
       
       Merge pull request #3915 from spesmilo/add_transaction
       
       Add transaction
       Diffstat:
         M lib/wallet.py                       |      96 +++++++++++++++----------------
       
       1 file changed, 47 insertions(+), 49 deletions(-)
       ---
 (DIR) diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -278,12 +278,10 @@ class Abstract_Wallet(PrintError):
            @profiler
            def build_spent_outpoints(self):
                self.spent_outpoints = {}
       -        for txid, tx in self.transactions.items():
       -            for txi in tx.inputs():
       -                ser = Transaction.get_outpoint_from_txin(txi)
       -                if ser is None:
       -                    continue
       -                self.spent_outpoints[ser] = txid
       +        for txid, items in self.txi.items():
       +            for addr, l in items.items():
       +                for ser, v in l:
       +                    self.spent_outpoints[ser] = txid
        
            @profiler
            def check_history(self):
       t@@ -709,7 +707,12 @@ class Abstract_Wallet(PrintError):
                            h.append((tx_hash, tx_height))
                return h
        
       -    def find_pay_to_pubkey_address(self, prevout_hash, prevout_n):
       +    def get_txin_address(self, txi):
       +        addr = txi.get('address')
       +        if addr != "(pubkey)":
       +            return addr
       +        prevout_hash = txi.get('prevout_hash')
       +        prevout_n = txi.get('prevout_n')
                dd = self.txo.get(prevout_hash, {})
                for addr, l in dd.items():
                    for n, v, is_cb in l:
       t@@ -717,6 +720,16 @@ class Abstract_Wallet(PrintError):
                            self.print_error("found pay-to-pubkey address:", addr)
                            return addr
        
       +    def get_txout_address(self, txo):
       +        _type, x, v = txo
       +        if _type == TYPE_ADDRESS:
       +            addr = x
       +        elif _type == TYPE_PUBKEY:
       +            addr = bitcoin.public_key_to_p2pkh(bfh(x))
       +        else:
       +            addr = None
       +        return addr
       +
            def get_conflicting_transactions(self, tx):
                """Returns a set of transaction hashes from the wallet history that are
                directly conflicting with tx, i.e. they have common outpoints being
       t@@ -737,11 +750,20 @@ class Abstract_Wallet(PrintError):
                    return conflicting_txns
        
            def add_transaction(self, tx_hash, tx):
       -        if tx in self.transactions:
       -            return True
       -        is_coinbase = tx.inputs()[0]['type'] == 'coinbase'
       -        related = False
                with self.transaction_lock:
       +            if tx in self.transactions:
       +                return True
       +            is_coinbase = tx.inputs()[0]['type'] == 'coinbase'
       +            tx_height = self.get_tx_height(tx_hash)[0]
       +            is_mine = any([self.is_mine(txin['address']) for txin in tx.inputs()])
       +            # do not save if tx is local and not mine
       +            if tx_height == TX_HEIGHT_LOCAL and not is_mine:
       +                # FIXME the test here should be for "not all is_mine"; cannot detect conflict in some cases
       +                return False
       +            # raise exception if unrelated to wallet
       +            is_for_me = any([self.is_mine(self.get_txout_address(txo)) for txo in tx.outputs()])
       +            if not is_mine and not is_for_me:
       +                raise UnrelatedTransactionException()
                    # Find all conflicting transactions.
                    # In case of a conflict,
                    #     1. confirmed > mempool > local
       t@@ -751,7 +773,6 @@ class Abstract_Wallet(PrintError):
                    #     or drop this txn
                    conflicting_txns = self.get_conflicting_transactions(tx)
                    if conflicting_txns:
       -                tx_height = self.get_tx_height(tx_hash)[0]
                        existing_mempool_txn = any(
                            self.get_tx_height(tx_hash2)[0] in (TX_HEIGHT_UNCONFIRMED, TX_HEIGHT_UNCONF_PARENT)
                            for tx_hash2 in conflicting_txns)
       t@@ -771,21 +792,17 @@ class Abstract_Wallet(PrintError):
                            to_remove |= self.get_depending_transactions(conflicting_tx_hash)
                        for tx_hash2 in to_remove:
                            self.remove_transaction(tx_hash2)
       -
                    # add inputs
                    self.txi[tx_hash] = d = {}
                    for txi in tx.inputs():
       -                addr = txi.get('address')
       +                addr = self.get_txin_address(txi)
                        if txi['type'] != 'coinbase':
                            prevout_hash = txi['prevout_hash']
                            prevout_n = txi['prevout_n']
                            ser = prevout_hash + ':%d'%prevout_n
                            self.spent_outpoints[ser] = tx_hash
       -                if addr == "(pubkey)":
       -                    addr = self.find_pay_to_pubkey_address(prevout_hash, prevout_n)
                        # find value from prev output
                        if addr and self.is_mine(addr):
       -                    related = True
                            dd = self.txo.get(prevout_hash, {})
                            for n, v, is_cb in dd.get(addr, []):
                                if n == prevout_n:
       t@@ -795,20 +812,13 @@ class Abstract_Wallet(PrintError):
                                    break
                            else:
                                self.pruned_txo[ser] = tx_hash
       -
                    # add outputs
                    self.txo[tx_hash] = d = {}
                    for n, txo in enumerate(tx.outputs()):
       +                v = txo[2]
                        ser = tx_hash + ':%d'%n
       -                _type, x, v = txo
       -                if _type == TYPE_ADDRESS:
       -                    addr = x
       -                elif _type == TYPE_PUBKEY:
       -                    addr = bitcoin.public_key_to_p2pkh(bfh(x))
       -                else:
       -                    addr = None
       +                addr = self.get_txout_address(txo)
                        if addr and self.is_mine(addr):
       -                    related = True
                            if d.get(addr) is None:
                                d[addr] = []
                            d[addr].append((n, v, is_coinbase))
       t@@ -820,30 +830,19 @@ class Abstract_Wallet(PrintError):
                            if dd.get(addr) is None:
                                dd[addr] = []
                            dd[addr].append((ser, v))
       -
       -            if not related:
       -                raise UnrelatedTransactionException()
       -
                    # save
                    self.transactions[tx_hash] = tx
                    return True
        
            def remove_transaction(self, tx_hash):
                def undo_spend(outpoint_to_txid_map):
       -            if tx:
       -                # if we have the tx, this should often be faster
       -                for txi in tx.inputs():
       -                    ser = Transaction.get_outpoint_from_txin(txi)
       +            for addr, l in self.txi[tx_hash].items():
       +                for ser, v in l:
                            outpoint_to_txid_map.pop(ser, None)
       -            else:
       -                for ser, hh in list(outpoint_to_txid_map.items()):
       -                    if hh == tx_hash:
       -                        outpoint_to_txid_map.pop(ser)
        
                with self.transaction_lock:
                    self.print_error("removing tx from history", tx_hash)
       -            #tx = self.transactions.pop(tx_hash)
       -            tx = self.transactions.get(tx_hash, None)
       +            self.transactions.pop(tx_hash, None)
                    undo_spend(self.pruned_txo)
                    undo_spend(self.spent_outpoints)
        
       t@@ -873,13 +872,17 @@ class Abstract_Wallet(PrintError):
        
            def receive_history_callback(self, addr, hist, tx_fees):
                with self.lock:
       -            old_hist = self.history.get(addr, [])
       +            old_hist = self.get_address_history(addr)
                    for tx_hash, height in old_hist:
                        if (tx_hash, height) not in hist:
                            # make tx local
                            self.unverified_tx.pop(tx_hash, None)
                            self.verified_tx.pop(tx_hash, None)
                            self.verifier.merkle_roots.pop(tx_hash, None)
       +                    # but remove completely if not is_mine
       +                    if self.txi[tx_hash] == {}:
       +                        # FIXME the test here should be for "not all is_mine"; cannot detect conflict in some cases
       +                        self.remove_transaction(tx_hash)
                    self.history[addr] = hist
        
                for tx_hash, tx_height in hist:
       t@@ -981,14 +984,9 @@ class Abstract_Wallet(PrintError):
                        output_addresses = []
                        for x in tx.inputs():
                            if x['type'] == 'coinbase': continue
       -                    addr = x.get('address')
       -                    if addr == None: continue
       -                    if addr == "(pubkey)":
       -                        prevout_hash = x.get('prevout_hash')
       -                        prevout_n = x.get('prevout_n')
       -                        _addr = self.wallet.find_pay_to_pubkey_address(prevout_hash, prevout_n)
       -                        if _addr:
       -                            addr = _addr
       +                    addr = self.get_txin_address(x)
       +                    if addr is None:
       +                        continue
                            input_addresses.append(addr)
                        for addr, v in tx.get_outputs():
                            output_addresses.append(addr)