mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-20 10:38:42 +01:00
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.
This commit is contained in:
parent
927586990e
commit
0fbaef9676
@ -7,6 +7,8 @@
|
||||
|
||||
#include <tinyformat.h>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
CFeeRate::CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes)
|
||||
{
|
||||
const int64_t nSize{num_bytes};
|
||||
@ -22,7 +24,9 @@ CAmount CFeeRate::GetFee(uint32_t num_bytes) const
|
||||
{
|
||||
const int64_t nSize{num_bytes};
|
||||
|
||||
CAmount nFee = nSatoshisPerK * nSize / 1000;
|
||||
// Be explicit that we're converting from a double to int64_t (CAmount) here.
|
||||
// We've previously had issues with the silent double->int64_t conversion.
|
||||
CAmount nFee{static_cast<CAmount>(std::ceil(nSatoshisPerK * nSize / 1000.0))};
|
||||
|
||||
if (nFee == 0 && nSize != 0) {
|
||||
if (nSatoshisPerK > 0) nFee = CAmount(1);
|
||||
|
@ -48,6 +48,7 @@ public:
|
||||
CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes);
|
||||
/**
|
||||
* Return the fee in satoshis for the given size in bytes.
|
||||
* If the calculated fee would have fractional satoshis, then the returned fee will always be rounded up to the nearest satoshi.
|
||||
*/
|
||||
CAmount GetFee(uint32_t num_bytes) const;
|
||||
/**
|
||||
|
@ -48,13 +48,13 @@ BOOST_AUTO_TEST_CASE(GetFeeTest)
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(-9e3));
|
||||
|
||||
feeRate = CFeeRate(123);
|
||||
// Truncates the result, if not integer
|
||||
// Rounds up the result, if not integer
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(0), CAmount(0));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(8), CAmount(1)); // Special case: returns 1 instead of 0
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(9), CAmount(1));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(14));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(122), CAmount(15));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(122));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(9), CAmount(2));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(15));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(122), CAmount(16));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(123));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(123));
|
||||
BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(1107));
|
||||
|
||||
|
@ -810,10 +810,10 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
|
||||
// nDustThreshold = 182 * 3702 / 1000
|
||||
dustRelayFee = CFeeRate(3702);
|
||||
// dust:
|
||||
t.vout[0].nValue = 673 - 1;
|
||||
t.vout[0].nValue = 674 - 1;
|
||||
CheckIsNotStandard(t, "dust");
|
||||
// not dust:
|
||||
t.vout[0].nValue = 673;
|
||||
t.vout[0].nValue = 674;
|
||||
CheckIsStandard(t);
|
||||
dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE);
|
||||
|
||||
|
@ -179,7 +179,7 @@ class KeyPoolTest(BitcoinTestFramework):
|
||||
assert_equal("psbt" in res, True)
|
||||
|
||||
# create a transaction without change at the maximum fee rate, such that the output is still spendable:
|
||||
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.0008824})
|
||||
res = w2.walletcreatefundedpsbt(inputs=[], outputs=[{destination: 0.00010000}], options={"subtractFeeFromOutputs": [0], "feeRate": 0.0008823})
|
||||
assert_equal("psbt" in res, True)
|
||||
assert_equal(res["fee"], Decimal("0.00009706"))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user