backup.cpp raw

   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