electrum

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

commit 24054ac39986c7bf9e1b6e30671606eec493807d
parent d38a50b119aa15a2be5cba504eb5d7c0a144d56b
Author: ThomasV <thomasv@electrum.org>
Date:   Fri, 23 Feb 2018 10:11:20 +0100

Merge pull request #3943 from SomberNight/fee_cleanup

clean up fees a bit
Diffstat:
Mgui/kivy/uix/dialogs/bump_fee_dialog.py | 49++++++++++++++++++++++++++-----------------------
Mgui/kivy/uix/dialogs/fee_dialog.py | 1-
Mgui/kivy/uix/dialogs/settings.py | 1-
Mgui/qt/main_window.py | 2+-
Mgui/qt/transaction_dialog.py | 9+++++++--
Mlib/bitcoin.py | 4----
Mlib/simple_config.py | 83+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mlib/util.py | 1-
Mlib/wallet.py | 4++--
9 files changed, 90 insertions(+), 64 deletions(-)

diff --git a/gui/kivy/uix/dialogs/bump_fee_dialog.py b/gui/kivy/uix/dialogs/bump_fee_dialog.py @@ -3,7 +3,6 @@ from kivy.factory import Factory from kivy.properties import ObjectProperty from kivy.lang import Builder -from electrum.util import fee_levels from electrum_gui.kivy.i18n import _ Builder.load_string(''' @@ -29,7 +28,11 @@ Builder.load_string(''' text: _('New Fee') value: '' Label: - id: tooltip + id: tooltip1 + text: '' + size_hint_y: None + Label: + id: tooltip2 text: '' size_hint_y: None Slider: @@ -72,39 +75,39 @@ class BumpFeeDialog(Factory.Popup): self.tx_size = size self.callback = callback self.config = app.electrum_config - self.fee_step = self.config.max_fee_rate() / 10 - self.dynfees = self.config.is_dynfee() and self.app.network + self.mempool = self.config.use_mempool_fees() + self.dynfees = self.config.is_dynfee() and self.app.network and self.config.has_dynamic_fees_ready() self.ids.old_fee.value = self.app.format_amount_and_units(self.init_fee) self.update_slider() self.update_text() def update_text(self): - value = int(self.ids.slider.value) - self.ids.new_fee.value = self.app.format_amount_and_units(self.get_fee()) - if self.dynfees: - value = int(self.ids.slider.value) - self.ids.tooltip.text = fee_levels[value] + fee = self.get_fee() + self.ids.new_fee.value = self.app.format_amount_and_units(fee) + pos = int(self.ids.slider.value) + fee_rate = self.get_fee_rate() + text, tooltip = self.config.get_fee_text(pos, self.dynfees, self.mempool, fee_rate) + self.ids.tooltip1.text = text + self.ids.tooltip2.text = tooltip def update_slider(self): slider = self.ids.slider + maxp, pos, fee_rate = self.config.get_fee_slider(self.dynfees, self.mempool) + slider.range = (0, maxp) + slider.step = 1 + slider.value = pos + + def get_fee_rate(self): + pos = int(self.ids.slider.value) if self.dynfees: - slider.range = (0, 4) - slider.step = 1 - slider.value = 3 + fee_rate = self.config.depth_to_fee(pos) if self.mempool else self.config.eta_to_fee(pos) else: - slider.range = (1, 10) - slider.step = 1 - rate = self.init_fee*1000//self.tx_size - slider.value = min( rate * 2 // self.fee_step, 10) + fee_rate = self.config.static_fee(pos) + return fee_rate def get_fee(self): - value = int(self.ids.slider.value) - if self.dynfees: - if self.config.has_fee_estimates(): - dynfee = self.config.dynfee(value) - return int(dynfee * self.tx_size // 1000) - else: - return int(value*self.fee_step * self.tx_size // 1000) + fee_rate = self.get_fee_rate() + return int(fee_rate * self.tx_size // 1000) def on_ok(self): new_fee = self.get_fee() diff --git a/gui/kivy/uix/dialogs/fee_dialog.py b/gui/kivy/uix/dialogs/fee_dialog.py @@ -3,7 +3,6 @@ from kivy.factory import Factory from kivy.properties import ObjectProperty from kivy.lang import Builder -from electrum.util import fee_levels from electrum_gui.kivy.i18n import _ Builder.load_string(''' diff --git a/gui/kivy/uix/dialogs/settings.py b/gui/kivy/uix/dialogs/settings.py @@ -8,7 +8,6 @@ from electrum.i18n import languages from electrum_gui.kivy.i18n import _ from electrum.plugins import run_hook from electrum import coinchooser -from electrum.util import fee_levels from .choice_dialog import ChoiceDialog diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py @@ -1512,7 +1512,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): x_fee_address, x_fee_amount = x_fee msg.append( _("Additional fees") + ": " + self.format_amount_and_units(x_fee_amount) ) - confirm_rate = 2 * self.config.max_fee_rate() + confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE if fee > confirm_rate * tx.estimated_size() / 1000: msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high.")) diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py @@ -34,6 +34,7 @@ from PyQt5.QtWidgets import * from electrum.bitcoin import base_encode from electrum.i18n import _ from electrum.plugins import run_hook +from electrum import simple_config from electrum.util import bfh from electrum.wallet import AddTransactionException @@ -240,9 +241,13 @@ class TxDialog(QDialog, MessageBoxMixin): else: amount_str = _("Amount sent:") + ' %s'% format_amount(-amount) + ' ' + base_unit size_str = _("Size:") + ' %d bytes'% size - fee_str = _("Fee") + ': %s'% (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown')) + fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown')) if fee is not None: - fee_str += ' ( %s ) '% self.main_window.format_fee_rate(fee/size*1000) + fee_rate = fee/size*1000 + fee_str += ' ( %s ) ' % self.main_window.format_fee_rate(fee_rate) + confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE + if fee_rate > confirm_rate: + fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!' self.amount_label.setText(amount_str) self.fee_label.setText(fee_str) self.size_label.setText(size_str) diff --git a/lib/bitcoin.py b/lib/bitcoin.py @@ -108,10 +108,6 @@ NetworkConstants.set_mainnet() ################################## transactions -FEE_STEP = 10000 -MAX_FEE_RATE = 300000 - - COINBASE_MATURITY = 100 COIN = 100000000 diff --git a/lib/simple_config.py b/lib/simple_config.py @@ -5,14 +5,22 @@ import os import stat from copy import deepcopy + from .util import (user_dir, print_error, PrintError, NoDynamicFeeEstimates, format_satoshis) - -from .bitcoin import MAX_FEE_RATE +from .i18n import _ FEE_ETA_TARGETS = [25, 10, 5, 2] FEE_DEPTH_TARGETS = [10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000] +# satoshi per kbyte +FEERATE_MAX_DYNAMIC = 1500000 +FEERATE_WARNING_HIGH_FEE = 600000 +FEERATE_FALLBACK_STATIC_FEE = 150000 +FEERATE_DEFAULT_RELAY = 1000 +FEERATE_STATIC_VALUES = [5000, 10000, 20000, 30000, 50000, 70000, 100000, 150000, 200000, 300000] + + config = None @@ -39,7 +47,6 @@ class SimpleConfig(PrintError): 2. User configuration (in the user's config directory) They are taken in order (1. overrides config options set in 2.) """ - fee_rates = [5000, 10000, 20000, 30000, 50000, 70000, 100000, 150000, 200000, 300000] def __init__(self, options=None, read_user_config_function=None, read_user_dir_function=None): @@ -261,13 +268,19 @@ class SimpleConfig(PrintError): path = wallet.storage.path self.set_key('gui_last_wallet', path) - def max_fee_rate(self): - f = self.get('max_fee_rate', MAX_FEE_RATE) - if f==0: - f = MAX_FEE_RATE - return f - + def impose_hard_limits_on_fee(func): + def get_fee_within_limits(self, *args, **kwargs): + fee = func(self, *args, **kwargs) + if fee is None: + return fee + fee = min(FEERATE_MAX_DYNAMIC, fee) + fee = max(FEERATE_DEFAULT_RELAY, fee) + return fee + return get_fee_within_limits + + @impose_hard_limits_on_fee def eta_to_fee(self, i): + """Returns fee in sat/kbyte.""" if i < 4: j = FEE_ETA_TARGETS[i] fee = self.fee_estimates.get(j) @@ -276,8 +289,6 @@ class SimpleConfig(PrintError): fee = self.fee_estimates.get(2) if fee is not None: fee += fee/2 - if fee is not None: - fee = min(5*MAX_FEE_RATE, fee) return fee def fee_to_depth(self, target_fee): @@ -290,7 +301,9 @@ class SimpleConfig(PrintError): return 0 return depth + @impose_hard_limits_on_fee def depth_to_fee(self, i): + """Returns fee in sat/kbyte.""" target = self.depth_target(i) depth = 0 for fee, s in self.mempool_fees: @@ -305,6 +318,8 @@ class SimpleConfig(PrintError): return FEE_DEPTH_TARGETS[i] def eta_target(self, i): + if i == len(FEE_ETA_TARGETS): + return 1 return FEE_ETA_TARGETS[i] def fee_to_eta(self, fee_per_kb): @@ -320,7 +335,12 @@ class SimpleConfig(PrintError): return "%.1f MB from tip"%(depth/1000000) def eta_tooltip(self, x): - return 'Low fee' if x < 0 else 'Within %d blocks'%x + if x < 0: + return _('Low fee') + elif x == 1: + return _('In the next block') + else: + return _('Within {} blocks').format(x) def get_fee_status(self): dyn = self.is_dynfee() @@ -331,6 +351,10 @@ class SimpleConfig(PrintError): return target def get_fee_text(self, pos, dyn, mempool, fee_rate): + """Returns (text, tooltip) where + text is what we target: static fee / num blocks to confirm in / mempool depth + tooltip is the corresponding estimate (e.g. num blocks for a static fee) + """ rate_str = (format_satoshis(fee_rate/1000, False, 0, 0, False) + ' sat/byte') if fee_rate is not None else 'unknown' if dyn: if mempool: @@ -342,18 +366,14 @@ class SimpleConfig(PrintError): tooltip = rate_str else: text = rate_str - if mempool: - if self.has_fee_mempool(): - depth = self.fee_to_depth(fee_rate) - tooltip = self.depth_tooltip(depth) - else: - tooltip = '' + if mempool and self.has_fee_mempool(): + depth = self.fee_to_depth(fee_rate) + tooltip = self.depth_tooltip(depth) + elif not mempool and self.has_fee_etas(): + eta = self.fee_to_eta(fee_rate) + tooltip = self.eta_tooltip(eta) else: - if self.has_fee_etas(): - eta = self.fee_to_eta(fee_rate) - tooltip = self.eta_tooltip(eta) - else: - tooltip = '' + tooltip = '' return text, tooltip def get_depth_level(self): @@ -361,7 +381,7 @@ class SimpleConfig(PrintError): return min(maxp, self.get('depth_level', 2)) def get_fee_level(self): - maxp = len(FEE_ETA_TARGETS) - 1 + maxp = len(FEE_ETA_TARGETS) # not (-1) to have "next block" return min(maxp, self.get('fee_level', 2)) def get_fee_slider(self, dyn, mempool): @@ -372,7 +392,7 @@ class SimpleConfig(PrintError): fee_rate = self.depth_to_fee(pos) else: pos = self.get_fee_level() - maxp = len(FEE_ETA_TARGETS) - 1 + maxp = len(FEE_ETA_TARGETS) # not (-1) to have "next block" fee_rate = self.eta_to_fee(pos) else: fee_rate = self.fee_per_kb() @@ -380,12 +400,11 @@ class SimpleConfig(PrintError): maxp = 9 return maxp, pos, fee_rate - def static_fee(self, i): - return self.fee_rates[i] + return FEERATE_STATIC_VALUES[i] def static_fee_index(self, value): - dist = list(map(lambda x: abs(x - value), self.fee_rates)) + dist = list(map(lambda x: abs(x - value), FEERATE_STATIC_VALUES)) return min(range(len(dist)), key=dist.__getitem__) def has_fee_etas(self): @@ -394,6 +413,12 @@ class SimpleConfig(PrintError): def has_fee_mempool(self): return bool(self.mempool_fees) + def has_dynamic_fees_ready(self): + if self.use_mempool_fees(): + return self.has_fee_mempool() + else: + return self.has_fee_etas() + def is_dynfee(self): return bool(self.get('dynamic_fees', True)) @@ -410,7 +435,7 @@ class SimpleConfig(PrintError): else: fee_rate = self.eta_to_fee(self.get_fee_level()) else: - fee_rate = self.get('fee_per_kb', self.max_fee_rate()/2) + fee_rate = self.get('fee_per_kb', FEERATE_FALLBACK_STATIC_FEE) return fee_rate def fee_per_byte(self): diff --git a/lib/util.py b/lib/util.py @@ -41,7 +41,6 @@ def inv_dict(d): base_units = {'BTC':8, 'mBTC':5, 'uBTC':2} -fee_levels = [_('Within 25 blocks'), _('Within 10 blocks'), _('Within 5 blocks'), _('Within 2 blocks'), _('In the next block')] def normalize_version(v): return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")] diff --git a/lib/wallet.py b/lib/wallet.py @@ -78,9 +78,9 @@ TX_HEIGHT_UNCONFIRMED = 0 def relayfee(network): - RELAY_FEE = 1000 + from .simple_config import FEERATE_DEFAULT_RELAY MAX_RELAY_FEE = 50000 - f = network.relay_fee if network and network.relay_fee else RELAY_FEE + f = network.relay_fee if network and network.relay_fee else FEERATE_DEFAULT_RELAY return min(f, MAX_RELAY_FEE) def dust_threshold(network):