1 // Copyright (c) 2009-present The Limenka developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 5 #include <limenka-build-config.h> // IWYU pragma: keep
6 7 #include <chain.h>
8 #include <clientversion.h>
9 #include <codex32.h>
10 #include <core_io.h>
11 #include <hash.h>
12 #include <interfaces/chain.h>
13 #include <key_io.h>
14 #include <merkleblock.h>
15 #include <rpc/util.h>
16 #include <script/descriptor.h>
17 #include <script/script.h>
18 #include <script/solver.h>
19 #include <sync.h>
20 #include <uint256.h>
21 #include <util/bip32.h>
22 #include <util/fs.h>
23 #include <util/time.h>
24 #include <util/translation.h>
25 #include <wallet/rpc/util.h>
26 #include <wallet/wallet.h>
27 28 #include <cstdint>
29 #include <fstream>
30 #include <tuple>
31 #include <string>
32 33 #include <univalue.h>
34 35 36 37 using interfaces::FoundBlock;
38 using util::SplitString;
39 40 namespace wallet {
41 std::string static EncodeDumpString(const std::string &str) {
42 std::stringstream ret;
43 for (const unsigned char c : str) {
44 if (c <= 32 || c >= 128 || c == '%') {
45 ret << '%' << HexStr({&c, 1});
46 } else {
47 ret << c;
48 }
49 }
50 return ret.str();
51 }
52 53 static std::string DecodeDumpString(const std::string &str) {
54 std::stringstream ret;
55 for (unsigned int pos = 0; pos < str.length(); pos++) {
56 unsigned char c = str[pos];
57 if (c == '%' && pos+2 < str.length()) {
58 c = (((str[pos+1]>>6)*9+((str[pos+1]-'0')&15)) << 4) |
59 ((str[pos+2]>>6)*9+((str[pos+2]-'0')&15));
60 pos += 2;
61 }
62 ret << c;
63 }
64 return ret.str();
65 }
66 67 static bool GetWalletAddressesForKey(const LegacyScriptPubKeyMan* spk_man, const CWallet& wallet, const CKeyID& keyid, std::string& strAddr, std::string& strLabel) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
68 {
69 bool fLabelFound = false;
70 CKey key;
71 spk_man->GetKey(keyid, key);
72 for (const auto& dest : GetAllDestinationsForKey(key.GetPubKey())) {
73 const auto* address_book_entry = wallet.FindAddressBookEntry(dest);
74 if (address_book_entry) {
75 if (!strAddr.empty()) {
76 strAddr += ",";
77 }
78 strAddr += EncodeDestination(dest);
79 strLabel = EncodeDumpString(address_book_entry->GetLabel());
80 fLabelFound = true;
81 }
82 }
83 if (!fLabelFound) {
84 strAddr = EncodeDestination(GetDestinationForKey(key.GetPubKey(), wallet.m_default_address_type));
85 }
86 return fLabelFound;
87 }
88 89 static const int64_t TIMESTAMP_MIN = 0;
90 91 static void RescanWallet(CWallet& wallet, const WalletRescanReserver& reserver, int64_t time_begin = TIMESTAMP_MIN, bool update = true)
92 {
93 int64_t scanned_time = wallet.RescanFromTime(time_begin, reserver, update);
94 if (wallet.IsAbortingRescan()) {
95 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
96 } else if (scanned_time > time_begin) {
97 throw JSONRPCError(RPC_WALLET_ERROR, "Rescan was unable to fully rescan the blockchain. Some transactions may be missing.");
98 }
99 }
100 101 static void EnsureBlockDataFromTime(const CWallet& wallet, int64_t timestamp)
102 {
103 auto& chain{wallet.chain()};
104 if (!chain.havePruned()) {
105 return;
106 }
107 108 int height{0};
109 const bool found{chain.findFirstBlockWithTimeAndHeight(timestamp - TIMESTAMP_WINDOW, 0, FoundBlock().height(height))};
110 111 uint256 tip_hash{WITH_LOCK(wallet.cs_wallet, return wallet.GetLastBlockHash())};
112 if (found && !chain.hasBlocks(tip_hash, height)) {
113 throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Pruned blocks from height %d required to import keys. Use RPC call getblockchaininfo to determine your pruned height.", height));
114 }
115 }
116 117 RPCHelpMan importprivkey()
118 {
119 return RPCHelpMan{"importprivkey",
120 "\nAdds a private key (as returned by dumpprivkey) to your wallet. Requires a new wallet backup.\n"
121 "Hint: use importmulti to import more than one private key.\n"
122 "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
123 "may report that the imported key exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
124 "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
125 "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n"
126 "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
127 "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"combo(X)\" for descriptor wallets.\n",
128 {
129 {"privkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The private key (see dumpprivkey)"},
130 {"label", RPCArg::Type::STR, RPCArg::DefaultHint{"current label if address exists, otherwise \"\""}, "An optional label"},
131 {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
132 },
133 RPCResult{RPCResult::Type::NONE, "", ""},
134 RPCExamples{
135 "\nDump a private key\n"
136 + HelpExampleCli("dumpprivkey", "\"myaddress\"") +
137 "\nImport the private key with rescan\n"
138 + HelpExampleCli("importprivkey", "\"mykey\"") +
139 "\nImport using a label and without rescan\n"
140 + HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
141 "\nImport using default blank label and without rescan\n"
142 + HelpExampleCli("importprivkey", "\"mykey\" \"\" false") +
143 "\nAs a JSON-RPC call\n"
144 + HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false")
145 },
146 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
147 {
148 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
149 if (!pwallet) return UniValue::VNULL;
150 151 if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
152 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
153 }
154 155 EnsureLegacyScriptPubKeyMan(*pwallet, true);
156 157 WalletRescanReserver reserver(*pwallet);
158 bool fRescan = true;
159 {
160 LOCK(pwallet->cs_wallet);
161 162 EnsureWalletIsUnlocked(*pwallet);
163 164 std::string strSecret = request.params[0].get_str();
165 const std::string strLabel{LabelFromValue(request.params[1])};
166 167 // Whether to perform rescan after import
168 if (!request.params[2].isNull())
169 fRescan = request.params[2].get_bool();
170 171 if (fRescan && pwallet->chain().havePruned()) {
172 // Exit early and print an error.
173 // If a block is pruned after this check, we will import the key(s),
174 // but fail the rescan with a generic error.
175 throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
176 }
177 178 if (fRescan && !reserver.reserve()) {
179 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
180 }
181 182 CKey key = DecodeSecret(strSecret);
183 if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
184 185 CPubKey pubkey = key.GetPubKey();
186 CHECK_NONFATAL(key.VerifyPubKey(pubkey));
187 CKeyID vchAddress = pubkey.GetID();
188 {
189 pwallet->MarkDirty();
190 191 // We don't know which corresponding address will be used;
192 // label all new addresses, and label existing addresses if a
193 // label was passed.
194 for (const auto& dest : GetAllDestinationsForKey(pubkey)) {
195 if (!request.params[1].isNull() || !pwallet->FindAddressBookEntry(dest)) {
196 pwallet->SetAddressBook(dest, strLabel, AddressPurpose::RECEIVE);
197 }
198 }
199 200 // Use timestamp of 1 to scan the whole chain
201 if (!pwallet->ImportPrivKeys({{vchAddress, key}}, 1)) {
202 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
203 }
204 205 // Add the wpkh script for this key if possible
206 if (pubkey.IsCompressed()) {
207 pwallet->ImportScripts({GetScriptForDestination(WitnessV0KeyHash(vchAddress))}, /*timestamp=*/0);
208 }
209 }
210 }
211 if (fRescan) {
212 RescanWallet(*pwallet, reserver);
213 }
214 215 return UniValue::VNULL;
216 },
217 };
218 }
219 220 UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp, const std::vector<CExtKey>& master_keys = {}) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
221 222 RPCHelpMan importaddress()
223 {
224 return RPCHelpMan{"importaddress",
225 "\nAdds an address or script (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
226 "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
227 "may report that the imported address exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
228 "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
229 "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n"
230 "If you have the full public key, you should call importpubkey instead of this.\n"
231 "Hint: use importmulti to import more than one address.\n"
232 "\nNote: If you import a non-standard raw script in hex form, outputs sending to it will be treated\n"
233 "as change, and not show up in many RPCs.\n"
234 "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
235 "Note: For descriptor wallets, this command will create new descriptor/s, and only works if the wallet has private keys disabled.\n",
236 {
237 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The Limenka address (or hex-encoded script)"},
238 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"},
239 {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
240 {"p2sh", RPCArg::Type::BOOL, RPCArg::Default{false}, "Add the P2SH version of the script as well"},
241 },
242 RPCResult{RPCResult::Type::NONE, "", ""},
243 RPCExamples{
244 "\nImport an address with rescan\n"
245 + HelpExampleCli("importaddress", "\"myaddress\"") +
246 "\nImport using a label without rescan\n"
247 + HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") +
248 "\nAs a JSON-RPC call\n"
249 + HelpExampleRpc("importaddress", "\"myaddress\", \"testing\", false")
250 },
251 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
252 {
253 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
254 if (!pwallet) return UniValue::VNULL;
255 256 // Use legacy spkm only if the wallet does not support descriptors.
257 bool use_legacy = !pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS);
258 if (use_legacy) {
259 // In case the wallet is blank
260 EnsureLegacyScriptPubKeyMan(*pwallet, true);
261 } else {
262 // We don't allow mixing watch-only descriptors with spendable ones.
263 if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
264 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import address in wallet with private keys enabled. "
265 "Create wallet with no private keys to watch specific addresses/scripts");
266 }
267 }
268 269 const std::string strLabel{LabelFromValue(request.params[1])};
270 271 // Whether to perform rescan after import
272 bool fRescan = true;
273 if (!request.params[2].isNull())
274 fRescan = request.params[2].get_bool();
275 276 if (fRescan && pwallet->chain().havePruned()) {
277 // Exit early and print an error.
278 // If a block is pruned after this check, we will import the key(s),
279 // but fail the rescan with a generic error.
280 throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
281 }
282 283 WalletRescanReserver reserver(*pwallet);
284 if (fRescan && !reserver.reserve()) {
285 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
286 }
287 288 // Whether to import a p2sh version, too
289 bool fP2SH = false;
290 if (!request.params[3].isNull())
291 fP2SH = request.params[3].get_bool();
292 293 // Import descriptor helper function
294 const auto& import_descriptor = [pwallet](const std::string& desc, const std::string label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
295 UniValue data(UniValue::VType::VOBJ);
296 data.pushKV("desc", AddChecksum(desc));
297 if (!label.empty()) data.pushKV("label", label);
298 const UniValue& ret = ProcessDescriptorImport(*pwallet, data, /*timestamp=*/1);
299 if (ret.exists("error")) throw ret["error"];
300 };
301 302 {
303 LOCK(pwallet->cs_wallet);
304 305 const std::string& address = request.params[0].get_str();
306 CTxDestination dest = DecodeDestination(address);
307 if (IsValidDestination(dest)) {
308 if (fP2SH) {
309 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot use the p2sh flag with an address - use a script instead");
310 }
311 if (OutputTypeFromDestination(dest) == OutputType::BECH32M) {
312 if (use_legacy)
313 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets");
314 }
315 316 pwallet->MarkDirty();
317 318 if (use_legacy) {
319 pwallet->ImportScriptPubKeys(strLabel, {GetScriptForDestination(dest)}, /*have_solving_data=*/false, /*apply_label=*/true, /*timestamp=*/1);
320 } else {
321 import_descriptor("addr(" + address + ")", strLabel);
322 }
323 } else if (IsHex(request.params[0].get_str())) {
324 const std::string& hex = request.params[0].get_str();
325 326 if (use_legacy) {
327 std::vector<unsigned char> data(ParseHex(hex));
328 CScript redeem_script(data.begin(), data.end());
329 330 std::set<CScript> scripts = {redeem_script};
331 pwallet->ImportScripts(scripts, /*timestamp=*/0);
332 333 if (fP2SH) {
334 scripts.insert(GetScriptForDestination(ScriptHash(redeem_script)));
335 }
336 337 pwallet->ImportScriptPubKeys(strLabel, scripts, /*have_solving_data=*/false, /*apply_label=*/true, /*timestamp=*/1);
338 } else {
339 // P2SH Not allowed. Can't detect inner P2SH function from a raw hex.
340 if (fP2SH) throw JSONRPCError(RPC_WALLET_ERROR, "P2SH import feature disabled for descriptors' wallet. "
341 "Use 'importdescriptors' to specify inner P2SH function");
342 343 // Import descriptors
344 import_descriptor("raw(" + hex + ")", strLabel);
345 }
346 } else {
347 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Limenka address or script");
348 }
349 }
350 if (fRescan)
351 {
352 RescanWallet(*pwallet, reserver);
353 pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
354 }
355 356 return UniValue::VNULL;
357 },
358 };
359 }
360 361 RPCHelpMan importprunedfunds()
362 {
363 return RPCHelpMan{"importprunedfunds",
364 "\nImports funds without rescan. Corresponding address or script must previously be included in wallet. Aimed towards pruned wallets. The end-user is responsible to import additional transactions that subsequently spend the imported outputs or rescan after the point in the blockchain the transaction is included.\n",
365 {
366 {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
367 {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
368 },
369 RPCResult{RPCResult::Type::NONE, "", ""},
370 RPCExamples{""},
371 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
372 {
373 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
374 if (!pwallet) return UniValue::VNULL;
375 376 CMutableTransaction tx;
377 if (!DecodeHexTx(tx, request.params[0].get_str())) {
378 throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
379 }
380 uint256 hashTx = tx.GetHash();
381 382 DataStream ssMB{ParseHexV(request.params[1], "proof")};
383 CMerkleBlock merkleBlock;
384 ssMB >> merkleBlock;
385 386 //Search partial merkle tree in proof for our transaction and index in valid block
387 std::vector<uint256> vMatch;
388 std::vector<unsigned int> vIndex;
389 if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) {
390 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
391 }
392 393 LOCK(pwallet->cs_wallet);
394 int height;
395 if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) {
396 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
397 }
398 399 std::vector<uint256>::const_iterator it;
400 if ((it = std::find(vMatch.begin(), vMatch.end(), hashTx)) == vMatch.end()) {
401 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
402 }
403 404 unsigned int txnIndex = vIndex[it - vMatch.begin()];
405 406 CTransactionRef tx_ref = MakeTransactionRef(tx);
407 if (pwallet->IsMine(*tx_ref)) {
408 pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)});
409 return UniValue::VNULL;
410 }
411 412 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
413 },
414 };
415 }
416 417 RPCHelpMan removeprunedfunds()
418 {
419 return RPCHelpMan{"removeprunedfunds",
420 "\nDeletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n",
421 {
422 {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
423 },
424 RPCResult{RPCResult::Type::NONE, "", ""},
425 RPCExamples{
426 HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
427 "\nAs a JSON-RPC call\n"
428 + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
429 },
430 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
431 {
432 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
433 if (!pwallet) return UniValue::VNULL;
434 435 LOCK(pwallet->cs_wallet);
436 437 uint256 hash(ParseHashV(request.params[0], "txid"));
438 std::vector<uint256> vHash;
439 vHash.push_back(hash);
440 if (auto res = pwallet->RemoveTxs(vHash); !res) {
441 throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
442 }
443 444 return UniValue::VNULL;
445 },
446 };
447 }
448 449 RPCHelpMan importpubkey()
450 {
451 return RPCHelpMan{"importpubkey",
452 "\nAdds a public key (in hex) that can be watched as if it were in your wallet but cannot be used to spend. Requires a new wallet backup.\n"
453 "Hint: use importmulti to import more than one public key.\n"
454 "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
455 "may report that the imported pubkey exists but related transactions are still missing, leading to temporarily incorrect/bogus balances and unspent outputs until rescan completes.\n"
456 "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
457 "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n"
458 "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
459 "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" with \"combo(X)\" for descriptor wallets.\n",
460 {
461 {"pubkey", RPCArg::Type::STR, RPCArg::Optional::NO, "The hex-encoded public key"},
462 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "An optional label"},
463 {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions."},
464 },
465 RPCResult{RPCResult::Type::NONE, "", ""},
466 RPCExamples{
467 "\nImport a public key with rescan\n"
468 + HelpExampleCli("importpubkey", "\"mypubkey\"") +
469 "\nImport using a label without rescan\n"
470 + HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") +
471 "\nAs a JSON-RPC call\n"
472 + HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false")
473 },
474 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
475 {
476 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
477 if (!pwallet) return UniValue::VNULL;
478 479 EnsureLegacyScriptPubKeyMan(*pwallet, true);
480 481 const std::string strLabel{LabelFromValue(request.params[1])};
482 483 // Whether to perform rescan after import
484 bool fRescan = true;
485 if (!request.params[2].isNull())
486 fRescan = request.params[2].get_bool();
487 488 if (fRescan && pwallet->chain().havePruned()) {
489 // Exit early and print an error.
490 // If a block is pruned after this check, we will import the key(s),
491 // but fail the rescan with a generic error.
492 throw JSONRPCError(RPC_WALLET_ERROR, "Rescan is disabled when blocks are pruned");
493 }
494 495 WalletRescanReserver reserver(*pwallet);
496 if (fRescan && !reserver.reserve()) {
497 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
498 }
499 500 CPubKey pubKey = HexToPubKey(request.params[0].get_str());
501 502 {
503 LOCK(pwallet->cs_wallet);
504 505 std::set<CScript> script_pub_keys;
506 for (const auto& dest : GetAllDestinationsForKey(pubKey)) {
507 script_pub_keys.insert(GetScriptForDestination(dest));
508 }
509 510 pwallet->MarkDirty();
511 512 pwallet->ImportScriptPubKeys(strLabel, script_pub_keys, /*have_solving_data=*/true, /*apply_label=*/true, /*timestamp=*/1);
513 514 pwallet->ImportPubKeys({{pubKey.GetID(), false}}, {{pubKey.GetID(), pubKey}} , /*key_origins=*/{}, /*add_keypool=*/false, /*timestamp=*/1);
515 }
516 if (fRescan)
517 {
518 RescanWallet(*pwallet, reserver);
519 pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
520 }
521 522 return UniValue::VNULL;
523 },
524 };
525 }
526 527 528 RPCHelpMan importwallet()
529 {
530 return RPCHelpMan{"importwallet",
531 "\nImports keys from a wallet dump file (see dumpwallet). Requires a new wallet backup to include imported keys.\n"
532 "Note: Blockchain and Mempool will be rescanned after a successful import. Use \"getwalletinfo\" to query the scanning progress.\n"
533 "Note: This command is only compatible with legacy wallets.\n",
534 {
535 {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet file"},
536 },
537 RPCResult{RPCResult::Type::NONE, "", ""},
538 RPCExamples{
539 "\nDump the wallet\n"
540 + HelpExampleCli("dumpwallet", "\"test\"") +
541 "\nImport the wallet\n"
542 + HelpExampleCli("importwallet", "\"test\"") +
543 "\nImport using the json rpc call\n"
544 + HelpExampleRpc("importwallet", "\"test\"")
545 },
546 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
547 {
548 EnsureNotWalletRestricted(request);
549 550 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
551 if (!pwallet) return UniValue::VNULL;
552 553 EnsureLegacyScriptPubKeyMan(*pwallet, true);
554 555 WalletRescanReserver reserver(*pwallet);
556 if (!reserver.reserve()) {
557 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
558 }
559 560 int64_t nTimeBegin = 0;
561 bool fGood = true;
562 {
563 LOCK(pwallet->cs_wallet);
564 565 EnsureWalletIsUnlocked(*pwallet);
566 567 std::ifstream file;
568 file.open(fs::u8path(request.params[0].get_str()), std::ios::in | std::ios::ate);
569 if (!file.is_open()) {
570 throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
571 }
572 CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nTimeBegin)));
573 574 int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
575 file.seekg(0, file.beg);
576 577 // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because pwallet.ShowProgress has a cancel button tied to AbortRescan which
578 // we don't want for this progress bar showing the import progress. uiInterface.ShowProgress does not have a cancel button.
579 pwallet->chain().showProgress(strprintf("%s %s", pwallet->GetDisplayName(), _("Importing…")), 0, false); // show progress dialog in GUI
580 std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys;
581 std::vector<std::pair<CScript, int64_t>> scripts;
582 while (file.good()) {
583 pwallet->chain().showProgress("", std::max(1, std::min(50, (int)(((double)file.tellg() / (double)nFilesize) * 100))), false);
584 std::string line;
585 std::getline(file, line);
586 if (line.empty() || line[0] == '#')
587 continue;
588 589 std::vector<std::string> vstr = SplitString(line, ' ');
590 if (vstr.size() < 2)
591 continue;
592 CKey key = DecodeSecret(vstr[0]);
593 if (key.IsValid()) {
594 int64_t nTime{ParseISO8601DateTime(vstr[1]).value_or(0)};
595 std::string strLabel;
596 bool fLabel = true;
597 for (unsigned int nStr = 2; nStr < vstr.size(); nStr++) {
598 if (vstr[nStr].front() == '#')
599 break;
600 if (vstr[nStr] == "change=1")
601 fLabel = false;
602 if (vstr[nStr] == "reserve=1")
603 fLabel = false;
604 if (vstr[nStr].substr(0,6) == "label=") {
605 strLabel = DecodeDumpString(vstr[nStr].substr(6));
606 fLabel = true;
607 }
608 }
609 nTimeBegin = std::min(nTimeBegin, nTime);
610 keys.emplace_back(key, nTime, fLabel, strLabel);
611 } else if(IsHex(vstr[0])) {
612 std::vector<unsigned char> vData(ParseHex(vstr[0]));
613 CScript script = CScript(vData.begin(), vData.end());
614 int64_t birth_time{ParseISO8601DateTime(vstr[1]).value_or(0)};
615 if (birth_time > 0) nTimeBegin = std::min(nTimeBegin, birth_time);
616 scripts.emplace_back(script, birth_time);
617 }
618 }
619 file.close();
620 EnsureBlockDataFromTime(*pwallet, nTimeBegin);
621 // We now know whether we are importing private keys, so we can error if private keys are disabled
622 if (keys.size() > 0 && pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
623 pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI
624 throw JSONRPCError(RPC_WALLET_ERROR, "Importing wallets is disabled when private keys are disabled");
625 }
626 double total = (double)(keys.size() + scripts.size());
627 double progress = 0;
628 for (const auto& key_tuple : keys) {
629 pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false);
630 const CKey& key = std::get<0>(key_tuple);
631 int64_t time = std::get<1>(key_tuple);
632 bool has_label = std::get<2>(key_tuple);
633 std::string label = std::get<3>(key_tuple);
634 635 CPubKey pubkey = key.GetPubKey();
636 CHECK_NONFATAL(key.VerifyPubKey(pubkey));
637 CKeyID keyid = pubkey.GetID();
638 639 pwallet->WalletLogPrintf("Importing %s...\n", EncodeDestination(PKHash(keyid)));
640 641 if (!pwallet->ImportPrivKeys({{keyid, key}}, time)) {
642 pwallet->WalletLogPrintf("Error importing key for %s\n", EncodeDestination(PKHash(keyid)));
643 fGood = false;
644 continue;
645 }
646 647 if (has_label)
648 pwallet->SetAddressBook(PKHash(keyid), label, AddressPurpose::RECEIVE);
649 progress++;
650 }
651 for (const auto& script_pair : scripts) {
652 pwallet->chain().showProgress("", std::max(50, std::min(75, (int)((progress / total) * 100) + 50)), false);
653 const CScript& script = script_pair.first;
654 int64_t time = script_pair.second;
655 656 if (!pwallet->ImportScripts({script}, time)) {
657 pwallet->WalletLogPrintf("Error importing script %s\n", HexStr(script));
658 fGood = false;
659 continue;
660 }
661 662 progress++;
663 }
664 pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI
665 }
666 pwallet->chain().showProgress("", 100, false); // hide progress dialog in GUI
667 RescanWallet(*pwallet, reserver, nTimeBegin, /*update=*/false);
668 pwallet->MarkDirty();
669 670 if (!fGood)
671 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding some keys/scripts to wallet");
672 673 return UniValue::VNULL;
674 },
675 };
676 }
677 678 RPCHelpMan dumpprivkey()
679 {
680 return RPCHelpMan{"dumpprivkey",
681 "\nReveals the private key corresponding to 'address'.\n"
682 "Then the importprivkey can be used with this output\n"
683 "Note: This command is only compatible with legacy wallets.\n",
684 {
685 {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The limenka address for the private key"},
686 },
687 RPCResult{
688 RPCResult::Type::STR, "key", "The private key"
689 },
690 RPCExamples{
691 HelpExampleCli("dumpprivkey", "\"myaddress\"")
692 + HelpExampleCli("importprivkey", "\"mykey\"")
693 + HelpExampleRpc("dumpprivkey", "\"myaddress\"")
694 },
695 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
696 {
697 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
698 if (!pwallet) return UniValue::VNULL;
699 700 const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(*pwallet);
701 702 LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
703 704 EnsureWalletIsUnlocked(*pwallet);
705 706 std::string strAddress = request.params[0].get_str();
707 CTxDestination dest = DecodeDestination(strAddress);
708 if (!IsValidDestination(dest)) {
709 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Limenka address");
710 }
711 auto keyid = GetKeyForDestination(spk_man, dest);
712 if (keyid.IsNull()) {
713 throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to a key");
714 }
715 CKey vchSecret;
716 if (!spk_man.GetKey(keyid, vchSecret)) {
717 throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known");
718 }
719 return EncodeSecret(vchSecret);
720 },
721 };
722 }
723 724 RPCHelpMan dumpmasterprivkey()
725 {
726 return RPCHelpMan{"dumpmasterprivkey",
727 "Reveals the current master private key.\n",
728 {},
729 RPCResult{
730 RPCResult::Type::STR, "key", "The HD master private key"
731 },
732 RPCExamples{
733 HelpExampleCli("dumpmasterprivkey", "")
734 },
735 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
736 {
737 std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
738 if (!wallet) return NullUniValue;
739 const CWallet* const pwallet = wallet.get();
740 741 LegacyScriptPubKeyMan& spk_man = EnsureLegacyScriptPubKeyMan(*wallet);
742 743 LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
744 745 EnsureWalletIsUnlocked(*pwallet);
746 747 CKeyID seed_id = spk_man.GetHDChain().seed_id;
748 if (!spk_man.IsHDEnabled()) {
749 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is not a HD wallet.");
750 }
751 CKey seed;
752 if (spk_man.GetKey(seed_id, seed)) {
753 CExtKey masterKey;
754 masterKey.SetSeed(seed);
755 756 return EncodeExtKey(masterKey);
757 } else {
758 throw JSONRPCError(RPC_WALLET_ERROR, "Unable to retrieve HD master private key");
759 return NullUniValue;
760 }
761 },
762 };
763 }
764 765 766 RPCHelpMan dumpwallet()
767 {
768 return RPCHelpMan{"dumpwallet",
769 "\nDumps all wallet keys in a human-readable format to a server-side file. This does not allow overwriting existing files.\n"
770 "Imported scripts are included in the dumpfile, but corresponding BIP173 addresses, etc. may not be added automatically by importwallet.\n"
771 "Note that if your wallet contains keys which are not derived from your HD seed (e.g. imported keys), these are not covered by\n"
772 "only backing up the seed itself, and must be backed up too (e.g. ensure you back up the whole dumpfile).\n"
773 "Note: This command is only compatible with legacy wallets.\n",
774 {
775 {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The filename with path (absolute path recommended)"},
776 },
777 RPCResult{
778 RPCResult::Type::OBJ, "", "",
779 {
780 {RPCResult::Type::STR, "filename", "The filename with full absolute path"},
781 }
782 },
783 RPCExamples{
784 HelpExampleCli("dumpwallet", "\"test\"")
785 + HelpExampleRpc("dumpwallet", "\"test\"")
786 },
787 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
788 {
789 EnsureNotWalletRestricted(request);
790 791 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
792 if (!pwallet) return UniValue::VNULL;
793 794 const CWallet& wallet = *pwallet;
795 const LegacyScriptPubKeyMan& spk_man = EnsureConstLegacyScriptPubKeyMan(wallet);
796 797 // Make sure the results are valid at least up to the most recent block
798 // the user could have gotten from another RPC command prior to now
799 wallet.BlockUntilSyncedToCurrentChain();
800 801 LOCK(wallet.cs_wallet);
802 803 EnsureWalletIsUnlocked(wallet);
804 805 fs::path filepath = fs::u8path(request.params[0].get_str());
806 filepath = fs::absolute(filepath);
807 808 /* Prevent arbitrary files from being overwritten. There have been reports
809 * that users have overwritten wallet files this way:
810 * https://github.com/limenka/limenka/issues/9934
811 * It may also avoid other security issues.
812 */
813 if (fs::exists(filepath)) {
814 throw JSONRPCError(RPC_INVALID_PARAMETER, filepath.utf8string() + " already exists. If you are sure this is what you want, move it out of the way first");
815 }
816 817 std::ofstream file;
818 file.open(filepath);
819 if (!file.is_open())
820 throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file");
821 822 std::map<CKeyID, int64_t> mapKeyBirth;
823 wallet.GetKeyBirthTimes(mapKeyBirth);
824 825 int64_t block_time = 0;
826 CHECK_NONFATAL(wallet.chain().findBlock(wallet.GetLastBlockHash(), FoundBlock().time(block_time)));
827 828 // Note: To avoid a lock order issue, access to cs_main must be locked before cs_KeyStore.
829 // So we do the two things in this function that lock cs_main first: GetKeyBirthTimes, and findBlock.
830 LOCK(spk_man.cs_KeyStore);
831 832 const std::map<CKeyID, int64_t>& mapKeyPool = spk_man.GetAllReserveKeys();
833 std::set<CScriptID> scripts = spk_man.GetCScripts();
834 835 // sort time/key pairs
836 std::vector<std::pair<int64_t, CKeyID> > vKeyBirth;
837 vKeyBirth.reserve(mapKeyBirth.size());
838 for (const auto& entry : mapKeyBirth) {
839 vKeyBirth.emplace_back(entry.second, entry.first);
840 }
841 mapKeyBirth.clear();
842 std::sort(vKeyBirth.begin(), vKeyBirth.end());
843 844 // produce output
845 file << strprintf("# Wallet dump created by %s %s\n", CLIENT_NAME, FormatFullVersion());
846 file << strprintf("# * Created on %s\n", FormatISO8601DateTime(GetTime()));
847 file << strprintf("# * Best block at time of backup was %i (%s),\n", wallet.GetLastBlockHeight(), wallet.GetLastBlockHash().ToString());
848 file << strprintf("# mined on %s\n", FormatISO8601DateTime(block_time));
849 file << "\n";
850 851 // add the base58check encoded extended master if the wallet uses HD
852 CKeyID seed_id = spk_man.GetHDChain().seed_id;
853 if (!seed_id.IsNull())
854 {
855 CKey seed;
856 if (spk_man.GetKey(seed_id, seed)) {
857 CExtKey masterKey;
858 masterKey.SetSeed(seed);
859 860 file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n\n";
861 }
862 }
863 for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) {
864 const CKeyID &keyid = it->second;
865 std::string strTime = FormatISO8601DateTime(it->first);
866 std::string strAddr;
867 std::string strLabel;
868 CKey key;
869 if (spk_man.GetKey(keyid, key)) {
870 CKeyMetadata metadata;
871 const auto it{spk_man.mapKeyMetadata.find(keyid)};
872 if (it != spk_man.mapKeyMetadata.end()) metadata = it->second;
873 file << strprintf("%s %s ", EncodeSecret(key), strTime);
874 if (GetWalletAddressesForKey(&spk_man, wallet, keyid, strAddr, strLabel)) {
875 file << strprintf("label=%s", strLabel);
876 } else if (keyid == seed_id) {
877 file << "hdseed=1";
878 } else if (mapKeyPool.count(keyid)) {
879 file << "reserve=1";
880 } else if (metadata.hdKeypath == "s") {
881 file << "inactivehdseed=1";
882 } else {
883 file << "change=1";
884 }
885 if (metadata.has_key_origin) {
886 file << " hdkeypath=" + WriteHDKeypath(metadata.key_origin.path, /*apostrophe=*/true);
887 if (!(metadata.hd_seed_id.IsNull() || (metadata.hdKeypath == "s" && metadata.hd_seed_id == keyid))) {
888 file << " hdseedid=" + metadata.hd_seed_id.GetHex();
889 }
890 }
891 file << strprintf(" # addr=%s\n", strAddr);
892 }
893 }
894 file << "\n";
895 for (const CScriptID &scriptid : scripts) {
896 CScript script;
897 std::string create_time = "0";
898 std::string address = EncodeDestination(ScriptHash(scriptid));
899 // get birth times for scripts with metadata
900 auto it = spk_man.m_script_metadata.find(scriptid);
901 if (it != spk_man.m_script_metadata.end()) {
902 create_time = FormatISO8601DateTime(it->second.nCreateTime);
903 }
904 if(spk_man.GetCScript(scriptid, script)) {
905 file << strprintf("%s %s script=1", HexStr(script), create_time);
906 file << strprintf(" # addr=%s\n", address);
907 }
908 }
909 file << "\n";
910 file << "# End of dump\n";
911 file.close();
912 913 UniValue reply(UniValue::VOBJ);
914 reply.pushKV("filename", filepath.utf8string());
915 916 return reply;
917 },
918 };
919 }
920 921 struct ImportData
922 {
923 // Input data
924 std::unique_ptr<CScript> redeemscript; //!< Provided redeemScript; will be moved to `import_scripts` if relevant.
925 std::unique_ptr<CScript> witnessscript; //!< Provided witnessScript; will be moved to `import_scripts` if relevant.
926 927 // Output data
928 std::set<CScript> import_scripts;
929 std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability)
930 std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> key_origins;
931 };
932 933 enum class ScriptContext
934 {
935 TOP, //!< Top-level scriptPubKey
936 P2SH, //!< P2SH redeemScript
937 WITNESS_V0, //!< P2WSH witnessScript
938 };
939 940 // Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used.
941 // Returns an error string, or the empty string for success.
942 // NOLINTNEXTLINE(misc-no-recursion)
943 static std::string RecurseImportData(const CScript& script, ImportData& import_data, const ScriptContext script_ctx)
944 {
945 // Use Solver to obtain script type and parsed pubkeys or hashes:
946 std::vector<std::vector<unsigned char>> solverdata;
947 TxoutType script_type = Solver(script, solverdata);
948 949 switch (script_type) {
950 case TxoutType::PUBKEY: {
951 CPubKey pubkey(solverdata[0]);
952 import_data.used_keys.emplace(pubkey.GetID(), false);
953 return "";
954 }
955 case TxoutType::PUBKEYHASH: {
956 CKeyID id = CKeyID(uint160(solverdata[0]));
957 import_data.used_keys[id] = true;
958 return "";
959 }
960 case TxoutType::SCRIPTHASH: {
961 if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH");
962 if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside a P2WSH");
963 CHECK_NONFATAL(script_ctx == ScriptContext::TOP);
964 CScriptID id = CScriptID(uint160(solverdata[0]));
965 auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later.
966 if (!subscript) return "missing redeemscript";
967 if (CScriptID(*subscript) != id) return "redeemScript does not match the scriptPubKey";
968 import_data.import_scripts.emplace(*subscript);
969 return RecurseImportData(*subscript, import_data, ScriptContext::P2SH);
970 }
971 case TxoutType::MULTISIG: {
972 for (size_t i = 1; i + 1< solverdata.size(); ++i) {
973 CPubKey pubkey(solverdata[i]);
974 import_data.used_keys.emplace(pubkey.GetID(), false);
975 }
976 return "";
977 }
978 case TxoutType::WITNESS_V0_SCRIPTHASH: {
979 if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WSH inside another P2WSH");
980 CScriptID id{RIPEMD160(solverdata[0])};
981 auto subscript = std::move(import_data.witnessscript); // Remove redeemscript from import_data to check for superfluous script later.
982 if (!subscript) return "missing witnessscript";
983 if (CScriptID(*subscript) != id) return "witnessScript does not match the scriptPubKey or redeemScript";
984 if (script_ctx == ScriptContext::TOP) {
985 import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WSH requires the TOP script imported (see script/ismine.cpp)
986 }
987 import_data.import_scripts.emplace(*subscript);
988 return RecurseImportData(*subscript, import_data, ScriptContext::WITNESS_V0);
989 }
990 case TxoutType::WITNESS_V0_KEYHASH: {
991 if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WPKH inside P2WSH");
992 CKeyID id = CKeyID(uint160(solverdata[0]));
993 import_data.used_keys[id] = true;
994 if (script_ctx == ScriptContext::TOP) {
995 import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WPKH requires the TOP script imported (see script/ismine.cpp)
996 }
997 return "";
998 }
999 case TxoutType::NULL_DATA:
1000 return "unspendable script";
1001 case TxoutType::NONSTANDARD:
1002 case TxoutType::WITNESS_UNKNOWN:
1003 case TxoutType::WITNESS_V1_TAPROOT:
1004 case TxoutType::ANCHOR:
1005 return "unrecognized script";
1006 } // no default case, so the compiler can warn about missing cases
1007 NONFATAL_UNREACHABLE();
1008 }
1009 1010 static UniValue ProcessImportLegacy(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<std::pair<CKeyID, bool>>& ordered_pubkeys)
1011 {
1012 UniValue warnings(UniValue::VARR);
1013 1014 // First ensure scriptPubKey has either a script or JSON with "address" string
1015 const UniValue& scriptPubKey = data["scriptPubKey"];
1016 bool isScript = scriptPubKey.getType() == UniValue::VSTR;
1017 if (!isScript && !(scriptPubKey.getType() == UniValue::VOBJ && scriptPubKey.exists("address"))) {
1018 throw JSONRPCError(RPC_INVALID_PARAMETER, "scriptPubKey must be string with script or JSON with address string");
1019 }
1020 const std::string& output = isScript ? scriptPubKey.get_str() : scriptPubKey["address"].get_str();
1021 1022 // Optional fields.
1023 const std::string& strRedeemScript = data.exists("redeemscript") ? data["redeemscript"].get_str() : "";
1024 const std::string& witness_script_hex = data.exists("witnessscript") ? data["witnessscript"].get_str() : "";
1025 const UniValue& pubKeys = data.exists("pubkeys") ? data["pubkeys"].get_array() : UniValue();
1026 const UniValue& keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
1027 const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
1028 const bool watchOnly = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
1029 1030 if (data.exists("range")) {
1031 throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for a non-descriptor import");
1032 }
1033 1034 // Generate the script and destination for the scriptPubKey provided
1035 CScript script;
1036 if (!isScript) {
1037 CTxDestination dest = DecodeDestination(output);
1038 if (!IsValidDestination(dest)) {
1039 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\"");
1040 }
1041 if (OutputTypeFromDestination(dest) == OutputType::BECH32M) {
1042 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m addresses cannot be imported into legacy wallets");
1043 }
1044 script = GetScriptForDestination(dest);
1045 } else {
1046 if (!IsHex(output)) {
1047 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\"");
1048 }
1049 std::vector<unsigned char> vData(ParseHex(output));
1050 script = CScript(vData.begin(), vData.end());
1051 CTxDestination dest;
1052 if (!ExtractDestination(script, dest) && !internal) {
1053 throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal must be set to true for nonstandard scriptPubKey imports.");
1054 }
1055 }
1056 script_pub_keys.emplace(script);
1057 1058 // Parse all arguments
1059 if (strRedeemScript.size()) {
1060 if (!IsHex(strRedeemScript)) {
1061 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string");
1062 }
1063 auto parsed_redeemscript = ParseHex(strRedeemScript);
1064 import_data.redeemscript = std::make_unique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end());
1065 }
1066 if (witness_script_hex.size()) {
1067 if (!IsHex(witness_script_hex)) {
1068 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string");
1069 }
1070 auto parsed_witnessscript = ParseHex(witness_script_hex);
1071 import_data.witnessscript = std::make_unique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end());
1072 }
1073 for (size_t i = 0; i < pubKeys.size(); ++i) {
1074 CPubKey pubkey = HexToPubKey(pubKeys[i].get_str());
1075 pubkey_map.emplace(pubkey.GetID(), pubkey);
1076 ordered_pubkeys.emplace_back(pubkey.GetID(), internal);
1077 }
1078 for (size_t i = 0; i < keys.size(); ++i) {
1079 const auto& str = keys[i].get_str();
1080 CKey key = DecodeSecret(str);
1081 if (!key.IsValid()) {
1082 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
1083 }
1084 CPubKey pubkey = key.GetPubKey();
1085 CKeyID id = pubkey.GetID();
1086 if (pubkey_map.count(id)) {
1087 pubkey_map.erase(id);
1088 }
1089 privkey_map.emplace(id, key);
1090 }
1091 1092 1093 // Verify and process input data
1094 have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size();
1095 if (have_solving_data) {
1096 // Match up data in import_data with the scriptPubKey in script.
1097 auto error = RecurseImportData(script, import_data, ScriptContext::TOP);
1098 1099 // Verify whether the watchonly option corresponds to the availability of private keys.
1100 bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; });
1101 if (!watchOnly && !spendable) {
1102 warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
1103 }
1104 if (watchOnly && spendable) {
1105 warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag.");
1106 }
1107 1108 // Check that all required keys for solvability are provided.
1109 if (error.empty()) {
1110 for (const auto& require_key : import_data.used_keys) {
1111 if (!require_key.second) continue; // Not a required key
1112 if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) {
1113 error = "some required keys are missing";
1114 }
1115 }
1116 }
1117 1118 if (!error.empty()) {
1119 warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript.");
1120 import_data = ImportData();
1121 pubkey_map.clear();
1122 privkey_map.clear();
1123 have_solving_data = false;
1124 } else {
1125 // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided.
1126 if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script.");
1127 if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script.");
1128 for (auto it = privkey_map.begin(); it != privkey_map.end(); ) {
1129 auto oldit = it++;
1130 if (import_data.used_keys.count(oldit->first) == 0) {
1131 warnings.push_back("Ignoring irrelevant private key.");
1132 privkey_map.erase(oldit);
1133 }
1134 }
1135 for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) {
1136 auto oldit = it++;
1137 auto key_data_it = import_data.used_keys.find(oldit->first);
1138 if (key_data_it == import_data.used_keys.end() || !key_data_it->second) {
1139 warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH.");
1140 pubkey_map.erase(oldit);
1141 }
1142 }
1143 }
1144 }
1145 1146 return warnings;
1147 }
1148 1149 static UniValue ProcessImportDescriptor(ImportData& import_data, std::map<CKeyID, CPubKey>& pubkey_map, std::map<CKeyID, CKey>& privkey_map, std::set<CScript>& script_pub_keys, bool& have_solving_data, const UniValue& data, std::vector<std::pair<CKeyID, bool>>& ordered_pubkeys)
1150 {
1151 UniValue warnings(UniValue::VARR);
1152 1153 const std::string& descriptor = data["desc"].get_str();
1154 FlatSigningProvider keys;
1155 std::string error;
1156 auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true);
1157 if (parsed_descs.empty()) {
1158 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
1159 }
1160 if (parsed_descs.at(0)->GetOutputType() == OutputType::BECH32M) {
1161 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Bech32m descriptors cannot be imported into legacy wallets");
1162 }
1163 1164 std::optional<bool> internal;
1165 if (data.exists("internal")) {
1166 if (parsed_descs.size() > 1) {
1167 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'");
1168 }
1169 internal = data["internal"].get_bool();
1170 }
1171 1172 have_solving_data = parsed_descs.at(0)->IsSolvable();
1173 const bool watch_only = data.exists("watchonly") ? data["watchonly"].get_bool() : false;
1174 1175 int64_t range_start = 0, range_end = 0;
1176 if (!parsed_descs.at(0)->IsRange() && data.exists("range")) {
1177 throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
1178 } else if (parsed_descs.at(0)->IsRange()) {
1179 if (!data.exists("range")) {
1180 throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor is ranged, please specify the range");
1181 }
1182 std::tie(range_start, range_end) = ParseDescriptorRange(data["range"]);
1183 }
1184 1185 const UniValue& priv_keys = data.exists("keys") ? data["keys"].get_array() : UniValue();
1186 1187 for (size_t j = 0; j < parsed_descs.size(); ++j) {
1188 const auto& parsed_desc = parsed_descs.at(j);
1189 bool desc_internal = internal.has_value() && internal.value();
1190 if (parsed_descs.size() == 2) {
1191 desc_internal = j == 1;
1192 } else if (parsed_descs.size() > 2) {
1193 CHECK_NONFATAL(!desc_internal);
1194 }
1195 // Expand all descriptors to get public keys and scripts, and private keys if available.
1196 for (int i = range_start; i <= range_end; ++i) {
1197 FlatSigningProvider out_keys;
1198 std::vector<CScript> scripts_temp;
1199 parsed_desc->Expand(i, keys, scripts_temp, out_keys);
1200 std::copy(scripts_temp.begin(), scripts_temp.end(), std::inserter(script_pub_keys, script_pub_keys.end()));
1201 for (const auto& key_pair : out_keys.pubkeys) {
1202 ordered_pubkeys.emplace_back(key_pair.first, desc_internal);
1203 }
1204 1205 for (const auto& x : out_keys.scripts) {
1206 import_data.import_scripts.emplace(x.second);
1207 }
1208 1209 parsed_desc->ExpandPrivate(i, keys, out_keys);
1210 1211 std::copy(out_keys.pubkeys.begin(), out_keys.pubkeys.end(), std::inserter(pubkey_map, pubkey_map.end()));
1212 std::copy(out_keys.keys.begin(), out_keys.keys.end(), std::inserter(privkey_map, privkey_map.end()));
1213 import_data.key_origins.insert(out_keys.origins.begin(), out_keys.origins.end());
1214 }
1215 }
1216 1217 for (size_t i = 0; i < priv_keys.size(); ++i) {
1218 const auto& str = priv_keys[i].get_str();
1219 CKey key = DecodeSecret(str);
1220 if (!key.IsValid()) {
1221 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding");
1222 }
1223 CPubKey pubkey = key.GetPubKey();
1224 CKeyID id = pubkey.GetID();
1225 1226 // Check if this private key corresponds to a public key from the descriptor
1227 if (!pubkey_map.count(id)) {
1228 warnings.push_back("Ignoring irrelevant private key.");
1229 } else {
1230 privkey_map.emplace(id, key);
1231 }
1232 }
1233 1234 // Check if all the public keys have corresponding private keys in the import for spendability.
1235 // This does not take into account threshold multisigs which could be spendable without all keys.
1236 // Thus, threshold multisigs without all keys will be considered not spendable here, even if they are,
1237 // perhaps triggering a false warning message. This is consistent with the current wallet IsMine check.
1238 bool spendable = std::all_of(pubkey_map.begin(), pubkey_map.end(),
1239 [&](const std::pair<CKeyID, CPubKey>& used_key) {
1240 return privkey_map.count(used_key.first) > 0;
1241 }) && std::all_of(import_data.key_origins.begin(), import_data.key_origins.end(),
1242 [&](const std::pair<CKeyID, std::pair<CPubKey, KeyOriginInfo>>& entry) {
1243 return privkey_map.count(entry.first) > 0;
1244 });
1245 if (!watch_only && !spendable) {
1246 warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag.");
1247 }
1248 if (watch_only && spendable) {
1249 warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag.");
1250 }
1251 1252 return warnings;
1253 }
1254 1255 static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
1256 {
1257 UniValue warnings(UniValue::VARR);
1258 UniValue result(UniValue::VOBJ);
1259 1260 try {
1261 const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
1262 // Internal addresses should not have a label
1263 if (internal && data.exists("label")) {
1264 throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
1265 }
1266 const std::string label{LabelFromValue(data["label"])};
1267 const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false;
1268 1269 // Add to keypool only works with privkeys disabled
1270 if (add_keypool && !wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1271 throw JSONRPCError(RPC_INVALID_PARAMETER, "Keys can only be imported to the keypool when private keys are disabled");
1272 }
1273 1274 ImportData import_data;
1275 std::map<CKeyID, CPubKey> pubkey_map;
1276 std::map<CKeyID, CKey> privkey_map;
1277 std::set<CScript> script_pub_keys;
1278 std::vector<std::pair<CKeyID, bool>> ordered_pubkeys;
1279 bool have_solving_data;
1280 1281 if (data.exists("scriptPubKey") && data.exists("desc")) {
1282 throw JSONRPCError(RPC_INVALID_PARAMETER, "Both a descriptor and a scriptPubKey should not be provided.");
1283 } else if (data.exists("scriptPubKey")) {
1284 warnings = ProcessImportLegacy(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys);
1285 } else if (data.exists("desc")) {
1286 warnings = ProcessImportDescriptor(import_data, pubkey_map, privkey_map, script_pub_keys, have_solving_data, data, ordered_pubkeys);
1287 } else {
1288 throw JSONRPCError(RPC_INVALID_PARAMETER, "Either a descriptor or scriptPubKey must be provided.");
1289 }
1290 1291 // If private keys are disabled, abort if private keys are being imported
1292 if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !privkey_map.empty()) {
1293 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
1294 }
1295 1296 // Check whether we have any work to do
1297 for (const CScript& script : script_pub_keys) {
1298 if (wallet.IsMine(script) & ISMINE_SPENDABLE) {
1299 throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script (\"" + HexStr(script) + "\")");
1300 }
1301 }
1302 1303 // All good, time to import
1304 wallet.MarkDirty();
1305 if (!wallet.ImportScripts(import_data.import_scripts, timestamp)) {
1306 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet");
1307 }
1308 if (!wallet.ImportPrivKeys(privkey_map, timestamp)) {
1309 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet");
1310 }
1311 if (!wallet.ImportPubKeys(ordered_pubkeys, pubkey_map, import_data.key_origins, add_keypool, timestamp)) {
1312 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
1313 }
1314 if (!wallet.ImportScriptPubKeys(label, script_pub_keys, have_solving_data, !internal, timestamp)) {
1315 throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
1316 }
1317 1318 result.pushKV("success", UniValue(true));
1319 } catch (const UniValue& e) {
1320 result.pushKV("success", UniValue(false));
1321 result.pushKV("error", e);
1322 } catch (...) {
1323 result.pushKV("success", UniValue(false));
1324 1325 result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields"));
1326 }
1327 PushWarnings(warnings, result);
1328 return result;
1329 }
1330 1331 static int64_t GetImportTimestamp(const UniValue& data, int64_t now)
1332 {
1333 if (data.exists("timestamp")) {
1334 const UniValue& timestamp = data["timestamp"];
1335 if (timestamp.isNum()) {
1336 return timestamp.getInt<int64_t>();
1337 } else if (timestamp.isStr() && timestamp.get_str() == "now") {
1338 return now;
1339 }
1340 throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
1341 }
1342 throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
1343 }
1344 1345 RPCHelpMan importmulti()
1346 {
1347 return RPCHelpMan{"importmulti",
1348 "\nImport addresses/scripts (with private or public keys, redeem script (P2SH)), optionally rescanning the blockchain from the earliest creation time of the imported scripts. Requires a new wallet backup.\n"
1349 "If an address/script is imported without all of the private keys required to spend from that address, it will be watchonly. The 'watchonly' option must be set to true in this case or a warning will be returned.\n"
1350 "Conversely, if all the private keys are provided and the address/script is spendable, the watchonly option must be set to false, or a warning will be returned.\n"
1351 "\nNote: This call can take over an hour to complete if rescan is true, during that time, other rpc calls\n"
1352 "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
1353 "The rescan parameter can be set to false if the key was never used to create transactions. If it is set to false,\n"
1354 "but the key was used to create transactions, rescanblockchain needs to be called with the appropriate block range.\n"
1355 "Note: Use \"getwalletinfo\" to query the scanning progress.\n"
1356 "Note: This command is only compatible with legacy wallets. Use \"importdescriptors\" for descriptor wallets.\n",
1357 {
1358 {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
1359 {
1360 {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
1361 {
1362 {"desc", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Descriptor to import. If using descriptor, do not also provide address/scriptPubKey, scripts, or pubkeys"},
1363 {"scriptPubKey", RPCArg::Type::STR, RPCArg::Optional::NO, "Type of scriptPubKey (string for script, json for address). Should not be provided if using a descriptor",
1364 RPCArgOptions{.type_str={"\"<script>\" | { \"address\":\"<address>\" }", "string / json"}}
1365 },
1366 {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Creation time of the key expressed in " + UNIX_EPOCH_TIME + ",\n"
1367 "or the string \"now\" to substitute the current synced blockchain time. The timestamp of the oldest\n"
1368 "key will determine how far back blockchain rescans need to begin for missing wallet transactions.\n"
1369 "\"now\" can be specified to bypass scanning, for keys which are known to never have been used, and\n"
1370 "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key\n"
1371 "creation time of all keys being imported by the importmulti call will be scanned.",
1372 RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
1373 },
1374 {"redeemscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"},
1375 {"witnessscript", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"},
1376 {"pubkeys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Array of strings giving pubkeys to import. They must occur in P2PKH or P2WPKH scripts. They are not required when the private key is also provided (see the \"keys\" argument).",
1377 {
1378 {"pubKey", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""},
1379 }
1380 },
1381 {"keys", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "Array of strings giving private keys to import. The corresponding public keys must occur in the output or redeemscript.",
1382 {
1383 {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""},
1384 }
1385 },
1386 {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"},
1387 {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Stating whether matching outputs should be treated as not incoming payments (also known as change)"},
1388 {"watchonly", RPCArg::Type::BOOL, RPCArg::Default{false}, "Stating whether matching outputs should be considered watchonly."},
1389 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false"},
1390 {"keypool", RPCArg::Type::BOOL, RPCArg::Default{false}, "Stating whether imported public keys should be added to the keypool for when users request new addresses. Only allowed when wallet private keys are disabled"},
1391 },
1392 },
1393 },
1394 RPCArgOptions{.oneline_description="requests"}},
1395 {"options", RPCArg::Type::OBJ_NAMED_PARAMS, RPCArg::Optional::OMITTED, "",
1396 {
1397 {"rescan", RPCArg::Type::BOOL, RPCArg::Default{true}, "Scan the chain and mempool for wallet transactions after all imports."},
1398 },
1399 RPCArgOptions{.oneline_description="options"}},
1400 },
1401 RPCResult{
1402 RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
1403 {
1404 {RPCResult::Type::OBJ, "", "",
1405 {
1406 {RPCResult::Type::BOOL, "success", ""},
1407 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
1408 {
1409 {RPCResult::Type::STR, "", ""},
1410 }},
1411 {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
1412 {
1413 {RPCResult::Type::ELISION, "", "JSONRPC error"},
1414 }},
1415 }},
1416 }
1417 },
1418 RPCExamples{
1419 HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }, "
1420 "{ \"scriptPubKey\": { \"address\": \"<my 2nd address>\" }, \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
1421 HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'")
1422 },
1423 [&](const RPCHelpMan& self, const JSONRPCRequest& mainRequest) -> UniValue
1424 {
1425 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(mainRequest);
1426 if (!pwallet) return UniValue::VNULL;
1427 CWallet& wallet{*pwallet};
1428 1429 // Make sure the results are valid at least up to the most recent block
1430 // the user could have gotten from another RPC command prior to now
1431 wallet.BlockUntilSyncedToCurrentChain();
1432 1433 EnsureLegacyScriptPubKeyMan(*pwallet, true);
1434 1435 const UniValue& requests = mainRequest.params[0];
1436 1437 //Default options
1438 bool fRescan = true;
1439 1440 if (!mainRequest.params[1].isNull()) {
1441 const UniValue& options = mainRequest.params[1];
1442 1443 if (options.exists("rescan")) {
1444 fRescan = options["rescan"].get_bool();
1445 }
1446 }
1447 1448 WalletRescanReserver reserver(*pwallet);
1449 if (fRescan && !reserver.reserve()) {
1450 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
1451 }
1452 1453 int64_t now = 0;
1454 bool fRunScan = false;
1455 int64_t nLowestTimestamp = 0;
1456 UniValue response(UniValue::VARR);
1457 {
1458 LOCK(pwallet->cs_wallet);
1459 1460 // Check all requests are watchonly
1461 bool is_watchonly{true};
1462 for (size_t i = 0; i < requests.size(); ++i) {
1463 const UniValue& request = requests[i];
1464 if (!request.exists("watchonly") || !request["watchonly"].get_bool()) {
1465 is_watchonly = false;
1466 break;
1467 }
1468 }
1469 // Wallet does not need to be unlocked if all requests are watchonly
1470 if (!is_watchonly) EnsureWalletIsUnlocked(wallet);
1471 1472 // Verify all timestamps are present before importing any keys.
1473 CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(nLowestTimestamp).mtpTime(now)));
1474 for (const UniValue& data : requests.getValues()) {
1475 GetImportTimestamp(data, now);
1476 }
1477 1478 const int64_t minimumTimestamp = 1;
1479 1480 for (const UniValue& data : requests.getValues()) {
1481 const int64_t timestamp = std::max(GetImportTimestamp(data, now), minimumTimestamp);
1482 const UniValue result = ProcessImport(*pwallet, data, timestamp);
1483 response.push_back(result);
1484 1485 if (!fRescan) {
1486 continue;
1487 }
1488 1489 // If at least one request was successful then allow rescan.
1490 if (result["success"].get_bool()) {
1491 fRunScan = true;
1492 }
1493 1494 // Get the lowest timestamp.
1495 if (timestamp < nLowestTimestamp) {
1496 nLowestTimestamp = timestamp;
1497 }
1498 }
1499 }
1500 if (fRescan && fRunScan && requests.size()) {
1501 int64_t scannedTime = pwallet->RescanFromTime(nLowestTimestamp, reserver, /*update=*/true);
1502 pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
1503 1504 if (pwallet->IsAbortingRescan()) {
1505 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
1506 }
1507 if (scannedTime > nLowestTimestamp) {
1508 std::vector<UniValue> results = response.getValues();
1509 response.clear();
1510 response.setArray();
1511 size_t i = 0;
1512 for (const UniValue& request : requests.getValues()) {
1513 // If key creation date is within the successfully scanned
1514 // range, or if the import result already has an error set, let
1515 // the result stand unmodified. Otherwise replace the result
1516 // with an error message.
1517 if (scannedTime <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
1518 response.push_back(results.at(i));
1519 } else {
1520 UniValue result = UniValue(UniValue::VOBJ);
1521 result.pushKV("success", UniValue(false));
1522 result.pushKV(
1523 "error",
1524 JSONRPCError(
1525 RPC_MISC_ERROR,
1526 strprintf("Rescan failed for key with creation timestamp %d. There was an error reading a "
1527 "block from time %d, which is after or within %d seconds of key creation, and "
1528 "could contain transactions pertaining to the key. As a result, transactions "
1529 "and coins using this key may not appear in the wallet. This error could be "
1530 "caused by pruning or data corruption (see limenkad log for details) and could "
1531 "be dealt with by downloading and rescanning the relevant blocks (see -reindex "
1532 "option and rescanblockchain RPC).",
1533 GetImportTimestamp(request, now), scannedTime - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)));
1534 response.push_back(std::move(result));
1535 }
1536 ++i;
1537 }
1538 }
1539 }
1540 1541 return response;
1542 },
1543 };
1544 }
1545 1546 UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp, const std::vector<CExtKey>& master_keys) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
1547 {
1548 UniValue warnings(UniValue::VARR);
1549 UniValue result(UniValue::VOBJ);
1550 1551 try {
1552 if (!data.exists("desc")) {
1553 throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
1554 }
1555 1556 const std::string& descriptor = data["desc"].get_str();
1557 const bool active = data.exists("active") ? data["active"].get_bool() : false;
1558 const std::string label{LabelFromValue(data["label"])};
1559 1560 // Parse descriptor string
1561 FlatSigningProvider keys;
1562 for (const auto& mk : master_keys) {
1563 keys.AddMasterKey(mk);
1564 }
1565 1566 std::string error;
1567 auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true);
1568 if (parsed_descs.empty()) {
1569 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
1570 }
1571 std::optional<bool> internal;
1572 if (data.exists("internal")) {
1573 if (parsed_descs.size() > 1) {
1574 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'");
1575 }
1576 internal = data["internal"].get_bool();
1577 }
1578 1579 // Range check
1580 std::optional<bool> is_ranged;
1581 int64_t range_start = 0, range_end = 1, next_index = 0;
1582 if (!parsed_descs.at(0)->IsRange() && data.exists("range")) {
1583 throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
1584 } else if (parsed_descs.at(0)->IsRange()) {
1585 if (data.exists("range")) {
1586 auto range = ParseDescriptorRange(data["range"]);
1587 range_start = range.first;
1588 range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive
1589 } else {
1590 warnings.push_back("Range not given, using default keypool range");
1591 range_start = 0;
1592 range_end = wallet.m_keypool_size;
1593 }
1594 next_index = range_start;
1595 is_ranged = true;
1596 1597 if (data.exists("next_index")) {
1598 next_index = data["next_index"].getInt<int64_t>();
1599 // bound checks
1600 if (next_index < range_start || next_index >= range_end) {
1601 throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range");
1602 }
1603 }
1604 }
1605 1606 // Active descriptors must be ranged
1607 if (active && !parsed_descs.at(0)->IsRange()) {
1608 throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged");
1609 }
1610 1611 // Multipath descriptors should not have a label
1612 if (parsed_descs.size() > 1 && data.exists("label")) {
1613 throw JSONRPCError(RPC_INVALID_PARAMETER, "Multipath descriptors should not have a label");
1614 }
1615 1616 // Ranged descriptors should not have a label
1617 if (is_ranged.has_value() && is_ranged.value() && data.exists("label")) {
1618 throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label");
1619 }
1620 1621 bool desc_internal = internal.has_value() && internal.value();
1622 // Internal addresses should not have a label either
1623 if (desc_internal && data.exists("label")) {
1624 throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
1625 }
1626 1627 // Combo descriptor check
1628 if (active && !parsed_descs.at(0)->IsSingleType()) {
1629 throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active");
1630 }
1631 1632 // If the wallet disabled private keys, abort if private keys exist
1633 if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) {
1634 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
1635 }
1636 1637 for (size_t j = 0; j < parsed_descs.size(); ++j) {
1638 auto parsed_desc = std::move(parsed_descs[j]);
1639 if (parsed_descs.size() == 2) {
1640 desc_internal = j == 1;
1641 } else if (parsed_descs.size() > 2) {
1642 CHECK_NONFATAL(!desc_internal);
1643 }
1644 // Need to ExpandPrivate to check if private keys are available for all pubkeys
1645 FlatSigningProvider expand_keys;
1646 std::vector<CScript> scripts;
1647 if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
1648 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided");
1649 }
1650 parsed_desc->ExpandPrivate(0, keys, expand_keys);
1651 1652 // Check if all private keys are provided
1653 bool have_all_privkeys = !expand_keys.keys.empty();
1654 for (const auto& entry : expand_keys.origins) {
1655 const CKeyID& key_id = entry.first;
1656 CKey key;
1657 if (!expand_keys.GetKey(key_id, key)) {
1658 have_all_privkeys = false;
1659 break;
1660 }
1661 }
1662 1663 // If private keys are enabled, check some things.
1664 if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
1665 if (keys.keys.empty()) {
1666 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
1667 }
1668 if (!have_all_privkeys) {
1669 warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
1670 }
1671 }
1672 1673 WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
1674 1675 // Check if the wallet already contains the descriptor
1676 auto existing_spk_manager = wallet.GetDescriptorScriptPubKeyMan(w_desc);
1677 if (existing_spk_manager) {
1678 if (!existing_spk_manager->CanUpdateToWalletDescriptor(w_desc, error)) {
1679 throw JSONRPCError(RPC_INVALID_PARAMETER, error);
1680 }
1681 }
1682 1683 // Add descriptor to the wallet
1684 auto spk_manager = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal);
1685 if (spk_manager == nullptr) {
1686 throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s'", descriptor));
1687 }
1688 1689 // Set descriptor as active if necessary
1690 if (active) {
1691 if (!w_desc.descriptor->GetOutputType()) {
1692 warnings.push_back("Unknown output type, cannot set descriptor to active.");
1693 } else {
1694 wallet.AddActiveScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
1695 }
1696 } else {
1697 if (w_desc.descriptor->GetOutputType()) {
1698 wallet.DeactivateScriptPubKeyMan(spk_manager->GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
1699 }
1700 }
1701 }
1702 1703 result.pushKV("success", UniValue(true));
1704 } catch (const UniValue& e) {
1705 result.pushKV("success", UniValue(false));
1706 result.pushKV("error", e);
1707 }
1708 PushWarnings(warnings, result);
1709 return result;
1710 }
1711 1712 RPCHelpMan importdescriptors()
1713 {
1714 return RPCHelpMan{"importdescriptors",
1715 "\nImport descriptors. This will trigger a rescan of the blockchain based on the earliest timestamp of all descriptors being imported. Requires a new wallet backup.\n"
1716 "When importing descriptors with multipath key expressions, if the multipath specifier contains exactly two elements, the descriptor produced from the second elements will be imported as an internal descriptor.\n"
1717 "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
1718 "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
1719 "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
1720 {
1721 {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
1722 {
1723 {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
1724 {
1725 {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
1726 {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
1727 {"range", RPCArg::Type::RANGE, RPCArg::Optional::OMITTED, "If a ranged descriptor is used, this specifies the end or the range (in the form [begin,end]) to import"},
1728 {"next_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "If a ranged descriptor is set to active, this specifies the next index to generate addresses from"},
1729 {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
1730 "Use the string \"now\" to substitute the current synced blockchain time.\n"
1731 "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
1732 "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
1733 "of all descriptors being imported will be scanned as well as the mempool.",
1734 RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
1735 },
1736 {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
1737 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
1738 },
1739 },
1740 },
1741 RPCArgOptions{.oneline_description="requests"}},
1742 {"seeds", RPCArg::Type::ARR, RPCArg::Default{UniValue::VARR}, "BIP32 master seeds for the above descriptors",
1743 {
1744 {"shares", RPCArg::Type::ARR, RPCArg::Optional::OMITTED, "a codex32 (BIP 93) encoded seed, or list of codex32-encoded shares",
1745 {
1746 {"share 1", RPCArg::Type::STR, RPCArg::Optional::OMITTED, ""},
1747 },
1748 },
1749 },
1750 RPCArgOptions{.oneline_description="seeds"}},
1751 },
1752 RPCResult{
1753 RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
1754 {
1755 {RPCResult::Type::OBJ, "", "",
1756 {
1757 {RPCResult::Type::BOOL, "success", ""},
1758 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
1759 {
1760 {RPCResult::Type::STR, "", ""},
1761 }},
1762 {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
1763 {
1764 {RPCResult::Type::ELISION, "", "JSONRPC error"},
1765 }},
1766 }},
1767 }
1768 },
1769 RPCExamples{
1770 HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
1771 "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
1772 HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
1773 },
1774 [&](const RPCHelpMan& self, const JSONRPCRequest& main_request) -> UniValue
1775 {
1776 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
1777 if (!pwallet) return UniValue::VNULL;
1778 CWallet& wallet{*pwallet};
1779 1780 // Make sure the results are valid at least up to the most recent block
1781 // the user could have gotten from another RPC command prior to now
1782 wallet.BlockUntilSyncedToCurrentChain();
1783 1784 // Make sure wallet is a descriptor wallet
1785 if (!pwallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
1786 throw JSONRPCError(RPC_WALLET_ERROR, "importdescriptors is not available for non-descriptor wallets");
1787 }
1788 1789 WalletRescanReserver reserver(*pwallet);
1790 if (!reserver.reserve(/*with_passphrase=*/true)) {
1791 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
1792 }
1793 1794 // Ensure that the wallet is not locked for the remainder of this RPC, as
1795 // the passphrase is used to top up the keypool.
1796 LOCK(pwallet->m_relock_mutex);
1797 1798 const UniValue& requests = main_request.params[0];
1799 const int64_t minimum_timestamp = 1;
1800 int64_t now = 0;
1801 int64_t lowest_timestamp = 0;
1802 bool rescan = false;
1803 1804 // Parse codex32 strings
1805 std::vector<CExtKey> master_keys;
1806 if (main_request.params[1].isArray()) {
1807 const auto& req_seeds = main_request.params[1].get_array();
1808 master_keys.reserve(req_seeds.size());
1809 for (size_t i = 0; i < req_seeds.size(); ++i) {
1810 const auto& req_shares = req_seeds[i].get_array();
1811 std::vector<codex32::Result> shares;
1812 shares.reserve(req_shares.size());
1813 for (size_t j = 0; j < req_shares.size(); ++j) {
1814 if (!req_shares[j].isStr()) {
1815 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "codex32 shares must be strings");
1816 }
1817 codex32::Result key_res{req_shares[j].get_str()};
1818 if (!key_res.IsValid()) {
1819 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid codex32 share: " + codex32::ErrorString(key_res.error()));
1820 }
1821 shares.push_back(key_res);
1822 }
1823 1824 // Recover seed
1825 std::vector<unsigned char> seed;
1826 if (shares.size() == 1) {
1827 if (shares[0].GetShareIndex() != 's') {
1828 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid codex32: single share must be the S share");
1829 }
1830 seed = shares[0].GetPayload();
1831 } else {
1832 codex32::Result s{shares, 's'};
1833 if (!s.IsValid()) {
1834 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Failed to derive codex32 seed: " + codex32::ErrorString(s.error()));
1835 }
1836 seed = s.GetPayload();
1837 }
1838 1839 CExtKey master_key;
1840 master_key.SetSeed(Span{(std::byte*) seed.data(), seed.size()});
1841 master_keys.push_back(master_key);
1842 }
1843 }
1844 1845 UniValue response(UniValue::VARR);
1846 {
1847 LOCK(pwallet->cs_wallet);
1848 EnsureWalletIsUnlocked(*pwallet);
1849 1850 CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
1851 1852 // Get all timestamps and extract the lowest timestamp
1853 for (const UniValue& request : requests.getValues()) {
1854 // This throws an error if "timestamp" doesn't exist
1855 const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);
1856 const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp, master_keys);
1857 response.push_back(result);
1858 1859 if (lowest_timestamp > timestamp ) {
1860 lowest_timestamp = timestamp;
1861 }
1862 1863 // If we know the chain tip, and at least one request was successful then allow rescan
1864 if (!rescan && result["success"].get_bool()) {
1865 rescan = true;
1866 }
1867 }
1868 pwallet->ConnectScriptPubKeyManNotifiers();
1869 }
1870 1871 // Rescan the blockchain using the lowest timestamp
1872 if (rescan) {
1873 int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver, /*update=*/true);
1874 pwallet->ResubmitWalletTransactions(/*relay=*/false, /*force=*/true);
1875 1876 if (pwallet->IsAbortingRescan()) {
1877 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
1878 }
1879 1880 if (scanned_time > lowest_timestamp) {
1881 std::vector<UniValue> results = response.getValues();
1882 response.clear();
1883 response.setArray();
1884 1885 // Compose the response
1886 for (unsigned int i = 0; i < requests.size(); ++i) {
1887 const UniValue& request = requests.getValues().at(i);
1888 1889 // If the descriptor timestamp is within the successfully scanned
1890 // range, or if the import result already has an error set, let
1891 // the result stand unmodified. Otherwise replace the result
1892 // with an error message.
1893 if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
1894 response.push_back(results.at(i));
1895 } else {
1896 std::string error_msg{strprintf("Rescan failed for descriptor with timestamp %d. There "
1897 "was an error reading a block from time %d, which is after or within %d seconds "
1898 "of key creation, and could contain transactions pertaining to the desc. As a "
1899 "result, transactions and coins using this desc may not appear in the wallet.",
1900 GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)};
1901 if (pwallet->chain().havePruned()) {
1902 error_msg += strprintf(" This error could be caused by pruning or data corruption "
1903 "(see limenkad log for details) and could be dealt with by downloading and "
1904 "rescanning the relevant blocks (see -reindex option and rescanblockchain RPC).");
1905 } else if (pwallet->chain().hasAssumedValidChain()) {
1906 error_msg += strprintf(" This error is likely caused by an in-progress assumeutxo "
1907 "background sync. Check logs or getchainstates RPC for assumeutxo background "
1908 "sync progress and try again later.");
1909 } else {
1910 error_msg += strprintf(" This error could potentially caused by data corruption. If "
1911 "the issue persists you may want to reindex (see -reindex option).");
1912 }
1913 1914 UniValue result = UniValue(UniValue::VOBJ);
1915 result.pushKV("success", UniValue(false));
1916 result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, error_msg));
1917 response.push_back(std::move(result));
1918 }
1919 }
1920 }
1921 }
1922 1923 return response;
1924 },
1925 };
1926 }
1927 1928 RPCHelpMan listdescriptors()
1929 {
1930 return RPCHelpMan{
1931 "listdescriptors",
1932 "\nList all descriptors present in a descriptor-enabled wallet.\n",
1933 {
1934 {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
1935 },
1936 RPCResult{RPCResult::Type::OBJ, "", "", {
1937 {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
1938 {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
1939 {
1940 {RPCResult::Type::OBJ, "", "", {
1941 {RPCResult::Type::STR, "desc", "Descriptor string representation"},
1942 {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
1943 {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
1944 {RPCResult::Type::BOOL, "internal", /*optional=*/true, "True if this descriptor is used to generate change addresses. False if this descriptor is used to generate receiving addresses; defined only for active descriptors"},
1945 {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {
1946 {RPCResult::Type::NUM, "", "Range start inclusive"},
1947 {RPCResult::Type::NUM, "", "Range end inclusive"},
1948 }},
1949 {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},
1950 {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"},
1951 }},
1952 }}
1953 }},
1954 RPCExamples{
1955 HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
1956 + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
1957 },
1958 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
1959 {
1960 const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
1961 if (!wallet) return UniValue::VNULL;
1962 1963 if (!wallet->IsWalletFlagSet(WALLET_FLAG_DESCRIPTORS)) {
1964 throw JSONRPCError(RPC_WALLET_ERROR, "listdescriptors is not available for non-descriptor wallets");
1965 }
1966 1967 const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
1968 if (priv) {
1969 EnsureWalletIsUnlocked(*wallet);
1970 }
1971 1972 LOCK(wallet->cs_wallet);
1973 1974 const auto active_spk_mans = wallet->GetActiveScriptPubKeyMans();
1975 1976 struct WalletDescInfo {
1977 std::string descriptor;
1978 uint64_t creation_time;
1979 bool active;
1980 std::optional<bool> internal;
1981 std::optional<std::pair<int64_t,int64_t>> range;
1982 int64_t next_index;
1983 };
1984 1985 std::vector<WalletDescInfo> wallet_descriptors;
1986 for (const auto& spk_man : wallet->GetAllScriptPubKeyMans()) {
1987 const auto desc_spk_man = dynamic_cast<DescriptorScriptPubKeyMan*>(spk_man);
1988 if (!desc_spk_man) {
1989 throw JSONRPCError(RPC_WALLET_ERROR, "Unexpected ScriptPubKey manager type.");
1990 }
1991 LOCK(desc_spk_man->cs_desc_man);
1992 const auto& wallet_descriptor = desc_spk_man->GetWalletDescriptor();
1993 std::string descriptor;
1994 if (!desc_spk_man->GetDescriptorString(descriptor, priv)) {
1995 throw JSONRPCError(RPC_WALLET_ERROR, "Can't get descriptor string.");
1996 }
1997 const bool is_range = wallet_descriptor.descriptor->IsRange();
1998 wallet_descriptors.push_back({
1999 descriptor,
2000 wallet_descriptor.creation_time,
2001 active_spk_mans.count(desc_spk_man) != 0,
2002 wallet->IsInternalScriptPubKeyMan(desc_spk_man),
2003 is_range ? std::optional(std::make_pair(wallet_descriptor.range_start, wallet_descriptor.range_end)) : std::nullopt,
2004 wallet_descriptor.next_index
2005 });
2006 }
2007 2008 std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
2009 return a.descriptor < b.descriptor;
2010 });
2011 2012 UniValue descriptors(UniValue::VARR);
2013 for (const WalletDescInfo& info : wallet_descriptors) {
2014 UniValue spk(UniValue::VOBJ);
2015 spk.pushKV("desc", info.descriptor);
2016 spk.pushKV("timestamp", info.creation_time);
2017 spk.pushKV("active", info.active);
2018 if (info.internal.has_value()) {
2019 spk.pushKV("internal", info.internal.value());
2020 }
2021 if (info.range.has_value()) {
2022 UniValue range(UniValue::VARR);
2023 range.push_back(info.range->first);
2024 range.push_back(info.range->second - 1);
2025 spk.pushKV("range", std::move(range));
2026 spk.pushKV("next", info.next_index);
2027 spk.pushKV("next_index", info.next_index);
2028 }
2029 descriptors.push_back(std::move(spk));
2030 }
2031 2032 UniValue response(UniValue::VOBJ);
2033 response.pushKV("wallet_name", wallet->GetName());
2034 response.pushKV("descriptors", std::move(descriptors));
2035 2036 return response;
2037 },
2038 };
2039 }
2040 2041 RPCHelpMan backupwallet()
2042 {
2043 return RPCHelpMan{"backupwallet",
2044 "\nSafely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
2045 {
2046 {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
2047 },
2048 RPCResult{RPCResult::Type::NONE, "", ""},
2049 RPCExamples{
2050 HelpExampleCli("backupwallet", "\"backup.dat\"")
2051 + HelpExampleRpc("backupwallet", "\"backup.dat\"")
2052 },
2053 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2054 {
2055 EnsureNotWalletRestricted(request);
2056 2057 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
2058 if (!pwallet) return UniValue::VNULL;
2059 2060 // Make sure the results are valid at least up to the most recent block
2061 // the user could have gotten from another RPC command prior to now
2062 pwallet->BlockUntilSyncedToCurrentChain();
2063 2064 LOCK(pwallet->cs_wallet);
2065 2066 std::string strDest = request.params[0].get_str();
2067 if (!pwallet->BackupWallet(strDest)) {
2068 throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
2069 }
2070 2071 return UniValue::VNULL;
2072 },
2073 };
2074 }
2075 2076 2077 RPCHelpMan restorewallet()
2078 {
2079 return RPCHelpMan{
2080 "restorewallet",
2081 "\nRestores and loads a wallet from backup.\n"
2082 "\nThe rescan is significantly faster if a descriptor wallet is restored"
2083 "\nand block filters are available (using startup option \"-blockfilterindex=1\").\n",
2084 {
2085 {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
2086 {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
2087 {"load_on_startup", RPCArg::Type::BOOL, RPCArg::Optional::OMITTED, "Save wallet name to persistent settings and load on startup. True to add wallet to startup list, false to remove, null to leave unchanged."},
2088 },
2089 RPCResult{
2090 RPCResult::Type::OBJ, "", "",
2091 {
2092 {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
2093 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
2094 {
2095 {RPCResult::Type::STR, "", ""},
2096 }},
2097 }
2098 },
2099 RPCExamples{
2100 HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
2101 + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
2102 + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
2103 + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
2104 },
2105 [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue
2106 {
2107 EnsureNotWalletRestricted(request);
2108 2109 WalletContext& context = EnsureWalletContext(request.context);
2110 2111 auto backup_file = fs::u8path(request.params[1].get_str());
2112 2113 std::string wallet_name = request.params[0].get_str();
2114 2115 std::optional<bool> load_on_start = request.params[2].isNull() ? std::nullopt : std::optional<bool>(request.params[2].get_bool());
2116 2117 DatabaseStatus status;
2118 bilingual_str error;
2119 std::vector<bilingual_str> warnings;
2120 2121 const std::shared_ptr<CWallet> wallet = RestoreWallet(context, backup_file, wallet_name, load_on_start, status, error, warnings);
2122 2123 HandleWalletError(wallet, status, error);
2124 2125 UniValue obj(UniValue::VOBJ);
2126 obj.pushKV("name", wallet->GetName());
2127 PushWarnings(warnings, obj);
2128 2129 return obj;
2130 2131 },
2132 };
2133 }
2134 } // namespace wallet
2135