1
0

fee calculation should round up satoshis

so that with a feerate of 0.1 sat/vbyte, for a tx of size 141 vbytes, the fee is 15 sat (instead of 14 sat)
(assuming a min relay fee of 0.1 s/b, the tx needs to pay a minimum of 15 sats to propagate)
This commit is contained in:
SomberNight
2025-08-01 17:48:57 +00:00
parent 4684cdbd17
commit 5432228d17
4 changed files with 17 additions and 9 deletions

View File

@@ -2,6 +2,7 @@ from typing import Optional, Sequence, Tuple, Union, TYPE_CHECKING, Dict
from decimal import Decimal from decimal import Decimal
from numbers import Real from numbers import Real
from enum import IntEnum from enum import IntEnum
import math
from .i18n import _ from .i18n import _
from .util import NoDynamicFeeEstimates, quantize_feerate, format_fee_satoshis from .util import NoDynamicFeeEstimates, quantize_feerate, format_fee_satoshis
@@ -257,11 +258,15 @@ class FeePolicy(Logger):
else: else:
raise NoDynamicFeeEstimates() raise NoDynamicFeeEstimates()
return self.estimate_fee_for_feerate(fee_per_kb, size) return self.estimate_fee_for_feerate(fee_per_kb=fee_per_kb, size=size)
@classmethod @classmethod
def estimate_fee_for_feerate(cls, fee_per_kb: Union[int, float, Decimal], def estimate_fee_for_feerate(
size: Union[int, float, Decimal]) -> int: cls,
*,
fee_per_kb: Union[int, float, Decimal],
size: Union[int, float, Decimal],
) -> int:
# note: 'size' is in vbytes # note: 'size' is in vbytes
size = Decimal(size) size = Decimal(size)
fee_per_kb = Decimal(fee_per_kb) fee_per_kb = Decimal(fee_per_kb)
@@ -269,7 +274,7 @@ class FeePolicy(Logger):
# to be consistent with what is displayed in the GUI, # to be consistent with what is displayed in the GUI,
# the calculation needs to use the same precision: # the calculation needs to use the same precision:
fee_per_byte = quantize_feerate(fee_per_byte) fee_per_byte = quantize_feerate(fee_per_byte)
return round(fee_per_byte * size) return math.ceil(fee_per_byte * size)
class FixedFeePolicy(FeePolicy): class FixedFeePolicy(FeePolicy):

View File

@@ -887,8 +887,8 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin):
def get_child_fee_from_total_feerate(self, fee_per_kb: Optional[int]) -> Optional[int]: def get_child_fee_from_total_feerate(self, fee_per_kb: Optional[int]) -> Optional[int]:
if fee_per_kb is None: if fee_per_kb is None:
return None return None
fee = fee_per_kb * self._total_size / 1000 - self._parent_fee package_fee = FeePolicy.estimate_fee_for_feerate(fee_per_kb=fee_per_kb, size=self._total_size)
fee = round(fee) fee = package_fee - self._parent_fee
fee = min(self._max_fee, fee) fee = min(self._max_fee, fee)
fee = max(self._total_size, fee) # pay at least 1 sat/byte for combined size fee = max(self._total_size, fee) # pay at least 1 sat/byte for combined size
return fee return fee

View File

@@ -350,7 +350,10 @@ class TxEditor(WindowModalDialog):
# fallback to actual fee # fallback to actual fee
displayed_feerate = quantize_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 = round(displayed_feerate * size) if displayed_feerate is not None else None if displayed_feerate is not None:
displayed_fee = FeePolicy.estimate_fee_for_feerate(fee_per_kb=displayed_feerate * 1000, size=size)
else:
displayed_fee = None
self.fee_e.setAmount(displayed_fee) self.fee_e.setAmount(displayed_fee)
else: else:
if freeze_fee: if freeze_fee:

View File

@@ -2874,8 +2874,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def get_child_fee_from_total_feerate(fee_per_kb: Optional[int]) -> Optional[int]: def get_child_fee_from_total_feerate(fee_per_kb: Optional[int]) -> Optional[int]:
if fee_per_kb is None: if fee_per_kb is None:
return None return None
fee = fee_per_kb * total_size / 1000 - parent_fee package_fee = FeePolicy.estimate_fee_for_feerate(fee_per_kb=fee_per_kb, size=total_size)
fee = round(fee) fee = package_fee - parent_fee
fee = min(max_fee, fee) fee = min(max_fee, fee)
fee = max(total_size, fee) # pay at least 1 sat/byte for combined size fee = max(total_size, fee) # pay at least 1 sat/byte for combined size
return fee return fee