Line data Source code
1 : // Copyright (c) 2010 Satoshi Nakamoto
2 : // Copyright (c) 2009-2020 The Bitcoin Core developers
3 : // Copyright (c) 2014-2025 The Dash Core developers
4 : // Distributed under the MIT software license, see the accompanying
5 : // file COPYING or http://www.opensource.org/licenses/mit-license.php.
6 :
7 : #include <chainparams.h>
8 : #include <common/url.h>
9 : #include <core_io.h>
10 : #include <httpserver.h>
11 : #include <policy/policy.h>
12 : #include <rpc/blockchain.h>
13 : #include <rpc/rawtransaction_util.h>
14 : #include <rpc/server.h>
15 : #include <rpc/util.h>
16 : #include <util/bip32.h>
17 : #include <util/fees.h>
18 : #include <util/translation.h>
19 : #include <util/vector.h>
20 : #include <wallet/context.h>
21 : #include <wallet/receive.h>
22 : #include <wallet/rpc/wallet.h>
23 : #include <wallet/rpc/util.h>
24 : #include <wallet/scriptpubkeyman.h>
25 : #include <wallet/spend.h>
26 : #include <wallet/wallet.h>
27 : #include <key_io.h>
28 :
29 : #include <coinjoin/client.h>
30 : #include <coinjoin/options.h>
31 :
32 : #include <optional>
33 :
34 : #include <univalue.h>
35 :
36 : namespace wallet {
37 : /** Checks if a CKey is in the given CWallet compressed or otherwise*/
38 17 : bool HaveKey(const SigningProvider& wallet, const CKey& key)
39 : {
40 17 : CKey key2;
41 17 : key2.Set(key.begin(), key.end(), !key.IsCompressed());
42 17 : return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID());
43 17 : }
44 :
45 2906 : static RPCHelpMan setcoinjoinrounds()
46 : {
47 5812 : return RPCHelpMan{"setcoinjoinrounds",
48 2906 : "\nSet the number of rounds for CoinJoin.\n",
49 5812 : {
50 5812 : {"rounds", RPCArg::Type::NUM, RPCArg::Optional::NO,
51 2906 : "The default number of rounds is " + ToString(DEFAULT_COINJOIN_ROUNDS) +
52 2906 : " Cannot be more than " + ToString(MAX_COINJOIN_ROUNDS) + " nor less than " + ToString(MIN_COINJOIN_ROUNDS)},
53 : },
54 2906 : RPCResult{RPCResult::Type::NONE, "", ""},
55 2906 : RPCExamples{
56 2906 : HelpExampleCli("setcoinjoinrounds", "4")
57 2906 : + HelpExampleRpc("setcoinjoinrounds", "16")
58 : },
59 2916 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
60 : {
61 10 : const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
62 10 : if (!wallet) return UniValue::VNULL;
63 :
64 10 : int nRounds = request.params[0].getInt<int>();
65 :
66 10 : if (nRounds > MAX_COINJOIN_ROUNDS || nRounds < MIN_COINJOIN_ROUNDS)
67 4 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid number of rounds");
68 :
69 6 : CCoinJoinClientOptions::SetRounds(nRounds);
70 :
71 6 : return UniValue::VNULL;
72 14 : },
73 : };
74 0 : }
75 :
76 2908 : static RPCHelpMan setcoinjoinamount()
77 : {
78 5816 : return RPCHelpMan{"setcoinjoinamount",
79 2908 : "\nSet the goal amount in " + CURRENCY_UNIT + " for CoinJoin.\n",
80 5816 : {
81 5816 : {"amount", RPCArg::Type::NUM, RPCArg::Optional::NO,
82 2908 : "The default amount is " + ToString(DEFAULT_COINJOIN_AMOUNT) +
83 2908 : " Cannot be more than " + ToString(MAX_COINJOIN_AMOUNT) + " nor less than " + ToString(MIN_COINJOIN_AMOUNT)},
84 : },
85 2908 : RPCResult{RPCResult::Type::NONE, "", ""},
86 2908 : RPCExamples{
87 2908 : HelpExampleCli("setcoinjoinamount", "500")
88 2908 : + HelpExampleRpc("setcoinjoinamount", "208")
89 : },
90 2920 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
91 : {
92 12 : const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
93 12 : if (!wallet) return UniValue::VNULL;
94 :
95 12 : int nAmount = request.params[0].getInt<int>();
96 :
97 12 : if (nAmount > MAX_COINJOIN_AMOUNT || nAmount < MIN_COINJOIN_AMOUNT)
98 4 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount of " + CURRENCY_UNIT + " as mixing goal amount");
99 :
100 8 : CCoinJoinClientOptions::SetAmount(nAmount);
101 :
102 8 : return UniValue::VNULL;
103 16 : },
104 : };
105 0 : }
106 :
107 5026 : static RPCHelpMan getwalletinfo()
108 : {
109 10052 : return RPCHelpMan{"getwalletinfo",
110 5026 : "Returns an object containing various wallet state info.\n",
111 5026 : {},
112 5026 : RPCResult{
113 5026 : RPCResult::Type::OBJ, "", "",
114 120624 : {
115 5026 : {RPCResult::Type::STR, "walletname", "the wallet name"},
116 5026 : {RPCResult::Type::NUM, "walletversion", "the wallet version"},
117 5026 : {RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"},
118 5026 : {RPCResult::Type::NUM, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"},
119 5026 : {RPCResult::Type::NUM, "coinjoin_balance", "DEPRECATED. Identical to getbalances().mine.coinjoin"},
120 5026 : {RPCResult::Type::NUM, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
121 5026 : {RPCResult::Type::NUM, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
122 5026 : {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
123 5026 : {RPCResult::Type::NUM_TIME, "timefirstkey", "the " + UNIX_EPOCH_TIME + " of the oldest known key in the wallet"},
124 5026 : {RPCResult::Type::NUM_TIME, "keypoololdest", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " of the oldest pre-generated key in the key pool. Legacy wallets only"},
125 5026 : {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
126 5026 : {RPCResult::Type::NUM, "keypoolsize_hd_internal", /*optional=*/ true, "how many new keys are pre-generated for internal use (used for change outputs and mobile coinjoin, only appears if the wallet is using this feature, otherwise external keys are used)"},
127 5026 : {RPCResult::Type::NUM, "keys_left", "how many new keys are left since last automatic backup"},
128 5026 : {RPCResult::Type::NUM_TIME, "unlocked_until", /*optional=*/true, "the " + UNIX_EPOCH_TIME + " until which the wallet is unlocked for transfers, or 0 if the wallet is locked (only present for passphrase-encrypted wallets)"},
129 5026 : {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB"},
130 5026 : {RPCResult::Type::STR_HEX, "hdchainid", "the ID of the HD chain"},
131 5026 : {RPCResult::Type::NUM, "hdaccountcount", "how many accounts of the HD chain are in this wallet"},
132 10052 : {RPCResult::Type::ARR, "hdaccounts", "",
133 10052 : {
134 10052 : {RPCResult::Type::OBJ, "", "",
135 20104 : {
136 5026 : {RPCResult::Type::NUM, "hdaccountindex", "the index of the account"},
137 5026 : {RPCResult::Type::NUM, "hdexternalkeyindex", "current external childkey index"},
138 5026 : {RPCResult::Type::NUM, "hdinternalkeyindex", "current internal childkey index"},
139 : }},
140 : }},
141 5026 : {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
142 5026 : {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
143 10052 : {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
144 15078 : {
145 5026 : {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
146 5026 : {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
147 : }},
148 5026 : {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
149 5026 : {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
150 5026 : RESULT_LAST_PROCESSED_BLOCK,
151 : },
152 : },
153 5026 : RPCExamples{
154 5026 : HelpExampleCli("getwalletinfo", "")
155 5026 : + HelpExampleRpc("getwalletinfo", "")
156 : },
157 7098 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
158 : {
159 2072 : const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
160 2072 : if (!pwallet) return UniValue::VNULL;
161 :
162 : // Make sure the results are valid at least up to the most recent block
163 : // the user could have gotten from another RPC command prior to now
164 2072 : pwallet->BlockUntilSyncedToCurrentChain();
165 :
166 2072 : LOCK(pwallet->cs_wallet);
167 :
168 2072 : LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
169 2072 : CHDChain hdChainCurrent;
170 3356 : bool fHDEnabled = spk_man && spk_man->GetHDChain(hdChainCurrent);
171 2072 : UniValue obj(UniValue::VOBJ);
172 :
173 2072 : const auto bal = GetBalance(*pwallet);
174 2072 : obj.pushKV("walletname", pwallet->GetName());
175 2072 : obj.pushKV("walletversion", pwallet->GetVersion());
176 2072 : obj.pushKV("format", pwallet->GetDatabase().Format());
177 2072 : obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
178 2072 : obj.pushKV("coinjoin_balance", ValueFromAmount(bal.m_anonymized));
179 2072 : obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
180 2072 : obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
181 2072 : obj.pushKV("txcount", (int)pwallet->mapWallet.size());
182 : // TODO: implement timefirstkey for Descriptor KeyMan or explain why it's not provided
183 2072 : if (spk_man) {
184 1284 : obj.pushKV("timefirstkey", spk_man->GetTimeFirstKey());
185 1284 : }
186 2072 : const auto kp_oldest = pwallet->GetOldestKeyPoolTime();
187 2072 : if (kp_oldest.has_value()) {
188 1284 : obj.pushKV("keypoololdest", kp_oldest.value());
189 1284 : }
190 2072 : size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
191 2072 : obj.pushKV("keypoolsize", kpExternalSize);
192 2072 : obj.pushKV("keypoolsize_hd_internal", pwallet->GetKeyPoolSize() - kpExternalSize);
193 2072 : obj.pushKV("keys_left", pwallet->nKeysLeftSinceAutoBackup);
194 2072 : if (pwallet->IsCrypted()) {
195 102 : obj.pushKV("unlocked_until", pwallet->nRelockTime);
196 102 : }
197 2072 : obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK()));
198 2072 : if (fHDEnabled) {
199 1063 : obj.pushKV("hdchainid", hdChainCurrent.GetID().GetHex());
200 1063 : obj.pushKV("hdaccountcount", hdChainCurrent.CountAccounts());
201 1063 : UniValue accounts(UniValue::VARR);
202 2123 : for (size_t i = 0; i < hdChainCurrent.CountAccounts(); ++i)
203 : {
204 1060 : CHDAccount acc;
205 1060 : UniValue account(UniValue::VOBJ);
206 1060 : account.pushKV("hdaccountindex", i);
207 1060 : if(hdChainCurrent.GetAccount(i, acc)) {
208 1060 : account.pushKV("hdexternalkeyindex", acc.nExternalChainCounter);
209 1060 : account.pushKV("hdinternalkeyindex", acc.nInternalChainCounter);
210 1060 : } else {
211 0 : account.pushKV("error", strprintf("account %d is missing", i));
212 : }
213 1060 : accounts.push_back(account);
214 1060 : }
215 1063 : obj.pushKV("hdaccounts", accounts);
216 1063 : }
217 2072 : obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
218 2072 : obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
219 2072 : if (pwallet->IsScanning()) {
220 0 : UniValue scanning(UniValue::VOBJ);
221 0 : scanning.pushKV("duration", Ticks<std::chrono::seconds>(pwallet->ScanningDuration()));
222 0 : scanning.pushKV("progress", pwallet->ScanningProgress());
223 0 : obj.pushKV("scanning", scanning);
224 0 : } else {
225 2072 : obj.pushKV("scanning", false);
226 : }
227 2072 : obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
228 2072 : obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
229 :
230 2072 : AppendLastProcessedBlock(obj, *pwallet);
231 2072 : return obj;
232 2072 : },
233 : };
234 0 : }
235 :
236 2922 : static RPCHelpMan listwalletdir()
237 : {
238 5844 : return RPCHelpMan{"listwalletdir",
239 2922 : "Returns a list of wallets in the wallet directory.\n",
240 2922 : {},
241 2922 : RPCResult{
242 2922 : RPCResult::Type::OBJ, "", "",
243 5844 : {
244 5844 : {RPCResult::Type::ARR, "wallets", "",
245 5844 : {
246 5844 : {RPCResult::Type::OBJ, "", "",
247 5844 : {
248 2922 : {RPCResult::Type::STR, "name", "The wallet name"},
249 : }},
250 : }},
251 : }
252 : },
253 2922 : RPCExamples{
254 2922 : HelpExampleCli("listwalletdir", "")
255 2922 : + HelpExampleRpc("listwalletdir", "")
256 : },
257 2948 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
258 : {
259 26 : UniValue wallets(UniValue::VARR);
260 202 : for (const auto& path : ListDatabases(GetWalletDir())) {
261 176 : UniValue wallet(UniValue::VOBJ);
262 176 : wallet.pushKV("name", path.utf8string());
263 176 : wallets.push_back(wallet);
264 176 : }
265 :
266 26 : UniValue result(UniValue::VOBJ);
267 26 : result.pushKV("wallets", wallets);
268 26 : return result;
269 26 : },
270 : };
271 0 : }
272 :
273 3072 : static RPCHelpMan listwallets()
274 : {
275 6144 : return RPCHelpMan{"listwallets",
276 3072 : "Returns a list of currently loaded wallets.\n"
277 : "For full information on the wallet, use \"getwalletinfo\"\n",
278 3072 : {},
279 3072 : RPCResult{
280 3072 : RPCResult::Type::ARR, "", "",
281 6144 : {
282 3072 : {RPCResult::Type::STR, "walletname", "the wallet name"},
283 : }
284 : },
285 3072 : RPCExamples{
286 3072 : HelpExampleCli("listwallets", "")
287 3072 : + HelpExampleRpc("listwallets", "")
288 : },
289 3248 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
290 : {
291 176 : UniValue obj(UniValue::VARR);
292 :
293 176 : WalletContext& context = EnsureWalletContext(request.context);
294 896 : for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
295 720 : LOCK(wallet->cs_wallet);
296 720 : obj.push_back(wallet->GetName());
297 720 : }
298 :
299 176 : return obj;
300 176 : },
301 : };
302 0 : }
303 :
304 2968 : static RPCHelpMan upgradetohd()
305 : {
306 5936 : return RPCHelpMan{"upgradetohd",
307 2968 : "\nUpgrades non-HD wallets to HD.\n"
308 : "\nIf your wallet is encrypted, the wallet passphrase must be supplied. Supplying an incorrect"
309 : "\npassphrase may result in your wallet getting locked.\n"
310 : "\nWarning: You will need to make a new backup of your wallet after setting the HD wallet mnemonic.\n",
311 14840 : {
312 2968 : {"mnemonic", RPCArg::Type::STR, RPCArg::Default{""}, "Mnemonic as defined in BIP39 to use for the new HD wallet. Use an empty string \"\" to generate a new random mnemonic."},
313 2968 : {"mnemonicpassphrase", RPCArg::Type::STR, RPCArg::Default{""}, "Optional mnemonic passphrase as defined in BIP39"},
314 2968 : {"walletpassphrase", RPCArg::Type::STR, RPCArg::Default{""}, "If your wallet is encrypted you must have your wallet passphrase here. If your wallet is not encrypted, specifying wallet passphrase will trigger wallet encryption."},
315 2968 : {"rescan", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false if mnemonic is empty"}, "Whether to rescan the blockchain for missing transactions or not"},
316 : },
317 2968 : RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
318 2968 : RPCExamples{
319 2968 : HelpExampleCli("upgradetohd", "")
320 2968 : + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\"")
321 2968 : + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\"")
322 2968 : + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"\" \"walletpassphrase\"")
323 2968 : + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\" \"walletpassphrase\"")
324 : },
325 3040 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
326 : {
327 72 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
328 72 : if (!pwallet) return UniValue::VNULL;
329 :
330 72 : bool generate_mnemonic = request.params[0].isNull() || request.params[0].get_str().empty();
331 72 : bool mnemonic_passphrase_has_null{false};
332 : {
333 72 : LOCK(pwallet->cs_wallet);
334 :
335 72 : SecureString wallet_passphrase;
336 72 : wallet_passphrase.reserve(100);
337 :
338 72 : if (request.params[2].isNull()) {
339 48 : if (pwallet->IsCrypted()) {
340 4 : throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Wallet encrypted but passphrase not supplied to RPC.");
341 : }
342 44 : } else {
343 24 : wallet_passphrase = std::string_view{request.params[2].get_str()};
344 : }
345 :
346 68 : SecureString mnemonic;
347 68 : mnemonic.reserve(256);
348 68 : if (!generate_mnemonic) {
349 44 : mnemonic = std::string_view{request.params[0].get_str()};
350 44 : }
351 :
352 68 : SecureString mnemonic_passphrase;
353 68 : mnemonic_passphrase.reserve(256);
354 68 : if (!request.params[1].isNull()) {
355 44 : mnemonic_passphrase = std::string_view{request.params[1].get_str()};
356 44 : mnemonic_passphrase_has_null = (mnemonic_passphrase.find('\0') != std::string::npos);
357 44 : }
358 :
359 : // Do not do anything to HD wallets
360 68 : if (pwallet->IsHDEnabled()) {
361 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot upgrade a wallet to HD if it is already upgraded to HD");
362 : }
363 :
364 68 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
365 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Private keys are disabled for this wallet");
366 : }
367 :
368 68 : pwallet->WalletLogPrintf("Upgrading wallet to HD\n");
369 68 : pwallet->SetMinVersion(FEATURE_HD);
370 :
371 68 : if (pwallet->IsCrypted()) {
372 12 : if (wallet_passphrase.empty()) {
373 0 : throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: Wallet encrypted but supplied empty wallet passphrase");
374 : }
375 :
376 : // We are intentionally re-locking the wallet so we can validate passphrase
377 : // by verifying if it can unlock the wallet
378 12 : pwallet->Lock();
379 :
380 : // Unlock the wallet
381 12 : if (!pwallet->Unlock(wallet_passphrase)) {
382 : // Check if the passphrase has a null character (see bitcoin#27067 for details)
383 4 : if (wallet_passphrase.find('\0') == std::string::npos) {
384 4 : throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect.");
385 : } else {
386 0 : throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered is incorrect. "
387 : "It contains a null character (ie - a zero byte). "
388 : "If the passphrase was set with a version of this software prior to 23.0, "
389 : "please try again with only the characters up to — but not including — "
390 : "the first null character. If this is successful, please set a new "
391 : "passphrase to avoid this issue in the future.");
392 : }
393 : }
394 8 : }
395 :
396 64 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
397 28 : pwallet->SetupDescriptorScriptPubKeyMans(mnemonic, mnemonic_passphrase);
398 28 : } else {
399 36 : auto spk_man = pwallet->GetLegacyScriptPubKeyMan();
400 36 : if (!spk_man) {
401 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error: Legacy ScriptPubKeyMan is not available");
402 : }
403 :
404 36 : if (pwallet->IsCrypted()) {
405 12 : pwallet->WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
406 6 : spk_man->GenerateNewHDChain(mnemonic, mnemonic_passphrase, encryption_key);
407 6 : return true;
408 0 : });
409 6 : } else {
410 30 : spk_man->GenerateNewHDChain(mnemonic, mnemonic_passphrase);
411 : }
412 : }
413 :
414 64 : if (pwallet->IsCrypted()) {
415 : // Relock encrypted wallet
416 8 : pwallet->Lock();
417 64 : } else if (!wallet_passphrase.empty()) {
418 : // Encrypt non-encrypted wallet
419 4 : if (!pwallet->EncryptWallet(wallet_passphrase)) {
420 0 : throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet");
421 : }
422 4 : }
423 72 : } // pwallet->cs_wallet
424 :
425 : // If you are generating new mnemonic it is assumed that the addresses have never gotten a transaction before, so you don't need to rescan for transactions
426 64 : bool rescan = request.params[3].isNull() ? !generate_mnemonic : request.params[3].get_bool();
427 64 : if (rescan) {
428 40 : WalletRescanReserver reserver(*pwallet);
429 40 : if (!reserver.reserve()) {
430 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
431 : }
432 40 : CWallet::ScanResult result = pwallet->ScanForWalletTransactions(pwallet->chain().getBlockHash(0), 0, {}, reserver, /*fUpdate=*/true, /*save_progress=*/false);
433 40 : switch (result.status) {
434 : case CWallet::ScanResult::SUCCESS:
435 40 : break;
436 : case CWallet::ScanResult::FAILURE:
437 0 : throw JSONRPCError(RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files.");
438 : case CWallet::ScanResult::USER_ABORT:
439 0 : throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted.");
440 : // no default case, so the compiler can warn about missing cases
441 : }
442 40 : }
443 :
444 : // Check if the passphrase has a null character (see #27067 for details)
445 64 : if (!mnemonic_passphrase_has_null) {
446 60 : return "Make sure that you have backup of your mnemonic.";
447 : } else {
448 4 : return "Make sure that you have backup of your mnemonic. "
449 : "Your mnemonic passphrase contains a null character (ie - a zero byte). "
450 : "If the passphrase was created with a version of this software prior to 23.0, "
451 : "please try again with only the characters up to — but not including — "
452 : "the first null character. If this is successful, please set a new "
453 : "passphrase to avoid this issue in the future.";
454 : }
455 80 : },
456 : };
457 0 : }
458 :
459 3365 : static RPCHelpMan loadwallet()
460 : {
461 6730 : return RPCHelpMan{"loadwallet",
462 3365 : "\nLoads a wallet from a wallet file or directory."
463 : "\nNote that all wallet command-line options used when starting dashd will be"
464 : "\napplied to the new wallet (eg, rescan, etc).\n",
465 10095 : {
466 3365 : {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
467 3365 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
468 : },
469 3365 : RPCResult{
470 3365 : RPCResult::Type::OBJ, "", "",
471 10095 : {
472 3365 : {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
473 3365 : {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
474 : }
475 : },
476 3365 : RPCExamples{
477 3365 : HelpExampleCli("loadwallet", "\"test.dat\"")
478 3365 : + HelpExampleRpc("loadwallet", "\"test.dat\"")
479 : },
480 3834 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
481 : {
482 469 : WalletContext& context = EnsureWalletContext(request.context);
483 469 : const std::string name(request.params[0].get_str());
484 :
485 469 : DatabaseOptions options;
486 : DatabaseStatus status;
487 469 : ReadDatabaseArgs(*context.args, options);
488 469 : options.require_existing = true;
489 469 : bilingual_str error;
490 469 : std::vector<bilingual_str> warnings;
491 469 : std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
492 :
493 : {
494 469 : LOCK(context.wallets_mutex);
495 2439 : if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
496 6 : throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
497 : }
498 469 : }
499 :
500 463 : std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
501 :
502 463 : HandleWalletError(wallet, status, error);
503 :
504 409 : UniValue obj(UniValue::VOBJ);
505 409 : obj.pushKV("name", wallet->GetName());
506 409 : obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
507 :
508 409 : return obj;
509 475 : },
510 : };
511 0 : }
512 :
513 2926 : static RPCHelpMan setwalletflag()
514 : {
515 2926 : std::string flags;
516 23408 : for (auto& it : WALLET_FLAG_MAP)
517 20482 : if (it.second & MUTABLE_WALLET_FLAGS)
518 2926 : flags += (flags == "" ? "" : ", ") + it.first;
519 :
520 5852 : return RPCHelpMan{"setwalletflag",
521 2926 : "\nChange the state of the given wallet flag for a wallet.\n",
522 8778 : {
523 2926 : {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
524 2926 : {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
525 : },
526 2926 : RPCResult{
527 2926 : RPCResult::Type::OBJ, "", "",
528 11704 : {
529 2926 : {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
530 2926 : {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
531 2926 : {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
532 : }
533 : },
534 2926 : RPCExamples{
535 2926 : HelpExampleCli("setwalletflag", "avoid_reuse")
536 2926 : + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
537 : },
538 2956 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
539 : {
540 30 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
541 30 : if (!pwallet) return UniValue::VNULL;
542 :
543 :
544 30 : std::string flag_str = request.params[0].get_str();
545 30 : bool value = request.params[1].isNull() || request.params[1].get_bool();
546 :
547 30 : if (!WALLET_FLAG_MAP.count(flag_str)) {
548 4 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
549 : }
550 :
551 26 : auto flag = WALLET_FLAG_MAP.at(flag_str);
552 :
553 26 : if (!(flag & MUTABLE_WALLET_FLAGS)) {
554 10 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
555 : }
556 :
557 16 : UniValue res(UniValue::VOBJ);
558 :
559 16 : if (pwallet->IsWalletFlagSet(flag) == value) {
560 8 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
561 : }
562 :
563 8 : res.pushKV("flag_name", flag_str);
564 8 : res.pushKV("flag_state", value);
565 :
566 8 : if (value) {
567 4 : pwallet->SetWalletFlag(flag);
568 4 : } else {
569 4 : pwallet->UnsetWalletFlag(flag);
570 : }
571 :
572 8 : if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) {
573 4 : res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
574 4 : }
575 :
576 8 : return res;
577 52 : },
578 : };
579 2926 : }
580 :
581 4094 : static RPCHelpMan createwallet()
582 : {
583 4094 : return RPCHelpMan{
584 4094 : "createwallet",
585 4094 : "\nCreates and loads a new wallet.\n",
586 36846 : {
587 4094 : {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will be created at the path location."},
588 4094 : {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
589 4094 : {"blank", RPCArg::Type::BOOL, RPCArg::Default{false}, "Create a blank wallet. A blank wallet has no keys or HD seed. One can be set using upgradetohd (by mnemonic) or sethdseed (WIF private key)."},
590 4094 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Encrypt the wallet with this passphrase."},
591 4094 : {"avoid_reuse", RPCArg::Type::BOOL, RPCArg::Default{false}, "Keep track of coin reuse, and treat dirty and clean coins differently with privacy considerations in mind."},
592 4094 : {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation."},
593 4094 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
594 4094 : {"external_signer", RPCArg::Type::BOOL, RPCArg::Default{false}, "Use an external signer such as a hardware wallet. Requires -signer to be configured. Wallet creation will fail if keys cannot be fetched. Requires disable_private_keys and descriptors set to true."},
595 : },
596 4094 : RPCResult{
597 4094 : RPCResult::Type::OBJ, "", "",
598 12282 : {
599 4094 : {RPCResult::Type::STR, "name", "The wallet name if created successfully. If the wallet was created using a full path, the wallet_name will be the full path."},
600 4094 : {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
601 : }
602 : },
603 4094 : RPCExamples{
604 4094 : HelpExampleCli("createwallet", "\"testwallet\"")
605 4094 : + HelpExampleRpc("createwallet", "\"testwallet\"")
606 4094 : + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
607 4094 : + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
608 : },
609 5292 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
610 : {
611 1198 : WalletContext& context = EnsureWalletContext(request.context);
612 1198 : uint64_t flags = 0;
613 1198 : if (!request.params[1].isNull() && request.params[1].get_bool()) {
614 139 : flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
615 139 : }
616 :
617 1198 : if (!request.params[2].isNull() && request.params[2].get_bool()) {
618 118 : flags |= WALLET_FLAG_BLANK_WALLET;
619 118 : }
620 1198 : SecureString passphrase;
621 1198 : passphrase.reserve(100);
622 1198 : std::vector<bilingual_str> warnings;
623 1198 : if (!request.params[3].isNull()) {
624 1198 : passphrase = std::string_view{request.params[3].get_str()};
625 1198 : if (passphrase.empty()) {
626 : // Empty string means unencrypted
627 1170 : warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
628 1170 : }
629 1198 : }
630 :
631 1198 : if (!request.params[4].isNull() && request.params[4].get_bool()) {
632 10 : flags |= WALLET_FLAG_AVOID_REUSE;
633 10 : }
634 1198 : if (!request.params[5].isNull() && request.params[6].isNull()) {
635 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "The createwallet RPC requires specifying the 'load_on_startup' flag when param 'descriptors' is specified. Dash Core v21 introduced this requirement due to breaking changes in the createwallet RPC.");
636 : }
637 1198 : if (request.params[5].isNull() || request.params[5].get_bool()) {
638 : #ifndef USE_SQLITE
639 : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without sqlite support (required for descriptor wallets)");
640 : #endif
641 428 : flags |= WALLET_FLAG_DESCRIPTORS;
642 428 : }
643 1198 : if (!request.params[7].isNull() && request.params[7].get_bool()) {
644 : #ifdef ENABLE_EXTERNAL_SIGNER
645 10 : flags |= WALLET_FLAG_EXTERNAL_SIGNER;
646 : #else
647 : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without external signing support (required for external signing)");
648 : #endif
649 10 : }
650 :
651 : #ifndef USE_BDB
652 : if (!(flags & WALLET_FLAG_DESCRIPTORS)) {
653 : throw JSONRPCError(RPC_WALLET_ERROR, "Compiled without bdb support (required for legacy wallets)");
654 : }
655 : #endif
656 1198 : DatabaseOptions options;
657 : DatabaseStatus status;
658 1198 : ReadDatabaseArgs(*context.args, options);
659 1198 : options.require_create = true;
660 1198 : options.create_flags = flags;
661 1198 : options.create_passphrase = passphrase;
662 1198 : bilingual_str error;
663 1198 : std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
664 1198 : const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
665 1190 : if (!wallet) {
666 24 : RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
667 24 : throw JSONRPCError(code, error.original);
668 : }
669 1166 : wallet->SetupLegacyScriptPubKeyMan();
670 :
671 1166 : UniValue obj(UniValue::VOBJ);
672 1166 : obj.pushKV("name", wallet->GetName());
673 1166 : obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
674 :
675 1166 : return obj;
676 1198 : },
677 : };
678 0 : }
679 :
680 3284 : static RPCHelpMan unloadwallet()
681 : {
682 6568 : return RPCHelpMan{"unloadwallet",
683 3284 : "Unloads the wallet referenced by the request endpoint otherwise unloads the wallet specified in the argument.\n"
684 : "Specifying the wallet name on a wallet endpoint is invalid.",
685 9852 : {
686 3284 : {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to unload. If provided both here and in the RPC endpoint, the two must be identical."},
687 3284 : {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED_NAMED_ARG, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
688 : },
689 6568 : RPCResult{RPCResult::Type::OBJ, "", "", {
690 3284 : {RPCResult::Type::STR, "warning", "Warning message if wallet was not unloaded cleanly."},
691 : }},
692 3284 : RPCExamples{
693 3284 : HelpExampleCli("unloadwallet", "wallet_name")
694 3284 : + HelpExampleRpc("unloadwallet", "wallet_name")
695 : },
696 3672 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
697 : {
698 388 : std::string wallet_name;
699 388 : if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
700 118 : if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
701 6 : throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
702 : }
703 112 : } else {
704 270 : wallet_name = request.params[0].get_str();
705 : }
706 :
707 376 : WalletContext& context = EnsureWalletContext(request.context);
708 376 : std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
709 376 : if (!wallet) {
710 12 : throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
711 : }
712 :
713 364 : std::vector<bilingual_str> warnings;
714 : {
715 364 : WalletRescanReserver reserver(*wallet);
716 364 : if (!reserver.reserve()) {
717 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
718 : }
719 :
720 : // Release the "main" shared pointer and prevent further notifications.
721 : // Note that any attempt to load the same wallet would fail until the wallet
722 : // is destroyed (see CheckUniqueFileid).
723 364 : std::optional<bool> load_on_start{self.MaybeArg<bool>(1)};
724 364 : if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
725 0 : throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
726 : }
727 364 : }
728 :
729 364 : UnloadWallet(std::move(wallet));
730 :
731 364 : UniValue result(UniValue::VOBJ);
732 364 : result.pushKV("warning", Join(warnings, Untranslated("\n")).original);
733 364 : return result;
734 406 : },
735 : };
736 0 : }
737 :
738 2900 : static RPCHelpMan wipewallettxes()
739 : {
740 5800 : return RPCHelpMan{"wipewallettxes",
741 2900 : "\nWipe wallet transactions.\n"
742 : "Note: Use \"rescanblockchain\" to initiate the scanning progress and recover wallet transactions.\n",
743 5800 : {
744 2900 : {"keep_confirmed", RPCArg::Type::BOOL, RPCArg::Default{false}, "Do not wipe confirmed transactions"},
745 : },
746 2900 : RPCResult{RPCResult::Type::NONE, "", ""},
747 2900 : RPCExamples{
748 2900 : HelpExampleCli("wipewallettxes", "")
749 2900 : + HelpExampleRpc("wipewallettxes", "")
750 : },
751 2904 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
752 : {
753 4 : std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
754 4 : if (!wallet) return UniValue::VNULL;
755 4 : CWallet* const pwallet = wallet.get();
756 :
757 4 : WalletRescanReserver reserver(*pwallet);
758 4 : if (!reserver.reserve()) {
759 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort rescan or wait.");
760 : }
761 :
762 4 : LOCK(pwallet->cs_wallet);
763 :
764 4 : bool keep_confirmed{false};
765 4 : if (!request.params[0].isNull()) {
766 2 : keep_confirmed = request.params[0].get_bool();
767 2 : }
768 :
769 4 : const size_t WALLET_SIZE{pwallet->mapWallet.size()};
770 4 : const size_t STEPS{20};
771 4 : const size_t BATCH_SIZE = std::max(WALLET_SIZE / STEPS, size_t(1000));
772 :
773 4 : pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 0);
774 :
775 84 : for (size_t progress = 0; progress < STEPS; ++progress) {
776 80 : std::vector<uint256> vHashIn;
777 80 : std::vector<uint256> vHashOut;
778 80 : size_t count{0};
779 :
780 8530 : for (auto& [txid, wtx] : pwallet->mapWallet) {
781 4328 : if (progress < STEPS - 1 && ++count > BATCH_SIZE) break;
782 4328 : if (keep_confirmed && wtx.isConfirmed()) continue;
783 208 : vHashIn.push_back(txid);
784 : }
785 :
786 80 : if (vHashIn.size() > 0 && pwallet->ZapSelectTx(vHashIn, vHashOut) != DBErrors::LOAD_OK) {
787 0 : pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 100);
788 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Could not properly delete transactions.");
789 : }
790 :
791 80 : CHECK_NONFATAL(vHashOut.size() == vHashIn.size());
792 :
793 80 : if (pwallet->IsAbortingRescan() || pwallet->chain().shutdownRequested()) {
794 0 : pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 100);
795 0 : throw JSONRPCError(RPC_MISC_ERROR, "Wiping was aborted by user.");
796 : }
797 :
798 80 : pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), std::max(1, std::min(99, int(progress * 100 / STEPS))));
799 80 : }
800 :
801 4 : pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 100);
802 :
803 4 : return UniValue::VNULL;
804 4 : },
805 : };
806 0 : }
807 :
808 2935 : static RPCHelpMan sethdseed()
809 : {
810 5870 : return RPCHelpMan{"sethdseed",
811 : "\nSet or generate a new HD wallet seed. Non-HD wallets will not be upgraded to being a HD wallet. Wallets that are already\n"
812 : "HD can not be updated to a new HD seed.\n"
813 2935 : "\nNote that you will need to MAKE A NEW BACKUP of your wallet after setting the HD wallet seed." + HELP_REQUIRING_PASSPHRASE +
814 : "Note: This command is only compatible with legacy wallets.\n",
815 8805 : {
816 2935 : {"newkeypool", RPCArg::Type::BOOL, RPCArg::Default{true}, "Whether to flush old unused addresses, including change addresses, from the keypool and regenerate it.\n"
817 : "If true, the next address from getnewaddress and change address from getrawchangeaddress will be from this new seed.\n"
818 : "If false, addresses from the existing keypool will be used until it has been depleted."},
819 2935 : {"seed", RPCArg::Type::STR, RPCArg::DefaultHint{"random seed"}, "The WIF private key to use as the new HD seed.\n"
820 : "The seed value can be retrieved using the dumpwallet command. It is the private key marked hdseed=1"},
821 : },
822 2935 : RPCResult{RPCResult::Type::NONE, "", ""},
823 2935 : RPCExamples{
824 2935 : HelpExampleCli("sethdseed", "")
825 2935 : + HelpExampleCli("sethdseed", "false")
826 2935 : + HelpExampleCli("sethdseed", "true \"wifkey\"")
827 2935 : + HelpExampleRpc("sethdseed", "true, \"wifkey\"")
828 : },
829 2971 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
830 : {
831 36 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
832 36 : if (!pwallet) return UniValue::VNULL;
833 :
834 36 : LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
835 :
836 34 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
837 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled");
838 : }
839 :
840 34 : LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
841 :
842 : // Do not do anything to non-HD wallets
843 34 : if (!pwallet->CanSupportFeature(FEATURE_HD)) {
844 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed on a non-HD wallet. Use the upgradewallet RPC in order to upgrade a non-HD wallet to HD");
845 : }
846 34 : if (pwallet->IsHDEnabled()) {
847 3 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed. The wallet already has a seed");
848 : }
849 :
850 31 : EnsureWalletIsUnlocked(*pwallet);
851 :
852 31 : bool flush_key_pool = true;
853 31 : if (!request.params[0].isNull()) {
854 28 : flush_key_pool = request.params[0].get_bool();
855 25 : }
856 :
857 28 : if (request.params[1].isNull()) {
858 5 : spk_man.GenerateNewHDChain("", "");
859 5 : } else {
860 23 : CKey key = DecodeSecret(request.params[1].get_str());
861 20 : if (!key.IsValid()) {
862 3 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
863 : }
864 17 : if (HaveKey(spk_man, key)) {
865 3 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)");
866 : }
867 14 : CHDChain newHdChain;
868 14 : if (!newHdChain.SetSeed(SecureVector(key.begin(), key.end()), true)) {
869 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key: SetSeed failed");
870 : }
871 14 : if (!spk_man.AddHDChainSingle(newHdChain)) {
872 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key: AddHDChainSingle failed");
873 : }
874 : // add default account
875 14 : newHdChain.AddAccount();
876 20 : }
877 :
878 19 : if (flush_key_pool) spk_man.NewKeyPool();
879 :
880 19 : return UniValue::VNULL;
881 45 : },
882 : };
883 0 : }
884 :
885 2896 : static RPCHelpMan upgradewallet()
886 : {
887 5792 : return RPCHelpMan{"upgradewallet",
888 2896 : "\nUpgrade the wallet. Upgrades to the latest version if no version number is specified.\n"
889 : "New keys may be generated and a new wallet backup will need to be made.\n"
890 : "Consider using RPC upgradetohd instead upgradewallet if you have BIP39 mnemonic or want to set a wallet passphrase also (encrypt wallet).",
891 5792 : {
892 2896 : {"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}}, "The version number to upgrade to. Default is the latest wallet version."}
893 : },
894 2896 : RPCResult{
895 2896 : RPCResult::Type::OBJ, "", "",
896 17376 : {
897 2896 : {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
898 2896 : {RPCResult::Type::NUM, "previous_version", "Version of wallet before this operation"},
899 2896 : {RPCResult::Type::NUM, "current_version", "Version of wallet after this operation"},
900 2896 : {RPCResult::Type::STR, "result", /*optional=*/true, "Description of result, if no error"},
901 2896 : {RPCResult::Type::STR, "error", /*optional=*/true, "Error message (if there is one)"}
902 : },
903 : },
904 2896 : RPCExamples{
905 2896 : HelpExampleCli("upgradewallet", "120200")
906 2896 : + HelpExampleRpc("upgradewallet", "120200")
907 : },
908 2896 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
909 : {
910 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
911 0 : if (!pwallet) return UniValue::VNULL;
912 :
913 0 : RPCTypeCheck(request.params, {UniValue::VNUM}, true);
914 :
915 0 : EnsureWalletIsUnlocked(*pwallet);
916 :
917 0 : int version = 0;
918 0 : if (!request.params[0].isNull()) {
919 0 : version = request.params[0].getInt<int>();
920 0 : }
921 0 : bilingual_str error;
922 0 : const int previous_version{pwallet->GetVersion()};
923 0 : const bool wallet_upgraded{pwallet->UpgradeWallet(version, error)};
924 0 : const int current_version{pwallet->GetVersion()};
925 0 : std::string result;
926 :
927 0 : if (wallet_upgraded) {
928 0 : if (previous_version == current_version) {
929 0 : result = "Already at latest version. Wallet version unchanged.";
930 0 : } else {
931 0 : result = strprintf("Wallet upgraded successfully from version %i to version %i.", previous_version, current_version);
932 : }
933 0 : }
934 :
935 0 : UniValue obj(UniValue::VOBJ);
936 0 : obj.pushKV("wallet_name", pwallet->GetName());
937 0 : obj.pushKV("previous_version", previous_version);
938 0 : obj.pushKV("current_version", current_version);
939 0 : if (!result.empty()) {
940 0 : obj.pushKV("result", result);
941 0 : } else {
942 0 : CHECK_NONFATAL(!error.empty());
943 0 : obj.pushKV("error", error.original);
944 : }
945 0 : return obj;
946 0 : },
947 : };
948 0 : }
949 :
950 3000 : RPCHelpMan simulaterawtransaction()
951 : {
952 6000 : return RPCHelpMan{"simulaterawtransaction",
953 3000 : "\nCalculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
954 9000 : {
955 6000 : {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of hex strings of raw transactions.\n",
956 6000 : {
957 3000 : {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
958 : },
959 : },
960 6000 : {"options", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED_NAMED_ARG, "Options",
961 6000 : {
962 3000 : {"include_watchonly", RPCArg::Type::BOOL, RPCArg::DefaultHint{"true for watch-only wallets, otherwise false"}, "Whether to include watch-only addresses (see RPC importaddress)"},
963 : },
964 : },
965 : },
966 3000 : RPCResult{
967 3000 : RPCResult::Type::OBJ, "", "",
968 6000 : {
969 3000 : {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
970 : }
971 : },
972 3000 : RPCExamples{
973 3000 : HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
974 3000 : + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
975 : },
976 3104 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
977 : {
978 104 : const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
979 104 : if (!rpc_wallet) return UniValue::VNULL;
980 104 : const CWallet& wallet = *rpc_wallet;
981 :
982 104 : RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ}, true);
983 :
984 104 : LOCK(wallet.cs_wallet);
985 :
986 104 : UniValue include_watchonly(UniValue::VNULL);
987 104 : if (request.params[1].isObject()) {
988 0 : UniValue options = request.params[1];
989 0 : RPCTypeCheckObj(options,
990 0 : {
991 0 : {"include_watchonly", UniValueType(UniValue::VBOOL)},
992 : },
993 : true, true);
994 :
995 0 : include_watchonly = options["include_watchonly"];
996 0 : }
997 :
998 104 : isminefilter filter = ISMINE_SPENDABLE;
999 104 : if (ParseIncludeWatchonly(include_watchonly, wallet)) {
1000 20 : filter |= ISMINE_WATCH_ONLY;
1001 20 : }
1002 :
1003 104 : const auto& txs = request.params[0].get_array();
1004 104 : CAmount changes{0};
1005 104 : std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array
1006 104 : std::set<COutPoint> spent;
1007 :
1008 216 : for (size_t i = 0; i < txs.size(); ++i) {
1009 152 : CMutableTransaction mtx;
1010 152 : if (!DecodeHexTx(mtx, txs[i].get_str())) {
1011 0 : throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "Transaction hex string decoding failure.");
1012 : }
1013 :
1014 : // Fetch previous transactions (inputs)
1015 152 : std::map<COutPoint, Coin> coins;
1016 268 : for (const CTxIn& txin : mtx.vin) {
1017 116 : coins[txin.prevout]; // Create empty map entry keyed by prevout.
1018 : }
1019 152 : wallet.chain().findCoins(coins);
1020 :
1021 : // Fetch debit; we are *spending* these; if the transaction is signed and
1022 : // broadcast, we will lose everything in these
1023 228 : for (const auto& txin : mtx.vin) {
1024 116 : const auto& outpoint = txin.prevout;
1025 116 : if (spent.count(outpoint)) {
1026 12 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
1027 : }
1028 104 : if (new_utxos.count(outpoint)) {
1029 24 : changes -= new_utxos.at(outpoint);
1030 24 : new_utxos.erase(outpoint);
1031 24 : } else {
1032 80 : if (coins.at(outpoint).IsSpent()) {
1033 28 : throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
1034 : }
1035 52 : changes -= wallet.GetDebit(txin, filter);
1036 : }
1037 76 : spent.insert(outpoint);
1038 : }
1039 :
1040 : // Iterate over outputs; we are *receiving* these, if the wallet considers
1041 : // them "mine"; if the transaction is signed and broadcast, we will receive
1042 : // everything in these
1043 : // Also populate new_utxos in case these are spent in later transactions
1044 :
1045 112 : const auto& hash = mtx.GetHash();
1046 276 : for (size_t i = 0; i < mtx.vout.size(); ++i) {
1047 164 : const auto& txout = mtx.vout[i];
1048 164 : bool is_mine = 0 < (wallet.IsMine(txout) & filter);
1049 164 : changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0;
1050 164 : }
1051 152 : }
1052 :
1053 64 : UniValue result(UniValue::VOBJ);
1054 64 : result.pushKV("balance_change", ValueFromAmount(changes));
1055 :
1056 64 : return result;
1057 144 : }
1058 : };
1059 0 : }
1060 :
1061 2934 : static RPCHelpMan migratewallet()
1062 : {
1063 5868 : return RPCHelpMan{"migratewallet",
1064 2934 : "EXPERIMENTAL warning: This call may not work as expected and may be changed in future releases\n"
1065 : "\nMigrate the wallet to a descriptor wallet.\n"
1066 : "A new wallet backup will need to be made.\n"
1067 : "\nThe migration process will create a backup of the wallet before migrating. This backup\n"
1068 : "file will be named <wallet name>-<timestamp>.legacy.bak and can be found in the directory\n"
1069 : "for this wallet. In the event of an incorrect migration, the backup can be restored using restorewallet."
1070 : "\nEncrypted wallets must have the passphrase provided as an argument to this call.",
1071 8802 : {
1072 2934 : {"wallet_name", RPCArg::Type::STR, RPCArg::DefaultHint{"the wallet name from the RPC endpoint"}, "The name of the wallet to migrate. If provided both here and in the RPC endpoint, the two must be identical."},
1073 2934 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
1074 : },
1075 2934 : RPCResult{
1076 2934 : RPCResult::Type::OBJ, "", "",
1077 14670 : {
1078 2934 : {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
1079 2934 : {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
1080 2934 : {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
1081 2934 : {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
1082 : }
1083 : },
1084 2934 : RPCExamples{
1085 2934 : HelpExampleCli("migratewallet", "")
1086 2934 : + HelpExampleRpc("migratewallet", "")
1087 : },
1088 2972 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1089 : {
1090 38 : std::string wallet_name;
1091 38 : if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
1092 32 : if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
1093 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
1094 : }
1095 30 : } else {
1096 6 : if (request.params[0].isNull()) {
1097 2 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Either RPC endpoint wallet or wallet_name parameter must be provided");
1098 : }
1099 4 : wallet_name = request.params[0].get_str();
1100 : }
1101 :
1102 34 : SecureString wallet_pass;
1103 34 : wallet_pass.reserve(100);
1104 34 : if (!request.params[1].isNull()) {
1105 6 : wallet_pass = std::string_view{request.params[1].get_str()};
1106 6 : }
1107 :
1108 34 : WalletContext& context = EnsureWalletContext(request.context);
1109 34 : util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context);
1110 34 : if (!res) {
1111 8 : throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
1112 : }
1113 :
1114 26 : UniValue r{UniValue::VOBJ};
1115 26 : r.pushKV("wallet_name", res->wallet_name);
1116 26 : if (res->watchonly_wallet) {
1117 4 : r.pushKV("watchonly_name", res->watchonly_wallet->GetName());
1118 4 : }
1119 26 : if (res->solvables_wallet) {
1120 2 : r.pushKV("solvables_name", res->solvables_wallet->GetName());
1121 2 : }
1122 26 : r.pushKV("backup_path", fs::PathToString(res->backup_path));
1123 :
1124 26 : return r;
1125 50 : },
1126 : };
1127 0 : }
1128 :
1129 : // addresses
1130 : RPCHelpMan getaddressinfo();
1131 : RPCHelpMan getnewaddress();
1132 : RPCHelpMan getrawchangeaddress();
1133 : RPCHelpMan setlabel();
1134 : RPCHelpMan listaddressgroupings();
1135 : RPCHelpMan addmultisigaddress();
1136 : RPCHelpMan keypoolrefill();
1137 : RPCHelpMan newkeypool();
1138 : RPCHelpMan getaddressesbylabel();
1139 : RPCHelpMan listlabels();
1140 : #ifdef ENABLE_EXTERNAL_SIGNER
1141 : RPCHelpMan walletdisplayaddress();
1142 : #endif // ENABLE_EXTERNAL_SIGNER
1143 :
1144 : // backup
1145 : RPCHelpMan dumpprivkey();
1146 : RPCHelpMan importprivkey();
1147 : RPCHelpMan importaddress();
1148 : RPCHelpMan importpubkey();
1149 : RPCHelpMan dumpwallet();
1150 : RPCHelpMan importwallet();
1151 : RPCHelpMan importprunedfunds();
1152 : RPCHelpMan removeprunedfunds();
1153 : RPCHelpMan importmulti();
1154 : RPCHelpMan importdescriptors();
1155 : RPCHelpMan listdescriptors();
1156 : RPCHelpMan backupwallet();
1157 : RPCHelpMan restorewallet();
1158 : RPCHelpMan dumphdinfo();
1159 : RPCHelpMan importelectrumwallet();
1160 :
1161 : // coins
1162 : RPCHelpMan getreceivedbyaddress();
1163 : RPCHelpMan getreceivedbylabel();
1164 : RPCHelpMan listaddressbalances();
1165 : RPCHelpMan getbalance();
1166 : RPCHelpMan getunconfirmedbalance();
1167 : RPCHelpMan lockunspent();
1168 : RPCHelpMan listlockunspent();
1169 : RPCHelpMan getbalances();
1170 : RPCHelpMan listunspent();
1171 :
1172 : // encryption
1173 : RPCHelpMan walletpassphrase();
1174 : RPCHelpMan walletpassphrasechange();
1175 : RPCHelpMan walletlock();
1176 : RPCHelpMan encryptwallet();
1177 :
1178 : // spend
1179 : RPCHelpMan sendtoaddress();
1180 : RPCHelpMan sendmany();
1181 : RPCHelpMan settxfee();
1182 : RPCHelpMan fundrawtransaction();
1183 : RPCHelpMan send();
1184 : RPCHelpMan sendall();
1185 : RPCHelpMan walletprocesspsbt();
1186 : RPCHelpMan walletcreatefundedpsbt();
1187 : RPCHelpMan signrawtransactionwithwallet();
1188 :
1189 : // signmessage
1190 : RPCHelpMan signmessage();
1191 :
1192 : // transactions
1193 : RPCHelpMan listreceivedbyaddress();
1194 : RPCHelpMan listreceivedbylabel();
1195 : RPCHelpMan listtransactions();
1196 : RPCHelpMan listsinceblock();
1197 : RPCHelpMan gettransaction();
1198 : RPCHelpMan abandontransaction();
1199 : RPCHelpMan rescanblockchain();
1200 : RPCHelpMan abortrescan();
1201 :
1202 1450 : Span<const CRPCCommand> GetWalletRPCCommands()
1203 : {
1204 105130 : static const CRPCCommand commands[]{
1205 1440 : {"rawtransactions", &fundrawtransaction},
1206 1440 : {"wallet", &abandontransaction},
1207 1440 : {"wallet", &abortrescan},
1208 1440 : {"wallet", &addmultisigaddress},
1209 1440 : {"wallet", &backupwallet},
1210 1440 : {"wallet", &createwallet},
1211 1440 : {"wallet", &restorewallet},
1212 1440 : {"wallet", &dumphdinfo},
1213 1440 : {"wallet", &dumpprivkey},
1214 1440 : {"wallet", &dumpwallet},
1215 1440 : {"wallet", &encryptwallet},
1216 1440 : {"wallet", &getaddressesbylabel},
1217 1440 : {"wallet", &getaddressinfo},
1218 1440 : {"wallet", &getbalance},
1219 1440 : {"wallet", &getnewaddress},
1220 1440 : {"wallet", &getrawchangeaddress},
1221 1440 : {"wallet", &getreceivedbyaddress},
1222 1440 : {"wallet", &getreceivedbylabel},
1223 1440 : {"wallet", &gettransaction},
1224 1440 : {"wallet", &getunconfirmedbalance},
1225 1440 : {"wallet", &getbalances},
1226 1440 : {"wallet", &getwalletinfo},
1227 1440 : {"wallet", &importaddress},
1228 1440 : {"wallet", &importelectrumwallet},
1229 1440 : {"wallet", &importdescriptors},
1230 1440 : {"wallet", &importmulti},
1231 1440 : {"wallet", &importprivkey},
1232 1440 : {"wallet", &importprunedfunds},
1233 1440 : {"wallet", &importpubkey},
1234 1440 : {"wallet", &importwallet},
1235 1440 : {"wallet", &keypoolrefill},
1236 1440 : {"wallet", &listaddressbalances},
1237 1440 : {"wallet", &listaddressgroupings},
1238 1440 : {"wallet", &listdescriptors},
1239 1440 : {"wallet", &listlabels},
1240 1440 : {"wallet", &listlockunspent},
1241 1440 : {"wallet", &listreceivedbyaddress},
1242 1440 : {"wallet", &listreceivedbylabel},
1243 1440 : {"wallet", &listsinceblock},
1244 1440 : {"wallet", &listtransactions},
1245 1440 : {"wallet", &listunspent},
1246 1440 : {"wallet", &listwalletdir},
1247 1440 : {"wallet", &listwallets},
1248 1440 : {"wallet", &loadwallet},
1249 1440 : {"wallet", &lockunspent},
1250 1440 : {"wallet", &migratewallet},
1251 1440 : {"wallet", &newkeypool},
1252 1440 : {"wallet", &removeprunedfunds},
1253 1440 : {"wallet", &rescanblockchain},
1254 1440 : {"wallet", &send},
1255 1440 : {"wallet", &sendmany},
1256 1440 : {"wallet", &sendtoaddress},
1257 1440 : {"wallet", &sethdseed},
1258 1440 : {"wallet", &setcoinjoinrounds},
1259 1440 : {"wallet", &setcoinjoinamount},
1260 1440 : {"wallet", &setlabel},
1261 1440 : {"wallet", &settxfee},
1262 1440 : {"wallet", &setwalletflag},
1263 1440 : {"wallet", &signmessage},
1264 1440 : {"wallet", &signrawtransactionwithwallet},
1265 1440 : {"wallet", &simulaterawtransaction},
1266 1440 : {"wallet", &sendall},
1267 1440 : {"wallet", &unloadwallet},
1268 1440 : {"wallet", &upgradewallet},
1269 1440 : {"wallet", &upgradetohd},
1270 : #ifdef ENABLE_EXTERNAL_SIGNER
1271 1440 : {"wallet", &walletdisplayaddress},
1272 : #endif // ENABLE_EXTERNAL_SIGNER
1273 1440 : {"wallet", &walletlock},
1274 1440 : {"wallet", &walletpassphrasechange},
1275 1440 : {"wallet", &walletpassphrase},
1276 1440 : {"wallet", &walletprocesspsbt},
1277 1440 : {"wallet", &walletcreatefundedpsbt},
1278 1440 : {"wallet", &wipewallettxes},
1279 : };
1280 1450 : return commands;
1281 0 : }
1282 : } // namespace wallet
|