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