diff --git a/gui/qt/lite_window.py b/gui/qt/lite_window.py index 26c179862..52cd75e20 100644 --- a/gui/qt/lite_window.py +++ b/gui/qt/lite_window.py @@ -450,7 +450,7 @@ class MiniWindow(QDialog): self.history_list.empty() for item in tx_history[-10:]: - tx_hash, conf, value, timestamp = item + tx_hash, conf, value, timestamp, balance = item label = self.actuator.g.wallet.get_label(tx_hash)[0] v_str = self.actuator.g.format_amount(value, True) self.history_list.append(label, v_str, age(timestamp)) diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py index 90646b0d6..62933e542 100644 --- a/gui/qt/main_window.py +++ b/gui/qt/main_window.py @@ -686,9 +686,8 @@ class ElectrumWindow(QMainWindow): def update_history_tab(self): self.history_list.clear() - balance = 0 for item in self.wallet.get_history(self.current_account): - tx_hash, conf, value, timestamp = item + tx_hash, conf, value, timestamp, balance = item time_str = _("unknown") if conf > 0: time_str = self.format_time(timestamp) @@ -706,18 +705,16 @@ class ElectrumWindow(QMainWindow): if value is not None: v_str = self.format_amount(value, True, whitespaces=True) else: - v_str = '--' + v_str = _('unknown') - balance += value - balance_str = self.format_amount(balance, whitespaces=True) - - if tx_hash: - label, is_default_label = self.wallet.get_label(tx_hash) - if is_default_label: - label = '' + if balance is not None: + balance_str = self.format_amount(balance, whitespaces=True) else: - label = _('Pruned transaction outputs') - is_default_label = False + balance_str = _('unknown') + + label, is_default_label = self.wallet.get_label(tx_hash) + if is_default_label: + label = '' item = QTreeWidgetItem( [ '', time_str, label, v_str, balance_str] ) item.setFont(2, QFont(MONOSPACE_FONT)) diff --git a/lib/wallet.py b/lib/wallet.py index 636d78fc4..dec271a22 100644 --- a/lib/wallet.py +++ b/lib/wallet.py @@ -192,7 +192,7 @@ class Abstract_Wallet(object): def load_transactions(self): self.txi = self.storage.get('txi', {}) self.txo = self.storage.get('txo', {}) - self.reverse_txo = self.storage.get('reverse_utxo', {}) + self.pruned_txo = self.storage.get('pruned_txo', {}) tx_list = self.storage.get('transactions', {}) self.transactions = {} for tx_hash, raw in tx_list.items(): @@ -211,7 +211,15 @@ class Abstract_Wallet(object): self.storage.put('transactions', tx) self.storage.put('txi', self.txi) self.storage.put('txo', self.txo) - self.storage.put('reverse_txo', self.reverse_txo) + self.storage.put('pruned_txo', self.pruned_txo) + + def clear_history(self): + with self.transaction_lock: + self.txi = {} + self.txo = {} + self.pruned_txo = {} + self.history = {} + self.save_transactions() def get_action(self): pass @@ -404,6 +412,9 @@ class Abstract_Wallet(object): def get_tx_delta(self, tx_hash, address): "effect of tx on address" + # pruned + if tx_hash in self.pruned_txo.values(): + return None delta = 0 # substract the value of coins sent from address d = self.txi.get(tx_hash, {}).get(address, []) @@ -467,18 +478,6 @@ class Abstract_Wallet(object): fee = v_out - v_in return is_relevant, is_send, v, fee - def get_addr_balance(self, address): - "returns the confirmed balance and pending (unconfirmed) balance change of a bitcoin address" - h = self.history.get(address, []) - c = u = 0 - for tx_hash, height in h: - v = self.get_tx_delta(tx_hash, address) - if height > 0: - c += v - else: - u += v - return c, u - def get_addr_utxo(self, address): h = self.history.get(address, []) coins = {} @@ -492,6 +491,19 @@ class Abstract_Wallet(object): coins.pop(txi) return coins.items() + def get_addr_balance(self, address): + "returns the confirmed balance and pending (unconfirmed) balance change of a bitcoin address" + coins = self.get_addr_utxo(address) + c = u = 0 + for txo, v in coins: + tx_height, v, is_cb = v + if tx_height > 0: + c += v + else: + u += v + return c, u + + def get_unspent_coins(self, domain=None): coins = [] if domain is None: @@ -611,7 +623,8 @@ class Abstract_Wallet(object): if addr: print_error("found pay-to-pubkey address:", addr) else: - self.reverse_txo[ser] = tx_hash + self.pruned_txo[ser] = tx_hash + # find value from prev output if addr and self.is_mine(addr): dd = self.txo.get(prevout_hash, {}) for n, v, is_cb in dd.get(addr, []): @@ -621,7 +634,7 @@ class Abstract_Wallet(object): d[addr].append((ser, v)) break else: - self.reverse_txo[ser] = tx_hash + self.pruned_txo[ser] = tx_hash # add outputs self.txo[tx_hash] = d = {} @@ -638,10 +651,10 @@ class Abstract_Wallet(object): if d.get(addr) is None: d[addr] = [] d[addr].append((n, v, is_coinbase)) - - next_tx = self.reverse_txo.get(ser) + # give v to txi that spends me + next_tx = self.pruned_txo.get(ser) if next_tx is not None: - self.reverse_txo.pop(ser) + self.pruned_txo.pop(ser) dd = self.txi.get(next_tx, {}) if dd.get(addr) is None: dd[addr] = [] @@ -649,11 +662,32 @@ class Abstract_Wallet(object): # save self.transactions[tx_hash] = tx + def remove_transaction(self, tx_hash, tx_height): + with self.transaction_lock: + print_error("removing tx from history", tx_hash) + #tx = self.transactions.pop(tx_hash) + for ser, hh in self.pruned_txo.items(): + if hh == tx_hash: + self.pruned_txo.pop(ser) + # add tx to pruned_txo, and undo the txi addition + for next_tx, dd in self.txi.items(): + for addr, l in dd.items(): + ll = l[:] + for item in ll: + ser, v = item + prev_hash, prev_n = ser.split(':') + if prev_hash == tx_hash: + l.remove(item) + self.pruned_txo[ser] = next_tx + if l == []: + dd.pop(addr) + else: + dd[addr] = l + self.txi.pop(tx_hash) + self.txo.pop(tx_hash) + + def receive_tx_callback(self, tx_hash, tx, tx_height): - if not self.check_new_tx(tx_hash, tx): - # may happen due to pruning - print_error("received transaction that is no longer referenced in history", tx_hash) - return self.add_transaction(tx_hash, tx, tx_height) #self.network.pending_transactions_for_notifications.append(tx) if self.verifier and tx_height>0: @@ -662,10 +696,12 @@ class Abstract_Wallet(object): def receive_history_callback(self, addr, hist): - #if not self.check_new_history(addr, hist): - # raise Exception("error: received history for %s is not consistent with known transactions"%addr) - with self.lock: + old_hist = self.history.get(addr, []) + for tx_hash, height in old_hist: + if (tx_hash, height) not in hist: + self.remove_transaction(tx_hash, height) + self.history[addr] = hist self.storage.put('addr_history', self.history, True) @@ -676,7 +712,6 @@ class Abstract_Wallet(object): self.verifier.add(tx_hash, tx_height) # if addr is new, we have to recompute txi and txo - # fixme: bad interaction with server hist limit? 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() @@ -707,16 +742,26 @@ class Abstract_Wallet(object): # 3. create sorted list history = [] + #balance = 0 for tx_hash, v in merged.items(): height, value = v - is_mine = 1 - fee = 0 - balance = 0 + #balance += value conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None) history.append( (tx_hash, conf, value, timestamp) ) - history.sort(key = lambda x: self.verifier.get_txpos(x[0])) - return history + + c, u = self.get_balance(domain) + balance = c + u + h2 = [] + for item in history[::-1]: + tx_hash, conf, value, timestamp = item + h2.insert( 0, (tx_hash, conf, value, timestamp, balance)) + if balance is not None and value is not None: + balance -= value + else: + balance = None + + return h2 def get_label(self, tx_hash): @@ -924,6 +969,7 @@ class Abstract_Wallet(object): vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys() for tx_hash in self.transactions.keys(): if tx_hash not in vr: + print_error("removing transaction", tx_hash) self.transactions.pop(tx_hash) def check_new_history(self, addr, hist): @@ -981,24 +1027,6 @@ class Abstract_Wallet(object): return True - def check_new_tx(self, tx_hash, tx): - # 1 check that tx is referenced in addr_history. - addresses = [] - for addr, hist in self.history.items(): - for txh, height in hist: - if txh == tx_hash: - addresses.append(addr) - - if not addresses: - return False - - # 2 check that referencing addresses are in the tx - for addr in addresses: - if not tx.has_address(addr): - return False - - return True - def start_threads(self, network): from verifier import SPV self.network = network diff --git a/plugins/exchange_rate.py b/plugins/exchange_rate.py index 16e72e5a4..a645f1ed5 100644 --- a/plugins/exchange_rate.py +++ b/plugins/exchange_rate.py @@ -500,7 +500,7 @@ class Plugin(BasePlugin): def load_wallet(self, wallet): tx_list = {} for item in self.wallet.get_history(self.wallet.storage.get("current_account", None)): - tx_hash, conf, value, timestamp = item + tx_hash, conf, value, timestamp, balance = item tx_list[tx_hash] = {'value': value, 'timestamp': timestamp } self.tx_list = tx_list