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 0 : bool HaveKey(const SigningProvider& wallet, const CKey& key)
39 : {
40 0 : CKey key2;
41 0 : key2.Set(key.begin(), key.end(), !key.IsCompressed());
42 0 : return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID());
43 0 : }
44 :
45 8 : static RPCHelpMan setcoinjoinrounds()
46 : {
47 16 : return RPCHelpMan{"setcoinjoinrounds",
48 8 : "\nSet the number of rounds for CoinJoin.\n",
49 16 : {
50 16 : {"rounds", RPCArg::Type::NUM, RPCArg::Optional::NO,
51 8 : "The default number of rounds is " + ToString(DEFAULT_COINJOIN_ROUNDS) +
52 8 : " Cannot be more than " + ToString(MAX_COINJOIN_ROUNDS) + " nor less than " + ToString(MIN_COINJOIN_ROUNDS)},
53 : },
54 8 : RPCResult{RPCResult::Type::NONE, "", ""},
55 8 : RPCExamples{
56 8 : HelpExampleCli("setcoinjoinrounds", "4")
57 8 : + HelpExampleRpc("setcoinjoinrounds", "16")
58 : },
59 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
60 : {
61 0 : const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
62 0 : if (!wallet) return UniValue::VNULL;
63 :
64 0 : int nRounds = request.params[0].getInt<int>();
65 :
66 0 : if (nRounds > MAX_COINJOIN_ROUNDS || nRounds < MIN_COINJOIN_ROUNDS)
67 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid number of rounds");
68 :
69 0 : CCoinJoinClientOptions::SetRounds(nRounds);
70 :
71 0 : return UniValue::VNULL;
72 0 : },
73 : };
74 0 : }
75 :
76 8 : static RPCHelpMan setcoinjoinamount()
77 : {
78 16 : return RPCHelpMan{"setcoinjoinamount",
79 8 : "\nSet the goal amount in " + CURRENCY_UNIT + " for CoinJoin.\n",
80 16 : {
81 16 : {"amount", RPCArg::Type::NUM, RPCArg::Optional::NO,
82 8 : "The default amount is " + ToString(DEFAULT_COINJOIN_AMOUNT) +
83 8 : " Cannot be more than " + ToString(MAX_COINJOIN_AMOUNT) + " nor less than " + ToString(MIN_COINJOIN_AMOUNT)},
84 : },
85 8 : RPCResult{RPCResult::Type::NONE, "", ""},
86 8 : RPCExamples{
87 8 : HelpExampleCli("setcoinjoinamount", "500")
88 8 : + HelpExampleRpc("setcoinjoinamount", "208")
89 : },
90 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
91 : {
92 0 : const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
93 0 : if (!wallet) return UniValue::VNULL;
94 :
95 0 : int nAmount = request.params[0].getInt<int>();
96 :
97 0 : if (nAmount > MAX_COINJOIN_AMOUNT || nAmount < MIN_COINJOIN_AMOUNT)
98 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount of " + CURRENCY_UNIT + " as mixing goal amount");
99 :
100 0 : CCoinJoinClientOptions::SetAmount(nAmount);
101 :
102 0 : return UniValue::VNULL;
103 0 : },
104 : };
105 0 : }
106 :
107 8 : static RPCHelpMan getwalletinfo()
108 : {
109 16 : return RPCHelpMan{"getwalletinfo",
110 8 : "Returns an object containing various wallet state info.\n",
111 8 : {},
112 8 : RPCResult{
113 8 : RPCResult::Type::OBJ, "", "",
114 192 : {
115 8 : {RPCResult::Type::STR, "walletname", "the wallet name"},
116 8 : {RPCResult::Type::NUM, "walletversion", "the wallet version"},
117 8 : {RPCResult::Type::STR, "format", "the database format (bdb or sqlite)"},
118 8 : {RPCResult::Type::NUM, "balance", "DEPRECATED. Identical to getbalances().mine.trusted"},
119 8 : {RPCResult::Type::NUM, "coinjoin_balance", "DEPRECATED. Identical to getbalances().mine.coinjoin"},
120 8 : {RPCResult::Type::NUM, "unconfirmed_balance", "DEPRECATED. Identical to getbalances().mine.untrusted_pending"},
121 8 : {RPCResult::Type::NUM, "immature_balance", "DEPRECATED. Identical to getbalances().mine.immature"},
122 8 : {RPCResult::Type::NUM, "txcount", "the total number of transactions in the wallet"},
123 8 : {RPCResult::Type::NUM_TIME, "timefirstkey", "the " + UNIX_EPOCH_TIME + " of the oldest known key in the wallet"},
124 8 : {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 8 : {RPCResult::Type::NUM, "keypoolsize", "how many new keys are pre-generated (only counts external keys)"},
126 8 : {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 8 : {RPCResult::Type::NUM, "keys_left", "how many new keys are left since last automatic backup"},
128 8 : {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 8 : {RPCResult::Type::STR_AMOUNT, "paytxfee", "the transaction fee configuration, set in " + CURRENCY_UNIT + "/kB"},
130 8 : {RPCResult::Type::STR_HEX, "hdchainid", "the ID of the HD chain"},
131 8 : {RPCResult::Type::NUM, "hdaccountcount", "how many accounts of the HD chain are in this wallet"},
132 16 : {RPCResult::Type::ARR, "hdaccounts", "",
133 16 : {
134 16 : {RPCResult::Type::OBJ, "", "",
135 32 : {
136 8 : {RPCResult::Type::NUM, "hdaccountindex", "the index of the account"},
137 8 : {RPCResult::Type::NUM, "hdexternalkeyindex", "current external childkey index"},
138 8 : {RPCResult::Type::NUM, "hdinternalkeyindex", "current internal childkey index"},
139 : }},
140 : }},
141 8 : {RPCResult::Type::BOOL, "private_keys_enabled", "false if privatekeys are disabled for this wallet (enforced watch-only wallet)"},
142 8 : {RPCResult::Type::BOOL, "avoid_reuse", "whether this wallet tracks clean/dirty coins in terms of reuse"},
143 16 : {RPCResult::Type::OBJ, "scanning", "current scanning details, or false if no scan is in progress",
144 24 : {
145 8 : {RPCResult::Type::NUM, "duration", "elapsed seconds since scan start"},
146 8 : {RPCResult::Type::NUM, "progress", "scanning progress percentage [0.0, 1.0]"},
147 : }},
148 8 : {RPCResult::Type::BOOL, "descriptors", "whether this wallet uses descriptors for scriptPubKey management"},
149 8 : {RPCResult::Type::BOOL, "external_signer", "whether this wallet is configured to use an external signer such as a hardware wallet"},
150 8 : RESULT_LAST_PROCESSED_BLOCK,
151 : },
152 : },
153 8 : RPCExamples{
154 8 : HelpExampleCli("getwalletinfo", "")
155 8 : + HelpExampleRpc("getwalletinfo", "")
156 : },
157 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
158 : {
159 0 : const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
160 0 : 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 0 : pwallet->BlockUntilSyncedToCurrentChain();
165 :
166 0 : LOCK(pwallet->cs_wallet);
167 :
168 0 : LegacyScriptPubKeyMan* spk_man = pwallet->GetLegacyScriptPubKeyMan();
169 0 : CHDChain hdChainCurrent;
170 0 : bool fHDEnabled = spk_man && spk_man->GetHDChain(hdChainCurrent);
171 0 : UniValue obj(UniValue::VOBJ);
172 :
173 0 : const auto bal = GetBalance(*pwallet);
174 0 : obj.pushKV("walletname", pwallet->GetName());
175 0 : obj.pushKV("walletversion", pwallet->GetVersion());
176 0 : obj.pushKV("format", pwallet->GetDatabase().Format());
177 0 : obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted));
178 0 : obj.pushKV("coinjoin_balance", ValueFromAmount(bal.m_anonymized));
179 0 : obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending));
180 0 : obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature));
181 0 : obj.pushKV("txcount", (int)pwallet->mapWallet.size());
182 : // TODO: implement timefirstkey for Descriptor KeyMan or explain why it's not provided
183 0 : if (spk_man) {
184 0 : obj.pushKV("timefirstkey", spk_man->GetTimeFirstKey());
185 0 : }
186 0 : const auto kp_oldest = pwallet->GetOldestKeyPoolTime();
187 0 : if (kp_oldest.has_value()) {
188 0 : obj.pushKV("keypoololdest", kp_oldest.value());
189 0 : }
190 0 : size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
191 0 : obj.pushKV("keypoolsize", kpExternalSize);
192 0 : obj.pushKV("keypoolsize_hd_internal", pwallet->GetKeyPoolSize() - kpExternalSize);
193 0 : obj.pushKV("keys_left", pwallet->nKeysLeftSinceAutoBackup);
194 0 : if (pwallet->IsCrypted()) {
195 0 : obj.pushKV("unlocked_until", pwallet->nRelockTime);
196 0 : }
197 0 : obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK()));
198 0 : if (fHDEnabled) {
199 0 : obj.pushKV("hdchainid", hdChainCurrent.GetID().GetHex());
200 0 : obj.pushKV("hdaccountcount", hdChainCurrent.CountAccounts());
201 0 : UniValue accounts(UniValue::VARR);
202 0 : for (size_t i = 0; i < hdChainCurrent.CountAccounts(); ++i)
203 : {
204 0 : CHDAccount acc;
205 0 : UniValue account(UniValue::VOBJ);
206 0 : account.pushKV("hdaccountindex", i);
207 0 : if(hdChainCurrent.GetAccount(i, acc)) {
208 0 : account.pushKV("hdexternalkeyindex", acc.nExternalChainCounter);
209 0 : account.pushKV("hdinternalkeyindex", acc.nInternalChainCounter);
210 0 : } else {
211 0 : account.pushKV("error", strprintf("account %d is missing", i));
212 : }
213 0 : accounts.push_back(account);
214 0 : }
215 0 : obj.pushKV("hdaccounts", accounts);
216 0 : }
217 0 : obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS));
218 0 : obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE));
219 0 : 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 0 : obj.pushKV("scanning", false);
226 : }
227 0 : obj.pushKV("descriptors", pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS));
228 0 : obj.pushKV("external_signer", pwallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER));
229 :
230 0 : AppendLastProcessedBlock(obj, *pwallet);
231 0 : return obj;
232 0 : },
233 : };
234 0 : }
235 :
236 8 : static RPCHelpMan listwalletdir()
237 : {
238 16 : return RPCHelpMan{"listwalletdir",
239 8 : "Returns a list of wallets in the wallet directory.\n",
240 8 : {},
241 8 : RPCResult{
242 8 : RPCResult::Type::OBJ, "", "",
243 16 : {
244 16 : {RPCResult::Type::ARR, "wallets", "",
245 16 : {
246 16 : {RPCResult::Type::OBJ, "", "",
247 16 : {
248 8 : {RPCResult::Type::STR, "name", "The wallet name"},
249 : }},
250 : }},
251 : }
252 : },
253 8 : RPCExamples{
254 8 : HelpExampleCli("listwalletdir", "")
255 8 : + HelpExampleRpc("listwalletdir", "")
256 : },
257 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
258 : {
259 0 : UniValue wallets(UniValue::VARR);
260 0 : for (const auto& path : ListDatabases(GetWalletDir())) {
261 0 : UniValue wallet(UniValue::VOBJ);
262 0 : wallet.pushKV("name", path.utf8string());
263 0 : wallets.push_back(wallet);
264 0 : }
265 :
266 0 : UniValue result(UniValue::VOBJ);
267 0 : result.pushKV("wallets", wallets);
268 0 : return result;
269 0 : },
270 : };
271 0 : }
272 :
273 8 : static RPCHelpMan listwallets()
274 : {
275 16 : return RPCHelpMan{"listwallets",
276 8 : "Returns a list of currently loaded wallets.\n"
277 : "For full information on the wallet, use \"getwalletinfo\"\n",
278 8 : {},
279 8 : RPCResult{
280 8 : RPCResult::Type::ARR, "", "",
281 16 : {
282 8 : {RPCResult::Type::STR, "walletname", "the wallet name"},
283 : }
284 : },
285 8 : RPCExamples{
286 8 : HelpExampleCli("listwallets", "")
287 8 : + HelpExampleRpc("listwallets", "")
288 : },
289 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
290 : {
291 0 : UniValue obj(UniValue::VARR);
292 :
293 0 : WalletContext& context = EnsureWalletContext(request.context);
294 0 : for (const std::shared_ptr<CWallet>& wallet : GetWallets(context)) {
295 0 : LOCK(wallet->cs_wallet);
296 0 : obj.push_back(wallet->GetName());
297 0 : }
298 :
299 0 : return obj;
300 0 : },
301 : };
302 0 : }
303 :
304 8 : static RPCHelpMan upgradetohd()
305 : {
306 16 : return RPCHelpMan{"upgradetohd",
307 8 : "\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 40 : {
312 8 : {"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 8 : {"mnemonicpassphrase", RPCArg::Type::STR, RPCArg::Default{""}, "Optional mnemonic passphrase as defined in BIP39"},
314 8 : {"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 8 : {"rescan", RPCArg::Type::BOOL, RPCArg::DefaultHint{"false if mnemonic is empty"}, "Whether to rescan the blockchain for missing transactions or not"},
316 : },
317 8 : RPCResult{RPCResult::Type::STR, "", "A string with further instructions"},
318 8 : RPCExamples{
319 8 : HelpExampleCli("upgradetohd", "")
320 8 : + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\"")
321 8 : + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\"")
322 8 : + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"\" \"walletpassphrase\"")
323 8 : + HelpExampleCli("upgradetohd", "\"mnemonicword1 ... mnemonicwordN\" \"mnemonicpassphrase\" \"walletpassphrase\"")
324 : },
325 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
326 : {
327 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
328 0 : if (!pwallet) return UniValue::VNULL;
329 :
330 0 : bool generate_mnemonic = request.params[0].isNull() || request.params[0].get_str().empty();
331 0 : bool mnemonic_passphrase_has_null{false};
332 : {
333 0 : LOCK(pwallet->cs_wallet);
334 :
335 0 : SecureString wallet_passphrase;
336 0 : wallet_passphrase.reserve(100);
337 :
338 0 : if (request.params[2].isNull()) {
339 0 : if (pwallet->IsCrypted()) {
340 0 : throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Wallet encrypted but passphrase not supplied to RPC.");
341 : }
342 0 : } else {
343 0 : wallet_passphrase = std::string_view{request.params[2].get_str()};
344 : }
345 :
346 0 : SecureString mnemonic;
347 0 : mnemonic.reserve(256);
348 0 : if (!generate_mnemonic) {
349 0 : mnemonic = std::string_view{request.params[0].get_str()};
350 0 : }
351 :
352 0 : SecureString mnemonic_passphrase;
353 0 : mnemonic_passphrase.reserve(256);
354 0 : if (!request.params[1].isNull()) {
355 0 : mnemonic_passphrase = std::string_view{request.params[1].get_str()};
356 0 : mnemonic_passphrase_has_null = (mnemonic_passphrase.find('\0') != std::string::npos);
357 0 : }
358 :
359 : // Do not do anything to HD wallets
360 0 : 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 0 : 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 0 : pwallet->WalletLogPrintf("Upgrading wallet to HD\n");
369 0 : pwallet->SetMinVersion(FEATURE_HD);
370 :
371 0 : if (pwallet->IsCrypted()) {
372 0 : 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 0 : pwallet->Lock();
379 :
380 : // Unlock the wallet
381 0 : if (!pwallet->Unlock(wallet_passphrase)) {
382 : // Check if the passphrase has a null character (see bitcoin#27067 for details)
383 0 : if (wallet_passphrase.find('\0') == std::string::npos) {
384 0 : 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 0 : }
395 :
396 0 : if (pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
397 0 : pwallet->SetupDescriptorScriptPubKeyMans(mnemonic, mnemonic_passphrase);
398 0 : } else {
399 0 : auto spk_man = pwallet->GetLegacyScriptPubKeyMan();
400 0 : if (!spk_man) {
401 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Error: Legacy ScriptPubKeyMan is not available");
402 : }
403 :
404 0 : if (pwallet->IsCrypted()) {
405 0 : pwallet->WithEncryptionKey([&](const CKeyingMaterial& encryption_key) {
406 0 : spk_man->GenerateNewHDChain(mnemonic, mnemonic_passphrase, encryption_key);
407 0 : return true;
408 0 : });
409 0 : } else {
410 0 : spk_man->GenerateNewHDChain(mnemonic, mnemonic_passphrase);
411 : }
412 : }
413 :
414 0 : if (pwallet->IsCrypted()) {
415 : // Relock encrypted wallet
416 0 : pwallet->Lock();
417 0 : } else if (!wallet_passphrase.empty()) {
418 : // Encrypt non-encrypted wallet
419 0 : if (!pwallet->EncryptWallet(wallet_passphrase)) {
420 0 : throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Failed to encrypt HD wallet");
421 : }
422 0 : }
423 0 : } // 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 0 : bool rescan = request.params[3].isNull() ? !generate_mnemonic : request.params[3].get_bool();
427 0 : if (rescan) {
428 0 : WalletRescanReserver reserver(*pwallet);
429 0 : if (!reserver.reserve()) {
430 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
431 : }
432 0 : CWallet::ScanResult result = pwallet->ScanForWalletTransactions(pwallet->chain().getBlockHash(0), 0, {}, reserver, /*fUpdate=*/true, /*save_progress=*/false);
433 0 : switch (result.status) {
434 : case CWallet::ScanResult::SUCCESS:
435 0 : 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 0 : }
443 :
444 : // Check if the passphrase has a null character (see #27067 for details)
445 0 : if (!mnemonic_passphrase_has_null) {
446 0 : return "Make sure that you have backup of your mnemonic.";
447 : } else {
448 0 : 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 0 : },
456 : };
457 0 : }
458 :
459 8 : static RPCHelpMan loadwallet()
460 : {
461 16 : return RPCHelpMan{"loadwallet",
462 8 : "\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 24 : {
466 8 : {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."},
467 8 : {"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 8 : RPCResult{
470 8 : RPCResult::Type::OBJ, "", "",
471 24 : {
472 8 : {RPCResult::Type::STR, "name", "The wallet name if loaded successfully."},
473 8 : {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
474 : }
475 : },
476 8 : RPCExamples{
477 8 : HelpExampleCli("loadwallet", "\"test.dat\"")
478 8 : + HelpExampleRpc("loadwallet", "\"test.dat\"")
479 : },
480 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
481 : {
482 0 : WalletContext& context = EnsureWalletContext(request.context);
483 0 : const std::string name(request.params[0].get_str());
484 :
485 0 : DatabaseOptions options;
486 : DatabaseStatus status;
487 0 : ReadDatabaseArgs(*context.args, options);
488 0 : options.require_existing = true;
489 0 : bilingual_str error;
490 0 : std::vector<bilingual_str> warnings;
491 0 : std::optional<bool> load_on_start = request.params[1].isNull() ? std::nullopt : std::optional<bool>(request.params[1].get_bool());
492 :
493 : {
494 0 : LOCK(context.wallets_mutex);
495 0 : if (std::any_of(context.wallets.begin(), context.wallets.end(), [&name](const auto& wallet) { return wallet->GetName() == name; })) {
496 0 : throw JSONRPCError(RPC_WALLET_ALREADY_LOADED, "Wallet \"" + name + "\" is already loaded.");
497 : }
498 0 : }
499 :
500 0 : std::shared_ptr<CWallet> const wallet = LoadWallet(context, name, load_on_start, options, status, error, warnings);
501 :
502 0 : HandleWalletError(wallet, status, error);
503 :
504 0 : UniValue obj(UniValue::VOBJ);
505 0 : obj.pushKV("name", wallet->GetName());
506 0 : obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
507 :
508 0 : return obj;
509 0 : },
510 : };
511 0 : }
512 :
513 8 : static RPCHelpMan setwalletflag()
514 : {
515 8 : std::string flags;
516 64 : for (auto& it : WALLET_FLAG_MAP)
517 56 : if (it.second & MUTABLE_WALLET_FLAGS)
518 8 : flags += (flags == "" ? "" : ", ") + it.first;
519 :
520 16 : return RPCHelpMan{"setwalletflag",
521 8 : "\nChange the state of the given wallet flag for a wallet.\n",
522 24 : {
523 8 : {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags},
524 8 : {"value", RPCArg::Type::BOOL, RPCArg::Default{true}, "The new state."},
525 : },
526 8 : RPCResult{
527 8 : RPCResult::Type::OBJ, "", "",
528 32 : {
529 8 : {RPCResult::Type::STR, "flag_name", "The name of the flag that was modified"},
530 8 : {RPCResult::Type::BOOL, "flag_state", "The new state of the flag"},
531 8 : {RPCResult::Type::STR, "warnings", /*optional=*/true, "Any warnings associated with the change"},
532 : }
533 : },
534 8 : RPCExamples{
535 8 : HelpExampleCli("setwalletflag", "avoid_reuse")
536 8 : + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")
537 : },
538 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
539 : {
540 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
541 0 : if (!pwallet) return UniValue::VNULL;
542 :
543 :
544 0 : std::string flag_str = request.params[0].get_str();
545 0 : bool value = request.params[1].isNull() || request.params[1].get_bool();
546 :
547 0 : if (!WALLET_FLAG_MAP.count(flag_str)) {
548 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str));
549 : }
550 :
551 0 : auto flag = WALLET_FLAG_MAP.at(flag_str);
552 :
553 0 : if (!(flag & MUTABLE_WALLET_FLAGS)) {
554 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str));
555 : }
556 :
557 0 : UniValue res(UniValue::VOBJ);
558 :
559 0 : if (pwallet->IsWalletFlagSet(flag) == value) {
560 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str));
561 : }
562 :
563 0 : res.pushKV("flag_name", flag_str);
564 0 : res.pushKV("flag_state", value);
565 :
566 0 : if (value) {
567 0 : pwallet->SetWalletFlag(flag);
568 0 : } else {
569 0 : pwallet->UnsetWalletFlag(flag);
570 : }
571 :
572 0 : if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) {
573 0 : res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag));
574 0 : }
575 :
576 0 : return res;
577 0 : },
578 : };
579 8 : }
580 :
581 8 : static RPCHelpMan createwallet()
582 : {
583 8 : return RPCHelpMan{
584 8 : "createwallet",
585 8 : "\nCreates and loads a new wallet.\n",
586 72 : {
587 8 : {"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 8 : {"disable_private_keys", RPCArg::Type::BOOL, RPCArg::Default{false}, "Disable the possibility of private keys (only watchonlys are possible in this mode)."},
589 8 : {"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 8 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Encrypt the wallet with this passphrase."},
591 8 : {"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 8 : {"descriptors", RPCArg::Type::BOOL, RPCArg::Default{true}, "Create a native descriptor wallet. The wallet will use descriptors internally to handle address creation."},
593 8 : {"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 8 : {"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 8 : RPCResult{
597 8 : RPCResult::Type::OBJ, "", "",
598 24 : {
599 8 : {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 8 : {RPCResult::Type::STR, "warning", "Warning message if wallet was not loaded cleanly."},
601 : }
602 : },
603 8 : RPCExamples{
604 8 : HelpExampleCli("createwallet", "\"testwallet\"")
605 8 : + HelpExampleRpc("createwallet", "\"testwallet\"")
606 8 : + HelpExampleCliNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
607 8 : + HelpExampleRpcNamed("createwallet", {{"wallet_name", "descriptors"}, {"avoid_reuse", true}, {"descriptors", true}, {"load_on_startup", true}})
608 : },
609 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
610 : {
611 0 : WalletContext& context = EnsureWalletContext(request.context);
612 0 : uint64_t flags = 0;
613 0 : if (!request.params[1].isNull() && request.params[1].get_bool()) {
614 0 : flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS;
615 0 : }
616 :
617 0 : if (!request.params[2].isNull() && request.params[2].get_bool()) {
618 0 : flags |= WALLET_FLAG_BLANK_WALLET;
619 0 : }
620 0 : SecureString passphrase;
621 0 : passphrase.reserve(100);
622 0 : std::vector<bilingual_str> warnings;
623 0 : if (!request.params[3].isNull()) {
624 0 : passphrase = std::string_view{request.params[3].get_str()};
625 0 : if (passphrase.empty()) {
626 : // Empty string means unencrypted
627 0 : warnings.emplace_back(Untranslated("Empty string given as passphrase, wallet will not be encrypted."));
628 0 : }
629 0 : }
630 :
631 0 : if (!request.params[4].isNull() && request.params[4].get_bool()) {
632 0 : flags |= WALLET_FLAG_AVOID_REUSE;
633 0 : }
634 0 : 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 0 : 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 0 : flags |= WALLET_FLAG_DESCRIPTORS;
642 0 : }
643 0 : if (!request.params[7].isNull() && request.params[7].get_bool()) {
644 : #ifdef ENABLE_EXTERNAL_SIGNER
645 0 : 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 0 : }
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 0 : DatabaseOptions options;
657 : DatabaseStatus status;
658 0 : ReadDatabaseArgs(*context.args, options);
659 0 : options.require_create = true;
660 0 : options.create_flags = flags;
661 0 : options.create_passphrase = passphrase;
662 0 : bilingual_str error;
663 0 : std::optional<bool> load_on_start = request.params[6].isNull() ? std::nullopt : std::optional<bool>(request.params[6].get_bool());
664 0 : const std::shared_ptr<CWallet> wallet = CreateWallet(context, request.params[0].get_str(), load_on_start, options, status, error, warnings);
665 0 : if (!wallet) {
666 0 : RPCErrorCode code = status == DatabaseStatus::FAILED_ENCRYPT ? RPC_WALLET_ENCRYPTION_FAILED : RPC_WALLET_ERROR;
667 0 : throw JSONRPCError(code, error.original);
668 : }
669 0 : wallet->SetupLegacyScriptPubKeyMan();
670 :
671 0 : UniValue obj(UniValue::VOBJ);
672 0 : obj.pushKV("name", wallet->GetName());
673 0 : obj.pushKV("warning", Join(warnings, Untranslated("\n")).original);
674 :
675 0 : return obj;
676 0 : },
677 : };
678 0 : }
679 :
680 8 : static RPCHelpMan unloadwallet()
681 : {
682 16 : return RPCHelpMan{"unloadwallet",
683 8 : "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 24 : {
686 8 : {"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 8 : {"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 16 : RPCResult{RPCResult::Type::OBJ, "", "", {
690 8 : {RPCResult::Type::STR, "warning", "Warning message if wallet was not unloaded cleanly."},
691 : }},
692 8 : RPCExamples{
693 8 : HelpExampleCli("unloadwallet", "wallet_name")
694 8 : + HelpExampleRpc("unloadwallet", "wallet_name")
695 : },
696 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
697 : {
698 0 : std::string wallet_name;
699 0 : if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
700 0 : if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
701 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
702 : }
703 0 : } else {
704 0 : wallet_name = request.params[0].get_str();
705 : }
706 :
707 0 : WalletContext& context = EnsureWalletContext(request.context);
708 0 : std::shared_ptr<CWallet> wallet = GetWallet(context, wallet_name);
709 0 : if (!wallet) {
710 0 : throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded");
711 : }
712 :
713 0 : std::vector<bilingual_str> warnings;
714 : {
715 0 : WalletRescanReserver reserver(*wallet);
716 0 : 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 0 : std::optional<bool> load_on_start{self.MaybeArg<bool>(1)};
724 0 : if (!RemoveWallet(context, wallet, load_on_start, warnings)) {
725 0 : throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded");
726 : }
727 0 : }
728 :
729 0 : UnloadWallet(std::move(wallet));
730 :
731 0 : UniValue result(UniValue::VOBJ);
732 0 : result.pushKV("warning", Join(warnings, Untranslated("\n")).original);
733 0 : return result;
734 0 : },
735 : };
736 0 : }
737 :
738 8 : static RPCHelpMan wipewallettxes()
739 : {
740 16 : return RPCHelpMan{"wipewallettxes",
741 8 : "\nWipe wallet transactions.\n"
742 : "Note: Use \"rescanblockchain\" to initiate the scanning progress and recover wallet transactions.\n",
743 16 : {
744 8 : {"keep_confirmed", RPCArg::Type::BOOL, RPCArg::Default{false}, "Do not wipe confirmed transactions"},
745 : },
746 8 : RPCResult{RPCResult::Type::NONE, "", ""},
747 8 : RPCExamples{
748 8 : HelpExampleCli("wipewallettxes", "")
749 8 : + HelpExampleRpc("wipewallettxes", "")
750 : },
751 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
752 : {
753 0 : std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
754 0 : if (!wallet) return UniValue::VNULL;
755 0 : CWallet* const pwallet = wallet.get();
756 :
757 0 : WalletRescanReserver reserver(*pwallet);
758 0 : if (!reserver.reserve()) {
759 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort rescan or wait.");
760 : }
761 :
762 0 : LOCK(pwallet->cs_wallet);
763 :
764 0 : bool keep_confirmed{false};
765 0 : if (!request.params[0].isNull()) {
766 0 : keep_confirmed = request.params[0].get_bool();
767 0 : }
768 :
769 0 : const size_t WALLET_SIZE{pwallet->mapWallet.size()};
770 0 : const size_t STEPS{20};
771 0 : const size_t BATCH_SIZE = std::max(WALLET_SIZE / STEPS, size_t(1000));
772 :
773 0 : pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 0);
774 :
775 0 : for (size_t progress = 0; progress < STEPS; ++progress) {
776 0 : std::vector<uint256> vHashIn;
777 0 : std::vector<uint256> vHashOut;
778 0 : size_t count{0};
779 :
780 0 : for (auto& [txid, wtx] : pwallet->mapWallet) {
781 0 : if (progress < STEPS - 1 && ++count > BATCH_SIZE) break;
782 0 : if (keep_confirmed && wtx.isConfirmed()) continue;
783 0 : vHashIn.push_back(txid);
784 : }
785 :
786 0 : 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 0 : CHECK_NONFATAL(vHashOut.size() == vHashIn.size());
792 :
793 0 : 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 0 : pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), std::max(1, std::min(99, int(progress * 100 / STEPS))));
799 0 : }
800 :
801 0 : pwallet->ShowProgress(strprintf("%s " + _("Wiping wallet transactions…").translated, pwallet->GetDisplayName()), 100);
802 :
803 0 : return UniValue::VNULL;
804 0 : },
805 : };
806 0 : }
807 :
808 8 : static RPCHelpMan sethdseed()
809 : {
810 16 : 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 8 : "\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 24 : {
816 8 : {"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 8 : {"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 8 : RPCResult{RPCResult::Type::NONE, "", ""},
823 8 : RPCExamples{
824 8 : HelpExampleCli("sethdseed", "")
825 8 : + HelpExampleCli("sethdseed", "false")
826 8 : + HelpExampleCli("sethdseed", "true \"wifkey\"")
827 8 : + HelpExampleRpc("sethdseed", "true, \"wifkey\"")
828 : },
829 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
830 : {
831 0 : std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
832 0 : if (!pwallet) return UniValue::VNULL;
833 :
834 0 : LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*pwallet, true);
835 :
836 0 : 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 0 : LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
841 :
842 : // Do not do anything to non-HD wallets
843 0 : 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 0 : if (pwallet->IsHDEnabled()) {
847 0 : throw JSONRPCError(RPC_WALLET_ERROR, "Cannot set a HD seed. The wallet already has a seed");
848 : }
849 :
850 0 : EnsureWalletIsUnlocked(*pwallet);
851 :
852 0 : bool flush_key_pool = true;
853 0 : if (!request.params[0].isNull()) {
854 0 : flush_key_pool = request.params[0].get_bool();
855 0 : }
856 :
857 0 : if (request.params[1].isNull()) {
858 0 : spk_man.GenerateNewHDChain("", "");
859 0 : } else {
860 0 : CKey key = DecodeSecret(request.params[1].get_str());
861 0 : if (!key.IsValid()) {
862 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key");
863 : }
864 0 : if (HaveKey(spk_man, key)) {
865 0 : throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or as a loose private key)");
866 : }
867 0 : CHDChain newHdChain;
868 0 : 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 0 : 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 0 : newHdChain.AddAccount();
876 0 : }
877 :
878 0 : if (flush_key_pool) spk_man.NewKeyPool();
879 :
880 0 : return UniValue::VNULL;
881 0 : },
882 : };
883 0 : }
884 :
885 8 : static RPCHelpMan upgradewallet()
886 : {
887 16 : return RPCHelpMan{"upgradewallet",
888 8 : "\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 16 : {
892 8 : {"version", RPCArg::Type::NUM, RPCArg::Default{int{FEATURE_LATEST}}, "The version number to upgrade to. Default is the latest wallet version."}
893 : },
894 8 : RPCResult{
895 8 : RPCResult::Type::OBJ, "", "",
896 48 : {
897 8 : {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
898 8 : {RPCResult::Type::NUM, "previous_version", "Version of wallet before this operation"},
899 8 : {RPCResult::Type::NUM, "current_version", "Version of wallet after this operation"},
900 8 : {RPCResult::Type::STR, "result", /*optional=*/true, "Description of result, if no error"},
901 8 : {RPCResult::Type::STR, "error", /*optional=*/true, "Error message (if there is one)"}
902 : },
903 : },
904 8 : RPCExamples{
905 8 : HelpExampleCli("upgradewallet", "120200")
906 8 : + HelpExampleRpc("upgradewallet", "120200")
907 : },
908 8 : [&](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 8 : RPCHelpMan simulaterawtransaction()
951 : {
952 16 : return RPCHelpMan{"simulaterawtransaction",
953 8 : "\nCalculate the balance change resulting in the signing and broadcasting of the given transaction(s).\n",
954 24 : {
955 16 : {"rawtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "An array of hex strings of raw transactions.\n",
956 16 : {
957 8 : {"rawtx", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, ""},
958 : },
959 : },
960 16 : {"options", RPCArg::Type::OBJ_USER_KEYS, RPCArg::Optional::OMITTED_NAMED_ARG, "Options",
961 16 : {
962 8 : {"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 8 : RPCResult{
967 8 : RPCResult::Type::OBJ, "", "",
968 16 : {
969 8 : {RPCResult::Type::STR_AMOUNT, "balance_change", "The wallet balance change (negative means decrease)."},
970 : }
971 : },
972 8 : RPCExamples{
973 8 : HelpExampleCli("simulaterawtransaction", "[\"myhex\"]")
974 8 : + HelpExampleRpc("simulaterawtransaction", "[\"myhex\"]")
975 : },
976 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
977 : {
978 0 : const std::shared_ptr<const CWallet> rpc_wallet = GetWalletForJSONRPCRequest(request);
979 0 : if (!rpc_wallet) return UniValue::VNULL;
980 0 : const CWallet& wallet = *rpc_wallet;
981 :
982 0 : RPCTypeCheck(request.params, {UniValue::VARR, UniValue::VOBJ}, true);
983 :
984 0 : LOCK(wallet.cs_wallet);
985 :
986 0 : UniValue include_watchonly(UniValue::VNULL);
987 0 : 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 0 : isminefilter filter = ISMINE_SPENDABLE;
999 0 : if (ParseIncludeWatchonly(include_watchonly, wallet)) {
1000 0 : filter |= ISMINE_WATCH_ONLY;
1001 0 : }
1002 :
1003 0 : const auto& txs = request.params[0].get_array();
1004 0 : CAmount changes{0};
1005 0 : std::map<COutPoint, CAmount> new_utxos; // UTXO:s that were made available in transaction array
1006 0 : std::set<COutPoint> spent;
1007 :
1008 0 : for (size_t i = 0; i < txs.size(); ++i) {
1009 0 : CMutableTransaction mtx;
1010 0 : 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 0 : std::map<COutPoint, Coin> coins;
1016 0 : for (const CTxIn& txin : mtx.vin) {
1017 0 : coins[txin.prevout]; // Create empty map entry keyed by prevout.
1018 : }
1019 0 : 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 0 : for (const auto& txin : mtx.vin) {
1024 0 : const auto& outpoint = txin.prevout;
1025 0 : if (spent.count(outpoint)) {
1026 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Transaction(s) are spending the same output more than once");
1027 : }
1028 0 : if (new_utxos.count(outpoint)) {
1029 0 : changes -= new_utxos.at(outpoint);
1030 0 : new_utxos.erase(outpoint);
1031 0 : } else {
1032 0 : if (coins.at(outpoint).IsSpent()) {
1033 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "One or more transaction inputs are missing or have been spent already");
1034 : }
1035 0 : changes -= wallet.GetDebit(txin, filter);
1036 : }
1037 0 : 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 0 : const auto& hash = mtx.GetHash();
1046 0 : for (size_t i = 0; i < mtx.vout.size(); ++i) {
1047 0 : const auto& txout = mtx.vout[i];
1048 0 : bool is_mine = 0 < (wallet.IsMine(txout) & filter);
1049 0 : changes += new_utxos[COutPoint(hash, i)] = is_mine ? txout.nValue : 0;
1050 0 : }
1051 0 : }
1052 :
1053 0 : UniValue result(UniValue::VOBJ);
1054 0 : result.pushKV("balance_change", ValueFromAmount(changes));
1055 :
1056 0 : return result;
1057 0 : }
1058 : };
1059 0 : }
1060 :
1061 8 : static RPCHelpMan migratewallet()
1062 : {
1063 16 : return RPCHelpMan{"migratewallet",
1064 8 : "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 24 : {
1072 8 : {"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 8 : {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "The wallet passphrase"},
1074 : },
1075 8 : RPCResult{
1076 8 : RPCResult::Type::OBJ, "", "",
1077 40 : {
1078 8 : {RPCResult::Type::STR, "wallet_name", "The name of the primary migrated wallet"},
1079 8 : {RPCResult::Type::STR, "watchonly_name", /*optional=*/true, "The name of the migrated wallet containing the watchonly scripts"},
1080 8 : {RPCResult::Type::STR, "solvables_name", /*optional=*/true, "The name of the migrated wallet containing solvable but not watched scripts"},
1081 8 : {RPCResult::Type::STR, "backup_path", "The location of the backup of the original wallet"},
1082 : }
1083 : },
1084 8 : RPCExamples{
1085 8 : HelpExampleCli("migratewallet", "")
1086 8 : + HelpExampleRpc("migratewallet", "")
1087 : },
1088 8 : [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1089 : {
1090 0 : std::string wallet_name;
1091 0 : if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) {
1092 0 : if (!(request.params[0].isNull() || request.params[0].get_str() == wallet_name)) {
1093 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "RPC endpoint wallet and wallet_name parameter specify different wallets");
1094 : }
1095 0 : } else {
1096 0 : if (request.params[0].isNull()) {
1097 0 : throw JSONRPCError(RPC_INVALID_PARAMETER, "Either RPC endpoint wallet or wallet_name parameter must be provided");
1098 : }
1099 0 : wallet_name = request.params[0].get_str();
1100 : }
1101 :
1102 0 : SecureString wallet_pass;
1103 0 : wallet_pass.reserve(100);
1104 0 : if (!request.params[1].isNull()) {
1105 0 : wallet_pass = std::string_view{request.params[1].get_str()};
1106 0 : }
1107 :
1108 0 : WalletContext& context = EnsureWalletContext(request.context);
1109 0 : util::Result<MigrationResult> res = MigrateLegacyToDescriptor(wallet_name, wallet_pass, context);
1110 0 : if (!res) {
1111 0 : throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
1112 : }
1113 :
1114 0 : UniValue r{UniValue::VOBJ};
1115 0 : r.pushKV("wallet_name", res->wallet_name);
1116 0 : if (res->watchonly_wallet) {
1117 0 : r.pushKV("watchonly_name", res->watchonly_wallet->GetName());
1118 0 : }
1119 0 : if (res->solvables_wallet) {
1120 0 : r.pushKV("solvables_name", res->solvables_wallet->GetName());
1121 0 : }
1122 0 : r.pushKV("backup_path", fs::PathToString(res->backup_path));
1123 :
1124 0 : return r;
1125 0 : },
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 14 : Span<const CRPCCommand> GetWalletRPCCommands()
1203 : {
1204 302 : static const CRPCCommand commands[]{
1205 4 : {"rawtransactions", &fundrawtransaction},
1206 4 : {"wallet", &abandontransaction},
1207 4 : {"wallet", &abortrescan},
1208 4 : {"wallet", &addmultisigaddress},
1209 4 : {"wallet", &backupwallet},
1210 4 : {"wallet", &createwallet},
1211 4 : {"wallet", &restorewallet},
1212 4 : {"wallet", &dumphdinfo},
1213 4 : {"wallet", &dumpprivkey},
1214 4 : {"wallet", &dumpwallet},
1215 4 : {"wallet", &encryptwallet},
1216 4 : {"wallet", &getaddressesbylabel},
1217 4 : {"wallet", &getaddressinfo},
1218 4 : {"wallet", &getbalance},
1219 4 : {"wallet", &getnewaddress},
1220 4 : {"wallet", &getrawchangeaddress},
1221 4 : {"wallet", &getreceivedbyaddress},
1222 4 : {"wallet", &getreceivedbylabel},
1223 4 : {"wallet", &gettransaction},
1224 4 : {"wallet", &getunconfirmedbalance},
1225 4 : {"wallet", &getbalances},
1226 4 : {"wallet", &getwalletinfo},
1227 4 : {"wallet", &importaddress},
1228 4 : {"wallet", &importelectrumwallet},
1229 4 : {"wallet", &importdescriptors},
1230 4 : {"wallet", &importmulti},
1231 4 : {"wallet", &importprivkey},
1232 4 : {"wallet", &importprunedfunds},
1233 4 : {"wallet", &importpubkey},
1234 4 : {"wallet", &importwallet},
1235 4 : {"wallet", &keypoolrefill},
1236 4 : {"wallet", &listaddressbalances},
1237 4 : {"wallet", &listaddressgroupings},
1238 4 : {"wallet", &listdescriptors},
1239 4 : {"wallet", &listlabels},
1240 4 : {"wallet", &listlockunspent},
1241 4 : {"wallet", &listreceivedbyaddress},
1242 4 : {"wallet", &listreceivedbylabel},
1243 4 : {"wallet", &listsinceblock},
1244 4 : {"wallet", &listtransactions},
1245 4 : {"wallet", &listunspent},
1246 4 : {"wallet", &listwalletdir},
1247 4 : {"wallet", &listwallets},
1248 4 : {"wallet", &loadwallet},
1249 4 : {"wallet", &lockunspent},
1250 4 : {"wallet", &migratewallet},
1251 4 : {"wallet", &newkeypool},
1252 4 : {"wallet", &removeprunedfunds},
1253 4 : {"wallet", &rescanblockchain},
1254 4 : {"wallet", &send},
1255 4 : {"wallet", &sendmany},
1256 4 : {"wallet", &sendtoaddress},
1257 4 : {"wallet", &sethdseed},
1258 4 : {"wallet", &setcoinjoinrounds},
1259 4 : {"wallet", &setcoinjoinamount},
1260 4 : {"wallet", &setlabel},
1261 4 : {"wallet", &settxfee},
1262 4 : {"wallet", &setwalletflag},
1263 4 : {"wallet", &signmessage},
1264 4 : {"wallet", &signrawtransactionwithwallet},
1265 4 : {"wallet", &simulaterawtransaction},
1266 4 : {"wallet", &sendall},
1267 4 : {"wallet", &unloadwallet},
1268 4 : {"wallet", &upgradewallet},
1269 4 : {"wallet", &upgradetohd},
1270 : #ifdef ENABLE_EXTERNAL_SIGNER
1271 4 : {"wallet", &walletdisplayaddress},
1272 : #endif // ENABLE_EXTERNAL_SIGNER
1273 4 : {"wallet", &walletlock},
1274 4 : {"wallet", &walletpassphrasechange},
1275 4 : {"wallet", &walletpassphrase},
1276 4 : {"wallet", &walletprocesspsbt},
1277 4 : {"wallet", &walletcreatefundedpsbt},
1278 4 : {"wallet", &wipewallettxes},
1279 : };
1280 14 : return commands;
1281 0 : }
1282 : } // namespace wallet
|