dynamic fees

This commit is contained in:
ThomasV 2015-08-04 07:15:54 +02:00
parent 959620db46
commit 43880d452e
12 changed files with 81 additions and 36 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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'

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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':

View File

@ -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

View File

@ -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)