commit 43880d452e9c0278ff114625d7871b06051647a0
parent 959620db4646d4ae1d570fef661239197eb4c722
Author: ThomasV <thomasv@gitorious>
Date: Tue, 4 Aug 2015 07:15:54 +0200
dynamic fees
Diffstat:
12 files changed, 81 insertions(+), 36 deletions(-)
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
@@ -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
diff --git a/gui/android.py b/gui/android.py
@@ -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()
@@ -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)
diff --git a/gui/gtk.py b/gui/gtk.py
@@ -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
@@ -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
diff --git a/gui/qt/amountedit.py b/gui/qt/amountedit.py
@@ -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'
diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
@@ -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
@@ -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("")
@@ -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
@@ -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:
@@ -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)
@@ -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'\
@@ -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))
diff --git a/gui/stdio.py b/gui/stdio.py
@@ -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
diff --git a/gui/text.py b/gui/text.py
@@ -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
diff --git a/lib/commands.py b/lib/commands.py
@@ -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)
diff --git a/lib/network.py b/lib/network.py
@@ -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':
@@ -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
diff --git a/lib/network_proxy.py b/lib/network_proxy.py
@@ -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):
@@ -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':
diff --git a/lib/wallet.py b/lib/wallet.py
@@ -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
@@ -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
@@ -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, [])
@@ -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 = []
@@ -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:
@@ -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))
@@ -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.
@@ -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
diff --git a/scripts/estimate_fee b/scripts/estimate_fee
@@ -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)