Skip to content

Commit 0fbaef9

Browse files
committed
fees: Always round up fee calculated from a feerate
When calculating the fee for a given tx size from a fee rate, we should always round up to the next satoshi. Otherwise, if we round down (via truncation), the calculated fee may result in a fee with a feerate slightly less than targeted. This is particularly important for coin selection as a slightly lower feerate than expected can result in a variety of issues.
1 parent 9275869 commit 0fbaef9

File tree

5 files changed

+14
-9
lines changed

5 files changed

+14
-9
lines changed

src/policy/feerate.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include <tinyformat.h>
99

10+
#include <cmath>
11+
1012
CFeeRate::CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes)
1113
{
1214
const int64_t nSize{num_bytes};
@@ -22,7 +24,9 @@ CAmount CFeeRate::GetFee(uint32_t num_bytes) const
2224
{
2325
const int64_t nSize{num_bytes};
2426

25-
CAmount nFee = nSatoshisPerK * nSize / 1000;
27+
// Be explicit that we're converting from a double to int64_t (CAmount) here.
28+
// We've previously had issues with the silent double->int64_t conversion.
29+
CAmount nFee{static_cast<CAmount>(std::ceil(nSatoshisPerK * nSize / 1000.0))};
2630

2731
if (nFee == 0 && nSize != 0) {
2832
if (nSatoshisPerK > 0) nFee = CAmount(1);

src/policy/feerate.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class CFeeRate
4848
CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes);
4949
/**
5050
* Return the fee in satoshis for the given size in bytes.
51+
* If the calculated fee would have fractional satoshis, then the returned fee will always be rounded up to the nearest satoshi.
5152
*/
5253
CAmount GetFee(uint32_t num_bytes) const;
5354
/**

src/test/amount_tests.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,13 @@ BOOST_AUTO_TEST_CASE(GetFeeTest)
4848
BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(-9e3));
4949

5050
feeRate = CFeeRate(123);
51-
// Truncates the result, if not integer
51+
// Rounds up the result, if not integer
5252
BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0));
5353
BOOST_CHECK_EQUAL(feeRate.GetFee(8), CAmount(1)); // Special case: returns 1 instead of 0
54-
BOOST_CHECK_EQUAL(feeRate.GetFee(9), CAmount(1));
55-
BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(14));
56-
BOOST_CHECK_EQUAL(feeRate.GetFee(122), CAmount(15));
57-
BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(122));
54+
BOOST_CHECK_EQUAL(feeRate.GetFee(9), CAmount(2));
55+
BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(15));
56+
BOOST_CHECK_EQUAL(feeRate.GetFee(122), CAmount(16));
57+
BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(123));
5858
BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(123));
5959
BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(1107));
6060

src/test/transaction_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -810,10 +810,10 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
810810
// nDustThreshold = 182 * 3702 / 1000
811811
dustRelayFee = CFeeRate(3702);
812812
// dust:
813-
t.vout[0].nValue = 673 - 1;
813+
t.vout[0].nValue = 674 - 1;
814814
CheckIsNotStandard(t, "dust");
815815
// not dust:
816-
t.vout[0].nValue = 673;
816+
t.vout[0].nValue = 674;
817817
CheckIsStandard(t);
818818
dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE);
819819

test/functional/wallet_keypool.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ def run_test(self):
179179
assert_equal("psbt" in res, True)
180180

181181
# create a transaction without change at the maximum fee rate, such that the output is still spendable:
182-
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.0008824})
182+
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.0008823})
183183
assert_equal("psbt" in res, True)
184184
assert_equal(res["fee"], Decimal("0.00009706"))
185185

0 commit comments

Comments
 (0)