commit 599906eef61832718b080b87612963f3077ce9be
parent bce42cb496e2516420623a455a6080848a2d3a7c
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:
4 files changed, 86 insertions(+), 58 deletions(-)
diff --git a/gui/kivy/uix/screens.py b/gui/kivy/uix/screens.py
@@ -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)
@@ -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 = ''
@@ -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:
diff --git a/gui/qt/history_list.py b/gui/qt/history_list.py
@@ -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):
@@ -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)
@@ -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)
@@ -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]
diff --git a/lib/synchronizer.py b/lib/synchronizer.py
@@ -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")
@@ -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
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -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):
@@ -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 = {}
@@ -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:
@@ -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:
@@ -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:
@@ -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
@@ -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