Browse Source

clean up fees a bit

3.1
SomberNight 7 years ago
parent
commit
4ddda74dad
  1. 49
      gui/kivy/uix/dialogs/bump_fee_dialog.py
  2. 1
      gui/kivy/uix/dialogs/fee_dialog.py
  3. 1
      gui/kivy/uix/dialogs/settings.py
  4. 2
      gui/qt/main_window.py
  5. 7
      gui/qt/transaction_dialog.py
  6. 4
      lib/bitcoin.py
  7. 71
      lib/simple_config.py
  8. 1
      lib/util.py
  9. 4
      lib/wallet.py

49
gui/kivy/uix/dialogs/bump_fee_dialog.py

@ -3,7 +3,6 @@ from kivy.factory import Factory
from kivy.properties import ObjectProperty from kivy.properties import ObjectProperty
from kivy.lang import Builder from kivy.lang import Builder
from electrum.util import fee_levels
from electrum_gui.kivy.i18n import _ from electrum_gui.kivy.i18n import _
Builder.load_string(''' Builder.load_string('''
@ -29,7 +28,11 @@ Builder.load_string('''
text: _('New Fee') text: _('New Fee')
value: '' value: ''
Label: Label:
id: tooltip id: tooltip1
text: ''
size_hint_y: None
Label:
id: tooltip2
text: '' text: ''
size_hint_y: None size_hint_y: None
Slider: Slider:
@ -72,39 +75,39 @@ class BumpFeeDialog(Factory.Popup):
self.tx_size = size self.tx_size = size
self.callback = callback self.callback = callback
self.config = app.electrum_config self.config = app.electrum_config
self.fee_step = self.config.max_fee_rate() / 10 self.mempool = self.config.use_mempool_fees()
self.dynfees = self.config.is_dynfee() and self.app.network self.dynfees = self.config.is_dynfee() and self.app.network and self.config.has_dynamic_fees_ready()
self.ids.old_fee.value = self.app.format_amount_and_units(self.init_fee) self.ids.old_fee.value = self.app.format_amount_and_units(self.init_fee)
self.update_slider() self.update_slider()
self.update_text() self.update_text()
def update_text(self): def update_text(self):
value = int(self.ids.slider.value) fee = self.get_fee()
self.ids.new_fee.value = self.app.format_amount_and_units(self.get_fee()) self.ids.new_fee.value = self.app.format_amount_and_units(fee)
if self.dynfees: pos = int(self.ids.slider.value)
value = int(self.ids.slider.value) fee_rate = self.get_fee_rate()
self.ids.tooltip.text = fee_levels[value] text, tooltip = self.config.get_fee_text(pos, self.dynfees, self.mempool, fee_rate)
self.ids.tooltip1.text = text
self.ids.tooltip2.text = tooltip
def update_slider(self): def update_slider(self):
slider = self.ids.slider slider = self.ids.slider
if self.dynfees: maxp, pos, fee_rate = self.config.get_fee_slider(self.dynfees, self.mempool)
slider.range = (0, 4) slider.range = (0, maxp)
slider.step = 1 slider.step = 1
slider.value = 3 slider.value = pos
else:
slider.range = (1, 10)
slider.step = 1
rate = self.init_fee*1000//self.tx_size
slider.value = min( rate * 2 // self.fee_step, 10)
def get_fee(self): def get_fee_rate(self):
value = int(self.ids.slider.value) pos = int(self.ids.slider.value)
if self.dynfees: if self.dynfees:
if self.config.has_fee_estimates(): fee_rate = self.config.depth_to_fee(pos) if self.mempool else self.config.eta_to_fee(pos)
dynfee = self.config.dynfee(value)
return int(dynfee * self.tx_size // 1000)
else: else:
return int(value*self.fee_step * self.tx_size // 1000) fee_rate = self.config.static_fee(pos)
return fee_rate
def get_fee(self):
fee_rate = self.get_fee_rate()
return int(fee_rate * self.tx_size // 1000)
def on_ok(self): def on_ok(self):
new_fee = self.get_fee() new_fee = self.get_fee()

1
gui/kivy/uix/dialogs/fee_dialog.py

@ -3,7 +3,6 @@ from kivy.factory import Factory
from kivy.properties import ObjectProperty from kivy.properties import ObjectProperty
from kivy.lang import Builder from kivy.lang import Builder
from electrum.util import fee_levels
from electrum_gui.kivy.i18n import _ from electrum_gui.kivy.i18n import _
Builder.load_string(''' Builder.load_string('''

1
gui/kivy/uix/dialogs/settings.py

@ -8,7 +8,6 @@ from electrum.i18n import languages
from electrum_gui.kivy.i18n import _ from electrum_gui.kivy.i18n import _
from electrum.plugins import run_hook from electrum.plugins import run_hook
from electrum import coinchooser from electrum import coinchooser
from electrum.util import fee_levels
from .choice_dialog import ChoiceDialog from .choice_dialog import ChoiceDialog

2
gui/qt/main_window.py

@ -1512,7 +1512,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
x_fee_address, x_fee_amount = x_fee x_fee_address, x_fee_amount = x_fee
msg.append( _("Additional fees") + ": " + self.format_amount_and_units(x_fee_amount) ) msg.append( _("Additional fees") + ": " + self.format_amount_and_units(x_fee_amount) )
confirm_rate = 2 * self.config.max_fee_rate() confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE
if fee > confirm_rate * tx.estimated_size() / 1000: if 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."))

7
gui/qt/transaction_dialog.py

@ -33,6 +33,7 @@ from PyQt5.QtWidgets import *
from electrum.bitcoin import base_encode from electrum.bitcoin import base_encode
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugins import run_hook from electrum.plugins import run_hook
from electrum import simple_config
from electrum.util import bfh from electrum.util import bfh
from electrum.wallet import UnrelatedTransactionException from electrum.wallet import UnrelatedTransactionException
@ -238,7 +239,11 @@ class TxDialog(QDialog, MessageBoxMixin):
size_str = _("Size:") + ' %d bytes'% size size_str = _("Size:") + ' %d bytes'% size
fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown')) fee_str = _("Fee") + ': %s' % (format_amount(fee) + ' ' + base_unit if fee is not None else _('unknown'))
if fee is not None: if fee is not None:
fee_str += ' ( %s ) '% self.main_window.format_fee_rate(fee/size*1000) fee_rate = fee/size*1000
fee_str += ' ( %s ) ' % self.main_window.format_fee_rate(fee_rate)
confirm_rate = simple_config.FEERATE_WARNING_HIGH_FEE
if fee_rate > confirm_rate:
fee_str += ' - ' + _('Warning') + ': ' + _("high fee") + '!'
self.amount_label.setText(amount_str) self.amount_label.setText(amount_str)
self.fee_label.setText(fee_str) self.fee_label.setText(fee_str)
self.size_label.setText(size_str) self.size_label.setText(size_str)

4
lib/bitcoin.py

@ -108,10 +108,6 @@ NetworkConstants.set_mainnet()
################################## transactions ################################## transactions
FEE_STEP = 10000
MAX_FEE_RATE = 300000
COINBASE_MATURITY = 100 COINBASE_MATURITY = 100
COIN = 100000000 COIN = 100000000

71
lib/simple_config.py

@ -5,14 +5,22 @@ import os
import stat import stat
from copy import deepcopy from copy import deepcopy
from .util import (user_dir, print_error, PrintError, from .util import (user_dir, print_error, PrintError,
NoDynamicFeeEstimates, format_satoshis) NoDynamicFeeEstimates, format_satoshis)
from .i18n import _
from .bitcoin import MAX_FEE_RATE
FEE_ETA_TARGETS = [25, 10, 5, 2] FEE_ETA_TARGETS = [25, 10, 5, 2]
FEE_DEPTH_TARGETS = [10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000] FEE_DEPTH_TARGETS = [10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000]
# satoshi per kbyte
FEERATE_MAX_DYNAMIC = 1500000
FEERATE_WARNING_HIGH_FEE = 600000
FEERATE_FALLBACK_STATIC_FEE = 150000
FEERATE_DEFAULT_RELAY = 1000
FEERATE_STATIC_VALUES = [5000, 10000, 20000, 30000, 50000, 70000, 100000, 150000, 200000, 300000]
config = None config = None
@ -39,7 +47,6 @@ class SimpleConfig(PrintError):
2. User configuration (in the user's config directory) 2. User configuration (in the user's config directory)
They are taken in order (1. overrides config options set in 2.) They are taken in order (1. overrides config options set in 2.)
""" """
fee_rates = [5000, 10000, 20000, 30000, 50000, 70000, 100000, 150000, 200000, 300000]
def __init__(self, options=None, read_user_config_function=None, def __init__(self, options=None, read_user_config_function=None,
read_user_dir_function=None): read_user_dir_function=None):
@ -261,13 +268,19 @@ class SimpleConfig(PrintError):
path = wallet.storage.path path = wallet.storage.path
self.set_key('gui_last_wallet', path) self.set_key('gui_last_wallet', path)
def max_fee_rate(self): def impose_hard_limits_on_fee(func):
f = self.get('max_fee_rate', MAX_FEE_RATE) def get_fee_within_limits(self, *args, **kwargs):
if f==0: fee = func(self, *args, **kwargs)
f = MAX_FEE_RATE if fee is None:
return f return fee
fee = min(FEERATE_MAX_DYNAMIC, fee)
fee = max(FEERATE_DEFAULT_RELAY, fee)
return fee
return get_fee_within_limits
@impose_hard_limits_on_fee
def eta_to_fee(self, i): def eta_to_fee(self, i):
"""Returns fee in sat/kbyte."""
if i < 4: if i < 4:
j = FEE_ETA_TARGETS[i] j = FEE_ETA_TARGETS[i]
fee = self.fee_estimates.get(j) fee = self.fee_estimates.get(j)
@ -276,8 +289,6 @@ class SimpleConfig(PrintError):
fee = self.fee_estimates.get(2) fee = self.fee_estimates.get(2)
if fee is not None: if fee is not None:
fee += fee/2 fee += fee/2
if fee is not None:
fee = min(5*MAX_FEE_RATE, fee)
return fee return fee
def fee_to_depth(self, target_fee): def fee_to_depth(self, target_fee):
@ -290,7 +301,9 @@ class SimpleConfig(PrintError):
return 0 return 0
return depth return depth
@impose_hard_limits_on_fee
def depth_to_fee(self, i): def depth_to_fee(self, i):
"""Returns fee in sat/kbyte."""
target = self.depth_target(i) target = self.depth_target(i)
depth = 0 depth = 0
for fee, s in self.mempool_fees: for fee, s in self.mempool_fees:
@ -305,6 +318,8 @@ class SimpleConfig(PrintError):
return FEE_DEPTH_TARGETS[i] return FEE_DEPTH_TARGETS[i]
def eta_target(self, i): def eta_target(self, i):
if i == len(FEE_ETA_TARGETS):
return 1
return FEE_ETA_TARGETS[i] return FEE_ETA_TARGETS[i]
def fee_to_eta(self, fee_per_kb): def fee_to_eta(self, fee_per_kb):
@ -320,7 +335,12 @@ class SimpleConfig(PrintError):
return "%.1f MB from tip"%(depth/1000000) return "%.1f MB from tip"%(depth/1000000)
def eta_tooltip(self, x): def eta_tooltip(self, x):
return 'Low fee' if x < 0 else 'Within %d blocks'%x if x < 0:
return _('Low fee')
elif x == 1:
return _('In the next block')
else:
return _('Within {} blocks').format(x)
def get_fee_status(self): def get_fee_status(self):
dyn = self.is_dynfee() dyn = self.is_dynfee()
@ -331,6 +351,10 @@ class SimpleConfig(PrintError):
return target return target
def get_fee_text(self, pos, dyn, mempool, fee_rate): def get_fee_text(self, pos, dyn, mempool, fee_rate):
"""Returns (text, tooltip) where
text is what we target: static fee / num blocks to confirm in / mempool depth
tooltip is the corresponding estimate (e.g. num blocks for a static fee)
"""
rate_str = (format_satoshis(fee_rate/1000, False, 0, 0, False) + ' sat/byte') if fee_rate is not None else 'unknown' rate_str = (format_satoshis(fee_rate/1000, False, 0, 0, False) + ' sat/byte') if fee_rate is not None else 'unknown'
if dyn: if dyn:
if mempool: if mempool:
@ -342,14 +366,10 @@ class SimpleConfig(PrintError):
tooltip = rate_str tooltip = rate_str
else: else:
text = rate_str text = rate_str
if mempool: if mempool and self.has_fee_mempool():
if self.has_fee_mempool():
depth = self.fee_to_depth(fee_rate) depth = self.fee_to_depth(fee_rate)
tooltip = self.depth_tooltip(depth) tooltip = self.depth_tooltip(depth)
else: elif not mempool and self.has_fee_etas():
tooltip = ''
else:
if self.has_fee_etas():
eta = self.fee_to_eta(fee_rate) eta = self.fee_to_eta(fee_rate)
tooltip = self.eta_tooltip(eta) tooltip = self.eta_tooltip(eta)
else: else:
@ -361,7 +381,7 @@ class SimpleConfig(PrintError):
return min(maxp, self.get('depth_level', 2)) return min(maxp, self.get('depth_level', 2))
def get_fee_level(self): def get_fee_level(self):
maxp = len(FEE_ETA_TARGETS) - 1 maxp = len(FEE_ETA_TARGETS) # not (-1) to have "next block"
return min(maxp, self.get('fee_level', 2)) return min(maxp, self.get('fee_level', 2))
def get_fee_slider(self, dyn, mempool): def get_fee_slider(self, dyn, mempool):
@ -372,7 +392,7 @@ class SimpleConfig(PrintError):
fee_rate = self.depth_to_fee(pos) fee_rate = self.depth_to_fee(pos)
else: else:
pos = self.get_fee_level() pos = self.get_fee_level()
maxp = len(FEE_ETA_TARGETS) - 1 maxp = len(FEE_ETA_TARGETS) # not (-1) to have "next block"
fee_rate = self.eta_to_fee(pos) fee_rate = self.eta_to_fee(pos)
else: else:
fee_rate = self.fee_per_kb() fee_rate = self.fee_per_kb()
@ -380,12 +400,11 @@ class SimpleConfig(PrintError):
maxp = 9 maxp = 9
return maxp, pos, fee_rate return maxp, pos, fee_rate
def static_fee(self, i): def static_fee(self, i):
return self.fee_rates[i] return FEERATE_STATIC_VALUES[i]
def static_fee_index(self, value): def static_fee_index(self, value):
dist = list(map(lambda x: abs(x - value), self.fee_rates)) dist = list(map(lambda x: abs(x - value), FEERATE_STATIC_VALUES))
return min(range(len(dist)), key=dist.__getitem__) return min(range(len(dist)), key=dist.__getitem__)
def has_fee_etas(self): def has_fee_etas(self):
@ -394,6 +413,12 @@ class SimpleConfig(PrintError):
def has_fee_mempool(self): def has_fee_mempool(self):
return bool(self.mempool_fees) return bool(self.mempool_fees)
def has_dynamic_fees_ready(self):
if self.use_mempool_fees():
return self.has_fee_mempool()
else:
return self.has_fee_etas()
def is_dynfee(self): def is_dynfee(self):
return bool(self.get('dynamic_fees', True)) return bool(self.get('dynamic_fees', True))
@ -410,7 +435,7 @@ class SimpleConfig(PrintError):
else: else:
fee_rate = self.eta_to_fee(self.get_fee_level()) fee_rate = self.eta_to_fee(self.get_fee_level())
else: else:
fee_rate = self.get('fee_per_kb', self.max_fee_rate()/2) fee_rate = self.get('fee_per_kb', FEERATE_FALLBACK_STATIC_FEE)
return fee_rate return fee_rate
def fee_per_byte(self): def fee_per_byte(self):

1
lib/util.py

@ -41,7 +41,6 @@ def inv_dict(d):
base_units = {'BTC':8, 'mBTC':5, 'uBTC':2} base_units = {'BTC':8, 'mBTC':5, 'uBTC':2}
fee_levels = [_('Within 25 blocks'), _('Within 10 blocks'), _('Within 5 blocks'), _('Within 2 blocks'), _('In the next block')]
def normalize_version(v): def normalize_version(v):
return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")] return [int(x) for x in re.sub(r'(\.0+)*$','', v).split(".")]

4
lib/wallet.py

@ -78,9 +78,9 @@ TX_HEIGHT_UNCONFIRMED = 0
def relayfee(network): def relayfee(network):
RELAY_FEE = 1000 from .simple_config import FEERATE_DEFAULT_RELAY
MAX_RELAY_FEE = 50000 MAX_RELAY_FEE = 50000
f = network.relay_fee if network and network.relay_fee else RELAY_FEE f = network.relay_fee if network and network.relay_fee else FEERATE_DEFAULT_RELAY
return min(f, MAX_RELAY_FEE) return min(f, MAX_RELAY_FEE)
def dust_threshold(network): def dust_threshold(network):

Loading…
Cancel
Save