Skip to content

Commit daca51b

Browse files
committed
Merge bitcoin/bitcoin#32750: refactor: CFeeRate encapsulates FeeFrac internally
d3b8a54 Refactor CFeeRate to use FeeFrac internally (Pol Espinasa) Pull request description: The `FeeFrac` type represents a fraction, intended to be used for `sats/vbyte` or `sats/WU`. It was added to improve accuracy when evaluating fee rates in cluster mempool. [1] But it can also be used to fix the precision issues that the current `CFeeRate` class has now. At the moment, `CFeeRate` handles the fee rate as satoshis per kilovirtualbyte: `CAmount / kvB` using an integer. This PR fix `CFeeRate` precision issues by encapsulating `FeeFrac` internally keeping backwards compatibility. This PR can also be used as a based to use multiple units on RPC calls as detailed in this issue [2]. Some previous discussions: [1] bitcoin/bitcoin#30535 [2] bitcoin/bitcoin#32093 ACKs for top commit: achow101: ACK d3b8a54 murchandamus: code review, lightly tested ACK d3b8a54 ismaelsadeeq: re-ACK d3b8a54 📦 theStack: Code-review ACK d3b8a54 Tree-SHA512: 5a8149d81e82ad4e60a0e76ff6a82a5b1c4e212cf5156c1cdd16bf9acbb351e7be458eac3f0a2ae89107f331062b299c1d9ca649d3b820ad0b68e6d1a14292e5
2 parents f679bad + d3b8a54 commit daca51b

File tree

11 files changed

+66
-65
lines changed

11 files changed

+66
-65
lines changed

src/policy/feerate.cpp

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,36 @@
11
// Copyright (c) 2009-2010 Satoshi Nakamoto
2-
// Copyright (c) 2009-2022 The Bitcoin Core developers
2+
// Copyright (c) 2009-present The Bitcoin Core developers
33
// Distributed under the MIT software license, see the accompanying
44
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

66
#include <consensus/amount.h>
77
#include <policy/feerate.h>
88
#include <tinyformat.h>
99

10-
#include <cmath>
1110

12-
CFeeRate::CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes)
11+
CFeeRate::CFeeRate(const CAmount& nFeePaid, int32_t virtual_bytes)
1312
{
14-
const int64_t nSize{num_bytes};
15-
16-
if (nSize > 0) {
17-
nSatoshisPerK = nFeePaid * 1000 / nSize;
13+
if (virtual_bytes > 0) {
14+
m_feerate = FeePerVSize(nFeePaid, virtual_bytes);
1815
} else {
19-
nSatoshisPerK = 0;
16+
m_feerate = FeePerVSize();
2017
}
2118
}
2219

23-
CAmount CFeeRate::GetFee(uint32_t num_bytes) const
20+
CAmount CFeeRate::GetFee(int32_t virtual_bytes) const
2421
{
25-
const int64_t nSize{num_bytes};
26-
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))};
30-
31-
if (nFee == 0 && nSize != 0) {
32-
if (nSatoshisPerK > 0) nFee = CAmount(1);
33-
if (nSatoshisPerK < 0) nFee = CAmount(-1);
34-
}
35-
22+
Assume(virtual_bytes >= 0);
23+
if (m_feerate.IsEmpty()) { return CAmount(0);}
24+
CAmount nFee = CAmount(m_feerate.EvaluateFeeUp(virtual_bytes));
25+
if (nFee == 0 && virtual_bytes != 0 && m_feerate.fee < 0) return CAmount(-1);
3626
return nFee;
3727
}
3828

3929
std::string CFeeRate::ToString(const FeeEstimateMode& fee_estimate_mode) const
4030
{
31+
const CAmount feerate_per_kvb = GetFeePerK();
4132
switch (fee_estimate_mode) {
42-
case FeeEstimateMode::SAT_VB: return strprintf("%d.%03d %s/vB", nSatoshisPerK / 1000, nSatoshisPerK % 1000, CURRENCY_ATOM);
43-
default: return strprintf("%d.%08d %s/kvB", nSatoshisPerK / COIN, nSatoshisPerK % COIN, CURRENCY_UNIT);
33+
case FeeEstimateMode::SAT_VB: return strprintf("%d.%03d %s/vB", feerate_per_kvb / 1000, feerate_per_kvb % 1000, CURRENCY_ATOM);
34+
default: return strprintf("%d.%08d %s/kvB", feerate_per_kvb / COIN, feerate_per_kvb % COIN, CURRENCY_UNIT);
4435
}
4536
}

src/policy/feerate.h

Lines changed: 30 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2009-2010 Satoshi Nakamoto
2-
// Copyright (c) 2009-2022 The Bitcoin Core developers
2+
// Copyright (c) 2009-present The Bitcoin Core developers
33
// Distributed under the MIT software license, see the accompanying
44
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

@@ -8,6 +8,7 @@
88

99
#include <consensus/amount.h>
1010
#include <serialize.h>
11+
#include <util/feefrac.h>
1112

1213

1314
#include <cstdint>
@@ -27,52 +28,59 @@ enum class FeeEstimateMode {
2728
};
2829

2930
/**
30-
* Fee rate in satoshis per kilovirtualbyte: CAmount / kvB
31+
* Fee rate in satoshis per virtualbyte: CAmount / vB
32+
* the feerate is represented internally as FeeFrac
3133
*/
3234
class CFeeRate
3335
{
3436
private:
35-
/** Fee rate in sat/kvB (satoshis per 1000 virtualbytes) */
36-
CAmount nSatoshisPerK;
37+
/** Fee rate in sats/vB (satoshis per N virtualbytes) */
38+
FeePerVSize m_feerate;
3739

3840
public:
39-
/** Fee rate of 0 satoshis per kvB */
40-
CFeeRate() : nSatoshisPerK(0) { }
41+
/** Fee rate of 0 satoshis per 0 vB */
42+
CFeeRate() = default;
4143
template<std::integral I> // Disallow silent float -> int conversion
42-
explicit CFeeRate(const I _nSatoshisPerK): nSatoshisPerK(_nSatoshisPerK) {
43-
}
44+
explicit CFeeRate(const I m_feerate_kvb) : m_feerate(FeePerVSize(m_feerate_kvb, 1000)) {}
4445

4546
/**
4647
* Construct a fee rate from a fee in satoshis and a vsize in vB.
4748
*
4849
* param@[in] nFeePaid The fee paid by a transaction, in satoshis
49-
* param@[in] num_bytes The vsize of a transaction, in vbytes
50+
* param@[in] virtual_bytes The vsize of a transaction, in vbytes
51+
*
52+
* Passing any virtual_bytes less than or equal to 0 will result in 0 fee rate per 0 size.
5053
*/
51-
CFeeRate(const CAmount& nFeePaid, uint32_t num_bytes);
54+
CFeeRate(const CAmount& nFeePaid, int32_t virtual_bytes);
5255

5356
/**
5457
* Return the fee in satoshis for the given vsize in vbytes.
5558
* If the calculated fee would have fractional satoshis, then the
5659
* returned fee will always be rounded up to the nearest satoshi.
5760
*/
58-
CAmount GetFee(uint32_t num_bytes) const;
61+
CAmount GetFee(int32_t virtual_bytes) const;
5962

6063
/**
6164
* Return the fee in satoshis for a vsize of 1000 vbytes
6265
*/
63-
CAmount GetFeePerK() const { return nSatoshisPerK; }
64-
friend bool operator<(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK < b.nSatoshisPerK; }
65-
friend bool operator>(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK > b.nSatoshisPerK; }
66-
friend bool operator==(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK == b.nSatoshisPerK; }
67-
friend bool operator<=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK <= b.nSatoshisPerK; }
68-
friend bool operator>=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK >= b.nSatoshisPerK; }
69-
friend bool operator!=(const CFeeRate& a, const CFeeRate& b) { return a.nSatoshisPerK != b.nSatoshisPerK; }
70-
CFeeRate& operator+=(const CFeeRate& a) { nSatoshisPerK += a.nSatoshisPerK; return *this; }
66+
CAmount GetFeePerK() const { return CAmount(m_feerate.EvaluateFeeDown(1000)); }
67+
friend std::weak_ordering operator<=>(const CFeeRate& a, const CFeeRate& b) noexcept
68+
{
69+
return FeeRateCompare(a.m_feerate, b.m_feerate);
70+
}
71+
friend bool operator==(const CFeeRate& a, const CFeeRate& b) noexcept
72+
{
73+
return FeeRateCompare(a.m_feerate, b.m_feerate) == std::weak_ordering::equivalent;
74+
}
75+
CFeeRate& operator+=(const CFeeRate& a) {
76+
m_feerate = FeePerVSize(GetFeePerK() + a.GetFeePerK(), 1000);
77+
return *this;
78+
}
7179
std::string ToString(const FeeEstimateMode& fee_estimate_mode = FeeEstimateMode::BTC_KVB) const;
72-
friend CFeeRate operator*(const CFeeRate& f, int a) { return CFeeRate(a * f.nSatoshisPerK); }
73-
friend CFeeRate operator*(int a, const CFeeRate& f) { return CFeeRate(a * f.nSatoshisPerK); }
80+
friend CFeeRate operator*(const CFeeRate& f, int a) { return CFeeRate(a * f.m_feerate.fee, f.m_feerate.size); }
81+
friend CFeeRate operator*(int a, const CFeeRate& f) { return CFeeRate(a * f.m_feerate.fee, f.m_feerate.size); }
7482

75-
SERIALIZE_METHODS(CFeeRate, obj) { READWRITE(obj.nSatoshisPerK); }
83+
SERIALIZE_METHODS(CFeeRate, obj) { READWRITE(obj.m_feerate.fee, obj.m_feerate.size); }
7684
};
7785

7886
#endif // BITCOIN_POLICY_FEERATE_H

src/test/amount_tests.cpp

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2016-2021 The Bitcoin Core developers
1+
// Copyright (c) 2016-present The Bitcoin Core developers
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

@@ -73,18 +73,21 @@ BOOST_AUTO_TEST_CASE(GetFeeTest)
7373
BOOST_CHECK(CFeeRate(CAmount(-1), 0) == CFeeRate(0));
7474
BOOST_CHECK(CFeeRate(CAmount(0), 0) == CFeeRate(0));
7575
BOOST_CHECK(CFeeRate(CAmount(1), 0) == CFeeRate(0));
76+
BOOST_CHECK(CFeeRate(CAmount(1), -1000) == CFeeRate(0));
7677
// default value
7778
BOOST_CHECK(CFeeRate(CAmount(-1), 1000) == CFeeRate(-1));
7879
BOOST_CHECK(CFeeRate(CAmount(0), 1000) == CFeeRate(0));
7980
BOOST_CHECK(CFeeRate(CAmount(1), 1000) == CFeeRate(1));
80-
// lost precision (can only resolve satoshis per kB)
81-
BOOST_CHECK(CFeeRate(CAmount(1), 1001) == CFeeRate(0));
82-
BOOST_CHECK(CFeeRate(CAmount(2), 1001) == CFeeRate(1));
81+
// Previously, precision was limited to three decimal digits
82+
// due to only supporting satoshis per kB, so CFeeRate(CAmount(1), 1001) was equal to CFeeRate(0)
83+
// Since #32750, higher precision is maintained.
84+
BOOST_CHECK(CFeeRate(CAmount(1), 1001) > CFeeRate(0) && CFeeRate(CAmount(1), 1001) < CFeeRate(1));
85+
BOOST_CHECK(CFeeRate(CAmount(2), 1001) > CFeeRate(1) && CFeeRate(CAmount(2), 1001) < CFeeRate(2));
8386
// some more integer checks
84-
BOOST_CHECK(CFeeRate(CAmount(26), 789) == CFeeRate(32));
85-
BOOST_CHECK(CFeeRate(CAmount(27), 789) == CFeeRate(34));
87+
BOOST_CHECK(CFeeRate(CAmount(26), 789) > CFeeRate(32) && CFeeRate(CAmount(26), 789) < CFeeRate(33));
88+
BOOST_CHECK(CFeeRate(CAmount(27), 789) > CFeeRate(34) && CFeeRate(CAmount(27), 789) < CFeeRate(35));
8689
// Maximum size in bytes, should not crash
87-
CFeeRate(MAX_MONEY, std::numeric_limits<uint32_t>::max()).GetFeePerK();
90+
CFeeRate(MAX_MONEY, std::numeric_limits<int32_t>::max()).GetFeePerK();
8891

8992
// check multiplication operator
9093
// check multiplying by zero

src/test/fuzz/fee_rate.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2020-2021 The Bitcoin Core developers
1+
// Copyright (c) 2020-present The Bitcoin Core developers
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

@@ -20,7 +20,7 @@ FUZZ_TARGET(fee_rate)
2020
const CFeeRate fee_rate{satoshis_per_k};
2121

2222
(void)fee_rate.GetFeePerK();
23-
const auto bytes = fuzzed_data_provider.ConsumeIntegral<uint32_t>();
23+
const auto bytes = fuzzed_data_provider.ConsumeIntegralInRange<int32_t>(0, std::numeric_limits<int32_t>::max());
2424
if (!MultiplicationOverflow(int64_t{bytes}, satoshis_per_k)) {
2525
(void)fee_rate.GetFee(bytes);
2626
}

src/test/fuzz/feefrac.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ FUZZ_TARGET(feefrac_mul_div)
223223
if (mul64 < std::numeric_limits<int64_t>::max() / 1000 &&
224224
mul64 > std::numeric_limits<int64_t>::min() / 1000 &&
225225
quot_abs < arith_uint256{std::numeric_limits<int64_t>::max() / 1000}) {
226-
CFeeRate feerate(mul64, (uint32_t)div);
226+
CFeeRate feerate(mul64, div);
227227
CAmount feerate_fee{feerate.GetFee(mul32)};
228228
auto allowed_gap = static_cast<int64_t>(mul32 / 1000 + 3 + round_down);
229229
assert(feerate_fee - res_fee >= -allowed_gap);

src/test/fuzz/fees.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2020-2021 The Bitcoin Core developers
1+
// Copyright (c) 2020-present The Bitcoin Core developers
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

src/validation.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1406,7 +1406,7 @@ bool MemPoolAccept::SubmitPackage(const ATMPArgs& args, std::vector<Workspace>&
14061406
auto iter = m_pool.GetIter(ws.m_ptx->GetHash());
14071407
Assume(iter.has_value());
14081408
const auto effective_feerate = args.m_package_feerates ? ws.m_package_feerate :
1409-
CFeeRate{ws.m_modified_fees, static_cast<uint32_t>(ws.m_vsize)};
1409+
CFeeRate{ws.m_modified_fees, static_cast<int32_t>(ws.m_vsize)};
14101410
const auto effective_feerate_wtxids = args.m_package_feerates ? all_package_wtxids :
14111411
std::vector<Wtxid>{ws.m_ptx->GetWitnessHash()};
14121412
results.emplace(ws.m_ptx->GetWitnessHash(),
@@ -1470,7 +1470,7 @@ MempoolAcceptResult MemPoolAccept::AcceptSingleTransaction(const CTransactionRef
14701470

14711471
if (!ConsensusScriptChecks(args, ws)) return MempoolAcceptResult::Failure(ws.m_state);
14721472

1473-
const CFeeRate effective_feerate{ws.m_modified_fees, static_cast<uint32_t>(ws.m_vsize)};
1473+
const CFeeRate effective_feerate{ws.m_modified_fees, static_cast<int32_t>(ws.m_vsize)};
14741474
// Tx was accepted, but not added
14751475
if (args.m_test_accept) {
14761476
return MempoolAcceptResult::Success(std::move(m_subpackage.m_replaced_transactions), ws.m_vsize,
@@ -1626,7 +1626,7 @@ PackageMempoolAcceptResult MemPoolAccept::AcceptMultipleTransactions(const std::
16261626
}
16271627
if (args.m_test_accept) {
16281628
const auto effective_feerate = args.m_package_feerates ? ws.m_package_feerate :
1629-
CFeeRate{ws.m_modified_fees, static_cast<uint32_t>(ws.m_vsize)};
1629+
CFeeRate{ws.m_modified_fees, static_cast<int32_t>(ws.m_vsize)};
16301630
const auto effective_feerate_wtxids = args.m_package_feerates ? all_package_wtxids :
16311631
std::vector<Wtxid>{ws.m_ptx->GetWitnessHash()};
16321632
results.emplace(ws.m_ptx->GetWitnessHash(),

src/wallet/fees.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2009-2010 Satoshi Nakamoto
2-
// Copyright (c) 2009-2022 The Bitcoin Core developers
2+
// Copyright (c) 2009-present The Bitcoin Core developers
33
// Distributed under the MIT software license, see the accompanying
44
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

@@ -12,13 +12,13 @@
1212
namespace wallet {
1313
CAmount GetRequiredFee(const CWallet& wallet, unsigned int nTxBytes)
1414
{
15-
return GetRequiredFeeRate(wallet).GetFee(nTxBytes);
15+
return GetRequiredFeeRate(wallet).GetFee(static_cast<int32_t>(nTxBytes));
1616
}
1717

1818

1919
CAmount GetMinimumFee(const CWallet& wallet, unsigned int nTxBytes, const CCoinControl& coin_control, FeeCalculation* feeCalc)
2020
{
21-
return GetMinimumFeeRate(wallet, coin_control, feeCalc).GetFee(nTxBytes);
21+
return GetMinimumFeeRate(wallet, coin_control, feeCalc).GetFee(static_cast<int32_t>(nTxBytes));
2222
}
2323

2424
CFeeRate GetRequiredFeeRate(const CWallet& wallet)

src/wallet/fees.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// Copyright (c) 2009-2010 Satoshi Nakamoto
2-
// Copyright (c) 2009-2021 The Bitcoin Core developers
2+
// Copyright (c) 2009-present The Bitcoin Core developers
33
// Distributed under the MIT software license, see the accompanying
44
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
55

src/wallet/test/coinselector_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
244244
coin_selection_params_bnb.m_long_term_feerate = CFeeRate(3000);
245245

246246
// Add selectable outputs, increasing their raw amounts by their input fee to make the effective value equal to the raw amount
247-
CAmount input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(/*num_bytes=*/68); // bech32 input size (default test output type)
247+
CAmount input_fee = coin_selection_params_bnb.m_effective_feerate.GetFee(/*virtual_bytes=*/68); // bech32 input size (default test output type)
248248
add_coin(available_coins, *wallet, 10 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
249249
add_coin(available_coins, *wallet, 9 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);
250250
add_coin(available_coins, *wallet, 1 * CENT + input_fee, coin_selection_params_bnb.m_effective_feerate, 6 * 24, false, 0, true);

0 commit comments

Comments
 (0)