Browse Source

allow fractional feerates (#4324)

3.2.x
ghost43 7 years ago
committed by GitHub
parent
commit
dae187bada
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      gui/qt/amountedit.py
  2. 17
      gui/qt/main_window.py
  3. 10
      lib/simple_config.py
  4. 16
      lib/util.py

31
gui/qt/amountedit.py

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from decimal import Decimal
from PyQt5.QtCore import * from PyQt5.QtCore import *
from PyQt5.QtGui import * from PyQt5.QtGui import *
from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame) from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame)
from decimal import Decimal from electrum.util import format_satoshis_plain, decimal_point_to_base_unit_name, FEERATE_PRECISION
from electrum.util import format_satoshis_plain, decimal_point_to_base_unit_name
class MyLineEdit(QLineEdit): class MyLineEdit(QLineEdit):
@ -19,7 +20,7 @@ class MyLineEdit(QLineEdit):
class AmountEdit(MyLineEdit): class AmountEdit(MyLineEdit):
shortcut = pyqtSignal() shortcut = pyqtSignal()
def __init__(self, base_unit, is_int = False, parent=None): def __init__(self, base_unit, is_int=False, parent=None):
QLineEdit.__init__(self, parent) QLineEdit.__init__(self, parent)
# This seems sufficient for hundred-BTC amounts with 8 decimals # This seems sufficient for hundred-BTC amounts with 8 decimals
self.setFixedWidth(140) self.setFixedWidth(140)
@ -28,10 +29,14 @@ class AmountEdit(MyLineEdit):
self.is_int = is_int self.is_int = is_int
self.is_shortcut = False self.is_shortcut = False
self.help_palette = QPalette() self.help_palette = QPalette()
self.extra_precision = 0
def decimal_point(self): def decimal_point(self):
return 8 return 8
def max_precision(self):
return self.decimal_point() + self.extra_precision
def numbify(self): def numbify(self):
text = self.text().strip() text = self.text().strip()
if text == '!': if text == '!':
@ -45,7 +50,7 @@ class AmountEdit(MyLineEdit):
if '.' in s: if '.' in s:
p = s.find('.') p = s.find('.')
s = s.replace('.','') s = s.replace('.','')
s = s[:p] + '.' + s[p:p+self.decimal_point()] s = s[:p] + '.' + s[p:p+self.max_precision()]
self.setText(s) self.setText(s)
# setText sets Modified to False. Instead we want to remember # setText sets Modified to False. Instead we want to remember
# if updates were because of user modification. # if updates were because of user modification.
@ -75,7 +80,7 @@ class AmountEdit(MyLineEdit):
class BTCAmountEdit(AmountEdit): class BTCAmountEdit(AmountEdit):
def __init__(self, decimal_point, is_int = False, parent=None): def __init__(self, decimal_point, is_int=False, parent=None):
AmountEdit.__init__(self, self._base_unit, is_int, parent) AmountEdit.__init__(self, self._base_unit, is_int, parent)
self.decimal_point = decimal_point self.decimal_point = decimal_point
@ -87,8 +92,15 @@ class BTCAmountEdit(AmountEdit):
x = Decimal(str(self.text())) x = Decimal(str(self.text()))
except: except:
return None return None
p = pow(10, self.decimal_point()) # scale it to max allowed precision, make it an int
return int( p * x ) power = pow(10, self.max_precision())
max_prec_amount = int(power * x)
# if the max precision is simply what unit conversion allows, just return
if self.max_precision() == self.decimal_point():
return max_prec_amount
# otherwise, scale it back to the expected unit
amount = Decimal(max_prec_amount) / pow(10, self.max_precision()-self.decimal_point())
return Decimal(amount) if not self.is_int else int(amount)
def setAmount(self, amount): def setAmount(self, amount):
if amount is None: if amount is None:
@ -98,6 +110,11 @@ class BTCAmountEdit(AmountEdit):
class FeerateEdit(BTCAmountEdit): class FeerateEdit(BTCAmountEdit):
def __init__(self, decimal_point, is_int=False, parent=None):
super().__init__(decimal_point, is_int, parent)
self.extra_precision = FEERATE_PRECISION
def _base_unit(self): def _base_unit(self):
return 'sat/byte' return 'sat/byte'

17
gui/qt/main_window.py

@ -49,7 +49,7 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
UserCancelled, NoDynamicFeeEstimates, profiler, UserCancelled, NoDynamicFeeEstimates, profiler,
export_meta, import_meta, bh2u, bfh, InvalidPassword, export_meta, import_meta, bh2u, bfh, InvalidPassword,
base_units, base_units_list, base_unit_name_to_decimal_point, base_units, base_units_list, base_unit_name_to_decimal_point,
decimal_point_to_base_unit_name) decimal_point_to_base_unit_name, quantize_feerate)
from electrum import Transaction from electrum import Transaction
from electrum import util, bitcoin, commands, coinchooser from electrum import util, bitcoin, commands, coinchooser
from electrum import paymentrequest from electrum import paymentrequest
@ -1102,7 +1102,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.config.set_key('fee_per_kb', fee_rate, False) self.config.set_key('fee_per_kb', fee_rate, False)
if fee_rate: if fee_rate:
self.feerate_e.setAmount(fee_rate // 1000) fee_rate = Decimal(fee_rate)
self.feerate_e.setAmount(quantize_feerate(fee_rate / 1000))
else: else:
self.feerate_e.setAmount(None) self.feerate_e.setAmount(None)
self.fee_e.setModified(False) self.fee_e.setModified(False)
@ -1334,12 +1335,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if freeze_feerate or self.fee_slider.is_active(): if freeze_feerate or self.fee_slider.is_active():
displayed_feerate = self.feerate_e.get_amount() displayed_feerate = self.feerate_e.get_amount()
if displayed_feerate: if displayed_feerate:
displayed_feerate = displayed_feerate // 1000 displayed_feerate = quantize_feerate(displayed_feerate / 1000)
else: else:
# fallback to actual fee # fallback to actual fee
displayed_feerate = fee // size if fee is not None else None displayed_feerate = quantize_feerate(fee / size) if fee is not None else None
self.feerate_e.setAmount(displayed_feerate) self.feerate_e.setAmount(displayed_feerate)
displayed_fee = displayed_feerate * size if displayed_feerate is not None else None displayed_fee = round(displayed_feerate * size) if displayed_feerate is not None else None
self.fee_e.setAmount(displayed_fee) self.fee_e.setAmount(displayed_fee)
else: else:
if freeze_fee: if freeze_fee:
@ -1349,14 +1350,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
displayed_fee = fee displayed_fee = fee
self.fee_e.setAmount(displayed_fee) self.fee_e.setAmount(displayed_fee)
displayed_fee = displayed_fee if displayed_fee else 0 displayed_fee = displayed_fee if displayed_fee else 0
displayed_feerate = displayed_fee // size if displayed_fee is not None else None displayed_feerate = quantize_feerate(displayed_fee / size) if displayed_fee is not None else None
self.feerate_e.setAmount(displayed_feerate) self.feerate_e.setAmount(displayed_feerate)
# show/hide fee rounding icon # show/hide fee rounding icon
feerounding = (fee - displayed_fee) if fee else 0 feerounding = (fee - displayed_fee) if fee else 0
self.set_feerounding_text(feerounding) self.set_feerounding_text(int(feerounding))
self.feerounding_icon.setToolTip(self.feerounding_text) self.feerounding_icon.setToolTip(self.feerounding_text)
self.feerounding_icon.setVisible(bool(feerounding)) self.feerounding_icon.setVisible(abs(feerounding) >= 1)
if self.is_max: if self.is_max:
amount = tx.output_value() amount = tx.output_value()

10
lib/simple_config.py

@ -3,6 +3,7 @@ import threading
import time import time
import os import os
import stat import stat
from decimal import Decimal
from copy import deepcopy from copy import deepcopy
@ -473,12 +474,9 @@ class SimpleConfig(PrintError):
@classmethod @classmethod
def estimate_fee_for_feerate(cls, fee_per_kb, size): def estimate_fee_for_feerate(cls, fee_per_kb, size):
# note: We only allow integer sat/byte values atm. fee_per_kb = Decimal(fee_per_kb)
# The GUI for simplicity reasons only displays integer sat/byte, fee_per_byte = fee_per_kb / 1000
# and for the sake of consistency, we thus only use integer sat/byte in return round(fee_per_byte * size)
# the backend too.
fee_per_byte = int(fee_per_kb / 1000)
return int(fee_per_byte * size)
def update_fee_estimates(self, key, value): def update_fee_estimates(self, key, value):
self.fee_estimates[key] = value self.fee_estimates[key] = value

16
lib/util.py

@ -24,6 +24,7 @@ import binascii
import os, sys, re, json import os, sys, re, json
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
import decimal
from decimal import Decimal from decimal import Decimal
import traceback import traceback
import urllib import urllib
@ -472,8 +473,21 @@ def format_satoshis(x, num_zeros=0, decimal_point=8, precision=None, is_diff=Fal
result = " " * (15 - len(result)) + result result = " " * (15 - len(result)) + result
return result return result
FEERATE_PRECISION = 1 # num fractional decimal places for sat/byte fee rates
_feerate_quanta = Decimal(10) ** (-FEERATE_PRECISION)
def format_fee_satoshis(fee, num_zeros=0): def format_fee_satoshis(fee, num_zeros=0):
return format_satoshis(fee, num_zeros, 0, precision=1) return format_satoshis(fee, num_zeros, 0, precision=FEERATE_PRECISION)
def quantize_feerate(fee):
"""Strip sat/byte fee rate of excess precision."""
if fee is None:
return None
return Decimal(fee).quantize(_feerate_quanta, rounding=decimal.ROUND_HALF_DOWN)
def timestamp_to_datetime(timestamp): def timestamp_to_datetime(timestamp):
if timestamp is None: if timestamp is None:

Loading…
Cancel
Save