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)