URI: 
       tshow warning icon if unconfirmed tx has low fee. fixes 1798 - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 599906eef61832718b080b87612963f3077ce9be
   DIR parent bce42cb496e2516420623a455a6080848a2d3a7c
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon, 30 May 2016 18:26:58 +0200
       
       show warning icon if unconfirmed tx has low fee. fixes 1798
       
       Diffstat:
         M gui/kivy/uix/screens.py             |      42 +++++++++++++------------------
         M gui/qt/history_list.py              |      51 +++++++++++++------------------
         M lib/synchronizer.py                 |       5 ++++-
         M lib/wallet.py                       |      46 +++++++++++++++++++++++++++++--
       
       4 files changed, 86 insertions(+), 58 deletions(-)
       ---
   DIR diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py
       t@@ -88,6 +88,20 @@ class CScreen(Factory.Screen):
                self.add_widget(self.context_menu)
        
        
       +TX_ICONS = [
       +    "close",
       +    "close",
       +    "close",
       +    "unconfirmed",
       +    "close",
       +    "clock1",
       +    "clock2",
       +    "clock3",
       +    "clock4",
       +    "clock5",
       +    "confirmed",
       +]
       +
        class HistoryScreen(CScreen):
        
            tab = ObjectProperty(None)
       t@@ -119,30 +133,8 @@ class HistoryScreen(CScreen):
            def parse_history(self, items):
                for item in items:
                    tx_hash, height, conf, timestamp, value, balance = item
       -            time_str = datetime.datetime.fromtimestamp(timestamp).isoformat(' ')[:-3] if timestamp else _("unknown")
       -            if conf == 0:
       -                tx = self.app.wallet.transactions.get(tx_hash)
       -                is_final = tx.is_final()
       -            else:
       -                is_final = True
       -            if not is_final:
       -                time_str = _('Replaceable')
       -                icon = "atlas://gui/kivy/theming/light/close"
       -            elif height < 0:
       -                time_str = _('Unconfirmed inputs')
       -                icon = "atlas://gui/kivy/theming/light/close"
       -            elif height == 0:
       -                time_str = _('Unconfirmed')
       -                icon = "atlas://gui/kivy/theming/light/unconfirmed"
       -            elif conf == 0:
       -                time_str = _('Not Verified')
       -                icon = "atlas://gui/kivy/theming/light/close"
       -            elif conf < 6:
       -                conf = max(1, conf)
       -                icon = "atlas://gui/kivy/theming/light/clock{}".format(conf)
       -            else:
       -                icon = "atlas://gui/kivy/theming/light/confirmed"
       -
       +            status, status_str = self.app.wallet.get_tx_status(tx_hash, height, conf, timestamp)
       +            icon = "atlas://gui/kivy/theming/light/" + TX_ICONS[status]
                    label = self.app.wallet.get_label(tx_hash) if tx_hash else _('Pruned transaction outputs')
                    date = timestamp_to_datetime(timestamp)
                    quote_text = ''
       t@@ -151,7 +143,7 @@ class HistoryScreen(CScreen):
                        if rate:
                            s = run_hook('value_str', value, rate)
                            quote_text = '' if s is None else s + ' ' + self.app.fiat_unit
       -            yield (conf, icon, time_str, label, value, tx_hash, quote_text)
       +            yield (conf, icon, status_str, label, value, tx_hash, quote_text)
        
            def update(self, see_all=False):
                if self.app.wallet is None:
   DIR diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py
       t@@ -32,6 +32,21 @@ from electrum.util import block_explorer_URL, format_satoshis, format_time
        from electrum.plugins import run_hook
        
        
       +TX_ICONS = [
       +    "warning.png",
       +    "warning.png",
       +    "warning.png",
       +    "unconfirmed.png",
       +    "unconfirmed.png",
       +    "clock1.png",
       +    "clock2.png",
       +    "clock3.png",
       +    "clock4.png",
       +    "clock5.png",
       +    "confirmed.png",
       +]
       +
       +
        class HistoryList(MyTreeWidget):
        
            def __init__(self, parent=None):
       t@@ -40,31 +55,10 @@ class HistoryList(MyTreeWidget):
                self.setColumnHidden(1, True)
        
            def refresh_headers(self):
       -        headers = ['', '', _('Date'), _('Description') , _('Amount'),
       -                   _('Balance')]
       +        headers = ['', '', _('Date'), _('Description') , _('Amount'), _('Balance')]
                run_hook('history_tab_headers', headers)
                self.update_headers(headers)
        
       -    def get_icon(self, height, conf, timestamp, is_final):
       -        time_str = format_time(timestamp) if timestamp else _("unknown")
       -        if not is_final:
       -            time_str = _('Replaceable')
       -            icon = QIcon(":icons/warning.png")
       -        elif height < 0:
       -            time_str = _('Unconfirmed inputs')
       -            icon = QIcon(":icons/warning.png")
       -        elif height == 0:
       -            time_str = _('Unconfirmed')
       -            icon = QIcon(":icons/unconfirmed.png")
       -        elif conf == 0:
       -            time_str = _('Not Verified')
       -            icon = QIcon(":icons/unconfirmed.png")
       -        elif conf < 6:
       -            icon = QIcon(":icons/clock%d.png"%conf)
       -        else:
       -            icon = QIcon(":icons/confirmed.png")
       -        return icon, time_str
       -
            def get_domain(self):
                '''Replaced in address_dialog.py'''
                return self.wallet.get_account_addresses(self.parent.current_account)
       t@@ -78,16 +72,12 @@ class HistoryList(MyTreeWidget):
                run_hook('history_tab_update_begin')
                for h_item in h:
                    tx_hash, height, conf, timestamp, value, balance = h_item
       -            if conf == 0:
       -                tx = self.wallet.transactions.get(tx_hash)
       -                is_final = tx and tx.is_final()
       -            else:
       -                is_final = True
       -            icon, time_str = self.get_icon(height, conf, timestamp, is_final)
       +            status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp)
       +            icon = QIcon(":icons/" + TX_ICONS[status])
                    v_str = self.parent.format_amount(value, True, whitespaces=True)
                    balance_str = self.parent.format_amount(balance, whitespaces=True)
                    label = self.wallet.get_label(tx_hash)
       -            entry = ['', tx_hash, time_str, label, v_str, balance_str]
       +            entry = ['', tx_hash, status_str, label, v_str, balance_str]
                    run_hook('history_tab_update', h_item, entry)
                    item = QTreeWidgetItem(entry)
                    item.setIcon(0, icon)
       t@@ -106,7 +96,8 @@ class HistoryList(MyTreeWidget):
                        self.setCurrentItem(item)
        
            def update_item(self, tx_hash, height, conf, timestamp):
       -        icon, time_str = self.get_icon(height, conf, timestamp, True)
       +        status, status_str = self.wallet.get_tx_status(tx_hash, height, conf, timestamp)
       +        icon = QIcon(":icons/" +  TX_ICONS[status])
                items = self.findItems(tx_hash, Qt.UserRole|Qt.MatchContains|Qt.MatchRecursive, column=1)
                if items:
                    item = items[0]
   DIR diff --git a/lib/synchronizer.py b/lib/synchronizer.py
       t@@ -111,6 +111,9 @@ class Synchronizer(ThreadJob):
                server_status = self.requested_histories[addr]
                hashes = set(map(lambda item: item['tx_hash'], result))
                hist = map(lambda item: (item['tx_hash'], item['height']), result)
       +        # tx_fees
       +        tx_fees = [(item['tx_hash'], item.get('fee')) for item in result]
       +        tx_fees = dict(filter(lambda x:x[1] is not None, tx_fees))
                # Note if the server hasn't been patched to sort the items properly
                if hist != sorted(hist, key=lambda x:x[1]):
                    self.network.interface.print_error("serving improperly sorted address histories")
       t@@ -122,7 +125,7 @@ class Synchronizer(ThreadJob):
                    self.print_error("error: status mismatch: %s" % addr)
                else:
                    # Store received history
       -            self.wallet.receive_history_callback(addr, hist)
       +            self.wallet.receive_history_callback(addr, hist, tx_fees)
                    # Request transactions we don't have
                    self.request_missing_txs(hist)
                # Remove request; this allows up_to_date to be True
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -57,6 +57,16 @@ import paymentrequest
        # internal ID for imported account
        IMPORTED_ACCOUNT = '/x'
        
       +
       +TX_STATUS = [
       +    _('Replaceable'),
       +    _('Unconfirmed parent'),
       +    _('Low fee'),
       +    _('Unconfirmed'),
       +    _('Not Verified'),
       +]
       +
       +
        class WalletStorage(PrintError):
        
            def __init__(self, path):
       t@@ -225,6 +235,7 @@ class Abstract_Wallet(PrintError):
            def load_transactions(self):
                self.txi = self.storage.get('txi', {})
                self.txo = self.storage.get('txo', {})
       +        self.tx_fees = self.storage.get('tx_fees', {})
                self.pruned_txo = self.storage.get('pruned_txo', {})
                tx_list = self.storage.get('transactions', {})
                self.transactions = {}
       t@@ -244,6 +255,7 @@ class Abstract_Wallet(PrintError):
                    self.storage.put('transactions', tx)
                    self.storage.put('txi', self.txi)
                    self.storage.put('txo', self.txo)
       +            self.storage.put('tx_fees', self.tx_fees)
                    self.storage.put('pruned_txo', self.pruned_txo)
                    self.storage.put('addr_history', self.history)
                    if write:
       t@@ -253,6 +265,7 @@ class Abstract_Wallet(PrintError):
                with self.transaction_lock:
                    self.txi = {}
                    self.txo = {}
       +            self.tx_fees = {}
                    self.pruned_txo = {}
                self.save_transactions()
                with self.lock:
       t@@ -804,8 +817,7 @@ class Abstract_Wallet(PrintError):
                self.save_transactions()
                self.add_unverified_tx(tx_hash, tx_height)
        
       -
       -    def receive_history_callback(self, addr, hist):
       +    def receive_history_callback(self, addr, hist, tx_fees):
                with self.lock:
                    old_hist = self.history.get(addr, [])
                    for tx_hash, height in old_hist:
       t@@ -830,6 +842,8 @@ class Abstract_Wallet(PrintError):
        
                # Write updated TXI, TXO etc.
                self.save_transactions()
       +        # Store fees
       +        self.tx_fees.update(tx_fees)
        
            def get_history(self, domain=None):
                # get domain
       t@@ -905,6 +919,34 @@ class Abstract_Wallet(PrintError):
                else:
                    return F
        
       +    def get_tx_status(self, tx_hash, height, conf, timestamp):
       +        from util import format_time
       +        if conf == 0:
       +            tx = self.transactions.get(tx_hash)
       +            is_final = tx and tx.is_final()
       +            fee = self.tx_fees.get(tx_hash)
       +            if fee and self.network and self.network.fee:
       +                size = len(tx.raw)/2
       +                network_fee = int(self.network.fee * size / 1000)
       +                is_lowfee = fee < network_fee * 0.25
       +            else:
       +                is_lowfee = False
       +            if not is_final:
       +                status = 0
       +            elif height < 0:
       +                status = 1
       +            elif height == 0 and is_lowfee:
       +                status = 2
       +            elif height == 0:
       +                status = 3
       +            else:
       +                status = 4
       +        else:
       +            status = 4 + min(conf, 6)
       +        time_str = format_time(timestamp) if timestamp else _("unknown")
       +        status_str = TX_STATUS[status] if status < 5 else time_str
       +        return status, status_str
       +
            def relayfee(self):
                RELAY_FEE = 5000
                MAX_RELAY_FEE = 50000