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