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 <tinyformat.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
CFeeRate::CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes)
|
CFeeRate::CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes)
|
||||||
{
|
{
|
||||||
const int64_t nSize{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};
|
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 (nFee == 0 && nSize != 0) {
|
||||||
if (nSatoshisPerK > 0) nFee = CAmount(1);
|
if (nSatoshisPerK > 0) nFee = CAmount(1);
|
||||||
|
|
|
@ -48,6 +48,7 @@ public:
|
||||||
CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes);
|
CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes);
|
||||||
/**
|
/**
|
||||||
* Return the fee in satoshis for the given size in 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;
|
CAmount GetFee(uint32_t num_bytes) const;
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -48,13 +48,13 @@ BOOST_AUTO_TEST_CASE(GetFeeTest)
|
||||||
BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(-9e3));
|
BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(-9e3));
|
||||||
|
|
||||||
feeRate = CFeeRate(123);
|
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(0), CAmount(0));
|
||||||
BOOST_CHECK_EQUAL(feeRate.GetFee(8), CAmount(1)); // Special case: returns 1 instead of 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(9), CAmount(2));
|
||||||
BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(14));
|
BOOST_CHECK_EQUAL(feeRate.GetFee(121), CAmount(15));
|
||||||
BOOST_CHECK_EQUAL(feeRate.GetFee(122), CAmount(15));
|
BOOST_CHECK_EQUAL(feeRate.GetFee(122), CAmount(16));
|
||||||
BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(122));
|
BOOST_CHECK_EQUAL(feeRate.GetFee(999), CAmount(123));
|
||||||
BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(123));
|
BOOST_CHECK_EQUAL(feeRate.GetFee(1e3), CAmount(123));
|
||||||
BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(1107));
|
BOOST_CHECK_EQUAL(feeRate.GetFee(9e3), CAmount(1107));
|
||||||
|
|
||||||
|
|
|
@ -807,12 +807,12 @@ BOOST_AUTO_TEST_CASE(test_IsStandard)
|
||||||
// nDustThreshold = 182 * 3702 / 1000
|
// nDustThreshold = 182 * 3702 / 1000
|
||||||
dustRelayFee = CFeeRate(3702);
|
dustRelayFee = CFeeRate(3702);
|
||||||
// dust:
|
// dust:
|
||||||
t.vout[0].nValue = 673 - 1;
|
t.vout[0].nValue = 674 - 1;
|
||||||
reason.clear();
|
reason.clear();
|
||||||
BOOST_CHECK(!IsStandardTx(CTransaction(t), reason));
|
BOOST_CHECK(!IsStandardTx(CTransaction(t), reason));
|
||||||
BOOST_CHECK_EQUAL(reason, "dust");
|
BOOST_CHECK_EQUAL(reason, "dust");
|
||||||
// not dust:
|
// not dust:
|
||||||
t.vout[0].nValue = 673;
|
t.vout[0].nValue = 674;
|
||||||
BOOST_CHECK(IsStandardTx(CTransaction(t), reason));
|
BOOST_CHECK(IsStandardTx(CTransaction(t), reason));
|
||||||
dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE);
|
dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE);
|
||||||
|
|
||||||
|
|
|
@ -179,7 +179,7 @@ class KeyPoolTest(BitcoinTestFramework):
|
||||||
assert_equal("psbt" in res, True)
|
assert_equal("psbt" in res, True)
|
||||||
|
|
||||||
# create a transaction without change at the maximum fee rate, such that the output is still spendable:
|
# 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("psbt" in res, True)
|
||||||
assert_equal(res["fee"], Decimal("0.00009706"))
|
assert_equal(res["fee"], Decimal("0.00009706"))
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue