electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

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:
Mgui/kivy/uix/screens.py | 42+++++++++++++++++-------------------------
Mgui/qt/history_list.py | 51+++++++++++++++++++++------------------------------
Mlib/synchronizer.py | 5++++-
Mlib/wallet.py | 46++++++++++++++++++++++++++++++++++++++++++++--
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