Skip to content

Conversation

ismaelsadeeq
Copy link
Member

@ismaelsadeeq ismaelsadeeq commented Jan 18, 2024

This PR fixes #29220

  • The PR adds a wallet -maxfeerate startup option, as the upper limit of wallet transactions fee rate.

  • This fixes the ambiguity of using maxtxfee value to check the upper limit of transactions fee rate.

  • Wallet will not create a transaction with fee rate above maxfeerate value. And you can not set wallet fee rate with settxfee RPC above maxfeerate value.

  • This PR adds a functional test that ensure the behavior is enforced.

@DrahtBot
Copy link
Contributor

DrahtBot commented Jan 18, 2024

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/29278.

Reviews

See the guideline for information on the review process.

Type Reviewers
Stale ACK josibake, murchandamus

If your review is incorrectly listed, please react with 👎 to this comment and the bot will ignore it on the next update.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #32636 (Split CWallet::Create() into CreateNew and LoadExisting by davidgumberg)
  • #32138 (wallet, rpc: remove settxfee and paytxfee by polespinasa)
  • #29415 (Broadcast own transactions only via short-lived Tor or I2P connections by vasild)
  • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

LLM Linter (✨ experimental)

Possible typos and grammar issues:

  • "a sequentially RPC" -> "a sequential RPC" [incorrect adverb "sequentially" used before noun; "sequential RPC" is correct and improves comprehension]

drahtbot_id_5_m

Copy link
Member

@glozow glozow left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned adding a maxfeerate option in #29220, but I was more imagining it as a wallet-only option. I don't think that wallet configurations should bleed into how the node RPCs function. Instead, the interaction there should be for wallet to pass it as a param to BroadcastTransaction when the it submits a tx to the node.

I'm also not sure about maxburnamount being a node-wide config (#29217 (comment))

@ismaelsadeeq
Copy link
Member Author

Thanks @glozow for your review.
Will put this PR in draft while addressing Approach feedback

@ismaelsadeeq ismaelsadeeq marked this pull request as draft January 19, 2024 16:12
@luke-jr
Copy link
Member

luke-jr commented Jan 23, 2024

I agree this feels more like it should be a wallet option.

@ismaelsadeeq ismaelsadeeq force-pushed the 01-2024-maxfeerate-fix branch 2 times, most recently from b9f2ddc to 0c9e0c5 Compare January 25, 2024 21:53
@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.

Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.

Leave a comment here, if you need help tracking down a confusing failure.

Debug: https://github.com/bitcoin/bitcoin/runs/20878506397

@ismaelsadeeq ismaelsadeeq changed the title RPC: Wallet: Add maxfeerate and maxburnamount startup option Wallet: Add maxfeerate wallet startup option Jan 25, 2024
@DrahtBot DrahtBot changed the title Wallet: Add maxfeerate wallet startup option Wallet: Add maxfeerate wallet startup option Jan 25, 2024
@ismaelsadeeq
Copy link
Member Author

Forced pushed from 2576efc to 0c9e0c5 Compare the diff

  • All review comments are addressed
  • Removed maxburnamount option
  • Made maxfeerate wallet option
  • Added a check in CreateTransactionInternal that makes sure we don't make transactions above this -maxfeerate.
  • Fixed ambiguity between -maxtxfee and maxfeerate
  • Prevent setting wallet feerate above maxfeerate from settxfee RPC
  • Added a functional test case
  • Updated PR OP

@ismaelsadeeq ismaelsadeeq marked this pull request as ready for review January 25, 2024 22:19
Copy link
Member

@josibake josibake left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems odd to me that we would ever have maxtxfee and maxfeerate set together as they seem to cover entirely different scenarios.

For maxtxfee, a user is expressing "I don't or care what my transaction size is but I know I never want to spend more than X in total fees."

For maxfeerate as user is expressing "I know that my transactions will vary a lot in size, so I don't know what the total fees will be, but I do know that I don't want to pay more than X sats/per vbyte."

I am struggling to think of a scenario where it makes sense to combine them. On the other hand, if I set a maxtxfee of Y and a maxfeerate of X, there input sets that are valid for Y but not valid for X and also inputs that are valid for X but not for Y.

What do you think about only letting the user set one or the other? Or is there a use case for caring about the total fees you pay AND caring about the sats per vbyte you pay that I'm not understanding?

@achow101
Copy link
Member

Or is there a use case for caring about the total fees you pay AND caring about the sats per vbyte you pay that I'm not understanding?

I think it makes sense to have both. I could have a maxtxfee of the upper bound of what I'm willing to pay ever, and also set maxfeerate to something reasonable just to make sure I don't typo a feerate and accidentally pay a feerate orders of magnitude higher than intended. At maxfeerate, a small transaction can still pay a smaller fee than maxtxfee, but a large transaction could exceed maxtxfee.

@josibake
Copy link
Member

I think it makes sense to have both. I could have a maxtxfee of the upper bound of what I'm willing to pay ever, and also set maxfeerate to something reasonable just to make sure I don't typo a feerate and accidentally pay a feerate orders of magnitude higher than intended. At maxfeerate, a small transaction can still pay a smaller fee than maxtxfee, but a large transaction could exceed maxtxfee.

But in this example, if you typo'd a feerate orders of magnitude higher, your maxtxfee should get hit. If you accidentally typo a higher fee rate than intended but it's below the maxtxfee you're willing to pay, that doesn't seem like a big problem to me.

Seems less footgunny to only let the user set one or the other (and then as a belt and suspenders still check the defaults for both, regardless of what is set?). But I might be overthinking this.. just seems really gross to have to now consider both of these in coin selection, especially if a user sets wonky values.

@ismaelsadeeq
Copy link
Member Author

Thank you for your comments @josibake @glozow @achow101 , will address review comments shortly.

if you typo'd a feerate orders of magnitude higher, your maxtxfee should get hit.

I don't think this is true; it depends on what your maxtxfee is. Going by the default value of 0.10 BTC.

This is not likely to occur.

I did a manual test on master.

When I set maxtxfee to 0.10 BTC on master, if you mistakenly type 10,000 as the fee rate, the max fee exceed error will not be hit.

However, in some cases where you set a low maxtxfeeit may be hit or not depending on the transaction size.
It's uncertain.

abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoind -regtest -maxtxfee=0.10  -daemon                                                                                          
Bitcoin Core starting

abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest loadwallet abubakar-test                                                                          
{
  "name": "abubakar-test"
}

abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest -named sendtoaddress address="bcrt1qt3fvqww6reduq287nhl59cu05hmyrj7achar44" fee_rate=10000 amount=1
22a92fdfa68581ba1d3da4aeedaeb20c7570b8c8eff9c29699c846aa1dacd73d


On this branch, if I set my -maxtxfee as 0.10 absolute fee and -maxfeerate as (0.010 which is 1000s/vb), and make a typo in a sendtoaddress call with a fee_rate of 10,000 s/vb instead of 100, the maxfeerate check will be hit.

 ./src/bitcoind -regtest -maxfeerate=0.01 -maxtxfee=0.10 -daemon                                                                         
Bitcoin Core starting
abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest loadwallet abubakar-test                                                                         
{
  "name": "abubakar-test"
}

abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest -named sendtoaddress address="bcrt1qt3fvqww6reduq287nhl59cu05hmyrj7achar44" fee_rate=10000 amount=1
error code: -6
error message:
Fee exceeds maximum configured by user (e.g. -maxtxfee, maxfeerate)

IIUC -maxtxfee should generally be high as the maximum absolute fee you think you can ever pay, and -maxfeerate as a cautionary check against an absurd fee rate for a transaction based on current fee estimates.

Maybe I should differentiate between the two error messages and make it more verbose, so that users can know which one is hit and adjust if its not intentional.

@DrahtBot
Copy link
Contributor

🚧 At least one of the CI tasks failed.
Task lint: https://github.com/bitcoin/bitcoin/runs/47890704383
LLM reason (✨ experimental): The CI failure is caused by a lint error due to missing a trailing newline in a markdown file.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@ismaelsadeeq ismaelsadeeq force-pushed the 01-2024-maxfeerate-fix branch 2 times, most recently from b592d27 to 4d6de3c Compare August 13, 2025 10:49
// Warn when a 1kvb transaction, with a base fee set to maxtxfee, has a fee rate less than minrelaytxfee.
// It is likely that some transactions with fee rates greater than or equal to the minrelaytxfee will exceed maxtxfee.
// In such cases, the wallet won't be able to create transactions. Therefore, warn the user.
} else if (chain && CFeeRate(max_tx_fee.value(), 1000) < chain->relayMinFee()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 99b4584 "[wallet]: warn user if 1kvb tx with -maxtxfee base fee has fee rate less than minrelaytxfee"

This check does not make sense to me. This converts the max tx fee back into a feerate just for this check, even though -maxfeerate has taken over that check. I don't think we need to have this check here.

Copy link
Member Author

@ismaelsadeeq ismaelsadeeq Sep 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just serves as a floor value for -maxtxfee.

Since this is a configurable knob, there is a "far-fetched edge case where " a user could start the node with a value so low that it would prevent creating transactions.

Because while creating transaction we ensure the fee is not above -maxtxfee.

A more accurate check would use the actual transaction size, but since size varies per transaction, 1000 is chosen arbitrarily. The idea is to calculate the fee rate when the fee is max_tx_fee and the size is 1000.

If that fee rate is less than minrelaytxfee, we should at least warn the user.
cc @josibake

@DrahtBot
Copy link
Contributor

DrahtBot commented Sep 1, 2025

🚧 At least one of the CI tasks failed.
Task ARM, unit tests, no functional tests: https://github.com/bitcoin/bitcoin/runs/49336793803
LLM reason (✨ experimental): CTest timeout on the mptest test causing the CI failure.

Hints

Try to run the tests locally, according to the documentation. However, a CI failure may still
happen due to a number of reasons, for example:

  • Possibly due to a silent merge conflict (the changes in this pull request being
    incompatible with the current code in the target branch). If so, make sure to rebase on the latest
    commit of the target branch.

  • A sanitizer issue, which can only be found by compiling with the sanitizer and running the
    affected test.

  • An intermittent issue.

Leave a comment here, if you need help tracking down a confusing failure.

@ismaelsadeeq
Copy link
Member Author

C.I Failure is not related see #33244

Copy link
Member

@furszy furszy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(very) light review

@@ -3008,13 +3008,25 @@ std::shared_ptr<CWallet> CWallet::Create(WalletContext& context, const std::stri
warnings.push_back(strprintf(_("%s is set very high! Fees this large could be paid on a single transaction."), "-maxtxfee"));
}

if (chain && CFeeRate{max_tx_fee.value(), 1000} < chain->relayMinFee()) {
walletInstance->m_max_tx_fee = max_tx_fee.value();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In 677be65:

It seems to me that we had certain relation between max_fee and maxfeerate before that we no longer have. What do you think about parsing maxfeerate first so that you could still check any relation between these two configurations? e.g. that max_tx_fee isn't ridiculously low/high compared to maxfeerate.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, these are different options that can be set for various use cases see comment
#29278 (comment) and #29278 (comment)

Not exactly sure which relation do you want a check for because there is already check if bounds for each individual option.
Perhaps suggest code ?

-BEGIN VERIFY SCRIPT-
git grep -l "m_default_max_tx_fee" src | xargs sed -i "s/m_default_max_tx_fee/m_max_tx_fee/g"
git grep -l "getDefaultMaxTxFee" src | xargs sed -i "s/getDefaultMaxTxFee/getMaxTxFee/g"
-END VERIFY SCRIPT-

- The value is not always the default but can be configured
  during startup so `m_max_tx_fee` is the right name not
  `m_default_max_tx_fee`.
- Also update `-maxapsfee` option from `max_fee` to `max_aps_fee`.
  This fixes the ambiguity in the variable name.
- The commits adds a new wallet startup option `-maxfeerate`

- The value will be used as the upper limit of wallet transaction fee rate.

- The commit updates all instances where `-maxtxfee` value is utilized to check
  wallet transactions fee rate upper limit to now use `-maxfeerate` value.

- `settxfee` RPC now only check if the fee rate user set is less `minrelaytxfee`,
   less than wallet min fee, or whether it exceed `maxfeerate`.
   We should not check whether `settxfee` RPC respects `maxtxfee` limit in
   in the functional test because its not enforced.
   We now only check if `settxfee` respects `maxfeerate` limit.
- `BroadcastTransaction` now accepts a fee rate limit as an additional
  input parameter. It ensures that the transaction with fee rate exceeding
  the specified limit are not added to mempool and not broadcasted
  to peers.

- This will allow differentiating between the error messages
  when either `-maxtxfee` or `maxfeerate` is hit.

- `TestSimpleSpend` creates transaction with base fee of
  `DEFAULT_TRANSACTION_MAXFEE`, since we now also check fee rate
  limit in broadcastTransaction, creating transaction with the base fee of
  `DEFAULT_TRANSACTION_MAXFEE` will prevent the transaction from being
  broadcasted because its fee rate will be above
  `DEFAULT_MAX_TRANSACTION_FEERATE`, hence update the maximum during broadcast
  to be the expected fee rate of the transaction.
- This distinguishes maxfeerate and maxtxfee error messages.
- This commit prevents the wallet from creating transactions
  above `maxfeerate`, and tests the new functionality.
… less than `minrelaytxfee`

- Wallet prevents creating transactions with fee rates lower than `-minrelaytxfee`.
  Also prevents creating a transaction with a base fee above `-maxtxfee`.

- Warn user if a 1kvb transaction, with a base fee set to `-maxtxfee`, has a fee rate less than
  `-minrelaytxfee`. It is likely that some transactions with fee rates greater than or equal
  to the `-minrelaytxfee` will also likely exceed `-maxtxfee`, the wallet won't be able to create
  transactions.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

-maxtxfee is used as a fee and a feerate