electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit dae187badabf4a8e458a217455e33a8a97c6f2f2
parent 3337af0734cf17b7510783bf9bdecb8e4c4f6f17
Author: ghost43 <somber.night@protonmail.com>
Date:   Wed,  9 May 2018 19:30:18 +0200

allow fractional feerates (#4324)


Diffstat:
Mgui/qt/amountedit.py | 31++++++++++++++++++++++++-------
Mgui/qt/main_window.py | 17+++++++++--------
Mlib/simple_config.py | 10++++------
Mlib/util.py | 16+++++++++++++++-
4 files changed, 52 insertions(+), 22 deletions(-)

diff --git a/gui/qt/amountedit.py b/gui/qt/amountedit.py @@ -1,11 +1,12 @@ # -*- coding: utf-8 -*- +from decimal import Decimal + from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import (QLineEdit, QStyle, QStyleOptionFrame) -from decimal import Decimal -from electrum.util import format_satoshis_plain, decimal_point_to_base_unit_name +from electrum.util import format_satoshis_plain, decimal_point_to_base_unit_name, FEERATE_PRECISION class MyLineEdit(QLineEdit): @@ -19,7 +20,7 @@ class MyLineEdit(QLineEdit): class AmountEdit(MyLineEdit): 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) # This seems sufficient for hundred-BTC amounts with 8 decimals self.setFixedWidth(140) @@ -28,10 +29,14 @@ class AmountEdit(MyLineEdit): self.is_int = is_int self.is_shortcut = False self.help_palette = QPalette() + self.extra_precision = 0 def decimal_point(self): return 8 + def max_precision(self): + return self.decimal_point() + self.extra_precision + def numbify(self): text = self.text().strip() if text == '!': @@ -45,7 +50,7 @@ class AmountEdit(MyLineEdit): if '.' in s: p = s.find('.') s = s.replace('.','') - s = s[:p] + '.' + s[p:p+self.decimal_point()] + s = s[:p] + '.' + s[p:p+self.max_precision()] self.setText(s) # setText sets Modified to False. Instead we want to remember # if updates were because of user modification. @@ -75,7 +80,7 @@ class AmountEdit(MyLineEdit): 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) self.decimal_point = decimal_point @@ -87,8 +92,15 @@ class BTCAmountEdit(AmountEdit): x = Decimal(str(self.text())) except: return None - p = pow(10, self.decimal_point()) - return int( p * x ) + # scale it to max allowed precision, make it an int + 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): if amount is None: @@ -98,6 +110,11 @@ class BTCAmountEdit(AmountEdit): 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): return 'sat/byte' diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py @@ -49,7 +49,7 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis, UserCancelled, NoDynamicFeeEstimates, profiler, export_meta, import_meta, bh2u, bfh, InvalidPassword, 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 util, bitcoin, commands, coinchooser from electrum import paymentrequest @@ -1102,7 +1102,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): self.config.set_key('fee_per_kb', fee_rate, False) 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: self.feerate_e.setAmount(None) self.fee_e.setModified(False) @@ -1334,12 +1335,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): if freeze_feerate or self.fee_slider.is_active(): displayed_feerate = self.feerate_e.get_amount() if displayed_feerate: - displayed_feerate = displayed_feerate // 1000 + displayed_feerate = quantize_feerate(displayed_feerate / 1000) else: # 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) - 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) else: if freeze_fee: @@ -1349,14 +1350,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): displayed_fee = fee self.fee_e.setAmount(displayed_fee) 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) # show/hide fee rounding icon 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.setVisible(bool(feerounding)) + self.feerounding_icon.setVisible(abs(feerounding) >= 1) if self.is_max: amount = tx.output_value() diff --git a/lib/simple_config.py b/lib/simple_config.py @@ -3,6 +3,7 @@ import threading import time import os import stat +from decimal import Decimal from copy import deepcopy @@ -473,12 +474,9 @@ class SimpleConfig(PrintError): @classmethod def estimate_fee_for_feerate(cls, fee_per_kb, size): - # note: We only allow integer sat/byte values atm. - # The GUI for simplicity reasons only displays integer sat/byte, - # and for the sake of consistency, we thus only use integer sat/byte in - # the backend too. - fee_per_byte = int(fee_per_kb / 1000) - return int(fee_per_byte * size) + fee_per_kb = Decimal(fee_per_kb) + fee_per_byte = fee_per_kb / 1000 + return round(fee_per_byte * size) def update_fee_estimates(self, key, value): self.fee_estimates[key] = value diff --git a/lib/util.py b/lib/util.py @@ -24,6 +24,7 @@ import binascii import os, sys, re, json from collections import defaultdict from datetime import datetime +import decimal from decimal import Decimal import traceback 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 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): - 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): if timestamp is None: