ttx dialog: uniform high fee warnings between GUIs - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit f9f49daad7a9eee80abccd49ce43bee07258fd99 DIR parent 84326cf1f78f60201e5bac44ff306c240147adde HTML Author: SomberNight <somber.night@protonmail.com> Date: Fri, 26 Feb 2021 19:02:24 +0100 ttx dialog: uniform high fee warnings between GUIs Diffstat: M electrum/gui/kivy/uix/dialogs/conf… | 23 ++++++++++++++--------- M electrum/gui/qt/confirm_tx_dialog.… | 23 +++++++---------------- M electrum/gui/qt/main_window.py | 29 ++++++++++++++++------------- M electrum/gui/qt/transaction_dialog… | 41 ++++++++++++++++++++++++------- M electrum/wallet.py | 36 ++++++++++++++++++++++++++++++- 5 files changed, 104 insertions(+), 48 deletions(-) --- DIR diff --git a/electrum/gui/kivy/uix/dialogs/confirm_tx_dialog.py b/electrum/gui/kivy/uix/dialogs/confirm_tx_dialog.py t@@ -1,3 +1,6 @@ +from decimal import Decimal +from typing import TYPE_CHECKING + from kivy.app import App from kivy.factory import Factory from kivy.properties import ObjectProperty t@@ -7,8 +10,6 @@ from kivy.uix.label import Label from kivy.uix.widget import Widget from kivy.clock import Clock -from decimal import Decimal - from electrum.simple_config import FEERATE_WARNING_HIGH_FEE, FEE_RATIO_HIGH_WARNING from electrum.gui.kivy.i18n import _ from electrum.plugin import run_hook t@@ -16,6 +17,9 @@ from electrum.util import NotEnoughFunds from .fee_dialog import FeeSliderDialog, FeeDialog +if TYPE_CHECKING: + from electrum.gui.kivy.main_window import ElectrumWindow + Builder.load_string(''' <ConfirmTxDialog@Popup> id: popup t@@ -106,7 +110,7 @@ Builder.load_string(''' class ConfirmTxDialog(FeeSliderDialog, Factory.Popup): - def __init__(self, app, invoice): + def __init__(self, app: 'ElectrumWindow', invoice): Factory.Popup.__init__(self) FeeSliderDialog.__init__(self, app.electrum_config, self.ids.slider) t@@ -133,8 +137,9 @@ class ConfirmTxDialog(FeeSliderDialog, Factory.Popup): rbf = not bool(self.ids.final_cb.active) if self.show_final else False tx.set_rbf(rbf) amount = sum(map(lambda x: x.value, outputs)) if '!' not in [x.value for x in outputs] else tx.output_value() + tx_size = tx.estimated_size() fee = tx.get_fee() - feerate = Decimal(fee) / tx.estimated_size() # sat/byte + feerate = Decimal(fee) / tx_size # sat/byte self.ids.fee_label.text = self.app.format_amount_and_units(fee) + f' ({feerate:.1f} sat/B)' self.ids.amount_label.text = self.app.format_amount_and_units(amount) x_fee = run_hook('get_tx_extra_fee', self.app.wallet, tx) t@@ -143,11 +148,11 @@ class ConfirmTxDialog(FeeSliderDialog, Factory.Popup): self.extra_fee = self.app.format_amount_and_units(x_fee_amount) else: self.extra_fee = '' - fee_ratio = Decimal(fee) / amount if amount else 1 - if fee_ratio >= FEE_RATIO_HIGH_WARNING: - self.warning = _('Warning') + ': ' + _("The fee for this transaction seems unusually high.") + f' ({fee_ratio*100:.2f}% of amount)' - elif feerate > FEERATE_WARNING_HIGH_FEE / 1000: - self.warning = _('Warning') + ': ' + _("The fee for this transaction seems unusually high.") + f' (feerate: {feerate:.2f} sat/byte)' + fee_warning_tuple = self.app.wallet.get_tx_fee_warning( + invoice_amt=amount, tx_size=tx_size, fee=fee) + if fee_warning_tuple: + allow_send, long_warning, short_warning = fee_warning_tuple + self.warning = long_warning else: self.warning = '' self.tx = tx DIR diff --git a/electrum/gui/qt/confirm_tx_dialog.py b/electrum/gui/qt/confirm_tx_dialog.py t@@ -239,6 +239,7 @@ class ConfirmTxDialog(TxEditor, WindowModalDialog): return fee = tx.get_fee() + assert fee is not None self.fee_label.setText(self.main_window.format_amount_and_units(fee)) x_fee = run_hook('get_tx_extra_fee', self.wallet, tx) if x_fee: t@@ -248,21 +249,11 @@ class ConfirmTxDialog(TxEditor, WindowModalDialog): self.extra_fee_value.setText(self.main_window.format_amount_and_units(x_fee_amount)) amount = tx.output_value() if self.output_value == '!' else self.output_value - feerate = Decimal(fee) / tx.estimated_size() # sat/byte - fee_ratio = Decimal(fee) / amount if amount else 1 - if feerate < self.wallet.relayfee() / 1000: - msg = '\n'.join([ - _("This transaction requires a higher fee, or it will not be propagated by your current server"), - _("Try to raise your transaction fee, or use a server with a lower relay fee.") - ]) - self.toggle_send_button(False, message=msg) - elif fee_ratio >= FEE_RATIO_HIGH_WARNING: - self.toggle_send_button(True, - message=_('Warning') + ': ' + _("The fee for this transaction seems unusually high.") - + f'\n({fee_ratio*100:.2f}% of amount)') - elif feerate > FEERATE_WARNING_HIGH_FEE / 1000: - self.toggle_send_button(True, - message=_('Warning') + ': ' + _("The fee for this transaction seems unusually high.") - + f'\n(feerate: {feerate:.2f} sat/byte)') + tx_size = tx.estimated_size() + fee_warning_tuple = self.wallet.get_tx_fee_warning( + invoice_amt=amount, tx_size=tx_size, fee=fee) + if fee_warning_tuple: + allow_send, long_warning, short_warning = fee_warning_tuple + self.toggle_send_button(allow_send, message=long_warning) else: self.toggle_send_button(True) DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py t@@ -1670,22 +1670,26 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): return output_value = '!' if '!' in output_values else sum(output_values) - d = ConfirmTxDialog(window=self, make_tx=make_tx, output_value=output_value, is_sweep=is_sweep) - if d.not_enough_funds: + conf_dlg = ConfirmTxDialog(window=self, make_tx=make_tx, output_value=output_value, is_sweep=is_sweep) + if conf_dlg.not_enough_funds: # Check if we had enough funds excluding fees, # if so, still provide opportunity to set lower fees. - if not d.have_enough_funds_assuming_zero_fees(): + if not conf_dlg.have_enough_funds_assuming_zero_fees(): text = self.get_text_not_enough_funds_mentioning_frozen() self.show_message(text) return # shortcut to advanced preview (after "enough funds" check!) if self.config.get('advanced_preview'): - self.preview_tx_dialog(make_tx=make_tx, - external_keypairs=external_keypairs) + preview_dlg = PreviewTxDialog( + window=self, + make_tx=make_tx, + external_keypairs=external_keypairs, + output_value=output_value) + preview_dlg.show() return - cancelled, is_send, password, tx = d.run() + cancelled, is_send, password, tx = conf_dlg.run() if cancelled: return if is_send: t@@ -1696,13 +1700,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): self.sign_tx_with_password(tx, callback=sign_done, password=password, external_keypairs=external_keypairs) else: - self.preview_tx_dialog(make_tx=make_tx, - external_keypairs=external_keypairs) - - def preview_tx_dialog(self, *, make_tx, external_keypairs=None): - d = PreviewTxDialog(make_tx=make_tx, external_keypairs=external_keypairs, - window=self) - d.show() + preview_dlg = PreviewTxDialog( + window=self, + make_tx=make_tx, + external_keypairs=external_keypairs, + output_value=output_value) + preview_dlg.show() def broadcast_or_show(self, tx: Transaction): if not tx.is_complete(): DIR diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py t@@ -28,7 +28,7 @@ import copy import datetime import traceback import time -from typing import TYPE_CHECKING, Callable, Optional, List +from typing import TYPE_CHECKING, Callable, Optional, List, Union from functools import partial from decimal import Decimal t@@ -502,11 +502,22 @@ class BaseTxDialog(QDialog, MessageBoxMixin): size_str = _("Size:") + ' %d bytes'% size fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown')) if fee is not None: - fee_rate = fee/size*1000 - fee_str += ' ( %s ) ' % self.main_window.format_fee_rate(fee_rate) - feerate_warning = simple_config.FEERATE_WARNING_HIGH_FEE - if fee_rate > feerate_warning: - fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!' + fee_rate = Decimal(fee) / size # sat/byte + fee_str += ' ( %s ) ' % self.main_window.format_fee_rate(fee_rate * 1000) + if isinstance(self.tx, PartialTransaction): + if isinstance(self, PreviewTxDialog): + invoice_amt = self.tx.output_value() if self.output_value == '!' else self.output_value + else: + invoice_amt = amount + fee_warning_tuple = self.wallet.get_tx_fee_warning( + invoice_amt=invoice_amt, tx_size=size, fee=fee) + if fee_warning_tuple: + allow_send, long_warning, short_warning = fee_warning_tuple + fee_str += " - <font color={color}>{header}: {body}</font>".format( + header=_('Warning'), + body=short_warning, + color=ColorScheme.RED.as_color().name(), + ) if isinstance(self.tx, PartialTransaction): risk_of_burning_coins = (can_sign and fee is not None and self.wallet.get_warning_for_risk_of_burning_coins_as_fees(self.tx)) t@@ -742,11 +753,23 @@ class TxDialog(BaseTxDialog): self.update() - class PreviewTxDialog(BaseTxDialog, TxEditor): - def __init__(self, *, make_tx, external_keypairs, window: 'ElectrumWindow'): - TxEditor.__init__(self, window=window, make_tx=make_tx, is_sweep=bool(external_keypairs)) + def __init__( + self, + *, + make_tx, + external_keypairs, + window: 'ElectrumWindow', + output_value: Union[int, str], + ): + TxEditor.__init__( + self, + window=window, + make_tx=make_tx, + is_sweep=bool(external_keypairs), + output_value=output_value, + ) BaseTxDialog.__init__(self, parent=window, desc='', prompt_if_unsaved=False, finalized=False, external_keypairs=external_keypairs) BlockingWaitingDialog(window, _("Preparing transaction..."), DIR diff --git a/electrum/wallet.py b/electrum/wallet.py t@@ -58,7 +58,7 @@ from .util import (NotEnoughFunds, UserCancelled, profiler, InvalidPassword, format_time, timestamp_to_datetime, Satoshis, Fiat, bfh, bh2u, TxMinedInfo, quantize_feerate, create_bip21_uri, OrderedDictWithIndex) from .util import get_backup_dir -from .simple_config import SimpleConfig +from .simple_config import SimpleConfig, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE from .bitcoin import COIN, TYPE_ADDRESS from .bitcoin import is_address, address_to_script, is_minikey, relayfee, dust_threshold from .crypto import sha256d t@@ -2451,6 +2451,40 @@ class Abstract_Wallet(AddressSynchronizer, ABC): "do not accept to sign it more than once,\n" "otherwise you could end up paying a different fee.")) + def get_tx_fee_warning( + self, + *, + invoice_amt: int, + tx_size: int, + fee: int, + ) -> Optional[Tuple[bool, str, str]]: + feerate = Decimal(fee) / tx_size # sat/byte + fee_ratio = Decimal(fee) / invoice_amt if invoice_amt else 1 + long_warning = None + short_warning = None + allow_send = True + if feerate < self.relayfee() / 1000: + long_warning = ( + _("This transaction requires a higher fee, or it will not be propagated by your current server") + "\n" + + _("Try to raise your transaction fee, or use a server with a lower relay fee.") + ) + short_warning = _("below relay fee") + "!" + allow_send = False + elif fee_ratio >= FEE_RATIO_HIGH_WARNING: + long_warning = ( + _('Warning') + ': ' + _("The fee for this transaction seems unusually high.") + + f'\n({fee_ratio*100:.2f}% of amount)') + short_warning = _("high fee ratio") + "!" + elif feerate > FEERATE_WARNING_HIGH_FEE / 1000: + long_warning = ( + _('Warning') + ': ' + _("The fee for this transaction seems unusually high.") + + f'\n(feerate: {feerate:.2f} sat/byte)') + short_warning = _("high fee rate") + "!" + if long_warning is None: + return None + else: + return allow_send, long_warning, short_warning + class Simple_Wallet(Abstract_Wallet): # wallet with a single keystore