URI: 
       tMerge pull request #3943 from SomberNight/fee_cleanup - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 24054ac39986c7bf9e1b6e30671606eec493807d
   DIR parent d38a50b119aa15a2be5cba504eb5d7c0a144d56b
  HTML 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:
         M gui/kivy/uix/dialogs/bump_fee_dial… |      49 ++++++++++++++++---------------
         M gui/kivy/uix/dialogs/fee_dialog.py  |       1 -
         M gui/kivy/uix/dialogs/settings.py    |       1 -
         M gui/qt/main_window.py               |       2 +-
         M gui/qt/transaction_dialog.py        |       9 +++++++--
         M lib/bitcoin.py                      |       4 ----
         M lib/simple_config.py                |      83 ++++++++++++++++++++-----------
         M lib/util.py                         |       1 -
         M lib/wallet.py                       |       4 ++--
       
       9 files changed, 90 insertions(+), 64 deletions(-)
       ---
   DIR diff --git a/gui/kivy/uix/dialogs/bump_fee_dialog.py b/gui/kivy/uix/dialogs/bump_fee_dialog.py
       t@@ -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('''
       t@@ -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:
       t@@ -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()
   DIR diff --git a/gui/kivy/uix/dialogs/fee_dialog.py b/gui/kivy/uix/dialogs/fee_dialog.py
       t@@ -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('''
   DIR diff --git a/gui/kivy/uix/dialogs/settings.py b/gui/kivy/uix/dialogs/settings.py
       t@@ -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
        
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -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."))
        
   DIR diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py
       t@@ -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
       t@@ -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)
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -108,10 +108,6 @@ NetworkConstants.set_mainnet()
        
        ################################## transactions
        
       -FEE_STEP = 10000
       -MAX_FEE_RATE = 300000
       -
       -
        COINBASE_MATURITY = 100
        COIN = 100000000
        
   DIR diff --git a/lib/simple_config.py b/lib/simple_config.py
       t@@ -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
        
        
       t@@ -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):
       t@@ -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)
       t@@ -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):
       t@@ -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:
       t@@ -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):
       t@@ -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()
       t@@ -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:
       t@@ -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):
       t@@ -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):
       t@@ -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()
       t@@ -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):
       t@@ -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))
        
       t@@ -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):
   DIR diff --git a/lib/util.py b/lib/util.py
       t@@ -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(".")]
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -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):