URI: 
       tMerge branch 'feez' - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 59ed5932a859fd807232960404764b8686355830
   DIR parent 34a550864d21e286aa8cec20c112bc39a445c30e
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon,  9 Jan 2017 12:39:42 +0100
       
       Merge branch 'feez'
       
       Diffstat:
         A gui/qt/fee_slider.py                |      43 ++++++++++++++++++++++++++++++
         M gui/qt/main_window.py               |      80 +++++++++++--------------------
         M lib/bitcoin.py                      |       4 +++-
         M lib/network.py                      |      32 ++++---------------------------
         M lib/simple_config.py                |      41 +++++++++++++++++++++++++++++++
         M lib/wallet.py                       |      16 +++-------------
       
       6 files changed, 122 insertions(+), 94 deletions(-)
       ---
   DIR diff --git a/gui/qt/fee_slider.py b/gui/qt/fee_slider.py
       t@@ -0,0 +1,43 @@
       +from electrum.i18n import _
       +
       +import PyQt4
       +from PyQt4.QtGui import *
       +from PyQt4.QtCore import *
       +import PyQt4.QtCore as QtCore
       +
       +class FeeSlider(QSlider):
       +
       +    def __init__(self, window, config, callback):
       +        QSlider.__init__(self, Qt.Horizontal)
       +        self.config = config
       +        self.fee_step = self.config.max_fee_rate() / 10
       +        self.window = window
       +        self.callback = callback
       +        self.setToolTip('')
       +        self.update()
       +        self.valueChanged.connect(self.moved)
       +
       +    def moved(self, pos):
       +        from electrum.util import fee_levels
       +        dyn = self.config.is_dynfee()
       +        fee_rate = self.config.dynfee(pos) if dyn else pos * self.fee_step
       +        rate_str = self.window.format_amount(fee_rate) + ' ' + self.window.base_unit() + '/kB'
       +        if dyn:
       +            tooltip = fee_levels[pos] + '\n' + rate_str
       +        else:
       +            tooltip = rate_str
       +            if self.config.has_fee_estimates():
       +                i = self.config.reverse_dynfee(fee_rate)
       +                tooltip += '\n' + (_('low fee') if i < 0 else 'Within %d blocks'%i)
       +        QToolTip.showText(QCursor.pos(), tooltip, self)
       +        self.callback(dyn, pos, fee_rate)
       +
       +    def update(self):
       +        if self.config.is_dynfee():
       +            self.setRange(0, 4)
       +            self.setValue(self.config.get('fee_level', 2))
       +        else:
       +            self.setRange(1, 10)
       +            fee_rate = self.config.fee_per_kb()
       +            pos = min(fee_rate / self.fee_step, 10)
       +            self.setValue(pos)
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -59,7 +59,7 @@ from network_dialog import NetworkDialog
        from qrcodewidget import QRCodeWidget, QRDialog
        from qrtextedit import ShowQRTextEdit
        from transaction_dialog import show_transaction
       -
       +from fee_slider import FeeSlider
        
        
        from electrum import ELECTRUM_VERSION
       t@@ -161,7 +161,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if self.network:
                    self.connect(self, QtCore.SIGNAL('network'), self.on_network_qt)
                    interests = ['updated', 'new_transaction', 'status',
       -                         'banner', 'verified']
       +                         'banner', 'verified', 'fee']
                    # To avoid leaking references to "self" that prevent the
                    # window from being GC-ed when closed, callbacks should be
                    # methods of this class only, and specifically not be
       t@@ -169,12 +169,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    self.network.register_callback(self.on_network, interests)
                    # set initial message
                    self.console.showMessage(self.network.banner)
       -
                    self.network.register_callback(self.on_quotes, ['on_quotes'])
                    self.network.register_callback(self.on_history, ['on_history'])
                    self.connect(self, SIGNAL('new_fx_quotes'), self.on_fx_quotes)
                    self.connect(self, SIGNAL('new_fx_history'), self.on_fx_history)
        
       +        # update fee slider in case we missed the callback
       +        self.fee_slider.update()
                self.load_wallet(wallet)
                self.connect_slots(gui_object.timer)
                self.fetch_alias()
       t@@ -259,7 +260,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    self.need_update.set()
                elif event == 'new_transaction':
                    self.tx_notifications.append(args[0])
       -        elif event in ['status', 'banner', 'verified']:
       +        elif event in ['status', 'banner', 'verified', 'fee']:
                    # Handle in GUI thread
                    self.emit(QtCore.SIGNAL('network'), event, *args)
                else:
       t@@ -273,6 +274,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    self.console.showMessage(args[0])
                elif event == 'verified':
                    self.history_list.update_item(*args)
       +        elif event == 'fee':
       +            if self.config.is_dynfee():
       +                self.fee_slider.update()
                else:
                    self.print_error("unexpected network_qt signal:", event, args)
        
       t@@ -996,13 +1000,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                      + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')
                self.fee_e_label = HelpLabel(_('Fee'), msg)
        
       -        fee_cb = lambda x: self.spend_max() if self.is_max else self.update_fee()
       -        self.fee_slider = self.create_fee_slider(self, fee_cb)
       -        self.fee_slider.setValue(self.config.get('fee_level', 2))
       +        def fee_cb(dyn, pos, fee_rate):
       +            if dyn:
       +                self.config.set_key('fee_level', pos, False)
       +            else:
       +                self.config.set_key('fee_per_kb', fee_rate, False)
       +            self.spend_max() if self.is_max else self.update_fee()
       +
       +        self.fee_slider = FeeSlider(self, self.config, fee_cb)
                self.fee_slider.setFixedWidth(140)
        
                self.fee_e = BTCAmountEdit(self.get_decimal_point)
       -        self.fee_e.setVisible(self.config.get('show_fee', False))
       +        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.
       t@@ -1275,9 +1285,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if extra_fee:
                    msg.append( _("Additional fees") + ": " + self.format_amount_and_units(extra_fee) )
        
       -        confirm_rate = self.config.get('confirm_fee', 200000)
       +        confirm_rate = 2 * self.config.max_fee_rate()
                if tx.get_fee() > confirm_rate * tx.estimated_size() / 1000:
       -            msg.append(_('Warning')+ ': ' + _("The fee for this transaction seems unusually high."))
       +            msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high."))
        
                if self.wallet.has_password():
                    msg.append("")
       t@@ -2370,28 +2380,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                nz.valueChanged.connect(on_nz)
                gui_widgets.append((nz_label, nz))
        
       -        dynfee_cb = QCheckBox(_('Use static fees'))
       -        dynfee_cb.setChecked(not self.config.get('dynamic_fees', True))
       -        dynfee_cb.setToolTip(_("Do not use fees recommended by the server."))
       -        fee_e = BTCkBEdit(self.get_decimal_point)
       -        def on_fee(is_done):
       -            if self.config.get('dynamic_fees', True):
       -                return
       -            v = fee_e.get_amount() or 0
       -            self.config.set_key('fee_per_kb', v, is_done)
       -            self.update_fee()
       -        def update_feeperkb():
       -            fee_e.setAmount(self.config.get('fee_per_kb', bitcoin.RECOMMENDED_FEE))
       -            b = self.config.get('dynamic_fees', True)
       -            fee_e.setEnabled(not b)
       +        dynfee_cb = QCheckBox(_('Use dynamic fees'))
       +        dynfee_cb.setChecked(self.config.get('dynamic_fees', True))
       +        dynfee_cb.setToolTip(_("Use fees recommended by the server."))
                def on_dynfee(x):
       -            self.config.set_key('dynamic_fees', x != Qt.Checked)
       -            update_feeperkb()
       -        fee_e.editingFinished.connect(lambda: on_fee(True))
       -        fee_e.textEdited.connect(lambda: on_fee(False))
       +            self.config.set_key('dynamic_fees', x == Qt.Checked)
       +            self.fee_slider.update()
                dynfee_cb.stateChanged.connect(on_dynfee)
       -        update_feeperkb()
       -        fee_widgets.append((dynfee_cb, fee_e))
       +        fee_widgets.append((dynfee_cb, None))
        
                feebox_cb = QCheckBox(_('Edit fees manually'))
                feebox_cb.setChecked(self.config.get('show_fee', False))
       t@@ -2767,25 +2763,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                vbox.addLayout(Buttons(CloseButton(d)))
                d.exec_()
        
       -    def create_fee_slider(self, parent, callback):
       -        from electrum.util import fee_levels
       -        from electrum.bitcoin import FEE_STEP, RECOMMENDED_FEE
       -        fee_slider = QSlider(Qt.Horizontal, parent)
       -        def slider_moved():
       -            pos = fee_slider.sliderPosition()
       -            self.config.set_key('fee_level', pos, False)
       -            is_dyn = self.config.get('dynamic_fees') and self.network and self.network.dynfee(pos)
       -            fee_rate = self.wallet.fee_per_kb(self.config)
       -            tooltip = fee_levels[pos] + '\n' if is_dyn else ''
       -            tooltip += self.format_amount(fee_rate) + ' ' + self.base_unit() + '/kB'
       -            QToolTip.showText(QCursor.pos(), tooltip, self.fee_slider)
       -            callback(fee_rate)
       -
       -        fee_slider.setRange(0, 4)
       -        fee_slider.setToolTip('')
       -        fee_slider.setValue(self.config.get('fee_level', 2))
       -        fee_slider.valueChanged.connect(slider_moved)
       -        return fee_slider
        
            def bump_fee_dialog(self, tx):
                is_relevant, is_mine, v, fee = self.wallet.get_wallet_delta(tx)
       t@@ -2799,12 +2776,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                fee_e.setAmount(fee * 1.5)
                vbox.addWidget(fee_e)
        
       -        def on_rate(fee_rate):
       +        def on_rate(dyn, pos, fee_rate):
                    fee = fee_rate * tx_size / 1000
                    fee_e.setAmount(fee)
       -        fee_slider = self.create_fee_slider(d, on_rate)
       +        fee_slider = FeeSlider(self, self.config, on_rate)
                vbox.addWidget(fee_slider)
       -
                cb = QCheckBox(_('Final'))
                vbox.addWidget(cb)
                vbox.addLayout(Buttons(CancelButton(d), OkButton(d)))
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -58,7 +58,9 @@ def set_testnet():
        ################################## transactions
        
        FEE_STEP = 10000
       -RECOMMENDED_FEE = 50000
       +MAX_FEE_RATE = 100000
       +FEE_TARGETS = [25, 10, 5, 2]
       +
        COINBASE_MATURITY = 100
        COIN = 100000000
        
   DIR diff --git a/lib/network.py b/lib/network.py
       t@@ -37,13 +37,12 @@ import socket
        import json
        
        import util
       +import bitcoin
        from bitcoin import *
        from interface import Connection, Interface
        from blockchain import Blockchain
        from version import ELECTRUM_VERSION, PROTOCOL_VERSION
        
       -FEE_TARGETS = [25, 10, 5, 2]
       -
        DEFAULT_PORTS = {'t':'50001', 's':'50002'}
        
        DEFAULT_SERVERS = {
       t@@ -202,7 +201,6 @@ class Network(util.DaemonThread):
        
                self.banner = ''
                self.donation_address = ''
       -        self.fee_estimates = {}
                self.relay_fee = None
                self.heights = {}
                self.merkle_roots = {}
       t@@ -326,7 +324,7 @@ class Network(util.DaemonThread):
                self.queue_request('server.banner', [])
                self.queue_request('server.donation_address', [])
                self.queue_request('server.peers.subscribe', [])
       -        for i in FEE_TARGETS:
       +        for i in bitcoin.FEE_TARGETS:
                    self.queue_request('blockchain.estimatefee', [i])
                self.queue_request('blockchain.relayfee', [])
        
       t@@ -336,7 +334,7 @@ class Network(util.DaemonThread):
                elif key == 'banner':
                    value = self.banner
                elif key == 'fee':
       -            value = self.fee_estimates
       +            value = self.config.fee_estimates
                elif key == 'updated':
                    value = (self.get_local_height(), self.get_server_height())
                elif key == 'servers':
       t@@ -345,28 +343,6 @@ class Network(util.DaemonThread):
                    value = self.get_interfaces()
                return value
        
       -    def dynfee(self, i):
       -        from bitcoin import RECOMMENDED_FEE
       -        if i < 4:
       -            j = FEE_TARGETS[i]
       -            fee = self.fee_estimates.get(j)
       -        else:
       -            assert i == 4
       -            fee = self.fee_estimates.get(2)
       -            if fee is not None:
       -                fee += fee/2
       -        if fee is not None:
       -            fee = min(10*RECOMMENDED_FEE, fee)
       -        return fee
       -
       -    def reverse_dynfee(self, fee_per_kb):
       -        import operator
       -        dist = map(lambda x: (x[0], abs(x[1] - fee_per_kb)), self.fee_estimates.items())
       -        min_target, min_value = min(dist, key=operator.itemgetter(1))
       -        if fee_per_kb < self.fee_estimates.get(25)/2:
       -            min_target = -1
       -        return min_target
       -
            def notify(self, key):
                if key in ['status', 'updated']:
                    self.trigger_callback(key)
       t@@ -550,7 +526,7 @@ class Network(util.DaemonThread):
                elif method == 'blockchain.estimatefee':
                    if error is None:
                        i = params[0]
       -                self.fee_estimates[i] = int(result * COIN)
       +                self.config.fee_estimates[i] = int(result * COIN)
                        self.notify('fee')
                elif method == 'blockchain.relayfee':
                    if error is None:
   DIR diff --git a/lib/simple_config.py b/lib/simple_config.py
       t@@ -6,6 +6,8 @@ import os
        from copy import deepcopy
        from util import user_dir, print_error, print_msg, print_stderr, PrintError
        
       +from bitcoin import MAX_FEE_RATE, FEE_TARGETS
       +
        SYSTEM_CONFIG_PATH = "/etc/electrum.conf"
        
        config = None
       t@@ -40,6 +42,8 @@ class SimpleConfig(PrintError):
                # a thread-safe way.
                self.lock = threading.RLock()
        
       +        self.fee_estimates = {}
       +
                # The following two functions are there for dependency injection when
                # testing.
                if read_system_config_function is None:
       t@@ -190,6 +194,43 @@ class SimpleConfig(PrintError):
                    path = wallet.storage.path
                    self.set_key('gui_last_wallet', path)
        
       +    def max_fee_rate(self):
       +        return self.get('max_fee_rate', MAX_FEE_RATE)
       +
       +    def dynfee(self, i):
       +        if i < 4:
       +            j = FEE_TARGETS[i]
       +            fee = self.fee_estimates.get(j)
       +        else:
       +            assert i == 4
       +            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 reverse_dynfee(self, fee_per_kb):
       +        import operator
       +        dist = map(lambda x: (x[0], abs(x[1] - fee_per_kb)), self.fee_estimates.items())
       +        min_target, min_value = min(dist, key=operator.itemgetter(1))
       +        if fee_per_kb < self.fee_estimates.get(25)/2:
       +            min_target = -1
       +        return min_target
       +
       +    def has_fee_estimates(self):
       +        return len(self.fee_estimates)==4
       +
       +    def is_dynfee(self):
       +        return self.get('dynamic_fees') and self.has_fee_estimates()
       +
       +    def fee_per_kb(self):
       +        dyn = self.is_dynfee()
       +        if dyn:
       +            fee_rate = self.dynfee(self.get('fee_level', 2))
       +        else:
       +            fee_rate = self.get('fee_per_kb', self.max_fee_rate()/2)
       +        return fee_rate
        
        def read_system_config(path=SYSTEM_CONFIG_PATH):
            """Parse and return the system config settings in /etc/electrum.conf."""
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -754,16 +754,6 @@ class Abstract_Wallet(PrintError):
                    return ', '.join(labels)
                return ''
        
       -    def fee_per_kb(self, config):
       -        b = config.get('dynamic_fees', True)
       -        i = config.get('fee_level', 2)
       -        if b and self.network and self.network.dynfee(i):
       -            return self.network.dynfee(i)
       -        else:
       -            fee_per_kb = config.get('fee_per_kb', RECOMMENDED_FEE)
       -            coeff = {0:0.3, 1:0.5, 2:1, 3:1.5, 4:2}
       -            return fee_per_kb * coeff[i]
       -
            def get_tx_status(self, tx_hash, height, conf, timestamp):
                from util import format_time
                if conf == 0:
       t@@ -772,9 +762,9 @@ class Abstract_Wallet(PrintError):
                        return 3, 'unknown'
                    is_final = tx and tx.is_final()
                    fee = self.tx_fees.get(tx_hash)
       -            if fee and self.network and self.network.dynfee(0):
       +            if fee and self.network and self.network.config.has_fee_estimates():
                        size = len(tx.raw)/2
       -                low_fee = int(self.network.dynfee(0)*size/1000)
       +                low_fee = int(self.network.config.dynfee(0)*size/1000)
                        is_lowfee = fee < low_fee * 0.5
                    else:
                        is_lowfee = False
       t@@ -873,7 +863,7 @@ class Abstract_Wallet(PrintError):
                return tx
        
            def estimate_fee(self, config, size):
       -        fee = int(self.fee_per_kb(config) * size / 1000.)
       +        fee = int(config.fee_per_kb() * size / 1000.)
                return fee
        
            def mktx(self, outputs, password, config, fee=None, change_addr=None, domain=None):