-
Notifications
You must be signed in to change notification settings - Fork 37.6k
fee: Round up fee calculation to avoid a lower than expected feerate #22949
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Same for |
||
CheckIsStandard(t); | ||
dustRelayFee = CFeeRate(DUST_RELAY_TX_FEE); | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -36,12 +36,12 @@ def assert_approx(v, vexp, vspan=0.00001): | |
|
||
def assert_fee_amount(fee, tx_size, feerate_BTC_kvB): | ||
"""Assert the fee is in range.""" | ||
feerate_BTC_vB = feerate_BTC_kvB / 1000 | ||
target_fee = satoshi_round(tx_size * feerate_BTC_vB) | ||
target_fee = get_fee(tx_size, feerate_BTC_kvB) | ||
if fee < target_fee: | ||
raise AssertionError("Fee of %s BTC too low! (Should be %s BTC)" % (str(fee), str(target_fee))) | ||
# allow the wallet's estimation to be at most 2 bytes off | ||
if fee > (tx_size + 2) * feerate_BTC_vB: | ||
high_fee = get_fee(tx_size + 2, feerate_BTC_kvB) | ||
if fee > high_fee: | ||
raise AssertionError("Fee of %s BTC too high! (Should be %s BTC)" % (str(fee), str(target_fee))) | ||
|
||
|
||
|
@@ -218,6 +218,18 @@ def str_to_b64str(string): | |
return b64encode(string.encode('utf-8')).decode('ascii') | ||
|
||
|
||
def ceildiv(a, b): | ||
"""Divide 2 ints and round up to next int rather than round down""" | ||
return -(-a // b) | ||
|
||
|
||
def get_fee(tx_size, feerate_btc_kvb): | ||
"""Calculate the fee in BTC given a feerate is BTC/kvB. Reflects CFeeRate::GetFee""" | ||
feerate_sat_kvb = int(feerate_btc_kvb * Decimal(1e8)) # Fee in sat/kvb as an int to avoid float precision errors | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems a bug to silently accept sub-decimal feerates (Bitcoin Core doesn't accept them either). I think this can be fixed (and the whole conversion avoided) by simply changing all call sites to provide the value in feerate_sat_vB. I can create a pull for that, if this is too unrelated to this pull request. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This doesn't seem like bug conceptually. Fees are discrete values, so a fixed precision decimal representation makes sense for absolute fees. Feerates are continuous values (ratio of the discrete values, rational numbers) so any floating or fractional representation makes sense and while error feedback about being out of range would be useful, error feedback about being too precise would be strange. Also, this is just a drive-by review comment so feel free to ignore, but the math here converting between def ceildiv(a, b):
return -(-a // b) A There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Sure, feerates in theory are rational numbers. However, this function is there to replicate the behavior of Bitcoin Core when it comes to feerates. And Bitcoin Core doesn't accept rational numbers as feerates. Only natural numbers, which represent a sat/vB ratio. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. CFeeRates are BTC/kvb so we actually can get sub-decimal sat/vb feerates, to 4 decimal places of precision. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added a I think any broader feerate changes to the tests should be done in another PR. The goal of the test changes here is to just make sure that the calculation is the same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think this could be a nice simplification for another PR. This function is only used by |
||
target_fee_sat = ceildiv(feerate_sat_kvb * tx_size, 1000) # Round calculated fee up to nearest sat | ||
return satoshi_round(target_fee_sat / Decimal(1e8)) # Truncate BTC result to nearest sat | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In commit "tests: Calculate fees more similarly to CFeeRate::GetFee" (80dc829) I think the |
||
|
||
|
||
def satoshi_round(amount): | ||
return Decimal(amount).quantize(Decimal('0.00000001'), rounding=ROUND_DOWN) | ||
|
||
|
Uh oh!
There was an error while loading. Please reload this page.