/ src / wallet / rpc / util.cpp
util.cpp
  1  // Copyright (c) 2011-present The Bitcoin Core developers
  2  // Distributed under the MIT software license, see the accompanying
  3  // file COPYING or http://www.opensource.org/licenses/mit-license.php.
  4  
  5  #include <wallet/rpc/util.h>
  6  
  7  #include <common/url.h>
  8  #include <rpc/util.h>
  9  #include <util/any.h>
 10  #include <util/translation.h>
 11  #include <wallet/context.h>
 12  #include <wallet/wallet.h>
 13  
 14  #include <optional>
 15  #include <string_view>
 16  #include <univalue.h>
 17  
 18  namespace wallet {
 19  static const std::string WALLET_ENDPOINT_BASE = "/wallet/";
 20  const std::string HELP_REQUIRING_PASSPHRASE{"\nRequires wallet passphrase to be set with walletpassphrase call if wallet is encrypted.\n"};
 21  
 22  bool GetAvoidReuseFlag(const CWallet& wallet, const UniValue& param) {
 23      bool can_avoid_reuse = wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
 24      bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool();
 25  
 26      if (avoid_reuse && !can_avoid_reuse) {
 27          throw JSONRPCError(RPC_WALLET_ERROR, "wallet does not have the \"avoid reuse\" feature enabled");
 28      }
 29  
 30      return avoid_reuse;
 31  }
 32  
 33  std::string EnsureUniqueWalletName(const JSONRPCRequest& request, std::optional<std::string_view> wallet_name)
 34  {
 35      std::string endpoint_wallet;
 36      if (GetWalletNameFromJSONRPCRequest(request, endpoint_wallet)) {
 37          // wallet endpoint was used
 38          if (wallet_name && *wallet_name != endpoint_wallet) {
 39              throw JSONRPCError(RPC_INVALID_PARAMETER,
 40                  "The RPC endpoint wallet and the wallet name parameter specify different wallets");
 41          }
 42          return endpoint_wallet;
 43      }
 44  
 45      // Not a wallet endpoint; parameter must be provided
 46      if (!wallet_name) {
 47          throw JSONRPCError(RPC_INVALID_PARAMETER,
 48              "Either the RPC endpoint wallet or the wallet name parameter must be provided");
 49      }
 50  
 51      return std::string{*wallet_name};
 52  }
 53  
 54  bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name)
 55  {
 56      if (request.URI.starts_with(WALLET_ENDPOINT_BASE)) {
 57          // wallet endpoint was used
 58          wallet_name = UrlDecode(std::string_view{request.URI}.substr(WALLET_ENDPOINT_BASE.size()));
 59          return true;
 60      }
 61      return false;
 62  }
 63  
 64  std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
 65  {
 66      CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE);
 67      WalletContext& context = EnsureWalletContext(request.context);
 68  
 69      std::string wallet_name;
 70      if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
 71          std::shared_ptr<CWallet> pwallet = GetWallet(context, wallet_name);
 72          if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
 73          return pwallet;
 74      }
 75  
 76      size_t count{0};
 77      auto wallet = GetDefaultWallet(context, count);
 78      if (wallet) return wallet;
 79  
 80      if (count == 0) {
 81          throw JSONRPCError(
 82              RPC_WALLET_NOT_FOUND, "No wallet is loaded. Load a wallet using loadwallet or create a new one with createwallet. (Note: A default wallet is no longer automatically created)");
 83      }
 84      throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED,
 85          "Multiple wallets are loaded. Please select which wallet to use by requesting the RPC through the /wallet/<walletname> URI path.");
 86  }
 87  
 88  void EnsureWalletIsUnlocked(const CWallet& wallet)
 89  {
 90      if (wallet.IsLocked()) {
 91          throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first.");
 92      }
 93  }
 94  
 95  WalletContext& EnsureWalletContext(const std::any& context)
 96  {
 97      auto wallet_context = util::AnyPtr<WalletContext>(context);
 98      if (!wallet_context) {
 99          throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet context not found");
100      }
101      return *wallet_context;
102  }
103  
104  std::string LabelFromValue(const UniValue& value)
105  {
106      static const std::string empty_string;
107      if (value.isNull()) return empty_string;
108  
109      const std::string& label{value.get_str()};
110      if (label == "*")
111          throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
112      return label;
113  }
114  
115  void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry)
116  {
117      UniValue parent_descs(UniValue::VARR);
118      for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) {
119          std::string desc_str;
120          FlatSigningProvider dummy_provider;
121          if (!CHECK_NONFATAL(desc.descriptor->ToNormalizedString(dummy_provider, desc_str, &desc.cache))) continue;
122          parent_descs.push_back(desc_str);
123      }
124      entry.pushKV("parent_descs", std::move(parent_descs));
125  }
126  
127  void HandleWalletError(const std::shared_ptr<CWallet>& wallet, DatabaseStatus& status, bilingual_str& error)
128  {
129      if (!wallet) {
130          // Map bad format to not found, since bad format is returned when the
131          // wallet directory exists, but doesn't contain a data file.
132          RPCErrorCode code = RPC_WALLET_ERROR;
133          switch (status) {
134              case DatabaseStatus::FAILED_NOT_FOUND:
135              case DatabaseStatus::FAILED_BAD_FORMAT:
136              case DatabaseStatus::FAILED_LEGACY_DISABLED:
137                  code = RPC_WALLET_NOT_FOUND;
138                  break;
139              case DatabaseStatus::FAILED_ALREADY_LOADED:
140                  code = RPC_WALLET_ALREADY_LOADED;
141                  break;
142              case DatabaseStatus::FAILED_ALREADY_EXISTS:
143                  code = RPC_WALLET_ALREADY_EXISTS;
144                  break;
145              case DatabaseStatus::FAILED_NEW_UNNAMED:
146              case DatabaseStatus::FAILED_INVALID_BACKUP_FILE:
147                  code = RPC_INVALID_PARAMETER;
148                  break;
149              case DatabaseStatus::FAILED_ENCRYPT:
150                  code = RPC_WALLET_ENCRYPTION_FAILED;
151                  break;
152              default: // RPC_WALLET_ERROR is returned for all other cases.
153                  break;
154          }
155          throw JSONRPCError(code, error.original);
156      }
157  }
158  
159  void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet)
160  {
161      AssertLockHeld(wallet.cs_wallet);
162      UniValue lastprocessedblock{UniValue::VOBJ};
163      lastprocessedblock.pushKV("hash", wallet.GetLastBlockHash().GetHex());
164      lastprocessedblock.pushKV("height", wallet.GetLastBlockHeight());
165      entry.pushKV("lastprocessedblock", std::move(lastprocessedblock));
166  }
167  
168  } // namespace wallet