mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-04-29 14:59:39 -04: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.
Github-Pull: #22949
Rebased-From: 0fbaef9676
This commit is contained in:
parent
227ae65254
commit
bd7e08e36b
5 changed files with 14 additions and 9 deletions
|
@ -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));
|
||||
|
||||
|
|
|
@ -807,12 +807,12 @@ 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;
|
||||
reason.clear();
|
||||
BOOST_CHECK(!IsStandardTx(CTransaction(t), reason));
|
||||
BOOST_CHECK_EQUAL(reason, "dust");
|
||||
// not dust:
|
||||
t.vout[0].nValue = 673;
|
||||
t.vout[0].nValue = 674;
|
||||
BOOST_CHECK(IsStandardTx(CTransaction(t), reason));
|
||||
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…
Add table
Reference in a new issue