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