Skip to content

Wallet: Add maxfeerate wallet startup option#29278

Open
ismaelsadeeq wants to merge 9 commits into
bitcoin:masterfrom
ismaelsadeeq:01-2024-maxfeerate-fix
Open

Wallet: Add maxfeerate wallet startup option#29278
ismaelsadeeq wants to merge 9 commits into
bitcoin:masterfrom
ismaelsadeeq:01-2024-maxfeerate-fix

Conversation

@ismaelsadeeq

@ismaelsadeeq ismaelsadeeq commented Jan 18, 2024

Copy link
Copy Markdown
Member

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.

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

@DrahtBot

DrahtBot commented Jan 18, 2024

Copy link
Copy Markdown
Contributor

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
ACK polespinasa
Stale ACK josibake, murchandamus, achow101, furszy

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #35716 (wallet: Replace mapWallet and wtxOrdered with a boost::multi_index by achow101)
  • #35713 (Remove boost as a unit test runner by rustaceanrob)
  • #35501 (wallet: store all witness variants of a transaction by achow101)
  • #35429 (wallet: avoid global access in external signer SPKM by w0xlt)
  • #35105 (Refactor: Updated TransactionError to use util::Expected and removed TransactionError:OK by kevkevinpal)
  • #34681 (wallet: move rescan logic into ChainScanner and wallet/scan by Eunovo)
  • #34400 (wallet: parallel fast rescan (approx 8x speed up with 8 threads) by Eunovo)
  • #31260 (scripted-diff: Type-safe settings retrieval by ryanofsky)
  • #27865 (wallet: Track no-longer-spendable TXOs separately by achow101)
  • #25722 (refactor: Use util::Result class for wallet loading by ryanofsky)

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 places where named args for integral literals may be used (e.g. func(x, /*named_arg=*/0) in C++, and func(x, named_arg=0) in Python):

  • assert_raises_rpc_error(-4, "Fee exceeds maximum configured by user (maxtxfee)", self.nodes[1].walletcreatefundedpsbt, inputs, outputs, 0, ...) in test/functional/rpc_psbt.py
  • assert_raises_rpc_error(-4, msg, self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, ...) in test/functional/rpc_psbt.py
  • assert_raises_rpc_error(-4, msg, self.nodes[1].walletcreatefundedpsbt, inputs, outputs_array, 0, ...) in test/functional/rpc_psbt.py

2026-07-03 16:37:49

@glozow glozow left a comment

Copy link
Copy Markdown
Member

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))

Comment thread src/init.cpp Outdated
Comment thread src/rpc/mempool.cpp
Comment thread src/wallet/wallet.h Outdated
@ismaelsadeeq

Copy link
Copy Markdown
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

luke-jr commented Jan 23, 2024

Copy link
Copy Markdown
Member

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
Copy Markdown
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
Copy Markdown
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
Comment thread src/wallet/init.cpp Outdated
Comment thread test/functional/wallet_send.py
Comment thread src/wallet/wallet.h Outdated

@josibake josibake left a comment

Copy link
Copy Markdown
Member

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?

Comment thread src/wallet/rpc/spend.cpp Outdated
Comment thread src/wallet/wallet.cpp
@achow101

Copy link
Copy Markdown
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
Copy Markdown
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
Copy Markdown
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.

@ismaelsadeeq

ismaelsadeeq commented May 29, 2026

Copy link
Copy Markdown
Member Author

Rebased to fix merge conflict.

C.I failure in https://github.com/bitcoin/bitcoin/actions/runs/26634202425/job/78492906366?pr=29278 is unrelated.

@polespinasa polespinasa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approach ACK

Left some comments.
The PR is already well worked and reviewed so I mostly have nits before acking it.

Comment thread src/wallet/wallet.cpp Outdated
Comment thread src/wallet/wallet.h Outdated
Comment thread src/wallet/wallet.h Outdated
Comment thread src/wallet/wallet.h Outdated
Comment thread src/wallet/wallet.cpp Outdated
Comment thread src/node/interfaces.cpp Outdated
Comment thread src/interfaces/chain.h Outdated
Comment thread src/wallet/test/wallet_tests.cpp Outdated
Comment thread test/functional/rpc_psbt.py Outdated
@polespinasa

Copy link
Copy Markdown
Member
  • 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.

Note that after #32138 this line of the PR description is outdated.

-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`.
@DrahtBot

DrahtBot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

🚧 At least one of the CI tasks failed.
Task test ancestor commits: https://github.com/bitcoin/bitcoin/actions/runs/28513745459/job/84520144485
LLM reason (✨ experimental): CI failed due to a C++ compile error: TransactionError::MAX_FEE_RATE_EXCEEDED is referenced in src/node/transaction.cpp but no such enum member exists in node::TransactionError.

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.

- Also update `-maxapsfee` option from `max_fee` to `max_aps_fee`.
  This fixes the ambiguity in the variable name.
Comment thread src/wallet/init.cpp Outdated
Comment thread src/wallet/wallet.cpp Outdated
Comment thread doc/release-notes-29278.md
Comment thread src/common/messages.cpp
Comment on lines +141 to +143
return Untranslated("Fee exceeds maximum configured by user (maxtxfee)");
case TransactionError::MAX_FEE_RATE_EXCEEDED:
return Untranslated("Fee rate exceeds maximum configured by user (maxfeerate)");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

in 2baa98f [util]: add a new transaction error type
Missing - before maxtxfee and maxfeerate

- 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.
`BroadcastTransaction` now accepts a fee rate limit as an additional input
parameter. It rejects transactions whose fee exceeds the maximum fee amount
permitted by that fee rate for the transaction vsize.

Compare against `maxfeerate.GetFee(vsize)` instead of reconstructing a fee
rate from the accepted fee. This preserves the existing rounded fee cap
semantics used by `testmempoolaccept` and avoids rejecting transactions whose
rounded fee is still within the configured limit.

This allows callers to distinguish between failures caused by the absolute fee
limit (`-maxtxfee`) and the fee rate limit (`maxfeerate` or `-maxfeerate`).

`TestSimpleSpend` creates transactions paying `DEFAULT_TRANSACTION_MAXFEE`.
Since `BroadcastTransaction` now also checks the fee rate limit, these small
high-fee transactions would exceed `DEFAULT_MAX_TRANSACTION_FEERATE`. Update
the test broadcast limit to be slightly above the transaction fee rate.

In `wallet_fundrawtransaction.py`'s `test_22670` subtest, restart node 0 with
`-maxfeerate` above `-minrelaytxfee` so high-feerate transactions can be
broadcast.
- This distinguishes maxfeerate and maxtxfee error messages.
This commit prevents the wallet from submitting or broadcasting transactions
above `-maxfeerate`, and tests the new functionality.

Compare wallet transaction fees against `maxfeerate.GetFee(vsize)` instead of
reconstructing a fee rate from the rounded fee. This allows transactions whose
requested fee rate is at the configured limit, even when the resulting fee is
rounded up to whole satoshis.

`rpc_psbt.py` functional test nodes are modified to start with a custom
`-maxfeerate=1`, because the test requires creating and broadcasting a
transaction with a high fee rate.

A node in some subtests in `wallet_fundrawtransaction.py` is restarted with
`-maxfeerate=1` because those subtests require the node's wallet to be able to
create and broadcast transactions with a high fee rate.
- Wallets cannot create transactions with fee rates below
  `-minrelaytxfee`, and they also reject transactions whose total fee
  exceeds `-maxtxfee`.

- Warn when a 1 kvB transaction paying exactly `-maxtxfee` would still
  have a fee rate below `-minrelaytxfee`. In this configuration, some
  transactions that meet the minimum relay fee rate may exceed
  `-maxtxfee`, preventing the wallet from creating them.

- Keep this warning independent from the existing high `-maxtxfee`
  warning, so users see both warnings when both conditions apply.
@ismaelsadeeq

Copy link
Copy Markdown
Member Author

Forced pushed from 6f05410 to d46adaa 6f05410...d46adaa

@polespinasa polespinasa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

:)

ACK d46adaa

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