util.cpp raw
1 // Copyright (c) 2011-present The Limenka 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 <httprpc.h>
9 #include <rpc/util.h>
10 #include <util/any.h>
11 #include <util/translation.h>
12 #include <wallet/context.h>
13 #include <wallet/wallet.h>
14
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 /** Used by RPC commands that have an include_watchonly parameter.
34 * We default to true for watchonly wallets if include_watchonly isn't
35 * explicitly set.
36 */
37 bool ParseIncludeWatchonly(const UniValue& include_watchonly, const CWallet& wallet)
38 {
39 if (include_watchonly.isNull()) {
40 // if include_watchonly isn't explicitly set, then check if we have a watchonly wallet
41 return wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
42 }
43
44 // otherwise return whatever include_watchonly was set to
45 return include_watchonly.get_bool();
46 }
47
48 std::string EnsureUniqueWalletName(const JSONRPCRequest& request, const std::string* wallet_name)
49 {
50 std::string endpoint_wallet;
51 if (GetWalletNameFromJSONRPCRequest(request, endpoint_wallet)) {
52 // wallet endpoint was used
53 if (wallet_name && *wallet_name != endpoint_wallet) {
54 throw JSONRPCError(RPC_INVALID_PARAMETER,
55 "The RPC endpoint wallet and the wallet name parameter specify different wallets");
56 }
57 return endpoint_wallet;
58 }
59
60 // Not a wallet endpoint; parameter must be provided
61 if (!wallet_name) {
62 throw JSONRPCError(RPC_INVALID_PARAMETER,
63 "Either the RPC endpoint wallet or the wallet name parameter must be provided");
64 }
65
66 return *wallet_name;
67 }
68
69 bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest& request, std::string& wallet_name)
70 {
71 if (request.URI.starts_with(WALLET_ENDPOINT_BASE)) {
72 // wallet endpoint was used
73 wallet_name = UrlDecode(std::string_view{request.URI}.substr(WALLET_ENDPOINT_BASE.size()));
74 return true;
75 }
76 return false;
77 }
78
79 std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest& request)
80 {
81 CHECK_NONFATAL(request.mode == JSONRPCRequest::EXECUTE);
82 WalletContext& context = EnsureWalletContext(request.context);
83
84 bool have_wallet_restriction;
85 std::string authorized_wallet_name;
86 have_wallet_restriction = GetWalletRestrictionFromJSONRPCRequest(request, authorized_wallet_name);
87
88 bool have_requested_wallet;
89 std::string requested_wallet_name;
90 have_requested_wallet = GetWalletNameFromJSONRPCRequest(request, requested_wallet_name);
91
92 std::shared_ptr<CWallet> pwallet;
93 size_t count{0};
94
95 if (!have_wallet_restriction) {
96 // Any wallet is permitted; select by endpoint, or use the sole wallet
97 if (have_requested_wallet) {
98 pwallet = GetWallet(context, requested_wallet_name);
99 } else {
100 auto wallet = GetDefaultWallet(context, count);
101 if (wallet) pwallet = wallet;
102 }
103 } else if (authorized_wallet_name == "-") {
104 // Block wallet access always
105 } else if ((!have_requested_wallet) || requested_wallet_name == authorized_wallet_name) {
106 // Select specifically the authorized wallet
107 pwallet = GetWallet(context, authorized_wallet_name);
108 }
109
110 if (pwallet) {
111 return pwallet;
112 }
113
114 if (have_requested_wallet) {
115 throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
116 }
117 if (have_wallet_restriction
118 ? (authorized_wallet_name == "-" || !GetWallet(context, authorized_wallet_name))
119 : (count == 0)
120 ) {
121 throw JSONRPCError(
122 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)");
123 }
124 throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED,
125 "Multiple wallets are loaded. Please select which wallet to use by requesting the RPC through the /wallet/<walletname> URI path.");
126 }
127
128 void EnsureWalletIsUnlocked(const CWallet& wallet)
129 {
130 if (wallet.IsLocked()) {
131 throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with walletpassphrase first.");
132 }
133 }
134
135 WalletContext& EnsureWalletContext(const std::any& context)
136 {
137 auto wallet_context = util::AnyPtr<WalletContext>(context);
138 if (!wallet_context) {
139 throw JSONRPCError(RPC_INTERNAL_ERROR, "Wallet context not found");
140 }
141 return *wallet_context;
142 }
143
144 // also_create should only be set to true only when the RPC is expected to add things to a blank wallet and make it no longer blank
145 LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_create)
146 {
147 LegacyScriptPubKeyMan* spk_man = wallet.GetLegacyScriptPubKeyMan();
148 if (!spk_man && also_create) {
149 spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
150 }
151 if (!spk_man) {
152 throw JSONRPCError(RPC_WALLET_ERROR, "Only legacy wallets are supported by this command");
153 }
154 return *spk_man;
155 }
156
157 const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wallet)
158 {
159 const LegacyScriptPubKeyMan* spk_man = wallet.GetLegacyScriptPubKeyMan();
160 if (!spk_man) {
161 throw JSONRPCError(RPC_WALLET_ERROR, "Only legacy wallets are supported by this command");
162 }
163 return *spk_man;
164 }
165
166 std::string LabelFromValue(const UniValue& value)
167 {
168 static const std::string empty_string;
169 if (value.isNull()) return empty_string;
170
171 const std::string& label{value.get_str()};
172 if (label == "*")
173 throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
174 return label;
175 }
176
177 void PushParentDescriptors(const CWallet& wallet, const CScript& script_pubkey, UniValue& entry)
178 {
179 UniValue parent_descs(UniValue::VARR);
180 for (const auto& desc: wallet.GetWalletDescriptors(script_pubkey)) {
181 parent_descs.push_back(desc.descriptor->ToString());
182 }
183 entry.pushKV("parent_descs", std::move(parent_descs));
184 }
185
186 void HandleWalletError(const std::shared_ptr<CWallet> wallet, DatabaseStatus& status, bilingual_str& error)
187 {
188 if (!wallet) {
189 // Map bad format to not found, since bad format is returned when the
190 // wallet directory exists, but doesn't contain a data file.
191 RPCErrorCode code = RPC_WALLET_ERROR;
192 switch (status) {
193 case DatabaseStatus::FAILED_NOT_FOUND:
194 case DatabaseStatus::FAILED_BAD_FORMAT:
195 code = RPC_WALLET_NOT_FOUND;
196 break;
197 case DatabaseStatus::FAILED_ALREADY_LOADED:
198 code = RPC_WALLET_ALREADY_LOADED;
199 break;
200 case DatabaseStatus::FAILED_ALREADY_EXISTS:
201 code = RPC_WALLET_ALREADY_EXISTS;
202 break;
203 case DatabaseStatus::FAILED_INVALID_BACKUP_FILE:
204 code = RPC_INVALID_PARAMETER;
205 break;
206 default: // RPC_WALLET_ERROR is returned for all other cases.
207 break;
208 }
209 throw JSONRPCError(code, error.original);
210 }
211 }
212
213 void AppendLastProcessedBlock(UniValue& entry, const CWallet& wallet)
214 {
215 AssertLockHeld(wallet.cs_wallet);
216 UniValue lastprocessedblock{UniValue::VOBJ};
217 lastprocessedblock.pushKV("hash", wallet.GetLastBlockHash().GetHex());
218 lastprocessedblock.pushKV("height", wallet.GetLastBlockHeight());
219 entry.pushKV("lastprocessedblock", std::move(lastprocessedblock));
220 }
221
222 } // namespace wallet
223