rpc: validate fee estimation mode case insensitive #29175

pull torkelrogstad wants to merge 2 commits into bitcoin:master from torkelrogstad:2024-01-04-estimatemode changing 2 files +41 −2
  1. torkelrogstad commented at 10:33 AM on January 4, 2024: contributor

    The RPC docs for send claim that estimate_mode is case insensitive. This is not the case on master:

    https://github.com/bitcoin/bitcoin/blob/65c05db660b2ca1d0076b0d8573a6760b3228068/src/wallet/rpc/spend.cpp#L193-L195

    This PR fixes that, by converting the estimation mode to lower case before comparing to "unset". Note that the actual parsing of the estimation mode already happens case insensitively, further down in the same function.

  2. DrahtBot commented at 10:33 AM on January 4, 2024: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage

    For detailed information about the code coverage, see the test coverage report.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK ismaelsadeeq
    Concept NACK laanwj

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    No conflicts as of last run.

  3. DrahtBot added the label RPC/REST/ZMQ on Jan 4, 2024
  4. ismaelsadeeq commented at 5:48 PM on January 4, 2024: member

    Concept ACK

    Thanks for trying to solve this issue.

    This patch does not fix the issue.

    Behaviour on master 65c05db660b2ca1d0076b0d8573a6760b3228068

    abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoind -regtest  -fallbackfee=0.0000100 -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 send '{"bcrt1qledjqv5a23zjysjvk0204zgu50z2zwch9h88k8": 0.1}' 1 Economical
    {
      "txid": "fdbfc6e00efef18dcfde0f0f6c428bf7dbacb776cf0f1c710d410eba20dcfcf6",
      "complete": true
    }
    abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest send '{"bcrt1qledjqv5a23zjysjvk0204zgu50z2zwch9h88k8": 0.1}' 1           
    error code: -8
    error message:
    Specify estimate_mode
    abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest send '{"bcrt1qledjqv5a23zjysjvk0204zgu50z2zwch9h88k8": 0.1}' 1 unset
    error code: -8
    error message:
    Specify estimate_mode
    abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest send '{"bcrt1qledjqv5a23zjysjvk0204zgu50z2zwch9h88k8": 0.1}' 1 Unset     
    {
      "txid": "937b0205df5a56414e4751b162510e17955788f09670c76086e825253ac46e23",
      "complete": true
    }
    

    The last RPC command on should not pass at all, because Unset is an invalid estimatemode

    Same behavior on your PR head eed7296bba12dcf0df7c063d335dbf34bf358f9c

    abubakarismail@Abubakars-MacBook-Pro bitcoin % git cherry-pick eed7296bba12dcf0df7c063d335dbf34bf358f9c
    Auto-merging src/wallet/rpc/spend.cpp
    [01-2024-register-validation-interface-fix 3d94c6c3ca0] rpc: validate fee estimation mode case insensitive
     Author: Torkel Rogstad <torkel@rogstad.io>
     Date: Thu Jan 4 11:24:07 2024 +0100
     1 file changed, 1 insertion(+), 1 deletion(-)
    abubakarismail@Abubakars-MacBook-Pro bitcoin % make                                                                                                
    Making all in src
      CXX      wallet/rpc/libbitcoin_wallet_a-spend.o
      AR       libbitcoin_wallet.a
      GEN      obj/build.h
      CXX      libbitcoin_util_a-clientversion.o
      AR       libbitcoin_util.a
      CXXLD    bitcoind
      CXXLD    bitcoin-cli
      CXXLD    bitcoin-tx
      CXXLD    bitcoin-wallet
      CXXLD    bitcoin-util
      CXXLD    test/test_bitcoin
    Making all in doc/man
    make[1]: Nothing to be done for `all'.
    make[1]: Nothing to be done for `all-am'.
    abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest stop                                                                  
    Bitcoin Core stopping
    abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoind -regtest  -fallbackfee=0.0000100 -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 send '{"bcrt1qledjqv5a23zjysjvk0204zgu50z2zwch9h88k8": 0.1}' 1 Unset
    {
      "txid": "91e9f79d7b653023a522cd0bed715bc482b60eaf1036d35bb7c57d3fd630eecc",
      "complete": true
    }
    abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest send '{"bcrt1qledjqv5a23zjysjvk0204zgu50z2zwch9h88k8": 0.1}' 1 unset
    error code: -8
    error message:
    Specify estimate_mode
    

    This patch seems to have fix the issue

    diff --git a/src/wallet/rpc/spend.cpp b/src/wallet/rpc/spend.cpp
    index b26dd864951..172971cd9d2 100644
    --- a/src/wallet/rpc/spend.cpp
    +++ b/src/wallet/rpc/spend.cpp
    @@ -71,7 +71,7 @@ static void InterpretFeeEstimationInstructions(const UniValue& conf_target, cons
         } else {
             options.pushKV("fee_rate", fee_rate);
         }
    -    if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) {
    +    if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (ToLower(options["estimate_mode"].get_str()) == "unset"))) {
             throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode");
         }
     }
    
    abubakarismail@Abubakars-MacBook-Pro bitcoin % make
    Making all in src
      CXX      wallet/rpc/libbitcoin_wallet_a-spend.o
      AR       libbitcoin_wallet.a
      GEN      obj/build.h
      CXX      libbitcoin_util_a-clientversion.o
      AR       libbitcoin_util.a
      CXXLD    bitcoind
      CXXLD    bitcoin-cli
      CXXLD    bitcoin-tx
      CXXLD    bitcoin-wallet
      CXXLD    bitcoin-util
      CXXLD    test/test_bitcoin
    Making all in doc/man
    make[1]: Nothing to be done for `all'.
    make[1]: Nothing to be done for `all-am'.
    abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoin-cli -regtest stop                                                             
    Bitcoin Core stopping
    abubakarismail@Abubakars-MacBook-Pro bitcoin % ./src/bitcoind -regtest  -fallbackfee=0.0000100 -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 send '{"bcrt1qledjqv5a23zjysjvk0204zgu50z2zwch9h88k8": 0.1}' 1 Unset
    error code: -8
    error message:
    Specify estimate_mode
    

    But its worth investigating why we can be able to send with an invalid estimatemode like Unset, or uNset in current master 65c05db660b2ca1d0076b0d8573a6760b3228068.

    Please add a functional test that catches this issue

  5. torkelrogstad force-pushed on Jan 5, 2024
  6. torkelrogstad commented at 8:47 AM on January 5, 2024: contributor

    Thanks for the excellent review @ismaelsadeeq , very much appreciated. You're correct in your findings! I've updated the logic, and added functional tests for this

  7. DrahtBot added the label CI failed on Jan 5, 2024
  8. torkelrogstad force-pushed on Jan 5, 2024
  9. torkelrogstad force-pushed on Jan 5, 2024
  10. in test/functional/wallet_send.py:332 in c909aa5580 outdated
     327 | +            assert res["complete"]
     328 | +
     329 | +            res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
     330 | +                arg_estimate_mode=mode, fee_rate=1, add_to_wallet=False
     331 | +            )
     332 | +            assert res["complete"]
    


    ismaelsadeeq commented at 7:46 AM on January 6, 2024:

    This should test for a failed send RPC call because estimatemode is unset.

    It is passing because arg_conf_target was not given, currently estimatemode is only validated when a confirmation target is passed. I omit fee_rate because you should not pass both confirmation target and fee rate.

                self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
                    arg_estimate_mode=mode, arg_conf_target=1, add_to_wallet=False,  expect_error = (-8, 'Specify estimate_mode')
                )
    
  11. in test/functional/wallet_send.py:319 in c909aa5580 outdated
     314 | +            assert res["complete"]
     315 | +
     316 | +            res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
     317 | +                arg_estimate_mode=mode, add_to_wallet=False
     318 | +            )
     319 | +            assert res["complete"]
    


    ismaelsadeeq commented at 7:47 AM on January 6, 2024:

    nit

                assert_equal(res["complete"], True)
    

    ismaelsadeeq commented at 8:01 AM on January 6, 2024:

    estimatemode is validated when passed with confirmation target

                res = self.test_send(from_wallet=w0, to_wallet=w1, amount=1,
                    arg_conf_target=1, arg_estimate_mode=mode, add_to_wallet=False
                )
                assert_equal(res["complete"], True)
                
    
  12. in src/wallet/rpc/spend.cpp:59 in c909aa5580 outdated
      70 | @@ -71,7 +71,7 @@ static void InterpretFeeEstimationInstructions(const UniValue& conf_target, cons
      71 |      } else {
      72 |          options.pushKV("fee_rate", fee_rate);
      73 |      }
      74 | -    if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (options["estimate_mode"].get_str() == "unset"))) {
      75 | +    if (!options["conf_target"].isNull() && (options["estimate_mode"].isNull() || (ToLower(options["estimate_mode"].get_str()) == "unset"))) {
      76 |          throw JSONRPCError(RPC_INVALID_PARAMETER, "Specify estimate_mode");
      77 |      }
    


    ismaelsadeeq commented at 8:04 AM on January 6, 2024:

    I think we should also return error when estimatemode is passed without confirmation target and a a test for the case ? Maybe in its own commit or a follow-up PR.

        }
        if (options["conf_target"].isNull() && !options["estimate_mode"].isNull()) {
            throw JSONRPCError(RPC_INVALID_PARAMETER, "estimate_mode should be passed with conf_target");
        }
    
  13. ismaelsadeeq commented at 8:10 AM on January 6, 2024: member

    CI failure is unrelated?

  14. DrahtBot removed the label CI failed on Jan 6, 2024
  15. torkelrogstad force-pushed on Jan 15, 2024
  16. torkelrogstad commented at 7:59 AM on January 15, 2024: contributor

    @ismaelsadeeq I fixed the tests per your comments. Note that I pass the arguments through both the arg_ prefixed and non-prefixed versions. Figured we might as well validate that there's no behavior changes between passing as positional arguments or through options.

    Also added a new commits with tests that validates that estimate_mode is passed together with a confirmation target.

  17. torkelrogstad force-pushed on Jan 15, 2024
  18. DrahtBot added the label CI failed on Jan 15, 2024
  19. DrahtBot commented at 8:11 AM on January 15, 2024: contributor

    <!--85328a0da195eb286784d51f73fa0af9-->

    🚧 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.

    <sub>Debug: https://github.com/bitcoin/bitcoin/runs/20482987264</sub>

  20. DrahtBot removed the label CI failed on Jan 15, 2024
  21. torkelrogstad commented at 11:02 AM on January 15, 2024: contributor

    I believe CI failures are unrelated. Is it possible to force CI to re-run?

  22. in src/wallet/rpc/spend.cpp:53 in 51fdb4819e outdated
      70 | @@ -71,9 +71,13 @@ static void InterpretFeeEstimationInstructions(const UniValue& conf_target, cons
      71 |      } else {
    


    ismaelsadeeq commented at 12:07 PM on January 18, 2024:

    nit: iwyu

    #include <util/strencodings.h>
    

    torkelrogstad commented at 1:26 PM on January 25, 2024:

    Sorry, I don't understand what you mean. Do you want me to use one of the functions in strencodings to manipulate the fee_rate parameter before sending it to options.pushKV?


    ismaelsadeeq commented at 2:04 PM on January 25, 2024:

    torkelrogstad commented at 11:46 AM on January 26, 2024:

    Ah, thanks. I'm very new to C++, so I didn't realize I could even use things that weren't imported. Didn't realize I should also include a header statement, since it compiled just fine! Added the include.

  23. ismaelsadeeq commented at 12:12 PM on January 18, 2024: member

    Code review ACK 51fdb4819ebda4d4a8f64c8d36a471af65a033c9

    Just a single nit, happy to re ACK when fixed.

    I've tested this on regtest and it passes as expected.

  24. ismaelsadeeq commented at 12:15 PM on January 18, 2024: member

    I believe CI failures are unrelated. Is it possible to force CI to re-run?

    It has to do with #29234, If you rebase on master I think the CI will be happy, but IMO there is no need since it's unrelated

  25. torkelrogstad force-pushed on Jan 25, 2024
  26. torkelrogstad commented at 1:49 PM on January 25, 2024: contributor

    Rebased, let's see if that solves the CI failure.

  27. rpc: validate fee estimation mode case insensitive 8d40addbd2
  28. rpc: validate conf_target is set alongside estimate_mode be8ae64b82
  29. torkelrogstad force-pushed on Jan 26, 2024
  30. ismaelsadeeq approved
  31. ismaelsadeeq commented at 12:10 PM on January 30, 2024: member

    Re-ACK be8ae64b82e2c5b003a8703668ce1751442288e4

  32. DrahtBot added the label CI failed on Jan 31, 2024
  33. DrahtBot removed the label CI failed on Feb 5, 2024
  34. Retropex referenced this in commit b8e4d0a26a on Mar 28, 2024
  35. Retropex referenced this in commit cfd1c957a5 on Mar 28, 2024
  36. achow101 requested review from achow101 on Apr 9, 2024
  37. achow101 requested review from murchandamus on Apr 9, 2024
  38. achow101 requested review from laanwj on Apr 9, 2024
  39. achow101 removed review request from murchandamus on Apr 9, 2024
  40. maflcko commented at 3:38 PM on April 9, 2024: member

    Does it make sense to treat it case insensitive?

    It hasn't been treated that way, so it would be better to fix the docs?

    Also, it seems better to be strict when parsing input, as opposed to loosely accepting different values?

  41. laanwj commented at 3:42 PM on April 9, 2024: member

    Concept NACK. i think the solution here is to update the documentation. AFAIK, none of our RPC APIs have historically been case insensitive, and i don't think there's a good reason to start here. Ideally, imo, commands and field names should be considered "just blobs", not subject to unicode casing rules.

  42. torkelrogstad commented at 5:57 AM on April 10, 2024: contributor

    Fee estimation modes are currently parsed case-insensitively here:

    https://github.com/bitcoin/bitcoin/blob/e31956980e16ad3d619022e572bdf55a4eae8716/src/util/fees.cpp#L57-L67

    Which makes it possible to pass arguments case-insensitively to (at least) estimatesmartfee:

    $ bitcoin-cli estimatesmartfee 3 economical
    {
      "feerate": 0.00022015,
      "blocks": 3
    }
    
    $ bitcoin-cli estimatesmartfee 3 ECONOMICAL
    {
      "feerate": 0.00022015,
      "blocks": 3
    }
    

    Changing this would be a breaking change, and for example break client libraries like the de-facto standard Go-based rpcclient (from the btcd libraries): https://github.com/btcsuite/btcd/blob/ae5533602c46f4ea14a4da725c19cb3db7d5eb88/btcjson/walletsvrcmds.go#L118-L122

    Matter of fact, this PR was actually motivated by the fact that I couldn't get the send RPC to properly work with rpcclient out-of-the-box, without resorting to tweaking the Go code. It therefore seems to me that the best solution here is to parse case-insensitively, due to other, already established practices in other RPCs

  43. laanwj commented at 9:48 AM on April 10, 2024: member

    Right, there's something to be said to keep that where it already snuck in, to not break the existing interface. And i guess if the other enumeration items already are parsed case-insensitively, doing that for "UNSET" makes sense too, otherwise it's silly.

    i just think the consensus is that this is not a direction the RPC interface is moving in in general, and i think introducing case-insensitive API was a mistake. But maybe getting rid of this instance has too much breaking impact now, I don't know.

  44. torkelrogstad commented at 12:38 PM on April 10, 2024: contributor

    It's a fair point to not want to introduce new instances of undesirable behavior. But I definitely don't think we should change existing behavior. That would break ALL users that use Go for interfacing with Bitcoin Core, and maybe other languages as well

  45. maflcko commented at 2:17 PM on April 10, 2024: member

    This is probably more than you wanted to do, but I'd say it would be better to use FeeModeFromString to re-use the existing (case-insensitive) parsing code, than to re-implement the parsing and use hard-coded "unset" strings in all call places.

  46. achow101 commented at 6:04 PM on April 15, 2024: member

    This is probably more than you wanted to do, but I'd say it would be better to use FeeModeFromString to re-use the existing (case-insensitive) parsing code, than to re-implement the parsing and use hard-coded "unset" strings in all call places.

    I agree. I think it would make more sense for FeeModeFromString to handle "unset" and to check its return value rather than having these repeated string comparisons. It's a bit odd that the estimate mode is being validated in 3 places - InterpretFeeEstimationInstructions, SetFeeEstimateMode, and FeeModeFromString. These should be consolidated as much as possible.

  47. achow101 removed review request from achow101 on Apr 15, 2024
  48. pablomartin4btc commented at 4:28 PM on September 30, 2024: member

    @torkelrogstad, are you still working on this?

  49. torkelrogstad commented at 5:13 PM on September 30, 2024: contributor

    No, not really. Feel free to pick it up and get it in a mergeable state.

  50. torkelrogstad closed this on Sep 30, 2024

  51. luke-jr referenced this in commit afa7aa938b on Sep 23, 2025
  52. luke-jr referenced this in commit 4a833a7ae1 on Sep 23, 2025
  53. bitcoin locked this on Sep 30, 2025

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-04-13 15:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me