dynamic fees
This commit is contained in:
parent
959620db46
commit
43880d452e
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue