URI: 
       tdynamic fees - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 43880d452e9c0278ff114625d7871b06051647a0
   DIR parent 959620db4646d4ae1d570fef661239197eb4c722
  HTML Author: ThomasV <thomasv@gitorious>
       Date:   Tue,  4 Aug 2015 07:15:54 +0200
       
       dynamic fees
       
       Diffstat:
         M RELEASE-NOTES                       |       1 +
         M gui/android.py                      |       6 ++++--
         M gui/gtk.py                          |       4 ++--
         M gui/qt/amountedit.py                |       4 ++++
         M gui/qt/main_window.py               |      52 ++++++++++++++++++++++---------
         M gui/stdio.py                        |       2 +-
         M gui/text.py                         |       2 +-
         M lib/commands.py                     |       2 +-
         M lib/network.py                      |       8 ++++++++
         M lib/network_proxy.py                |       4 ++++
         M lib/wallet.py                       |      30 ++++++++++++++++--------------
         M scripts/estimate_fee                |       2 +-
       
       12 files changed, 81 insertions(+), 36 deletions(-)
       ---
   DIR diff --git a/RELEASE-NOTES b/RELEASE-NOTES
       t@@ -2,6 +2,7 @@
         * Use ssl.PROTOCOL_TLSv1
         * Fix DNSSEC issues with ECDSA signatures
         * Replace TLSLite dependency with minimal RSA implementation
       + * Dynamic fees, using estimatefee value returned by server
        
        # Release 2.4
         * Payment to DNS names storing a Bitcoin addresses (OpenAlias) is
   DIR diff --git a/gui/android.py b/gui/android.py
       t@@ -444,7 +444,7 @@ def pay_to(recipient, amount, label):
            droid.dialogShow()
        
            try:
       -        tx = wallet.mktx([('address', recipient, amount)], password)
       +        tx = wallet.mktx([('address', recipient, amount)], password, config)
            except Exception as e:
                modal_dialog('error', e.message)
                droid.dialogDismiss()
       t@@ -895,12 +895,14 @@ menu_commands = ["send", "receive", "settings", "contacts", "main"]
        wallet = None
        network = None
        contacts = None
       +config = None
        
        class ElectrumGui:
        
       -    def __init__(self, config, _network):
       +    def __init__(self, _config, _network):
                global wallet, network, contacts
                network = _network
       +        config = _config
                network.register_callback('updated', update_callback)
                network.register_callback('connected', update_callback)
                network.register_callback('disconnected', update_callback)
   DIR diff --git a/gui/gtk.py b/gui/gtk.py
       t@@ -690,7 +690,7 @@ class ElectrumWindow:
                        return
                    coins = self.wallet.get_spendable_coins()
                    try:
       -                tx = self.wallet.make_unsigned_transaction(coins, [('op_return', 'dummy_tx', amount)], fee)
       +                tx = self.wallet.make_unsigned_transaction(coins, [('op_return', 'dummy_tx', amount)], self.config, fee)
                        self.funds_error = False
                    except NotEnoughFunds:
                        self.funds_error = True
       t@@ -812,7 +812,7 @@ class ElectrumWindow:
                    password = None
        
                try:
       -            tx = self.wallet.mktx( [(to_address, amount)], password, fee )
       +            tx = self.wallet.mktx( [(to_address, amount)], password, self.config, fee)
                except Exception as e:
                    self.show_message(str(e))
                    return
   DIR diff --git a/gui/qt/amountedit.py b/gui/qt/amountedit.py
       t@@ -99,3 +99,7 @@ class BTCAmountEdit(AmountEdit):
                    self.setText("")
                else:
                    self.setText(format_satoshis_plain(amount, self.decimal_point()))
       +
       +class BTCkBEdit(BTCAmountEdit):
       +    def _base_unit(self):
       +        return BTCAmountEdit._base_unit(self) + '/kB'
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -46,7 +46,7 @@ from electrum import Imported_Wallet
        from electrum import paymentrequest
        from electrum.contacts import Contacts
        
       -from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
       +from amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, BTCkBEdit
        from network_dialog import NetworkDialog
        from qrcodewidget import QRCodeWidget, QRDialog
        from qrtextedit import ScanQRTextEdit, ShowQRTextEdit
       t@@ -980,7 +980,8 @@ class ElectrumWindow(QMainWindow):
                    output = ('address', addr, sendable)
                    dummy_tx = Transaction.from_io(inputs, [output])
                    if not self.fee_e.isModified():
       -                self.fee_e.setAmount(self.wallet.estimated_fee(dummy_tx))
       +                fee_per_kb = self.wallet.fee_per_kb(self.config)
       +                self.fee_e.setAmount(self.wallet.estimated_fee(dummy_tx, fee_per_kb))
                    self.amount_e.setAmount(max(0, sendable - self.fee_e.get_amount()))
                    self.amount_e.textEdited.emit("")
        
       t@@ -1059,7 +1060,7 @@ class ElectrumWindow(QMainWindow):
                        addr = self.payto_e.payto_address if self.payto_e.payto_address else self.dummy_address
                        outputs = [('address', addr, amount)]
                    try:
       -                tx = self.wallet.make_unsigned_transaction(self.get_coins(), outputs, fee)
       +                tx = self.wallet.make_unsigned_transaction(self.get_coins(), outputs, self.config, fee)
                        self.not_enough_funds = False
                    except NotEnoughFunds:
                        self.not_enough_funds = True
       t@@ -1195,7 +1196,7 @@ class ElectrumWindow(QMainWindow):
                    return
                outputs, fee, tx_desc, coins = r
                try:
       -            tx = self.wallet.make_unsigned_transaction(coins, outputs, fee)
       +            tx = self.wallet.make_unsigned_transaction(coins, outputs, self.config, fee)
                    if not tx:
                        raise BaseException(_("Insufficient funds"))
                except Exception as e:
       t@@ -2477,7 +2478,7 @@ class ElectrumWindow(QMainWindow):
                if not d.exec_():
                    return
        
       -        fee = self.wallet.fee_per_kb
       +        fee = self.wallet.fee_per_kb(self.config)
                tx = Transaction.sweep(get_pk(), self.network, get_address(), fee)
                self.show_transaction(tx)
        
       t@@ -2563,21 +2564,44 @@ class ElectrumWindow(QMainWindow):
                nz.valueChanged.connect(on_nz)
                gui_widgets.append((nz_label, nz))
        
       -        fee_help = _('Fee per kilobyte of transaction.') + '\n' \
       -                   + _('Recommended value') + ': ' + self.format_amount(bitcoin.RECOMMENDED_FEE) + ' ' + self.base_unit()
       -        fee_label = HelpLabel(_('Transaction fee per kb') + ':', fee_help)
       -        fee_e = BTCAmountEdit(self.get_decimal_point)
       -        fee_e.setAmount(self.wallet.fee_per_kb)
       -        if not self.config.is_modifiable('fee_per_kb'):
       -            for w in [fee_e, fee_label]: w.setEnabled(False)
       +        msg = _('Fee per kilobyte of transaction.') + '\n' \
       +              + _('If you enable dynamic fees, your client will use a value recommended by the server, and this parameter will be used as upper bound.')
       +        fee_label = HelpLabel(_('Transaction fee per kb') + ':', msg)
       +        fee_e = BTCkBEdit(self.get_decimal_point)
       +        fee_e.setAmount(self.config.get('fee_per_kb', bitcoin.RECOMMENDED_FEE))
                def on_fee(is_done):
       -            self.wallet.set_fee(fee_e.get_amount() or 0, is_done)
       +            v = fee_e.get_amount() or 0
       +            self.wallet.set_fee(v)
       +            self.config.set_key('fee_per_kb', v, is_done)
                    if not is_done:
                        self.update_fee()
                fee_e.editingFinished.connect(lambda: on_fee(True))
                fee_e.textEdited.connect(lambda: on_fee(False))
                tx_widgets.append((fee_label, fee_e))
        
       +        dynfee_cb = QCheckBox(_('Dynamic fees'))
       +        dynfee_cb.setChecked(self.config.get('dynamic_fees', False))
       +        dynfee_sl = QSlider(Qt.Horizontal, self)
       +        dynfee_sl.setValue(self.config.get('fee_factor', 50))
       +        dynfee_sl.setToolTip("Fee Multiplier. Min = 50%, Max = 150%")
       +        tx_widgets.append((dynfee_cb, dynfee_sl))
       +
       +        def update_feeperkb():
       +            fee_e.setAmount(self.wallet.fee_per_kb(self.config))
       +            b = self.config.get('dynamic_fees')
       +            dynfee_sl.setHidden(not b)
       +            fee_e.setEnabled(not b)
       +        def fee_factor_changed(b):
       +            self.config.set_key('fee_factor', b, False)
       +            update_feeperkb()
       +        def on_dynfee(x):
       +            dynfee = x == Qt.Checked
       +            self.config.set_key('dynamic_fees', dynfee)
       +            update_feeperkb()
       +        dynfee_cb.stateChanged.connect(on_dynfee)
       +        dynfee_sl.valueChanged[int].connect(fee_factor_changed)
       +        update_feeperkb()
       +
                msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\
                      + _('The following alias providers are available:') + '\n'\
                      + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\
       t@@ -2644,7 +2668,7 @@ class ElectrumWindow(QMainWindow):
                    self.update_history_tab()
                    self.update_receive_tab()
                    self.update_address_tab()
       -            fee_e.setAmount(self.wallet.fee_per_kb)
       +            fee_e.setAmount(self.wallet.fee_per_kb(self.config))
                    self.update_status()
                unit_combo.currentIndexChanged.connect(on_unit)
                gui_widgets.append((unit_label, unit_combo))
   DIR diff --git a/gui/stdio.py b/gui/stdio.py
       t@@ -201,7 +201,7 @@ class ElectrumGui:
                    if c == "n": return
        
                try:
       -            tx = self.wallet.mktx( [(self.str_recipient, amount)], password, fee)
       +            tx = self.wallet.mktx( [(self.str_recipient, amount)], password, self.config, fee)
                except Exception as e:
                    print(str(e))
                    return
   DIR diff --git a/gui/text.py b/gui/text.py
       t@@ -314,7 +314,7 @@ class ElectrumGui:
                    password = None
        
                try:
       -            tx = self.wallet.mktx( [(self.str_recipient, amount)], password, fee)
       +            tx = self.wallet.mktx( [(self.str_recipient, amount)], password, self.config, fee)
                except Exception as e:
                    self.show_message(str(e))
                    return
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -396,7 +396,7 @@ class Commands:
                    final_outputs.append(('address', address, amount))
        
                coins = self.wallet.get_spendable_coins(domain)
       -        tx = self.wallet.make_unsigned_transaction(coins, final_outputs, fee, change_addr)
       +        tx = self.wallet.make_unsigned_transaction(coins, final_outputs, self.config, fee, change_addr)
                str(tx) #this serializes
                if not unsigned:
                    self.wallet.sign_transaction(tx, self.password)
   DIR diff --git a/lib/network.py b/lib/network.py
       t@@ -235,12 +235,15 @@ class Network(util.DaemonThread):
                    self.interface.send_request({'method':'blockchain.address.subscribe','params':[addr]})
                self.interface.send_request({'method':'server.banner','params':[]})
                self.interface.send_request({'method':'server.peers.subscribe','params':[]})
       +        self.interface.send_request({'method':'blockchain.estimatefee','params':[2]})
        
            def get_status_value(self, key):
                if key == 'status':
                    value = self.connection_status
                elif key == 'banner':
                    value = self.banner
       +        elif key == 'fee':
       +            value = self.fee
                elif key == 'updated':
                    value = (self.get_local_height(), self.get_server_height())
                elif key == 'servers':
       t@@ -425,6 +428,11 @@ class Network(util.DaemonThread):
                elif method == 'server.banner':
                    self.banner = result
                    self.notify('banner')
       +        elif method == 'blockchain.estimatefee':
       +            from bitcoin import COIN
       +            self.fee = int(result * COIN)
       +            self.print_error("recommended fee", self.fee)
       +            self.notify('fee')
                elif method == 'blockchain.address.subscribe':
                    addr = response.get('params')[0]
                    self.addr_responses[addr] = result
   DIR diff --git a/lib/network_proxy.py b/lib/network_proxy.py
       t@@ -62,6 +62,8 @@ class NetworkProxy(util.DaemonThread):
                self.server_height = 0
                self.interfaces = []
                self.jobs = []
       +        # value returned by estimatefee
       +        self.fee = None
        
        
            def run(self):
       t@@ -90,6 +92,8 @@ class NetworkProxy(util.DaemonThread):
                        self.status = value
                    elif key == 'banner':
                        self.banner = value
       +            elif key == 'fee':
       +                self.fee = value
                    elif key == 'updated':
                        self.blockchain_height, self.server_height = value
                    elif key == 'servers':
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -143,6 +143,7 @@ class Abstract_Wallet(object):
            """
            def __init__(self, storage):
                self.storage = storage
       +        self.network = None
                self.electrum_version = ELECTRUM_VERSION
                self.gap_limit_for_change = 6 # constant
                # saved fields
       t@@ -153,9 +154,7 @@ class Abstract_Wallet(object):
                self.labels                = storage.get('labels', {})
                self.frozen_addresses      = set(storage.get('frozen_addresses',[]))
                self.stored_height         = storage.get('stored_height', 0)       # last known height (for offline mode)
       -
                self.history               = storage.get('addr_history',{})        # address -> list(txid, height)
       -        self.fee_per_kb            = int(storage.get('fee_per_kb', RECOMMENDED_FEE))
        
                # This attribute is set when wallet.start_threads is called.
                self.synchronizer = None
       t@@ -674,10 +673,6 @@ class Abstract_Wallet(object):
                    xx += x
                return cc, uu, xx
        
       -    def set_fee(self, fee, save = True):
       -        self.fee_per_kb = fee
       -        self.storage.put('fee_per_kb', self.fee_per_kb, save)
       -
            def get_address_history(self, address):
                with self.lock:
                    return self.history.get(address, [])
       t@@ -873,23 +868,30 @@ class Abstract_Wallet(object):
                    return ', '.join(labels)
                return ''
        
       +    def fee_per_kb(self, config):
       +        b = config.get('dynamic_fees')
       +        f = config.get('fee_factor', 50)
       +        F = config.get('fee_per_kb', bitcoin.RECOMMENDED_FEE)
       +        return min(F, self.network.fee*(50 + f)/100) if b and self.network and self.network.fee else F
       +
            def get_tx_fee(self, tx):
                # this method can be overloaded
                return tx.get_fee()
        
       -    def estimated_fee(self, tx):
       +    def estimated_fee(self, tx, fee_per_kb):
                estimated_size = len(tx.serialize(-1))/2
       -        fee = int(self.fee_per_kb*estimated_size/1000.)
       +        fee = int(fee_per_kb * estimated_size / 1000.)
                if fee < MIN_RELAY_TX_FEE: # and tx.requires_fee(self):
                    fee = MIN_RELAY_TX_FEE
                return fee
        
       -    def make_unsigned_transaction(self, coins, outputs, fixed_fee=None, change_addr=None):
       +    def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None, change_addr=None):
                # check outputs
                for type, data, value in outputs:
                    if type == 'address':
                        assert is_address(data), "Address " + data + " is invalid!"
        
       +        fee_per_kb = self.fee_per_kb(config)
                amount = sum(map(lambda x:x[2], outputs))
                total = fee = 0
                inputs = []
       t@@ -903,7 +905,7 @@ class Abstract_Wallet(object):
                    # no need to estimate fee until we have reached desired amount
                    if total < amount:
                        continue
       -            fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx)
       +            fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx, fee_per_kb)
                    if total >= amount + fee:
                        break
                else:
       t@@ -914,7 +916,7 @@ class Abstract_Wallet(object):
                    if total - v >= amount + fee:
                        tx.inputs.remove(item)
                        total -= v
       -                fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx)
       +                fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx, fee_per_kb)
                    else:
                        break
                print_error("using %d inputs"%len(tx.inputs))
       t@@ -943,7 +945,7 @@ class Abstract_Wallet(object):
                elif change_amount > DUST_THRESHOLD:
                    tx.outputs.append(('address', change_addr, change_amount))
                    # recompute fee including change output
       -            fee = self.estimated_fee(tx)
       +            fee = self.estimated_fee(tx, fee_per_kb)
                    # remove change output
                    tx.outputs.pop()
                    # if change is still above dust threshold, re-add change output.
       t@@ -962,9 +964,9 @@ class Abstract_Wallet(object):
                run_hook('make_unsigned_transaction', tx)
                return tx
        
       -    def mktx(self, outputs, password, fee=None, change_addr=None, domain=None):
       +    def mktx(self, outputs, password, config, fee=None, change_addr=None, domain=None):
                coins = self.get_spendable_coins(domain)
       -        tx = self.make_unsigned_transaction(coins, outputs, fee, change_addr)
       +        tx = self.make_unsigned_transaction(coins, outputs, config, fee, change_addr)
                self.sign_transaction(tx, password)
                return tx
        
   DIR diff --git a/scripts/estimate_fee b/scripts/estimate_fee
       t@@ -1,7 +1,7 @@
        #!/usr/bin/env python
        import util, json
        peers = util.get_peers()
       -results = util.send_request(peers, {'method':'blockchain.estimatefee','params':[1]})
       +results = util.send_request(peers, {'method':'blockchain.estimatefee','params':[2]})
        print json.dumps(results, indent=4)