-
Notifications
You must be signed in to change notification settings - Fork 36.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fee: Round up fee calculation to avoid a lower than expected feerate #22949
Conversation
9f2fa20
to
387ed1b
Compare
387ed1b
to
053ec2b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Modifying CFeeRate::GetFee
is a policy change. Increasing its return value (for some input) is potentially a policy rule tightening which may have security implications for applications with pre-signed transactions.
On the top of my head, i don't know of of an application for which it would be fatal. But could we instead go the safer path of containing the roundup in the wallet code rather than modifying CFeeRate::GetFee
?
It seems odd to me that the wallet and policy should have different fee calculation results for the same transaction. In terms of pre-signed transactions, what security implications do you imagine? I don't think it is possible for this to cause a transaction's feerate to fall below the default minimum relay fee because the result of the fee calculation at the minimum relay fee is always an integer since the minimum relay fee is an integer. I believe the same is true for the incremental relay fee and how that calculation works. |
I don't think it's true in all cases. Take clawback tx A with fee such as it would be rounded up after this PR, not before. The application wants to replace it with clawback tx B with fee equal to Of course it'd be reckless for such an application to not take some leeway in addition to the incremental fee here, but still this PR would be a behaviour change for this edge case. |
How so? Can you provide a example? I can't think of how that would happen. Consider the example I give in the test: tx A is a 1-in-1-out 110 vbyte tx that pays a feerate of 1.85 sat/vbyte for an absolute fee if 203.5 sats. With rounding down, A will pay 203 sats. We replace this with tx B where the only thing different is the output, but it is of the same type so the size remains the same. Paying the incremental relay fee should be enough for B to replace A. The incremental feerate is 1 sat/vbyte, so regardless of rounding, it is always 110 sats. So B pays 313 sats. When validating whether B is allowed to replace A, it needs to pass two fee related checks: At no point in this checking process is the original feerate of 1.85 sats/vbyte used because validation does not know that was the original targeted feerate. The only times where validation calls |
You are right. I also can't think of how this change would be a policy rule tightening. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tentative concept ACK after looking at the callers of GetFee()
(not GetFeePerK
, which should be a pass-through). The new functional tests fail where expected on master.
$ test/functional/rpc_fundrawtransaction.py
File "/home/jon/projects/bitcoin/bitcoin/test/functional/rpc_fundrawtransaction.py", line 1055, in test_feerate_rounding
assert_raises_rpc_error(-4, "Insufficient funds", w.fundrawtransaction, rawtx, {"fee_rate": 1.85})
AssertionError: Unexpected stderr bitcoind: wallet/spend.cpp:814: bool CreateTransactionInternal(CWallet &, const std::vector<CRecipient> &, CTransactionRef &, CAmount &, int &, bilingual_str &, const CCoinControl &, FeeCalculation &, bool): Assertion `coin_selection_params.m_subtract_fee_outputs || fee_needed <= change_and_fee - change_amount' failed. !=
$ test/functional/wallet_keypool.py
File "/home/jon/projects/bitcoin/bitcoin/test/functional/wallet_keypool.py", line 184, in run_test
assert_equal(res["fee"], Decimal("0.00009706"))
File "/home/jon/projects/bitcoin/bitcoin/test/functional/test_framework/util.py", line 49, in assert_equal
raise AssertionError("not(%s)" % " == ".join(str(arg) for arg in (thing1, thing2) + args))
AssertionError: not(0.00009705 == 0.00009706)
This changes policy for non-default fee rate settings. (Probably no one uses those, but wanted to mention it) |
053ec2b
to
d9ac0cd
Compare
I agree, I don't think this change qualifies as a policy rule tightening. IIUC, previously we had transaction A with absolute fee 203.5 sats. Currently, it's rounded down to 203 sats. After this PR, it will be rounded up to 204 sats. Note, I think (Though if it's change the displayed absolute fee at the interface-level, I think a release note would be appropriate, a user could consume the now-rounded up result and do a comparison with a still-rounded down result from another software than Core in a way which is obscure to us ?) |
This should also fix test failures like this: https://cirrus-ci.com/task/5800722060017664?logs=ci#L3900
Otherwise, the |
Concept ACK. If someone requests a feerate of 1.23 sat/vB, with a tx vsize of 208, it shouldn't result in 255 fee, but 256. Otherwise, the feerate will be 1.2259... sat/vB. I think you'll also need to adjust the test, otherwise they won't check this:
|
@MarcoFalke Not quite sure what you want me to adjust.
|
You are changing |
If you don't want to change |
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.
When calculating a txs absolute fee, if the fee is rounded down to the nearest satoshi, it is possible for the coin selection algorithms to undercalculate the fee needed. This can lead to an assertion error in some situations. One such scenario is added to rpc_fundrawtransaction.py.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
utACK 80dc829
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: bitcoin#22949 Rebased-From: 0fbaef9
When calculating a txs absolute fee, if the fee is rounded down to the nearest satoshi, it is possible for the coin selection algorithms to undercalculate the fee needed. This can lead to an assertion error in some situations. One such scenario is added to rpc_fundrawtransaction.py. Github-Pull: bitcoin#22949 Rebased-From: ce2cc44
Because of floating point precision issues, not all of the rounding done is always correct. To fix this, the fee calculation for assert_fee_amount is changed to better reflect how CFeeRate::GetFee does it. First the feerate is converted to an int representing sat/kvb. Then this is multiplied by the transaction size, divivided by 1000, and rounded up to the nearest sat. The result is then converted back to BTC (divided by 1e8) and then rounded down to the nearest sat to avoid precision errors. Github-Pull: bitcoin#22949 Rebased-From: 80dc829
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: bitcoin#22949 Rebased-From: 0fbaef9
When calculating a txs absolute fee, if the fee is rounded down to the nearest satoshi, it is possible for the coin selection algorithms to undercalculate the fee needed. This can lead to an assertion error in some situations. One such scenario is added to rpc_fundrawtransaction.py. Github-Pull: bitcoin#22949 Rebased-From: ce2cc44
Because of floating point precision issues, not all of the rounding done is always correct. To fix this, the fee calculation for assert_fee_amount is changed to better reflect how CFeeRate::GetFee does it. First the feerate is converted to an int representing sat/kvb. Then this is multiplied by the transaction size, divivided by 1000, and rounded up to the nearest sat. The result is then converted back to BTC (divided by 1e8) and then rounded down to the nearest sat to avoid precision errors. Github-Pull: bitcoin#22949 Rebased-From: 80dc829
Added to #23276 for backport to 22.x |
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: bitcoin#22949 Rebased-From: 0fbaef9
When calculating a txs absolute fee, if the fee is rounded down to the nearest satoshi, it is possible for the coin selection algorithms to undercalculate the fee needed. This can lead to an assertion error in some situations. One such scenario is added to rpc_fundrawtransaction.py. Github-Pull: bitcoin#22949 Rebased-From: ce2cc44
Because of floating point precision issues, not all of the rounding done is always correct. To fix this, the fee calculation for assert_fee_amount is changed to better reflect how CFeeRate::GetFee does it. First the feerate is converted to an int representing sat/kvb. Then this is multiplied by the transaction size, divivided by 1000, and rounded up to the nearest sat. The result is then converted back to BTC (divided by 1e8) and then rounded down to the nearest sat to avoid precision errors. Github-Pull: bitcoin#22949 Rebased-From: 80dc829
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: bitcoin#22949 Rebased-From: 0fbaef9
When calculating a txs absolute fee, if the fee is rounded down to the nearest satoshi, it is possible for the coin selection algorithms to undercalculate the fee needed. This can lead to an assertion error in some situations. One such scenario is added to rpc_fundrawtransaction.py. Github-Pull: bitcoin#22949 Rebased-From: ce2cc44
Because of floating point precision issues, not all of the rounding done is always correct. To fix this, the fee calculation for assert_fee_amount is changed to better reflect how CFeeRate::GetFee does it. First the feerate is converted to an int representing sat/kvb. Then this is multiplied by the transaction size, divivided by 1000, and rounded up to the nearest sat. The result is then converted back to BTC (divided by 1e8) and then rounded down to the nearest sat to avoid precision errors. Github-Pull: bitcoin#22949 Rebased-From: 80dc829
269553f test: Call ceildiv helper with integer (Martin Zumsande) 2f60fc6 ci: Replace soon EOL hirsute with jammy (MarcoFalke) 801b0f0 build: patch qt to explicitly define previously implicit header include (Kittywhiskers Van Gogh) c768bfa tests: Calculate fees more similarly to CFeeRate::GetFee (Andrew Chow) f66bc42 tests: Test for assertion when feerate is rounded down (Andrew Chow) bd7e08e fees: Always round up fee calculated from a feerate (Andrew Chow) 227ae65 wallet: fix segfault by avoiding invalid default-ctored `external_spk_managers` entry (Sebastian Falbesoner) 282863a refactor: include a missing <limits> header in fs.cpp (Joan Karadimov) 7febe4f consensus: don't call GetBlockPos in ReadBlockFromDisk without lock (Jon Atack) c671c6f the result of CWallet::IsHDEnabled() was initialized with true. (Saibato) a5a1538 build, qt: Fix typo in QtInputSupport check (Hennadii Stepanov) c95b188 system: skip trying to set the locale on NetBSD (fanquake) c1cdedd guix: Fix powerpc64(le) dynamic linker name (Carl Dong) 92d44ff doc: Add 23061 release notes (MarcoFalke) db76db7 Fix (inverse) meaning of -persistmempool (MarcoFalke) 85c78e0 build: Restrict check for CRC32C intrinsic to aarch64 (W. J. van der Laan) Pull request description: Collecting backports for the 22.1 release. Currently: * #23045 * #23061 * #23148 * #22390 * #22820 * #22781 * #22895 * #23335 * #23333 * #22949 * #23580 * #23504 * #24239 ACKs for top commit: achow101: ACK 269553f Tree-SHA512: b3a57ea241be7a83488eeb032276f4cf82a0987aada906a82f94a20c4acf9f2397708249dcecbe1c7575e70d09c60b835233d4718af4013c7bc58896c618274c
CheckIsNotStandard(t, "dust"); | ||
// not dust: | ||
t.vout[0].nValue = 673; | ||
t.vout[0].nValue = 674; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this changes behaviour between releases if the user changed the default dust rate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for -blockmintxfee
, and -incrementalRelayFee
, sendrawtransaction
/testmempoolaccept
feerate RPC arg, checks in CheckFeeRate
.
…g correctly Caution: This implementation is for a post-bitcoin#22949 codebase
When calculating the fee for a feerate, it is possible that the final calculation will have fractional satoshis. Currently those are ignored via truncation which results in the absolute fee being rounded down. Rounding down is problematic because it results in a feerate that is slightly lower than the feerate represented by the
CFeeRate
object. A slightly lower feerate particularly causes issues for coin selection as it can trigger an assertion error. To avoid potentially underpaying the feerate (and the assertion), always round up the calculated fee.A test is added for the assertion, along with a comment explaining what happens.
It is unlikely that a user can trigger this as it requires a very specific set of rounding errors to occur as well as the transaction not needing any change and being right on the lower bound of the exact match window. However I was able to trigger the assertion while running coin selection simulations, albeit after thousands of transactions and with some weird feerates.