util.cpp raw

   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      if (auto endpoint_wallet{GetWalletNameFromJSONRPCRequest(request)}) {
  36          // wallet endpoint was used
  37          if (wallet_name && *wallet_name != *endpoint_wallet) {
  38              throw JSONRPCError(RPC_INVALID_PARAMETER,
  39                  "The RPC endpoint wallet and the wallet name parameter specify different wallets");
  40          }
  41          return *endpoint_wallet;
  42      }
  43  
  44      // Not a wallet endpoint; parameter must be provided
  45      if (!wallet_name) {
  46          throw JSONRPCError(RPC_INVALID_PARAMETER,
  47              "Either the RPC endpoint wallet or the wallet name parameter must be provided");
  48      }
  49  
  50      return std::string{*wallet_name};
  51  }
  52  
  53  std::optional<std::string> GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request)
  54  {
  55      if (request.URI.starts_with(WALLET_ENDPOINT_BASE)) {
  56          // wallet endpoint was used
  57          return UrlDecode(std::string_view{request.URI}.substr(WALLET_ENDPOINT_BASE.size()));
  58      }
  59      return std::nullopt;
  60  }
  61  
  62  std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
  63  {
  64      CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE);
  65      WalletContext& context = EnsureWalletContext(request.context);
  66  
  67      if (auto wallet_name{GetWalletNameFromJSONRPCRequest(request)}) {
  68          std::shared_ptr<CWallet> pwallet{GetWallet(context, *wallet_name)};
  69          if (!pwallet) throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
  70          return pwallet;
  71      }
  72  
  73      size_t count{0};
  74      auto wallet = GetDefaultWallet(context, count);
  75      if (wallet) return wallet;
  76  
  77      if (count == 0) {
  78          throw JSONRPCError(
  79              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)");
  80      }
  81      throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED,
  82          "Multiple wallets are loaded. Please select which wallet to use by requesting the RPC through the /wallet/<walletname> URI path.");
  83  }
  84  
  85  void EnsureWalletIsUnlocked(const CWallet& wallet)
  86  {
  87      if (wallet.IsLocked()) {
  88          throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first.");
  89      }
  90  }
  91  
  92  WalletContext& EnsureWalletContext(const std::any& context)
  93  {
  94      auto wallet_context = util::AnyPtr<WalletContext>(context);
  95      if (!wallet_context) {
  96          throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet context not found");
  97      }
  98      return *wallet_context;
  99  }
 100  
 101  std::string LabelFromValue(const UniValue& value)
 102  {
 103      static const std::string empty_string;
 104      if (value.isNull()) return empty_string;
 105  
 106      const std::string& label{value.get_str()};
 107      if (label == "*")
 108          throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
 109      return label;
 110  }
 111  
 112  void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry)
 113  {
 114      UniValue parent_descs(UniValue::VARR);
 115      for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) {
 116          std::string desc_str;
 117          FlatSigningProvider dummy_provider;
 118          if (!CHECK_NONFATAL(desc.descriptor->ToNormalizedString(dummy_provider, desc_str, &desc.cache))) continue;
 119          parent_descs.push_back(desc_str);
 120      }
 121      entry.pushKV("parent_descs", std::move(parent_descs));
 122  }
 123  
 124  void HandleWalletError(const std::shared_ptr<CWallet>& wallet, DatabaseStatus& status, bilingual_str& error)
 125  {
 126      if (!wallet) {
 127          // Map bad format to not found, since bad format is returned when the
 128          // wallet directory exists, but doesn't contain a data file.
 129          RPCErrorCode code = RPC_WALLET_ERROR;
 130          switch (status) {
 131              case DatabaseStatus::FAILED_NOT_FOUND:
 132              case DatabaseStatus::FAILED_BAD_FORMAT:
 133              case DatabaseStatus::FAILED_LEGACY_DISABLED:
 134                  code = RPC_WALLET_NOT_FOUND;
 135                  break;
 136              case DatabaseStatus::FAILED_ALREADY_LOADED:
 137                  code = RPC_WALLET_ALREADY_LOADED;
 138                  break;
 139              case DatabaseStatus::FAILED_ALREADY_EXISTS:
 140                  code = RPC_WALLET_ALREADY_EXISTS;
 141                  break;
 142              case DatabaseStatus::FAILED_NEW_UNNAMED:
 143              case DatabaseStatus::FAILED_INVALID_BACKUP_FILE:
 144                  code = RPC_INVALID_PARAMETER;
 145                  break;
 146              case DatabaseStatus::FAILED_ENCRYPT:
 147                  code = RPC_WALLET_ENCRYPTION_FAILED;
 148                  break;
 149              default: // RPC_WALLET_ERROR is returned for all other cases.
 150                  break;
 151          }
 152          throw JSONRPCError(code, error.original);
 153      }
 154  }
 155  
 156  void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet)
 157  {
 158      AssertLockHeld(wallet.cs_wallet);
 159      UniValue lastprocessedblock{UniValue::VOBJ};
 160      lastprocessedblock.pushKV("hash", wallet.GetLastBlockHash().GetHex());
 161      lastprocessedblock.pushKV("height", wallet.GetLastBlockHeight());
 162      entry.pushKV("lastprocessedblock", std::move(lastprocessedblock));
 163  }
 164  
 165  } // namespace wallet
 166