rpc: Sanitize label name in various RPCs with tests #26186

pull aureleoules wants to merge 3 commits into bitcoin:master from aureleoules:2022-09-test-label-invalid changing 5 files +56 −24
  1. aureleoules commented at 10:06 AM on September 27, 2022: member

    The following RPCs did not sanitize the optional label name:

    • importprivkey
    • importaddress
    • importpubkey
    • importmulti
    • importdescriptors
    • listsinceblock

    Thus is was possible to import an address with a label * which should not be possible. The wildcard label is used for backwards compatibility in the listtransactions rpc. I added test coverage for these RPCs.

  2. fanquake added the label RPC/REST/ZMQ on Sep 27, 2022
  3. glozow removed the label RPC/REST/ZMQ on Sep 27, 2022
  4. glozow added the label Wallet on Sep 27, 2022
  5. glozow added the label RPC/REST/ZMQ on Sep 27, 2022
  6. DrahtBot commented at 12:51 PM on September 27, 2022: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

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

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK furszy, stickies-v, ajtowns, theStack, achow101
    Concept ACK MarcoFalke, 1440000bytes

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

    <!--174a7506f384e20aa4161008e828411d-->

    Conflicts

    Reviewers, this pull request conflicts with the following ones:

    • #26840 (refactor: importpubkey, importprivkey, importaddress, importmulti, and importdescriptors rpc by KolbyML)
    • #26174 (rpc, wallet: add ability to retrieve all address book entries by w0xlt)
    • #24897 ([Draft / POC] Silent Payments by w0xlt)
    • #22838 (descriptors: Be able to specify change and receiving in a single descriptor string by achow101)
    • #20892 (tests: Run both descriptor and legacy tests within a single test invocation 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.

  7. in test/functional/wallet_labels.py:44 in 3012200bc0 outdated
      39 | +        pubkey = node.getaddressinfo(address)['pubkey']
      40 | +        assert_raises_rpc_error(-11, "Invalid label name", node.importpubkey, pubkey, "*")
      41 | +        # addmultisigaddress
      42 | +        assert_raises_rpc_error(-11, "Invalid label name", node.addmultisigaddress, 1, [pubkey], "*")
      43 | +        # getreceivedbylabel
      44 | +        assert_raises_rpc_error(-11, "Invalid label name", node.getreceivedbylabel, "*")
    


    stickies-v commented at 2:18 PM on September 27, 2022:

    Given that all the rpc methods are at the same indentation and thus easily readable/enumerable, I don't think we need the comments here.

    Even though for the current implementation it doesn't reduce LoC that much, I think this alternative would be significantly more compact if in the future we'd also need to check for multiple invalid labels (not just '*'). But even in the current format, I think it's a bit more DRY?

            address = node.getnewaddress()
            pubkey = node.getaddressinfo(address)['pubkey']
            rpc_calls = [
                [node.getnewaddress],
                [node.setlabel, address],
                [node.getaddressesbylabel],
                [node.importpubkey, pubkey],
                [node.addmultisigaddress, 1, [pubkey]],
                [node.getreceivedbylabel],
            ]
            for rpc_call in rpc_calls:
                assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, "*")
    
  8. in test/functional/wallet_labels.py:70 in 3012200bc0 outdated
      65 | +                'timestamp': 'now',
      66 | +            }])
      67 | +
      68 | +            assert_equal(r[0]['success'], False)
      69 | +            assert_equal(r[0]['error']['code'], -11)
      70 | +            assert_equal(r[0]['error']['message'], "Invalid label name")
    


    stickies-v commented at 2:23 PM on September 27, 2022:

    I'd just move this out of the if/else loop and remove the duplicated lines in the if statement?

  9. in src/wallet/rpc/backup.cpp:145 in 3012200bc0 outdated
     141 | @@ -142,7 +142,7 @@ RPCHelpMan importprivkey()
     142 |          std::string strSecret = request.params[0].get_str();
     143 |          std::string strLabel;
     144 |          if (!request.params[1].isNull())
     145 | -            strLabel = request.params[1].get_str();
     146 | +            strLabel = LabelFromValue(request.params[1].get_str());
    


    stickies-v commented at 2:34 PM on September 27, 2022:
                strLabel = LabelFromValue(request.params[1]);
    
  10. in src/wallet/rpc/backup.cpp:238 in 3012200bc0 outdated
     234 | @@ -235,7 +235,7 @@ RPCHelpMan importaddress()
     235 |  
     236 |      std::string strLabel;
     237 |      if (!request.params[1].isNull())
     238 | -        strLabel = request.params[1].get_str();
     239 | +        strLabel = LabelFromValue(request.params[1].get_str());
    


    stickies-v commented at 2:34 PM on September 27, 2022:
            strLabel = LabelFromValue(request.params[1]);
    
  11. stickies-v commented at 2:43 PM on September 27, 2022: contributor

    Approach ACK 3012200bc0602fc35a53b6a25a7d23eaaed54f9c

  12. aureleoules force-pushed on Sep 27, 2022
  13. aureleoules commented at 2:55 PM on September 27, 2022: member

    Thanks for the review @stickies-v, I've addressed all your comments.

  14. aureleoules force-pushed on Sep 27, 2022
  15. in test/functional/wallet_labels.py:43 in b2d0d4b6f9 outdated
      38 | +            [node.getreceivedbylabel],
      39 | +        ]
      40 | +        for rpc_call in rpc_calls:
      41 | +            assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, "*")
      42 | +
      43 | +        r = None
    


    stickies-v commented at 3:01 PM on September 27, 2022:

    I don't think this line adds much, it's not the expected type of r and it's not necessary to declare it before the if/else. I'd prefer to remove, not really pythonic.

  16. in test/functional/wallet_labels.py:52 in b2d0d4b6f9 outdated
      47 | +                'label': '*',
      48 | +                'timestamp': 'now',
      49 | +            }])
      50 | +        else:
      51 | +            privkey = node.dumpprivkey(address)
      52 | +            assert_raises_rpc_error(-11, "Invalid label name", node.importprivkey, privkey, "*")
    


    stickies-v commented at 3:03 PM on September 27, 2022:

    nit: if you move the entire if/else loop to before the execution of the rpc calls, you could replace this with rpc_calls.append(...)

  17. aureleoules force-pushed on Sep 27, 2022
  18. aureleoules commented at 3:18 PM on September 27, 2022: member

    Pushed, thanks @stickies-v.

  19. in test/functional/wallet_labels.py:62 in d29358558e outdated
      57 | +        assert_equal(r[0]['error']['message'], "Invalid label name")
      58 | +
      59 | +        for rpc_call in rpc_calls:
      60 | +            assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, "*")
      61 | +
      62 | +
    


    stickies-v commented at 3:22 PM on September 27, 2022:

    nit: PEP8 suggests a single whiteline

  20. in test/functional/wallet_labels.py:41 in d29358558e outdated
      36 | +            [node.importpubkey, pubkey],
      37 | +            [node.addmultisigaddress, 1, [pubkey]],
      38 | +            [node.getreceivedbylabel],
      39 | +        ]
      40 | +        if self.options.descriptors:
      41 | +            r = node.importdescriptors([{
    


    stickies-v commented at 10:28 AM on September 28, 2022:

    nit: would avoid using one-letter variables, the brevity isn't necessary here imo and it's just slightly more confusing

                response = node.importdescriptors([{
    
  21. in test/functional/wallet_labels.py:42 in d29358558e outdated
      37 | +            [node.addmultisigaddress, 1, [pubkey]],
      38 | +            [node.getreceivedbylabel],
      39 | +        ]
      40 | +        if self.options.descriptors:
      41 | +            r = node.importdescriptors([{
      42 | +                'desc': 'pkh(' + address + ')',
    


    stickies-v commented at 10:37 AM on September 28, 2022:

    nit: f-string

                    'desc': f'pkh({address})',
    
  22. stickies-v approved
  23. stickies-v commented at 10:38 AM on September 28, 2022: contributor

    ACK d29358558 - nice improvement, straightforward code changes and test.

    Left a few nits, nothing blocking.

  24. in src/wallet/rpc/backup.cpp:434 in d29358558e outdated
     430 | @@ -431,7 +431,7 @@ RPCHelpMan importpubkey()
     431 |  
     432 |      std::string strLabel;
     433 |      if (!request.params[1].isNull())
     434 | -        strLabel = request.params[1].get_str();
     435 | +        strLabel = LabelFromValue(request.params[1]);
    


    maflcko commented at 11:21 AM on September 28, 2022:

    What about moving the null check into LabelFromValue (if needed with an optional arg to set the default value to something other than "")?


    aureleoules commented at 2:30 PM on September 28, 2022:

    What do you think of 5b98f5a2aeb0d49ab5c72732acb4ae0d00f907c7?

  25. maflcko commented at 11:22 AM on September 28, 2022: member

    Concept ACK. Left a follow-up idea

  26. aureleoules force-pushed on Sep 28, 2022
  27. aureleoules force-pushed on Sep 28, 2022
  28. aureleoules force-pushed on Sep 28, 2022
  29. maflcko added this to the milestone 24.0 on Sep 28, 2022
  30. aureleoules force-pushed on Sep 28, 2022
  31. in test/functional/wallet_labels.py:41 in 5b98f5a2ae outdated
      33 | +            [node.getnewaddress],
      34 | +            [node.setlabel, address],
      35 | +            [node.getaddressesbylabel],
      36 | +            [node.importpubkey, pubkey],
      37 | +            [node.addmultisigaddress, 1, [pubkey]],
      38 | +            [node.getreceivedbylabel],
    


    jonatack commented at 10:23 AM on September 30, 2022:

    Looks like listsinceblock ought to be added to this test if #25934 is merged. Edit: should the wildcard functionality be added either in that PR or this one?


    aureleoules commented at 11:16 AM on September 30, 2022:

    answered here: #25934 (review)

  32. in src/wallet/rpc/backup.cpp:1172 in b0fdad3436 outdated
    1168 | @@ -1169,7 +1169,7 @@ static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64
    1169 |          if (internal && data.exists("label")) {
    1170 |              throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
    1171 |          }
    1172 | -        const std::string& label = data.exists("label") ? data["label"].get_str() : "";
    1173 | +        const std::string& label = data.exists("label") ? LabelFromValue(data["label"]) : "";
    


    stickies-v commented at 9:49 AM on October 3, 2022:

    in b0fdad3436083aa8b965db374b7e97c94e6e246c: LabelFromValue() returns by value (as opposed to get_str() indeed return a ref to the data object, so having label be a const std::string& is quite risky from a dangling reference perspective. I would update label to be a std::string here (and similar cases further down) - even though it's changed to an auto in later commits.

  33. in src/wallet/rpc/coins.cpp:24 in 5b98f5a2ae outdated
      20 | @@ -21,7 +21,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b
      21 |      std::vector<CTxDestination> addresses;
      22 |      if (by_label) {
      23 |          // Get the set of addresses assigned to label
      24 | -        addresses = wallet.ListAddrBookAddresses(CWallet::AddrBookFilter{LabelFromValue(params[0])});
      25 | +        addresses = wallet.ListAddrBookAddresses(CWallet::AddrBookFilter{CHECK_NONFATAL(LabelFromValue(params[0]))});
    


    stickies-v commented at 11:24 AM on October 3, 2022:

    Since AddrBookFilter takes a std::optional<std::string>, I think there's no need for the CHECK_NONFATAL here? Wouldn't be any behaviour change either I think, because the existing code would throw if params[0] were to be null? (not 100% sure on that)

            addresses = wallet.ListAddrBookAddresses(CWallet::AddrBookFilter{LabelFromValue(params[0])});
    
  34. in src/wallet/rpc/addresses.cpp:142 in 5b98f5a2ae outdated
     137 | @@ -140,7 +138,7 @@ RPCHelpMan setlabel()
     138 |          throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
     139 |      }
     140 |  
     141 | -    std::string label = LabelFromValue(request.params[1]);
     142 | +    auto label = *CHECK_NONFATAL(LabelFromValue(request.params[1]));
    


    stickies-v commented at 11:27 AM on October 3, 2022:

    Missing #include <util/check.h> here and in other places.

  35. aureleoules force-pushed on Oct 3, 2022
  36. stickies-v approved
  37. stickies-v commented at 1:41 PM on October 3, 2022: contributor

    ACK 8b716611b

  38. maflcko requested review from achow101 on Oct 4, 2022
  39. maflcko removed this from the milestone 24.0 on Oct 17, 2022
  40. maflcko commented at 5:11 PM on October 17, 2022: member

    Removed from the milestone for now, as this is not a regression. Though, it can still be re-added and put into rc3 if merged in time.

  41. in test/functional/wallet_labels.py:47 in bb6d4670a2 outdated
      42 | +                'desc': f'pkh({pubkey})',
      43 | +                'label': '*',
      44 | +                'timestamp': 'now',
      45 | +            }])
      46 | +        else:
      47 | +            rpc_calls.append([node.importprivkey, node.dumpprivkey(address), "*"])
    


    theStack commented at 12:44 PM on December 6, 2022:

    Since the asterisk is added in the for-loop below, there is no need to add it here. I'd suggest to also add test coverage for importaddress here:

                rpc_calls.extend([
                    [node.importprivkey, node.dumpprivkey(address)],
                    [node.importaddress, address],
                ])
    

    aureleoules commented at 2:38 PM on December 7, 2022:

    Thanks done

  42. theStack commented at 12:44 PM on December 6, 2022: contributor

    Concept ACK

  43. aureleoules force-pushed on Dec 7, 2022
  44. theStack approved
  45. theStack commented at 3:15 PM on December 7, 2022: contributor

    ACK 493cadc8ee0339244b722b2c36dac078a3397800

  46. aureleoules force-pushed on Dec 8, 2022
  47. aureleoules commented at 12:16 PM on December 8, 2022: member

    Rebased to add coverage to listsinceblock and squashed 493cadc8ee0339244b722b2c36dac078a3397800 into the first commit.

  48. theStack approved
  49. theStack commented at 2:02 AM on December 11, 2022: contributor

    re-ACK 21b429632d15a170317a73fb5b7aa3cf95347c78

  50. in src/wallet/wallet.h:110 in e40b9a483a outdated
     106 | @@ -107,6 +107,7 @@ static const bool DEFAULT_WALLET_RBF = true;
     107 |  static const bool DEFAULT_WALLETBROADCAST = true;
     108 |  static const bool DEFAULT_DISABLE_WALLET = false;
     109 |  static const bool DEFAULT_WALLETCROSSCHAIN = false;
     110 | +static const std::string DEFAULT_WALLET_LABEL = "";
    


    furszy commented at 2:57 AM on December 11, 2022:

    Conceptually there isn't a default label, the record label is just empty.

    What if you return the empty string from LabelFromValue instead? It will also remove the need for all the .value_or(DEFAULT_WALLET_LABEL);.


    aureleoules commented at 10:44 AM on December 12, 2022:

    Some RPCs require the label to be optional for filtering. getaddressesbylabel will return all addresses if the label is nullopt for example. I could hard-code the "" in .value_or calls if you prefer though.


    furszy commented at 12:15 PM on December 12, 2022:

    Maybe you are referring to listsinceblock? (as far as I can see, it's the only place where the label is used as optional), getaddressesbylabel uses a string not an optional.

    Check this out: https://github.com/furszy/bitcoin-core/commit/8ce2d8965537a889fe88ca1a891763b35aba32ad. (If you like it, feel free to squash it on the first commit).


    aureleoules commented at 1:51 PM on December 12, 2022:

    The CI doesn't pass on your commit because of listsinceblock. A label can be empty in this RPC.

    https://cirrus-ci.com/task/6056876765347840?logs=ci#L3626


    furszy commented at 9:14 PM on December 12, 2022:

    will check it tonight 👍🏼. I coded a quick example. Shouldn't be hard to correct that single case.


    furszy commented at 9:51 PM on December 12, 2022:
  51. in src/wallet/rpc/addresses.cpp:47 in 21b429632d outdated
      43 | @@ -43,9 +44,7 @@ RPCHelpMan getnewaddress()
      44 |      }
      45 |  
      46 |      // Parse the label first so we don't generate a key if there's an error
      47 | -    std::string label;
      48 | -    if (!request.params[0].isNull())
      49 | -        label = LabelFromValue(request.params[0]);
      50 | +    auto label = LabelFromValue(request.params[0]).value_or(DEFAULT_WALLET_LABEL);
    


    stickies-v commented at 8:04 PM on December 12, 2022:

    nit: list initialization (in many other places too)

        auto label{LabelFromValue(request.params[0]).value_or(DEFAULT_WALLET_LABEL)};
    
  52. in src/wallet/rpc/util.h:44 in 21b429632d outdated
      40 | @@ -40,7 +41,7 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
      41 |  
      42 |  bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param);
      43 |  bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet);
      44 | -std::string LabelFromValue(const UniValue& value);
      45 | +std::optional<std::string> LabelFromValue(const UniValue& value);
    


    stickies-v commented at 8:15 PM on December 12, 2022:

    nit: missing optional and string includes

  53. stickies-v approved
  54. stickies-v commented at 8:25 PM on December 12, 2022: contributor

    re-ACK 21b429632

    2 non-blocking nits

  55. luke-jr commented at 8:55 PM on December 12, 2022: member

    You seem to have squashed the refactor into the fix commit. Please don't do that and re-separate them.

  56. aureleoules force-pushed on Dec 13, 2022
  57. aureleoules force-pushed on Dec 13, 2022
  58. aureleoules force-pushed on Dec 13, 2022
  59. aureleoules commented at 1:23 PM on December 13, 2022: member

    Thanks @furszy I cherry-picked, squashed your commit, and added you as co-author.

    You seem to have squashed the refactor into the fix commit. Please don't do that and re-separate them.

    The refactor commit is now gone thanks to furszy.

  60. aureleoules force-pushed on Dec 13, 2022
  61. in src/wallet/rpc/transactions.cpp:640 in ba1e78a2d5 outdated
     639 | -    if (!request.params[5].isNull()) {
     640 | -        filter_label = request.params[5].get_str();
     641 | -    }
     642 | +    // Only set it if 'label' was provided.
     643 | +    std::optional<std::string> filter_label = request.params[5].isNull() ? std::nullopt :
     644 | +                                              std::optional(LabelFromValue(request.params[5]));
    


    stickies-v commented at 5:08 PM on December 13, 2022:

    nit: could avoid multiline ternary this way:

        std::optional<std::string> filter_label;
        if (!request.params[5].isNull()) filter_label.emplace(LabelFromValue(request.params[5]));
    
  62. in src/wallet/rpc/backup.cpp:1161 in ba1e78a2d5 outdated
    1157 | @@ -1163,7 +1158,7 @@ static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64
    1158 |          if (internal && data.exists("label")) {
    1159 |              throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
    1160 |          }
    1161 | -        const std::string& label = data.exists("label") ? data["label"].get_str() : "";
    1162 | +        std::string label{LabelFromValue(data["label"])};
    


    stickies-v commented at 5:10 PM on December 13, 2022:

    This refactor introduces quite a few string copy operations, where previously references were passed. I think this can be quite easily avoided with the below patch. There are other callsites of LabelFromValue that would benefit from just changing their type to const std::string&, but to keep LoC change minimal I've not changed them in this patch.

    <details> <summary>git diff on ba1e78a2d58059f439f17e821eb2d1bab4db86cb</summary>

    diff --git a/src/wallet/rpc/addresses.cpp b/src/wallet/rpc/addresses.cpp
    index e819c09c5..5d04d3113 100644
    --- a/src/wallet/rpc/addresses.cpp
    +++ b/src/wallet/rpc/addresses.cpp
    @@ -43,7 +43,7 @@ RPCHelpMan getnewaddress()
         }
     
         // Parse the label first so we don't generate a key if there's an error
    -    std::string label{LabelFromValue(request.params[0])};
    +    const std::string& label{LabelFromValue(request.params[0])};
     
         OutputType output_type = pwallet->m_default_address_type;
         if (!request.params[1].isNull()) {
    @@ -256,7 +256,7 @@ RPCHelpMan addmultisigaddress()
     
         LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
     
    -    std::string label{LabelFromValue(request.params[2])};
    +    const std::string& label{LabelFromValue(request.params[2])};
     
         int required = request.params[0].getInt<int>();
     
    diff --git a/src/wallet/rpc/backup.cpp b/src/wallet/rpc/backup.cpp
    index 1abb2efda..7e5fbfae0 100644
    --- a/src/wallet/rpc/backup.cpp
    +++ b/src/wallet/rpc/backup.cpp
    @@ -140,7 +140,7 @@ RPCHelpMan importprivkey()
             EnsureWalletIsUnlocked(*pwallet);
     
             std::string strSecret = request.params[0].get_str();
    -        std::string strLabel{LabelFromValue(request.params[1])};
    +        const std::string& strLabel{LabelFromValue(request.params[1])};
     
             // Whether to perform rescan after import
             if (!request.params[2].isNull())
    @@ -232,7 +232,7 @@ RPCHelpMan importaddress()
         EnsureLegacyScriptPubKeyMan(*pwallet, true);
     
         // Parse label
    -    std::string strLabel{LabelFromValue(request.params[1])};
    +    const std::string& strLabel{LabelFromValue(request.params[1])};
     
         // Whether to perform rescan after import
         bool fRescan = true;
    @@ -423,7 +423,7 @@ RPCHelpMan importpubkey()
     
         EnsureLegacyScriptPubKeyMan(*pwallet, true);
     
    -    std::string strLabel{LabelFromValue(request.params[1])};
    +    const std::string& strLabel{LabelFromValue(request.params[1])};
     
         // Whether to perform rescan after import
         bool fRescan = true;
    @@ -1158,7 +1158,7 @@ static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64
             if (internal && data.exists("label")) {
                 throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
             }
    -        std::string label{LabelFromValue(data["label"])};
    +        const std::string& label{LabelFromValue(data["label"])};
             const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false;
     
             // Add to keypool only works with privkeys disabled
    @@ -1452,7 +1452,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
             const std::string& descriptor = data["desc"].get_str();
             const bool active = data.exists("active") ? data["active"].get_bool() : false;
             const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
    -        std::string label{LabelFromValue(data["label"])};
    +        const std::string& label{LabelFromValue(data["label"])};
     
             // Parse descriptor string
             FlatSigningProvider keys;
    diff --git a/src/wallet/rpc/transactions.cpp b/src/wallet/rpc/transactions.cpp
    index 7088ff4ab..9b03782d4 100644
    --- a/src/wallet/rpc/transactions.cpp
    +++ b/src/wallet/rpc/transactions.cpp
    @@ -636,8 +636,8 @@ RPCHelpMan listsinceblock()
         bool include_change = (!request.params[4].isNull() && request.params[4].get_bool());
     
         // Only set it if 'label' was provided.
    -    std::optional<std::string> filter_label = request.params[5].isNull() ? std::nullopt :
    -                                              std::optional(LabelFromValue(request.params[5]));
    +    const std::optional<std::string>& filter_label = request.params[5].isNull() ? std::nullopt :
    +                                                     std::optional{LabelFromValue(request.params[5])};
     
         int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
     
    diff --git a/src/wallet/rpc/util.cpp b/src/wallet/rpc/util.cpp
    index 1fdb80630..bc0c284dc 100644
    --- a/src/wallet/rpc/util.cpp
    +++ b/src/wallet/rpc/util.cpp
    @@ -130,11 +130,12 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
         return *spk_man;
     }
     
    -std::string LabelFromValue(const UniValue& value)
    +const std::string& LabelFromValue(const UniValue& value)
     {
    -    if (value.isNull()) return ""; // empty
    +    static std::string empty_string;
    +    if (value.isNull()) return empty_string;
     
    -    std::string label = value.get_str();
    +    const std::string& label{value.get_str()};
         if (label == "*")
             throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
         return label;
    diff --git a/src/wallet/rpc/util.h b/src/wallet/rpc/util.h
    index 87d34f7c1..9dd101167 100644
    --- a/src/wallet/rpc/util.h
    +++ b/src/wallet/rpc/util.h
    @@ -5,6 +5,7 @@
     #ifndef BITCOIN_WALLET_RPC_UTIL_H
     #define BITCOIN_WALLET_RPC_UTIL_H
     
    +#include <attributes.h>
     #include <script/script.h>
     
     #include <any>
    @@ -40,7 +41,7 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
     
     bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param);
     bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet);
    -std::string LabelFromValue(const UniValue& value);
    +const std::string& LabelFromValue(const UniValue& value LIFETIMEBOUND);
     //! Fetch parent descriptors of this scriptPubKey.
     void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry);
    

    </details>

  63. aureleoules force-pushed on Dec 15, 2022
  64. aureleoules commented at 9:11 AM on December 15, 2022: member

    Thanks @stickies-v I addressed your comments.

  65. luke-jr commented at 9:26 AM on December 15, 2022: member

    The refactor commit is now gone thanks to furszy.

    Please restore it.

  66. aureleoules force-pushed on Dec 15, 2022
  67. aureleoules commented at 10:04 AM on December 15, 2022: member

    Please restore it.

    Done @luke-jr.

  68. in src/wallet/rpc/util.h:44 in 8cf9f5dd81 outdated
      40 | @@ -40,7 +41,7 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
      41 |  
      42 |  bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param);
      43 |  bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet);
      44 | -std::string LabelFromValue(const UniValue& value);
      45 | +const std::string& LabelFromValue(const UniValue& value LIFETIMEBOUND);
    


    furszy commented at 3:25 PM on December 15, 2022:

    Guess that you wanted to set LIFETIMEBOUND for the return type instead of the function's argument right?.

    Still, would return the plain string and not a reference. There is no need to overcomplicate code for address book labels.


    aureleoules commented at 9:56 AM on December 16, 2022:

    Thanks, removed the reference.


    stickies-v commented at 11:42 AM on December 16, 2022:

    Guess that you wanted to set LIFETIMEBOUND for the return type instead of the function's argument right?

    The return value of LabelFromValue was bound to the lifetime of const UniValue& value so the attribute was correct I think? See https://clang.llvm.org/docs/AttributeReference.html#lifetimebound

    There is no need to overcomplicate code for address book labels.

    I'm honestly not sure why this got reverted. It introduces almost no additional complexity in the code, and callsites can now choose if they wish to have LabelFromValue return by value or by reference. Even though wallet labels aren't expected to be huge strings, I don't see the downside of doing it this way? There's no point bikeshedding over this, we won't be able to measure the performance impact, but it just seems like the more proper way of doing things, imo. Up to you, @aureleoules.


    furszy commented at 1:14 PM on December 16, 2022:

    I'm honestly not sure why this got reverted. It introduces almost no additional complexity in the code, and callsites can now choose if they wish to have LabelFromValue return by value or by reference. Even though wallet labels aren't expected to be huge strings, I don't see the downside of doing it this way? There's no point bikeshedding over this, we won't be able to measure the performance impact, but it just seems like the more proper way of doing things, imo. Up to you, @aureleoules.

    I'm actually not really happy with the revert neither but for another reason.

    But first, my point above was to take advantage of the compiler's rvo, which is designed for this kind of stuff.

    Now, the reason why I'm not particularly happy with the latest changes is the use of the auto keyword alone (which involves a string copy). Instead, should receive the function's output into a const ref string. @aureleoules.

    Thoughts @stickies-v?



    furszy commented at 12:11 PM on December 17, 2022:

    Have spoken with @stickies-v further and he made me notice that the string is not initialized inside LabelFromValue, the univalue get_str method returns a const reference.. so the compiler's rvo argument does not apply.

    So all good for me now, I'm aligned on returning the const ref string from LabelFromValue and adding the lifetime bound attribute once more. just need to re-add the following line:

    const std::string& LabelFromValue(const UniValue& value LIFETIMEBOUND);
    

    stickies-v commented at 11:23 AM on December 19, 2022:

    Can be marked as resolved, as furszy mentioned we're all in agreement here and I'm okay with the current implementation 👍


    ajtowns commented at 2:29 AM on January 3, 2023:

    Changing this to return a reference doesn't seem very safe -- in that case calling string& label = LabelFromValue(data["label"]) will mean label can become invalid if data gets changed (eg, if additional calls to data.pushKV() are made, the data.values vector may be reallocated, invalidating the reference), which is not something LIFETIMEBOUND can check.

    As far as I can see we're talking one copy of each fairly short label per RPC call here, which isn't something that needs to be aggressively optimised. So seems much better to me to just keep the implicit copy of the string and have a simple API that minimises the risk of errors creeping in.


    stickies-v commented at 3:53 PM on January 3, 2023:

    That's a great point I hadn't considered. Even though the data["label"] UniValue::VSTR object that we bind to doesn't use a vector to store its string value and would seem safe at first, data["label"] returns a reference to the data.values vector - so any changes to that parent UniValue::VOBJ that we can't control for here are potentially dangerous.

    I agree that this could lead to nasty issues and isn't worth the overhead. Sorry for causing the back and forth on this @aureleoules. The UniValue type can be a bit tricky to reason about, it seems.


  69. aureleoules force-pushed on Dec 16, 2022
  70. aureleoules force-pushed on Dec 16, 2022
  71. in src/wallet/rpc/util.cpp:135 in b119bb8425 outdated
     131 | @@ -132,7 +132,10 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
     132 |  
     133 |  std::string LabelFromValue(const UniValue& value)
     134 |  {
     135 | -    std::string label = value.get_str();
     136 | +    static std::string empty_string;
    


    luke-jr commented at 10:04 PM on December 17, 2022:
        static const std::string empty_string;
    
  72. ghost commented at 7:28 PM on December 18, 2022: none

    Concept ACK

  73. aureleoules force-pushed on Dec 19, 2022
  74. in src/wallet/rpc/transactions.cpp:490 in 408e375d15 outdated
     486 | @@ -487,7 +487,7 @@ RPCHelpMan listtransactions()
     487 |  
     488 |      std::optional<std::string> filter_label;
     489 |      if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
     490 | -        filter_label = request.params[0].get_str();
     491 | +        filter_label = LabelFromValue(request.params[0]);
    


    stickies-v commented at 11:22 AM on December 19, 2022:

    nit

            filter_label.emplace(LabelFromValue(request.params[0]));
    

    aureleoules commented at 11:31 AM on January 4, 2023:

    Done

  75. stickies-v approved
  76. stickies-v commented at 11:25 AM on December 19, 2022: contributor

    re-ACK 408e375d1

    Reduced string copying compared to my previous ACK. One non-blocking nit.

  77. furszy approved
  78. furszy commented at 12:43 PM on December 19, 2022: member

    ACK 893c2888

  79. ajtowns commented at 2:43 AM on January 3, 2023: contributor

    Concept ACK.

  80. aureleoules force-pushed on Jan 4, 2023
  81. rpc: Sanitize label name in various RPCs
    - importprivkey
    - importaddress
    - importpubkey
    - listtransactions
    - listsinceblock
    - importmulti
    - importdescriptors
    67e7ba8e1a
  82. aureleoules force-pushed on Jan 4, 2023
  83. in src/wallet/rpc/util.h:8 in 7d2d0c4d55 outdated
       4 | @@ -5,6 +5,7 @@
       5 |  #ifndef BITCOIN_WALLET_RPC_UTIL_H
       6 |  #define BITCOIN_WALLET_RPC_UTIL_H
       7 |  
       8 | +#include <attributes.h>
    


    furszy commented at 12:24 PM on January 4, 2023:

    In 7d2d0c4d: is this include intended?


    aureleoules commented at 12:45 PM on January 4, 2023:

    Removed thanks

  84. refactor: Add sanity checks in LabelFromValue 552b51e682
  85. test: Invalid label name coverage 65e78bda7c
  86. aureleoules force-pushed on Jan 4, 2023
  87. furszy approved
  88. furszy commented at 1:52 PM on January 4, 2023: member

    diff ACK 65e78bd

  89. in src/wallet/rpc/util.cpp:136 in 65e78bda7c
     131 | @@ -132,7 +132,10 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
     132 |  
     133 |  std::string LabelFromValue(const UniValue& value)
     134 |  {
     135 | -    std::string label = value.get_str();
     136 | +    static const std::string empty_string;
     137 | +    if (value.isNull()) return empty_string;
    


    stickies-v commented at 3:57 PM on January 4, 2023:

    nit: not needed anymore now that we're returning a string copy

        if (value.isNull()) return "";
    

    ajtowns commented at 3:55 AM on January 6, 2023:

    Or return {};

  90. stickies-v approved
  91. stickies-v commented at 3:58 PM on January 4, 2023: contributor

    re-ACK 65e78bda7cd23ad7b6ede63c6bf5ffe6f552c71d

  92. ajtowns commented at 4:03 AM on January 6, 2023: contributor

    ACK 65e78bda7cd23ad7b6ede63c6bf5ffe6f552c71d

  93. theStack approved
  94. theStack commented at 7:38 PM on January 8, 2023: contributor

    re-ACK 65e78bda7cd23ad7b6ede63c6bf5ffe6f552c71d

  95. maflcko assigned achow101 on Jan 9, 2023
  96. in test/functional/wallet_labels.py:39 in 65e78bda7c
      34 | +        pubkey = node.getaddressinfo(address)['pubkey']
      35 | +        rpc_calls = [
      36 | +            [node.getnewaddress],
      37 | +            [node.setlabel, address],
      38 | +            [node.getaddressesbylabel],
      39 | +            [node.importpubkey, pubkey],
    


    achow101 commented at 8:57 PM on January 9, 2023:

    In 65e78bda7cd23ad7b6ede63c6bf5ffe6f552c71d "test: Invalid label name coverage"

    importpubkey is a legacy wallet only RPC. The reason this test still passes with --descriptors is because the test framework has this (and some of the other import RPCs) aliased to importdescriptors.


    aureleoules commented at 7:54 AM on January 10, 2023:

    Good to know. But considering this PR has 4 ACKs, is it worth the change? The test is executed with both --legacy-wallet and --descriptors anyway.

  97. achow101 commented at 10:22 PM on January 10, 2023: member

    ACK 65e78bda7cd23ad7b6ede63c6bf5ffe6f552c71d

  98. achow101 merged this on Jan 10, 2023
  99. achow101 closed this on Jan 10, 2023

  100. sidhujag referenced this in commit 6c7bd8b4d9 on Jan 11, 2023
  101. aureleoules deleted the branch on Jan 12, 2023
  102. bitcoin locked this on Jan 12, 2024

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