electrum

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

commit 3ba864bbca1ece32316cf60430ddc222b1f748ea
parent 96c86dac27beec00be9556fab0ad2c9569c8059c
Author: SomberNight <somber.night@protonmail.com>
Date:   Mon, 18 Dec 2017 22:26:29 +0100

connect fee edits together

Diffstat:
Mgui/qt/amountedit.py | 17++++++++++++++---
Mgui/qt/main_window.py | 142+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
Mlib/simple_config.py | 6+++++-
Mlib/wallet.py | 10+++++++++-
4 files changed, 130 insertions(+), 45 deletions(-)

diff --git a/gui/qt/amountedit.py b/gui/qt/amountedit.py @@ -81,7 +81,6 @@ class BTCAmountEdit(AmountEdit): def _base_unit(self): p = self.decimal_point() - assert p in [2, 5, 8] if p == 8: return 'BTC' if p == 5: @@ -104,6 +103,18 @@ class BTCAmountEdit(AmountEdit): else: self.setText(format_satoshis_plain(amount, self.decimal_point())) -class BTCkBEdit(BTCAmountEdit): + +class FeerateEdit(BTCAmountEdit): def _base_unit(self): - return BTCAmountEdit._base_unit(self) + '/kB' + p = self.decimal_point() + if p == 2: + return 'mBTC/kB' + if p == 0: + return 'sat/byte' + raise Exception('Unknown base unit') + + def get_amount(self): + sat_per_byte_amount = BTCAmountEdit.get_amount(self) + if sat_per_byte_amount is None: + return None + return 1000 * sat_per_byte_amount diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py @@ -38,7 +38,7 @@ from PyQt5.QtWidgets import * from electrum.util import bh2u, bfh -from electrum import keystore +from electrum import keystore, simple_config from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS, NetworkConstants from electrum.plugins import run_hook from electrum.i18n import _ @@ -54,7 +54,7 @@ try: except: plot_history = None -from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit +from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit from .qrcodewidget import QRCodeWidget, QRDialog from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit from .transaction_dialog import show_transaction @@ -1073,19 +1073,30 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.fee_slider = FeeSlider(self, self.config, fee_cb) self.fee_slider.setFixedWidth(140) + def on_fee_or_feerate(edit_changed, editing_finished): + edit_other = self.feerate_e if edit_changed == self.fee_e else self.fee_e + if editing_finished: + if not edit_changed.get_amount(): + # This is so that when the user blanks the fee and moves on, + # we go back to auto-calculate mode and put a fee back. + edit_changed.setModified(False) + else: + # edit_changed was edited just now, so make sure we will + # freeze the correct fee setting (this) + edit_other.setModified(False) + self.update_fee() + self.size_e = AmountEdit(lambda: 'bytes') - self.size_e.setReadOnly(True) - self.feerate_e = AmountEdit(lambda: self.base_unit() + '/kB' if self.fee_unit else 'sat/bytes') - self.feerate_e.textEdited.connect(self.update_fee) - + self.size_e.setFrozen(True) + + self.feerate_e = FeerateEdit(lambda: 2 if self.fee_unit else 0) + self.feerate_e.textEdited.connect(partial(on_fee_or_feerate, self.feerate_e, False)) + self.feerate_e.editingFinished.connect(partial(on_fee_or_feerate, self.feerate_e, True)) + self.fee_e = BTCAmountEdit(self.get_decimal_point) - - if not self.config.get('show_fee', False): - self.fee_e.setVisible(False) - self.fee_e.textEdited.connect(self.update_fee) - # This is so that when the user blanks the fee and moves on, - # we go back to auto-calculate mode and put a fee back. - self.fee_e.editingFinished.connect(self.update_fee) + self.fee_e.textEdited.connect(partial(on_fee_or_feerate, self.fee_e, False)) + self.fee_e.editingFinished.connect(partial(on_fee_or_feerate, self.fee_e, True)) + self.connect_fields(self, self.amount_e, self.fiat_send_e, self.fee_e) #self.rbf_checkbox = QCheckBox(_('Replaceable')) @@ -1095,13 +1106,28 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): #self.rbf_checkbox.setToolTip('<p>' + ' '.join(msg) + '</p>') #self.rbf_checkbox.setVisible(False) - grid.addWidget(self.fee_e_label, 5, 0) - grid.addWidget(self.feerate_e, 5, 1) - grid.addWidget(self.size_e, 5, 2) - grid.addWidget(self.fee_e, 5, 3) + vbox_feelabel = QVBoxLayout() + vbox_feelabel.addWidget(self.fee_e_label) + vbox_feelabel.addStretch(1) + grid.addLayout(vbox_feelabel, 5, 0) + + self.fee_adv_controls = QWidget() + hbox = QHBoxLayout(self.fee_adv_controls) + hbox.setContentsMargins(0, 0, 0, 0) + hbox.addWidget(self.feerate_e) + hbox.addWidget(self.size_e) + hbox.addWidget(self.fee_e) + + self.size_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet()) - grid.addWidget(self.fee_slider, 6, 1) - #grid.addWidget(self.rbf_checkbox, 5, 3) + vbox_feecontrol = QVBoxLayout() + vbox_feecontrol.addWidget(self.fee_adv_controls) + vbox_feecontrol.addWidget(self.fee_slider) + + grid.addLayout(vbox_feecontrol, 5, 1, 1, 3) + + if not self.config.get('show_fee', False): + self.fee_adv_controls.setVisible(False) self.preview_button = EnterButton(_("Preview"), self.do_preview) self.preview_button.setToolTip(_('Display the details of your transactions before signing it.')) @@ -1112,7 +1138,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): buttons.addWidget(self.clear_button) buttons.addWidget(self.preview_button) buttons.addWidget(self.send_button) - grid.addLayout(buttons, 7, 1, 1, 3) + grid.addLayout(buttons, 6, 1, 1, 3) self.amount_e.shortcut.connect(self.spend_max) self.payto_e.textChanged.connect(self.update_fee) @@ -1126,26 +1152,40 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): def entry_changed(): text = "" + + amt_color = ColorScheme.DEFAULT + fee_color = ColorScheme.DEFAULT + feerate_color = ColorScheme.DEFAULT + if self.not_enough_funds: amt_color, fee_color = ColorScheme.RED, ColorScheme.RED + feerate_color = ColorScheme.RED text = _( "Not enough funds" ) c, u, x = self.wallet.get_frozen_balance() if c+u+x: text += ' (' + self.format_amount(c+u+x).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')' + # blue color denotes auto-filled values elif self.fee_e.isModified(): - amt_color, fee_color = ColorScheme.DEFAULT, ColorScheme.DEFAULT + feerate_color = ColorScheme.BLUE + elif self.feerate_e.isModified(): + fee_color = ColorScheme.BLUE elif self.amount_e.isModified(): - amt_color, fee_color = ColorScheme.DEFAULT, ColorScheme.BLUE + fee_color = ColorScheme.BLUE + feerate_color = ColorScheme.BLUE else: - amt_color, fee_color = ColorScheme.BLUE, ColorScheme.BLUE + amt_color = ColorScheme.BLUE + fee_color = ColorScheme.BLUE + feerate_color = ColorScheme.BLUE self.statusBar().showMessage(text) self.amount_e.setStyleSheet(amt_color.as_stylesheet()) self.fee_e.setStyleSheet(fee_color.as_stylesheet()) + self.feerate_e.setStyleSheet(feerate_color.as_stylesheet()) self.amount_e.textChanged.connect(entry_changed) self.fee_e.textChanged.connect(entry_changed) + self.feerate_e.textChanged.connect(entry_changed) self.invoices_label = QLabel(_('Invoices')) from .invoice_list import InvoiceList @@ -1186,8 +1226,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if not self.config.get('offline') and self.config.is_dynfee() and not self.config.has_fee_estimates(): self.statusBar().showMessage(_('Waiting for fee estimates...')) return False - freeze_fee = (self.fee_e.isModified() - and (self.fee_e.text() or self.fee_e.hasFocus())) + freeze_fee = self.is_send_fee_frozen() + freeze_feerate = self.is_send_feerate_frozen() amount = '!' if self.is_max else self.amount_e.get_amount() if amount is None: if not freeze_fee: @@ -1195,7 +1235,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.not_enough_funds = False self.statusBar().showMessage('') else: - fee = self.fee_e.get_amount() if freeze_fee else None + fee_estimator = self.get_send_fee_estimator() outputs = self.payto_e.get_outputs(self.is_max) if not outputs: _type, addr = self.get_payto_or_dummy() @@ -1203,7 +1243,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): try: is_sweep = bool(self.tx_external_keypairs) tx = self.wallet.make_unsigned_transaction( - self.get_coins(), outputs, self.config, fee, is_sweep=is_sweep) + self.get_coins(), outputs, self.config, + fixed_fee=fee_estimator, is_sweep=is_sweep) self.not_enough_funds = False except NotEnoughFunds: self.not_enough_funds = True @@ -1211,18 +1252,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.fee_e.setAmount(None) return except BaseException: + traceback.print_exc(file=sys.stderr) return size = tx.estimated_size() self.size_e.setAmount(size) - + + fee = tx.get_fee() if not freeze_fee: - fee_rate = self.config.fee_per_kb() - fee = None if self.not_enough_funds else tx.get_fee() + fee = None if self.not_enough_funds else fee self.fee_e.setAmount(fee) - elif fee: - print(size, fee) - fee_rate = fee // size + if not freeze_feerate: + fee_rate = fee // size if fee is not None else None self.feerate_e.setAmount(fee_rate) if self.is_max: @@ -1307,6 +1348,26 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): return func(self, *args, **kwargs) return request_password + def is_send_fee_frozen(self): + return self.fee_e.isVisible() and self.fee_e.isModified() \ + and (self.fee_e.text() or self.fee_e.hasFocus()) + + def is_send_feerate_frozen(self): + return self.feerate_e.isVisible() and self.feerate_e.isModified() \ + and (self.feerate_e.text() or self.feerate_e.hasFocus()) + + def get_send_fee_estimator(self): + if self.is_send_fee_frozen(): + fee_estimator = self.fee_e.get_amount() + elif self.is_send_feerate_frozen(): + amount = self.feerate_e.get_amount() + amount = 0 if amount is None else float(amount) + fee_estimator = partial( + simple_config.SimpleConfig.estimate_fee_for_feerate, amount) + else: + fee_estimator = None + return fee_estimator + def read_send_tab(self): if self.payment_request and self.payment_request.has_expired(): self.show_error(_('Payment request has expired')) @@ -1344,10 +1405,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.show_error(_('Invalid Amount')) return - freeze_fee = self.fee_e.isVisible() and self.fee_e.isModified() and (self.fee_e.text() or self.fee_e.hasFocus()) - fee = self.fee_e.get_amount() if freeze_fee else None + fee_estimator = self.get_send_fee_estimator() coins = self.get_coins() - return outputs, fee, label, coins + return outputs, fee_estimator, label, coins def do_preview(self): self.do_send(preview = True) @@ -1358,11 +1418,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): r = self.read_send_tab() if not r: return - outputs, fee, tx_desc, coins = r + outputs, fee_estimator, tx_desc, coins = r try: is_sweep = bool(self.tx_external_keypairs) tx = self.wallet.make_unsigned_transaction( - coins, outputs, self.config, fee, is_sweep=is_sweep) + coins, outputs, self.config, fixed_fee=fee_estimator, + is_sweep=is_sweep) except NotEnoughFunds: self.show_message(_("Insufficient funds")) return @@ -1581,7 +1642,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.not_enough_funds = False self.payment_request = None self.payto_e.is_pr = False - for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e, self.fee_e]: + for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e, + self.fee_e, self.feerate_e, self.size_e]: e.setText('') e.setFrozen(False) self.set_pay_from([]) @@ -2522,7 +2584,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): feebox_cb.setToolTip(_("Show fee edit box in send tab.")) def on_feebox(x): self.config.set_key('show_fee', x == Qt.Checked) - self.fee_e.setVisible(bool(x)) + self.fee_adv_controls.setVisible(bool(x)) feebox_cb.stateChanged.connect(on_feebox) fee_widgets.append((feebox_cb, None)) diff --git a/lib/simple_config.py b/lib/simple_config.py @@ -253,7 +253,11 @@ class SimpleConfig(PrintError): return fee_rate def estimate_fee(self, size): - return int(self.fee_per_kb() * size / 1000.) + return self.estimate_fee_for_feerate(self.fee_per_kb(), size) + + @classmethod + def estimate_fee_for_feerate(cls, fee_per_kb, size): + return int(fee_per_kb * size / 1000.) def update_fee_estimates(self, key, value): self.fee_estimates[key] = value diff --git a/lib/wallet.py b/lib/wallet.py @@ -34,8 +34,12 @@ import time import json import copy import errno +import traceback from functools import partial from collections import defaultdict +from numbers import Number + +import sys from .i18n import _ from .util import NotEnoughFunds, PrintError, UserCancelled, profiler, format_satoshis @@ -903,8 +907,12 @@ class Abstract_Wallet(PrintError): # Fee estimator if fixed_fee is None: fee_estimator = config.estimate_fee - else: + elif isinstance(fixed_fee, Number): fee_estimator = lambda size: fixed_fee + elif callable(fixed_fee): + fee_estimator = fixed_fee + else: + raise BaseException('Invalid argument fixed_fee: %s' % fixed_fee) if i_max is None: # Let the coin chooser select the coins to spend