URI: 
       thandle pruning in wallet.txi/txo - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit ec11e58added4aac33bb99ec08418ebc58deb000
   DIR parent 3d1fa1e27f06ac8b2f5db5a04e4333fc39a83a84
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Mon, 30 Mar 2015 12:58:52 +0200
       
       handle pruning in wallet.txi/txo
       
       Diffstat:
         M gui/qt/lite_window.py               |       2 +-
         M gui/qt/main_window.py               |      21 +++++++++------------
         M lib/wallet.py                       |     128 +++++++++++++++++++------------
         M plugins/exchange_rate.py            |       2 +-
       
       4 files changed, 89 insertions(+), 64 deletions(-)
       ---
   DIR diff --git a/gui/qt/lite_window.py b/gui/qt/lite_window.py
       t@@ -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))
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -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)
       t@@ -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))
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -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():
       t@@ -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
       t@@ -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, [])
       t@@ -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 = {}
       t@@ -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:
       t@@ -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, []):
       t@@ -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 = {}
       t@@ -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] = []
       t@@ -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:
       t@@ -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)
        
       t@@ -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()
       t@@ -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):
       t@@ -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):
       t@@ -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
   DIR diff --git a/plugins/exchange_rate.py b/plugins/exchange_rate.py
       t@@ -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