1 // Copyright (c) 2009-present The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 5 #include <chain.h>
6 #include <clientversion.h>
7 #include <core_io.h>
8 #include <hash.h>
9 #include <interfaces/chain.h>
10 #include <key_io.h>
11 #include <merkleblock.h>
12 #include <node/types.h>
13 #include <rpc/util.h>
14 #include <script/descriptor.h>
15 #include <script/script.h>
16 #include <script/solver.h>
17 #include <sync.h>
18 #include <uint256.h>
19 #include <util/bip32.h>
20 #include <util/check.h>
21 #include <util/fs.h>
22 #include <util/time.h>
23 #include <util/translation.h>
24 #include <wallet/export.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 39 namespace wallet {
40 RPCMethod importprunedfunds()
41 {
42 return RPCMethod{
43 "importprunedfunds",
44 "Imports 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",
45 {
46 {"rawtransaction", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A raw transaction in hex funding an already-existing address in wallet"},
47 {"txoutproof", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex output from gettxoutproof that contains the transaction"},
48 },
49 RPCResult{RPCResult::Type::NONE, "", ""},
50 RPCExamples{""},
51 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
52 {
53 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
54 if (!pwallet) return UniValue::VNULL;
55 56 CMutableTransaction tx;
57 if (!DecodeHexTx(tx, request.params[0].get_str())) {
58 throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed. Make sure the tx has at least one input.");
59 }
60 61 CMerkleBlock merkleBlock;
62 SpanReader{ParseHexV(request.params[1], "proof")} >> merkleBlock;
63 64 //Search partial merkle tree in proof for our transaction and index in valid block
65 std::vector<Txid> vMatch;
66 std::vector<unsigned int> vIndex;
67 if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) != merkleBlock.header.hashMerkleRoot) {
68 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Something wrong with merkleblock");
69 }
70 71 LOCK(pwallet->cs_wallet);
72 int height;
73 if (!pwallet->chain().findAncestorByHash(pwallet->GetLastBlockHash(), merkleBlock.header.GetHash(), FoundBlock().height(height))) {
74 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found in chain");
75 }
76 77 std::vector<Txid>::const_iterator it;
78 if ((it = std::find(vMatch.begin(), vMatch.end(), tx.GetHash())) == vMatch.end()) {
79 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction given doesn't exist in proof");
80 }
81 82 unsigned int txnIndex = vIndex[it - vMatch.begin()];
83 84 CTransactionRef tx_ref = MakeTransactionRef(tx);
85 if (pwallet->IsMine(*tx_ref)) {
86 pwallet->AddToWallet(std::move(tx_ref), TxStateConfirmed{merkleBlock.header.GetHash(), height, static_cast<int>(txnIndex)});
87 return UniValue::VNULL;
88 }
89 90 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "No addresses in wallet correspond to included transaction");
91 },
92 };
93 }
94 95 RPCMethod removeprunedfunds()
96 {
97 return RPCMethod{
98 "removeprunedfunds",
99 "Deletes the specified transaction from the wallet. Meant for use with pruned wallets and as a companion to importprunedfunds. This will affect wallet balances.\n",
100 {
101 {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex-encoded id of the transaction you are deleting"},
102 },
103 RPCResult{RPCResult::Type::NONE, "", ""},
104 RPCExamples{
105 HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"") +
106 "\nAs a JSON-RPC call\n"
107 + HelpExampleRpc("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166eae7628734ea0a5\"")
108 },
109 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
110 {
111 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(request);
112 if (!pwallet) return UniValue::VNULL;
113 114 LOCK(pwallet->cs_wallet);
115 116 Txid hash{Txid::FromUint256(ParseHashV(request.params[0], "txid"))};
117 std::vector<Txid> vHash;
118 vHash.push_back(hash);
119 if (auto res = pwallet->RemoveTxs(vHash); !res) {
120 throw JSONRPCError(RPC_WALLET_ERROR, util::ErrorString(res).original);
121 }
122 123 return UniValue::VNULL;
124 },
125 };
126 }
127 128 static int64_t GetImportTimestamp(const UniValue& data, int64_t now)
129 {
130 if (data.exists("timestamp")) {
131 const UniValue& timestamp = data["timestamp"];
132 if (timestamp.isNum()) {
133 return timestamp.getInt<int64_t>();
134 } else if (timestamp.isStr() && timestamp.get_str() == "now") {
135 return now;
136 }
137 throw JSONRPCError(RPC_TYPE_ERROR, strprintf("Expected number or \"now\" timestamp value for key. got type %s", uvTypeName(timestamp.type())));
138 }
139 throw JSONRPCError(RPC_TYPE_ERROR, "Missing required timestamp field for key");
140 }
141 142 static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
143 {
144 UniValue warnings(UniValue::VARR);
145 UniValue result(UniValue::VOBJ);
146 147 try {
148 if (!data.exists("desc")) {
149 throw JSONRPCError(RPC_INVALID_PARAMETER, "Descriptor not found.");
150 }
151 152 const std::string& descriptor = data["desc"].get_str();
153 const bool active = data.exists("active") ? data["active"].get_bool() : false;
154 const std::string label{LabelFromValue(data["label"])};
155 156 // Parse descriptor string
157 FlatSigningProvider keys;
158 std::string error;
159 auto parsed_descs = Parse(descriptor, keys, error, /* require_checksum = */ true);
160 if (parsed_descs.empty()) {
161 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, error);
162 }
163 std::optional<bool> internal;
164 if (data.exists("internal")) {
165 if (parsed_descs.size() > 1) {
166 throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Cannot have multipath descriptor while also specifying \'internal\'");
167 }
168 internal = data["internal"].get_bool();
169 }
170 171 // Range check
172 std::optional<bool> is_ranged;
173 int64_t range_start = 0, range_end = 1, next_index = 0;
174 if (!parsed_descs.at(0)->IsRange() && data.exists("range")) {
175 throw JSONRPCError(RPC_INVALID_PARAMETER, "Range should not be specified for an un-ranged descriptor");
176 } else if (parsed_descs.at(0)->IsRange()) {
177 if (data.exists("range")) {
178 auto range = ParseDescriptorRange(data["range"]);
179 range_start = range.first;
180 range_end = range.second + 1; // Specified range end is inclusive, but we need range end as exclusive
181 } else {
182 warnings.push_back("Range not given, using default keypool range");
183 range_start = 0;
184 range_end = wallet.m_keypool_size;
185 }
186 next_index = range_start;
187 is_ranged = true;
188 189 if (data.exists("next_index")) {
190 next_index = data["next_index"].getInt<int64_t>();
191 // bound checks
192 if (next_index < range_start || next_index >= range_end) {
193 throw JSONRPCError(RPC_INVALID_PARAMETER, "next_index is out of range");
194 }
195 }
196 }
197 198 // Active descriptors must be ranged
199 if (active && !parsed_descs.at(0)->IsRange()) {
200 throw JSONRPCError(RPC_INVALID_PARAMETER, "Active descriptors must be ranged");
201 }
202 203 // Multipath descriptors should not have a label
204 if (parsed_descs.size() > 1 && data.exists("label")) {
205 throw JSONRPCError(RPC_INVALID_PARAMETER, "Multipath descriptors should not have a label");
206 }
207 208 // Ranged descriptors should not have a label
209 if (is_ranged.has_value() && is_ranged.value() && data.exists("label")) {
210 throw JSONRPCError(RPC_INVALID_PARAMETER, "Ranged descriptors should not have a label");
211 }
212 213 bool desc_internal = internal.has_value() && internal.value();
214 // Internal addresses should not have a label either
215 if (desc_internal && data.exists("label")) {
216 throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
217 }
218 219 // Combo descriptor check
220 if (active && !parsed_descs.at(0)->IsSingleType()) {
221 throw JSONRPCError(RPC_WALLET_ERROR, "Combo descriptors cannot be set to active");
222 }
223 224 // If the wallet disabled private keys, abort if private keys exist
225 if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !keys.keys.empty()) {
226 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import private keys to a wallet with private keys disabled");
227 }
228 229 for (size_t j = 0; j < parsed_descs.size(); ++j) {
230 auto parsed_desc = std::move(parsed_descs[j]);
231 if (parsed_descs.size() == 2) {
232 desc_internal = j == 1;
233 } else if (parsed_descs.size() > 2) {
234 CHECK_NONFATAL(!desc_internal);
235 }
236 // Need to ExpandPrivate to check if private keys are available for all pubkeys
237 FlatSigningProvider expand_keys;
238 std::vector<CScript> scripts;
239 if (!parsed_desc->Expand(0, keys, scripts, expand_keys)) {
240 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot expand descriptor. Probably because of hardened derivations without private keys provided");
241 }
242 parsed_desc->ExpandPrivate(0, keys, expand_keys);
243 244 for (const auto& w : parsed_desc->Warnings()) {
245 warnings.push_back(w);
246 }
247 248 // Check if all private keys are provided
249 bool have_all_privkeys = !expand_keys.keys.empty();
250 for (const auto& entry : expand_keys.origins) {
251 const CKeyID& key_id = entry.first;
252 CKey key;
253 if (!expand_keys.GetKey(key_id, key)) {
254 have_all_privkeys = false;
255 break;
256 }
257 }
258 259 // If private keys are enabled, check some things.
260 if (!wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
261 if (keys.keys.empty()) {
262 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import descriptor without private keys to a wallet with private keys enabled");
263 }
264 if (!have_all_privkeys) {
265 warnings.push_back("Not all private keys provided. Some wallet functionality may return unexpected errors");
266 }
267 }
268 269 // If this is an unused(KEY) descriptor, check that the wallet doesn't already have other descriptors with this key
270 if (!parsed_desc->HasScripts()) {
271 if (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
272 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import unused() to wallet without private keys enabled");
273 }
274 // Unused descriptors must contain a single key.
275 // Earlier checks will have enforced that this key is either a private key when private keys are enabled,
276 // or that this key is a public key when private keys are disabled.
277 // If we can retrieve the corresponding private key from the wallet, then this key is already in the wallet
278 // and we should not import it.
279 std::set<CPubKey> pubkeys;
280 std::set<CExtPubKey> extpubs;
281 parsed_desc->GetPubKeys(pubkeys, extpubs);
282 std::transform(extpubs.begin(), extpubs.end(), std::inserter(pubkeys, pubkeys.begin()), [](const CExtPubKey& xpub) { return xpub.pubkey; });
283 CHECK_NONFATAL(pubkeys.size() == 1);
284 if (wallet.GetKey(pubkeys.begin()->GetID())) {
285 throw JSONRPCError(RPC_WALLET_ERROR, "Cannot import an unused() descriptor when its private key is already in the wallet");
286 }
287 }
288 289 WalletDescriptor w_desc(std::move(parsed_desc), timestamp, range_start, range_end, next_index);
290 291 // Add descriptor to the wallet
292 auto spk_manager_res = wallet.AddWalletDescriptor(w_desc, keys, label, desc_internal);
293 294 if (!spk_manager_res) {
295 throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Could not add descriptor '%s': %s", descriptor, util::ErrorString(spk_manager_res).original));
296 }
297 298 auto& spk_manager = spk_manager_res.value().get();
299 300 // Set descriptor as active if necessary
301 if (active) {
302 if (!w_desc.descriptor->GetOutputType()) {
303 warnings.push_back("Unknown output type, cannot set descriptor to active.");
304 } else {
305 wallet.AddActiveScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
306 }
307 } else {
308 if (w_desc.descriptor->GetOutputType()) {
309 wallet.DeactivateScriptPubKeyMan(spk_manager.GetID(), *w_desc.descriptor->GetOutputType(), desc_internal);
310 }
311 }
312 }
313 314 result.pushKV("success", UniValue(true));
315 } catch (const UniValue& e) {
316 result.pushKV("success", UniValue(false));
317 result.pushKV("error", e);
318 }
319 PushWarnings(warnings, result);
320 return result;
321 }
322 323 RPCMethod importdescriptors()
324 {
325 return RPCMethod{
326 "importdescriptors",
327 "Import 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"
328 "When importing descriptors with multipath key expressions, if the multipath specifier contains exactly two elements, the descriptor produced from the second element will be imported as an internal descriptor.\n"
329 "\nNote: This call can take over an hour to complete if using an early timestamp; during that time, other rpc calls\n"
330 "may report that the imported keys, addresses or scripts exist but related transactions are still missing.\n"
331 "The rescan is significantly faster if block filters are available (using startup option \"-blockfilterindex=1\").\n",
332 {
333 {"requests", RPCArg::Type::ARR, RPCArg::Optional::NO, "Data to be imported",
334 {
335 {"", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "",
336 {
337 {"desc", RPCArg::Type::STR, RPCArg::Optional::NO, "Descriptor to import."},
338 {"active", RPCArg::Type::BOOL, RPCArg::Default{false}, "Set this descriptor to be the active descriptor for the corresponding output type/externality"},
339 {"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"},
340 {"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"},
341 {"timestamp", RPCArg::Type::NUM, RPCArg::Optional::NO, "Time from which to start rescanning the blockchain for this descriptor, in " + UNIX_EPOCH_TIME + "\n"
342 "Use the string \"now\" to substitute the current synced blockchain time.\n"
343 "\"now\" can be specified to bypass scanning, for outputs which are known to never have been used, and\n"
344 "0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest timestamp\n"
345 "of all descriptors being imported will be scanned as well as the mempool.",
346 RPCArgOptions{.type_str={"timestamp | \"now\"", "integer / string"}}
347 },
348 {"internal", RPCArg::Type::BOOL, RPCArg::Default{false}, "Whether matching outputs should be treated as not incoming payments (e.g. change)"},
349 {"label", RPCArg::Type::STR, RPCArg::Default{""}, "Label to assign to the address, only allowed with internal=false. Disabled for ranged descriptors"},
350 },
351 },
352 },
353 RPCArgOptions{.oneline_description="requests"}},
354 },
355 RPCResult{
356 RPCResult::Type::ARR, "", "Response is an array with the same size as the input that has the execution result",
357 {
358 {RPCResult::Type::OBJ, "", "",
359 {
360 {RPCResult::Type::BOOL, "success", ""},
361 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "",
362 {
363 {RPCResult::Type::STR, "", ""},
364 }},
365 {RPCResult::Type::OBJ, "error", /*optional=*/true, "",
366 {
367 {RPCResult::Type::NUM, "code", "JSONRPC error code"},
368 {RPCResult::Type::STR, "message", "JSONRPC error message"},
369 }},
370 }},
371 }
372 },
373 RPCExamples{
374 HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"internal\": true }, "
375 "{ \"desc\": \"<my descriptor 2>\", \"label\": \"example 2\", \"timestamp\": 1455191480 }]'") +
376 HelpExampleCli("importdescriptors", "'[{ \"desc\": \"<my descriptor>\", \"timestamp\":1455191478, \"active\": true, \"range\": [0,100], \"label\": \"<my bech32 wallet>\" }]'")
377 },
378 [](const RPCMethod& self, const JSONRPCRequest& main_request) -> UniValue
379 {
380 std::shared_ptr<CWallet> const pwallet = GetWalletForJSONRPCRequest(main_request);
381 if (!pwallet) return UniValue::VNULL;
382 CWallet& wallet{*pwallet};
383 384 WalletRescanReserver reserver(*pwallet);
385 if (!reserver.reserve(/*with_passphrase=*/true)) {
386 throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait.");
387 }
388 389 // Make sure the results are valid at least up to the most recent block
390 // the user could have gotten from another RPC command prior to now
391 wallet.BlockUntilSyncedToCurrentChain();
392 393 // Ensure that the wallet is not locked for the remainder of this RPC, as
394 // the passphrase is used to top up the keypool.
395 LOCK(pwallet->m_relock_mutex);
396 397 const UniValue& requests = main_request.params[0];
398 const int64_t minimum_timestamp = 1;
399 int64_t now = 0;
400 int64_t lowest_timestamp = 0;
401 bool rescan = false;
402 UniValue response(UniValue::VARR);
403 {
404 LOCK(pwallet->cs_wallet);
405 EnsureWalletIsUnlocked(*pwallet);
406 407 CHECK_NONFATAL(pwallet->chain().findBlock(pwallet->GetLastBlockHash(), FoundBlock().time(lowest_timestamp).mtpTime(now)));
408 409 // Get all timestamps and extract the lowest timestamp
410 for (const UniValue& request : requests.getValues()) {
411 // This throws an error if "timestamp" doesn't exist
412 const int64_t timestamp = std::max(GetImportTimestamp(request, now), minimum_timestamp);
413 const UniValue result = ProcessDescriptorImport(*pwallet, request, timestamp);
414 response.push_back(result);
415 416 if (lowest_timestamp > timestamp ) {
417 lowest_timestamp = timestamp;
418 }
419 420 // If we know the chain tip, and at least one request was successful then allow rescan
421 if (!rescan && result["success"].get_bool()) {
422 rescan = true;
423 }
424 }
425 pwallet->ConnectScriptPubKeyManNotifiers();
426 pwallet->RefreshAllTXOs();
427 }
428 429 // Rescan the blockchain using the lowest timestamp
430 if (rescan) {
431 int64_t scanned_time = pwallet->RescanFromTime(lowest_timestamp, reserver);
432 pwallet->ResubmitWalletTransactions(node::TxBroadcast::MEMPOOL_NO_BROADCAST, /*force=*/true);
433 434 if (pwallet->IsAbortingRescan()) {
435 throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
436 }
437 438 if (scanned_time > lowest_timestamp) {
439 std::vector<UniValue> results = response.getValues();
440 response.clear();
441 response.setArray();
442 443 // Compose the response
444 for (unsigned int i = 0; i < requests.size(); ++i) {
445 const UniValue& request = requests.getValues().at(i);
446 447 // If the descriptor timestamp is within the successfully scanned
448 // range, or if the import result already has an error set, let
449 // the result stand unmodified. Otherwise replace the result
450 // with an error message.
451 if (scanned_time <= GetImportTimestamp(request, now) || results.at(i).exists("error")) {
452 response.push_back(results.at(i));
453 } else {
454 std::string error_msg{strprintf("Rescan failed for descriptor with timestamp %d. There "
455 "was an error reading a block from time %d, which is after or within %d seconds "
456 "of key creation, and could contain transactions pertaining to the desc. As a "
457 "result, transactions and coins using this desc may not appear in the wallet.",
458 GetImportTimestamp(request, now), scanned_time - TIMESTAMP_WINDOW - 1, TIMESTAMP_WINDOW)};
459 if (pwallet->chain().havePruned()) {
460 error_msg += strprintf(" This error could be caused by pruning or data corruption "
461 "(see bitcoind log for details) and could be dealt with by downloading and "
462 "rescanning the relevant blocks (see -reindex option and rescanblockchain RPC).");
463 } else if (pwallet->chain().hasAssumedValidChain()) {
464 error_msg += strprintf(" This error is likely caused by an in-progress assumeutxo "
465 "background sync. Check logs or getchainstates RPC for assumeutxo background "
466 "sync progress and try again later.");
467 } else {
468 error_msg += strprintf(" This error could potentially caused by data corruption. If "
469 "the issue persists you may want to reindex (see -reindex option).");
470 }
471 472 UniValue result = UniValue(UniValue::VOBJ);
473 result.pushKV("success", UniValue(false));
474 result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, error_msg));
475 response.push_back(std::move(result));
476 }
477 }
478 }
479 }
480 481 return response;
482 },
483 };
484 }
485 486 RPCMethod listdescriptors()
487 {
488 return RPCMethod{
489 "listdescriptors",
490 "List all descriptors present in a wallet.\n",
491 {
492 {"private", RPCArg::Type::BOOL, RPCArg::Default{false}, "Show private descriptors."}
493 },
494 RPCResult{RPCResult::Type::OBJ, "", "", {
495 {RPCResult::Type::STR, "wallet_name", "Name of wallet this operation was performed on"},
496 {RPCResult::Type::ARR, "descriptors", "Array of descriptor objects (sorted by descriptor string representation)",
497 {
498 {RPCResult::Type::OBJ, "", "", {
499 {RPCResult::Type::STR, "desc", "Descriptor string representation"},
500 {RPCResult::Type::NUM, "timestamp", "The creation time of the descriptor"},
501 {RPCResult::Type::BOOL, "active", "Whether this descriptor is currently used to generate new addresses"},
502 {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"},
503 {RPCResult::Type::ARR_FIXED, "range", /*optional=*/true, "Defined only for ranged descriptors", {
504 {RPCResult::Type::NUM, "", "Range start inclusive"},
505 {RPCResult::Type::NUM, "", "Range end inclusive"},
506 }},
507 {RPCResult::Type::NUM, "next", /*optional=*/true, "Same as next_index field. Kept for compatibility reason."},
508 {RPCResult::Type::NUM, "next_index", /*optional=*/true, "The next index to generate addresses from; defined only for ranged descriptors"},
509 }},
510 }}
511 }},
512 RPCExamples{
513 HelpExampleCli("listdescriptors", "") + HelpExampleRpc("listdescriptors", "")
514 + HelpExampleCli("listdescriptors", "true") + HelpExampleRpc("listdescriptors", "true")
515 },
516 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
517 {
518 const std::shared_ptr<const CWallet> wallet = GetWalletForJSONRPCRequest(request);
519 if (!wallet) return UniValue::VNULL;
520 521 const bool priv = !request.params[0].isNull() && request.params[0].get_bool();
522 if (wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && priv) {
523 throw JSONRPCError(RPC_WALLET_ERROR, "Can't get private descriptor string for watch-only wallets");
524 }
525 if (priv) {
526 EnsureWalletIsUnlocked(*wallet);
527 }
528 529 LOCK(wallet->cs_wallet);
530 util::Expected<std::vector<WalletDescInfo>, std::string> exported = ExportDescriptors(*wallet, priv);
531 if (!exported) {
532 throw JSONRPCError(RPC_WALLET_ERROR, exported.error());
533 }
534 std::vector<WalletDescInfo> wallet_descriptors = *exported;
535 536 std::sort(wallet_descriptors.begin(), wallet_descriptors.end(), [](const auto& a, const auto& b) {
537 return a.descriptor < b.descriptor;
538 });
539 540 UniValue descriptors(UniValue::VARR);
541 for (const WalletDescInfo& info : wallet_descriptors) {
542 UniValue spk(UniValue::VOBJ);
543 spk.pushKV("desc", info.descriptor);
544 spk.pushKV("timestamp", info.creation_time);
545 spk.pushKV("active", info.active);
546 if (info.internal.has_value()) {
547 spk.pushKV("internal", info.internal.value());
548 }
549 if (info.range.has_value()) {
550 UniValue range(UniValue::VARR);
551 range.push_back(info.range->first);
552 range.push_back(info.range->second - 1);
553 spk.pushKV("range", std::move(range));
554 spk.pushKV("next", info.next_index);
555 spk.pushKV("next_index", info.next_index);
556 }
557 descriptors.push_back(std::move(spk));
558 }
559 560 UniValue response(UniValue::VOBJ);
561 response.pushKV("wallet_name", wallet->GetName());
562 response.pushKV("descriptors", std::move(descriptors));
563 564 return response;
565 },
566 };
567 }
568 569 RPCMethod backupwallet()
570 {
571 return RPCMethod{
572 "backupwallet",
573 "Safely copies the current wallet file to the specified destination, which can either be a directory or a path with a filename.\n",
574 {
575 {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"},
576 },
577 RPCResult{RPCResult::Type::NONE, "", ""},
578 RPCExamples{
579 HelpExampleCli("backupwallet", "\"backup.dat\"")
580 + HelpExampleRpc("backupwallet", "\"backup.dat\"")
581 },
582 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
583 {
584 const std::shared_ptr<const CWallet> pwallet = GetWalletForJSONRPCRequest(request);
585 if (!pwallet) return UniValue::VNULL;
586 587 // Make sure the results are valid at least up to the most recent block
588 // the user could have gotten from another RPC command prior to now
589 pwallet->BlockUntilSyncedToCurrentChain();
590 591 LOCK(pwallet->cs_wallet);
592 593 std::string strDest = request.params[0].get_str();
594 if (!pwallet->BackupWallet(strDest)) {
595 throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!");
596 }
597 598 return UniValue::VNULL;
599 },
600 };
601 }
602 603 604 RPCMethod restorewallet()
605 {
606 return RPCMethod{
607 "restorewallet",
608 "Restores and loads a wallet from backup.\n"
609 "\nThe rescan is significantly faster if block filters are available"
610 "\n(using startup option \"-blockfilterindex=1\").\n",
611 {
612 {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name that will be applied to the restored wallet"},
613 {"backup_file", RPCArg::Type::STR, RPCArg::Optional::NO, "The backup file that will be used to restore the wallet."},
614 {"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."},
615 },
616 RPCResult{
617 RPCResult::Type::OBJ, "", "",
618 {
619 {RPCResult::Type::STR, "name", "The wallet name if restored successfully."},
620 {RPCResult::Type::ARR, "warnings", /*optional=*/true, "Warning messages, if any, related to restoring and loading the wallet.",
621 {
622 {RPCResult::Type::STR, "", ""},
623 }},
624 }
625 },
626 RPCExamples{
627 HelpExampleCli("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
628 + HelpExampleRpc("restorewallet", "\"testwallet\" \"home\\backups\\backup-file.bak\"")
629 + HelpExampleCliNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
630 + HelpExampleRpcNamed("restorewallet", {{"wallet_name", "testwallet"}, {"backup_file", "home\\backups\\backup-file.bak\""}, {"load_on_startup", true}})
631 },
632 [](const RPCMethod& self, const JSONRPCRequest& request) -> UniValue
633 {
634 635 WalletContext& context = EnsureWalletContext(request.context);
636 637 auto backup_file = fs::u8path(request.params[1].get_str());
638 639 std::string wallet_name = request.params[0].get_str();
640 641 std::optional<bool> load_on_start = request.params[2].isNull() ? std::nullopt : std::optional<bool>(request.params[2].get_bool());
642 643 DatabaseStatus status;
644 bilingual_str error;
645 std::vector<bilingual_str> warnings;
646 647 const std::shared_ptr<CWallet> wallet = RestoreWallet(context, backup_file, wallet_name, load_on_start, status, error, warnings);
648 649 HandleWalletError(wallet, status, error);
650 651 UniValue obj(UniValue::VOBJ);
652 obj.pushKV("name", wallet->GetName());
653 PushWarnings(warnings, obj);
654 655 return obj;
656 657 },
658 };
659 }
660 } // namespace wallet
661