LCOV - code coverage report
Current view: top level - src/wallet/rpc - backup.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 1183 1423 83.1 %
Date: 2026-06-25 07:23:43 Functions: 58 59 98.3 %

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

Generated by: LCOV version 1.16