<commit_before>// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chain.h>
#include <config.h>
#include <core_io.h>
#include <key_io.h>
#include <merkleblock.h>
#include <rpc/server.h>
#include <script/script.h>
#include <script/standard.h>
#include <sync.h>
#include <util/system.h>
#include <util/time.h>
#include <validation.h>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>

#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include <univalue.h>

#include <cstdint>
#include <fstream>
#include <iostream>

static int64_t DecodeDumpTime(const std::string &str) {
    static const boost::posix_time::ptime epoch =
        boost::posix_time::from_time_t(0);
    static const std::locale loc(
        std::locale::classic(),
        new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ"));
    std::istringstream iss(str);
    iss.imbue(loc);
    boost::posix_time::ptime ptime(boost::date_time::not_a_date_time);
    iss >> ptime;
    if (ptime.is_not_a_date_time()) return 0;
    return (ptime - epoch).total_seconds();
}

static std::string EncodeDumpString(const std::string &str) {
    std::stringstream ret;
    for (const uint8_t c : str) {
        if (c <= 32 || c >= 128 || c == '%') {
            ret << '%' << HexStr(&c, &c + 1);
        } else {
            ret << c;
        }
    }
    return ret.str();
}

static std::string DecodeDumpString(const std::string &str) {
    std::stringstream ret;
    for (unsigned int pos = 0; pos < str.length(); pos++) {
        uint8_t c = str[pos];
        if (c == '%' && pos + 2 < str.length()) {
            c = (((str[pos + 1] >> 6) * 9 + ((str[pos + 1] - '0') & 15)) << 4) |
                ((str[pos + 2] >> 6) * 9 + ((str[pos + 2] - '0') & 15));
            pos += 2;
        }
        ret << c;
    }
    return ret.str();
}

bool GetWalletAddressesForKey(const Config &config, CWallet *const pwallet,
                              const CKeyID &keyid, std::string &strAddr,
                              std::string &strLabel) {
    bool fLabelFound = false;
    CKey key;
    pwallet->GetKey(keyid, key);
    for (const auto &dest : GetAllDestinationsForKey(key.GetPubKey())) {
        if (pwallet->mapAddressBook.count(dest)) {
            if (!strAddr.empty()) {
                strAddr += ",";
            }
            strAddr += EncodeDestination(dest, config);
            strLabel = EncodeDumpString(pwallet->mapAddressBook[dest].name);
            fLabelFound = true;
        }
    }
    if (!fLabelFound) {
        strAddr = EncodeDestination(
            GetDestinationForKey(key.GetPubKey(),
                                 pwallet->m_default_address_type),
            config);
    }
    return fLabelFound;
}

static const int64_t TIMESTAMP_MIN = 0;

static void RescanWallet(CWallet &wallet, const WalletRescanReserver &reserver,
                         int64_t time_begin = TIMESTAMP_MIN,
                         bool update = true) {
    int64_t scanned_time = wallet.RescanFromTime(time_begin, reserver, update);
    if (wallet.IsAbortingRescan()) {
        throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
    } else if (scanned_time > time_begin) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Rescan was unable to fully rescan the blockchain. "
                           "Some transactions may be missing.");
    }
}

UniValue importprivkey(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() < 1 ||
        request.params.size() > 3) {
        throw std::runtime_error(
            "importprivkey \"privkey\" ( \"label\" ) ( rescan )\n"
            "\nAdds a private key (as returned by dumpprivkey) to your wallet. "
            "Requires a new wallet backup.\n"
            "Hint: use importmulti to import more than one private key.\n"
            "\nArguments:\n"
            "1. \"privkey\"   (string, required) The private key (see "
            "dumpprivkey)\n"
            "2. \"label\"            (string, optional, default=\"\") An "
            "optional label\n"
            "3. rescan               (boolean, optional, default=true) Rescan "
            "the wallet for transactions\n"
            "\nNote: This call can take minutes to complete if rescan is true, "
            "during that time, other rpc calls\n"
            "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"
            "\nExamples:\n"
            "\nDump a private key\n" +
            HelpExampleCli("dumpprivkey", "\"myaddress\"") +
            "\nImport the private key with rescan\n" +
            HelpExampleCli("importprivkey", "\"mykey\"") +
            "\nImport using a label and without rescan\n" +
            HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
            "\nImport using default blank label and without rescan\n" +
            HelpExampleCli("importprivkey", "\"mykey\" \"\" false") +
            "\nAs a JSON-RPC call\n" +
            HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false"));
    }

    if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Cannot import private keys to a wallet with "
                           "private keys disabled");
    }

    WalletRescanReserver reserver(pwallet);
    bool fRescan = true;
    {
        LOCK2(cs_main, pwallet->cs_wallet);

        EnsureWalletIsUnlocked(pwallet);

        std::string strSecret = request.params[0].get_str();
        std::string strLabel = "";
        if (!request.params[1].isNull()) {
            strLabel = request.params[1].get_str();
        }

        // Whether to perform rescan after import
        if (!request.params[2].isNull()) {
            fRescan = request.params[2].get_bool();
        }

        if (fRescan && fPruneMode) {
            throw JSONRPCError(RPC_WALLET_ERROR,
                               "Rescan is disabled in pruned mode");
        }

        if (fRescan && !reserver.reserve()) {
            throw JSONRPCError(RPC_WALLET_ERROR,
                               "Wallet is currently rescanning. Abort existing "
                               "rescan or wait.");
        }

        CKey key = DecodeSecret(strSecret);
        if (!key.IsValid()) {
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                               "Invalid private key encoding");
        }

        CPubKey pubkey = key.GetPubKey();
        assert(key.VerifyPubKey(pubkey));
        CKeyID vchAddress = pubkey.GetID();
        {
            pwallet->MarkDirty();
            // We don't know which corresponding address will be used; label
            // them all
            for (const auto &dest : GetAllDestinationsForKey(pubkey)) {
                pwallet->SetAddressBook(dest, strLabel, "receive");
            }

            // Don't throw error in case a key is already there
            if (pwallet->HaveKey(vchAddress)) {
                return NullUniValue;
            }

            pwallet->LearnAllRelatedScripts(pubkey);

            // whenever a key is imported, we need to scan the whole chain
            pwallet->UpdateTimeFirstKey(1);
            pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;

            if (!pwallet->AddKeyPubKey(key, pubkey)) {
                throw JSONRPCError(RPC_WALLET_ERROR,
                                   "Error adding key to wallet");
            }
        }
    }
    if (fRescan) {
        RescanWallet(*pwallet, reserver);
    }

    return NullUniValue;
}

UniValue abortrescan(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() > 0) {
        throw std::runtime_error("abortrescan\n"
                                 "\nStops current wallet rescan triggered by "
                                 "an RPC call, e.g. by an importprivkey call.\n"
                                 "\nExamples:\n"
                                 "\nImport a private key\n" +
                                 HelpExampleCli("importprivkey", "\"mykey\"") +
                                 "\nAbort the running wallet rescan\n" +
                                 HelpExampleCli("abortrescan", "") +
                                 "\nAs a JSON-RPC call\n" +
                                 HelpExampleRpc("abortrescan", ""));
    }

    if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) {
        return false;
    }
    pwallet->AbortRescan();
    return true;
}

static void ImportAddress(CWallet *, const CTxDestination &dest,
                          const std::string &strLabel);
static void ImportScript(CWallet *const pwallet, const CScript &script,
                         const std::string &strLabel, bool isRedeemScript)
    EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
    if (!isRedeemScript && ::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
        throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the "
                                             "private key for this address or "
                                             "script");
    }

    pwallet->MarkDirty();

    if (!pwallet->HaveWatchOnly(script) &&
        !pwallet->AddWatchOnly(script, 0 /* nCreateTime */)) {
        throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
    }

    if (isRedeemScript) {
        if (!pwallet->HaveCScript(script) && !pwallet->AddCScript(script)) {
            throw JSONRPCError(RPC_WALLET_ERROR,
                               "Error adding p2sh redeemScript to wallet");
        }
        ImportAddress(pwallet, CScriptID(script), strLabel);
    } else {
        CTxDestination destination;
        if (ExtractDestination(script, destination)) {
            pwallet->SetAddressBook(destination, strLabel, "receive");
        }
    }
}

static void ImportAddress(CWallet *const pwallet, const CTxDestination &dest,
                          const std::string &strLabel)
    EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
    CScript script = GetScriptForDestination(dest);
    ImportScript(pwallet, script, strLabel, false);
    // add to address book or update label
    if (IsValidDestination(dest)) {
        pwallet->SetAddressBook(dest, strLabel, "receive");
    }
}

UniValue importaddress(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() < 1 ||
        request.params.size() > 4) {
        throw std::runtime_error(
            "importaddress \"address\" ( \"label\" rescan p2sh )\n"
            "\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"
            "\nArguments:\n"
            "1. \"address\"          (string, required) The Bitcoin address "
            "(or hex-encoded script)\n"
            "2. \"label\"            (string, optional, default=\"\") An "
            "optional label\n"
            "3. rescan               (boolean, optional, default=true) Rescan "
            "the wallet for transactions\n"
            "4. p2sh                 (boolean, optional, default=false) Add "
            "the P2SH version of the script as well\n"
            "\nNote: This call can take minutes to complete if rescan is true, "
            "during that time, other rpc calls\n"
            "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"
            "If you have the full public key, you should call importpubkey "
            "instead of this.\n"
            "\nNote: If you import a non-standard raw script in hex form, "
            "outputs sending to it will be treated\n"
            "as change, and not show up in many RPCs.\n"
            "\nExamples:\n"
            "\nImport an address with rescan\n" +
            HelpExampleCli("importaddress", "\"myaddress\"") +
            "\nImport using a label without rescan\n" +
            HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") +
            "\nAs a JSON-RPC call\n" +
            HelpExampleRpc("importaddress",
                           "\"myaddress\", \"testing\", false"));
    }

    std::string strLabel;
    if (!request.params[1].isNull()) {
        strLabel = request.params[1].get_str();
    }

    // Whether to perform rescan after import
    bool fRescan = true;
    if (!request.params[2].isNull()) {
        fRescan = request.params[2].get_bool();
    }

    if (fRescan && fPruneMode) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Rescan is disabled in pruned mode");
    }

    WalletRescanReserver reserver(pwallet);
    if (fRescan && !reserver.reserve()) {
        throw JSONRPCError(
            RPC_WALLET_ERROR,
            "Wallet is currently rescanning. Abort existing rescan or wait.");
    }

    // Whether to import a p2sh version, too
    bool fP2SH = false;
    if (!request.params[3].isNull()) {
        fP2SH = request.params[3].get_bool();
    }

    {
        LOCK2(cs_main, pwallet->cs_wallet);

        CTxDestination dest = DecodeDestination(request.params[0].get_str(),
                                                config.GetChainParams());
        if (IsValidDestination(dest)) {
            if (fP2SH) {
                throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                   "Cannot use the p2sh flag with an address - "
                                   "use a script instead");
            }
            ImportAddress(pwallet, dest, strLabel);
        } else if (IsHex(request.params[0].get_str())) {
            std::vector<uint8_t> data(ParseHex(request.params[0].get_str()));
            ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel,
                         fP2SH);
        } else {
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                               "Invalid Bitcoin address or script");
        }
    }
    if (fRescan) {
        RescanWallet(*pwallet, reserver);
        pwallet->ReacceptWalletTransactions();
    }

    return NullUniValue;
}

UniValue importprunedfunds(const Config &config,
                           const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() != 2) {
        throw std::runtime_error(
            "importprunedfunds\n"
            "\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"
            "\nArguments:\n"
            "1. \"rawtransaction\" (string, required) A raw transaction in hex "
            "funding an already-existing address in wallet\n"
            "2. \"txoutproof\"     (string, required) The hex output from "
            "gettxoutproof that contains the transaction\n");
    }

    CMutableTransaction tx;
    if (!DecodeHexTx(tx, request.params[0].get_str())) {
        throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
    }
    uint256 txid = tx.GetId();
    CWalletTx wtx(pwallet, MakeTransactionRef(std::move(tx)));

    CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK,
                     PROTOCOL_VERSION);
    CMerkleBlock merkleBlock;
    ssMB >> merkleBlock;

    // Search partial merkle tree in proof for our transaction and index in
    // valid block
    std::vector<uint256> vMatch;
    std::vector<size_t> vIndex;
    size_t txnIndex = 0;
    if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) ==
        merkleBlock.header.hashMerkleRoot) {
        LOCK(cs_main);
        const CBlockIndex *pindex =
            LookupBlockIndex(merkleBlock.header.GetHash());
        if (!pindex || !chainActive.Contains(pindex)) {
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                               "Block not found in chain");
        }

        std::vector<uint256>::const_iterator it;
        if ((it = std::find(vMatch.begin(), vMatch.end(), txid)) ==
            vMatch.end()) {
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                               "Transaction given doesn't exist in proof");
        }

        txnIndex = vIndex[it - vMatch.begin()];
    } else {
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                           "Something wrong with merkleblock");
    }

    wtx.nIndex = txnIndex;
    wtx.hashBlock = merkleBlock.header.GetHash();

    LOCK2(cs_main, pwallet->cs_wallet);

    if (pwallet->IsMine(*wtx.tx)) {
        pwallet->AddToWallet(wtx, false);
        return NullUniValue;
    }

    throw JSONRPCError(
        RPC_INVALID_ADDRESS_OR_KEY,
        "No addresses in wallet correspond to included transaction");
}

UniValue removeprunedfunds(const Config &config,
                           const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() != 1) {
        throw std::runtime_error(
            "removeprunedfunds \"txid\"\n"
            "\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"
            "\nArguments:\n"
            "1. \"txid\"           (string, required) The hex-encoded id of "
            "the transaction you are deleting\n"
            "\nExamples:\n" +
            HelpExampleCli("removeprunedfunds", "\"a8d0c0184dde994a09ec054286f1"
                                                "ce581bebf46446a512166eae762873"
                                                "4ea0a5\"") +
            "\nAs a JSON-RPC call\n" +
            HelpExampleRpc("removeprunedfunds",
                           "\"a8d0c0184dde994a09ec054286f1ce581bebf46446a512166"
                           "eae7628734ea0a5\""));
    }

    LOCK2(cs_main, pwallet->cs_wallet);

    TxId txid;
    txid.SetHex(request.params[0].get_str());
    std::vector<TxId> txIds;
    txIds.push_back(txid);
    std::vector<TxId> txIdsOut;

    if (pwallet->ZapSelectTx(txIds, txIdsOut) != DBErrors::LOAD_OK) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Could not properly delete the transaction.");
    }

    if (txIdsOut.empty()) {
        throw JSONRPCError(RPC_INVALID_PARAMETER,
                           "Transaction does not exist in wallet.");
    }

    return NullUniValue;
}

UniValue importpubkey(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() < 1 ||
        request.params.size() > 4) {
        throw std::runtime_error(
            "importpubkey \"pubkey\" ( \"label\" rescan )\n"
            "\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"
            "\nArguments:\n"
            "1. \"pubkey\"           (string, required) The hex-encoded public "
            "key\n"
            "2. \"label\"            (string, optional, default=\"\") An "
            "optional label\n"
            "3. rescan               (boolean, optional, default=true) Rescan "
            "the wallet for transactions\n"
            "\nNote: This call can take minutes to complete if rescan is true, "
            "during that time, other rpc calls\n"
            "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"
            "\nExamples:\n"
            "\nImport a public key with rescan\n" +
            HelpExampleCli("importpubkey", "\"mypubkey\"") +
            "\nImport using a label without rescan\n" +
            HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") +
            "\nAs a JSON-RPC call\n" +
            HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false"));
    }

    std::string strLabel;
    if (!request.params[1].isNull()) {
        strLabel = request.params[1].get_str();
    }

    // Whether to perform rescan after import
    bool fRescan = true;
    if (!request.params[2].isNull()) {
        fRescan = request.params[2].get_bool();
    }

    if (fRescan && fPruneMode) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Rescan is disabled in pruned mode");
    }

    WalletRescanReserver reserver(pwallet);
    if (fRescan && !reserver.reserve()) {
        throw JSONRPCError(
            RPC_WALLET_ERROR,
            "Wallet is currently rescanning. Abort existing rescan or wait.");
    }

    if (!IsHex(request.params[0].get_str())) {
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                           "Pubkey must be a hex string");
    }
    std::vector<uint8_t> data(ParseHex(request.params[0].get_str()));
    CPubKey pubKey(data.begin(), data.end());
    if (!pubKey.IsFullyValid()) {
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                           "Pubkey is not a valid public key");
    }

    {
        LOCK2(cs_main, pwallet->cs_wallet);

        for (const auto &dest : GetAllDestinationsForKey(pubKey)) {
            ImportAddress(pwallet, dest, strLabel);
        }
        ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
        pwallet->LearnAllRelatedScripts(pubKey);
    }
    if (fRescan) {
        RescanWallet(*pwallet, reserver);
        pwallet->ReacceptWalletTransactions();
    }

    return NullUniValue;
}

UniValue importwallet(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() != 1) {
        throw std::runtime_error(
            "importwallet \"filename\"\n"
            "\nImports keys from a wallet dump file (see dumpwallet). Requires "
            "a new wallet backup to include imported keys.\n"
            "\nArguments:\n"
            "1. \"filename\"    (string, required) The wallet file\n"
            "\nExamples:\n"
            "\nDump the wallet\n" +
            HelpExampleCli("dumpwallet", "\"test\"") + "\nImport the wallet\n" +
            HelpExampleCli("importwallet", "\"test\"") +
            "\nImport using the json rpc call\n" +
            HelpExampleRpc("importwallet", "\"test\""));
    }

    if (fPruneMode) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Importing wallets is disabled in pruned mode");
    }

    WalletRescanReserver reserver(pwallet);
    if (!reserver.reserve()) {
        throw JSONRPCError(
            RPC_WALLET_ERROR,
            "Wallet is currently rescanning. Abort existing rescan or wait.");
    }

    int64_t nTimeBegin = 0;
    bool fGood = true;
    {
        LOCK2(cs_main, pwallet->cs_wallet);

        EnsureWalletIsUnlocked(pwallet);

        std::ifstream file;
        file.open(request.params[0].get_str().c_str(),
                  std::ios::in | std::ios::ate);
        if (!file.is_open()) {
            throw JSONRPCError(RPC_INVALID_PARAMETER,
                               "Cannot open wallet dump file");
        }
        nTimeBegin = chainActive.Tip()->GetBlockTime();

        int64_t nFilesize = std::max((int64_t)1, (int64_t)file.tellg());
        file.seekg(0, file.beg);

        // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because
        // pwallet.ShowProgress has a cancel button tied to AbortRescan which we
        // don't want for this progress bar showing the import progress.
        // uiInterface.ShowProgress does not have a cancel button.

        // show progress dialog in GUI
        uiInterface.ShowProgress(
            strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 0,
            false);
        std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys;
        std::vector<std::pair<CScript, int64_t>> scripts;
        while (file.good()) {
            uiInterface.ShowProgress(
                "",
                std::max(1, std::min<int>(50, 100 * double(file.tellg()) /
                                                  double(nFilesize))),
                false);
            std::string line;
            std::getline(file, line);
            if (line.empty() || line[0] == '#') {
                continue;
            }

            std::vector<std::string> vstr;
            boost::split(vstr, line, boost::is_any_of(" "));
            if (vstr.size() < 2) {
                continue;
            }
            CKey key = DecodeSecret(vstr[0]);
            if (key.IsValid()) {
                int64_t nTime = DecodeDumpTime(vstr[1]);
                std::string strLabel;
                bool fLabel = true;
                for (size_t nStr = 2; nStr < vstr.size(); nStr++) {
                    if (vstr[nStr].front() == '#') {
                        break;
                    }
                    if (vstr[nStr] == "change=1") {
                        fLabel = false;
                    }
                    if (vstr[nStr] == "reserve=1") {
                        fLabel = false;
                    }
                    if (vstr[nStr].substr(0, 6) == "label=") {
                        strLabel = DecodeDumpString(vstr[nStr].substr(6));
                        fLabel = true;
                    }
                }
                keys.push_back(std::make_tuple(key, nTime, fLabel, strLabel));
            } else if (IsHex(vstr[0])) {
                std::vector<uint8_t> vData(ParseHex(vstr[0]));
                CScript script = CScript(vData.begin(), vData.end());
                int64_t birth_time = DecodeDumpTime(vstr[1]);
                scripts.push_back(
                    std::pair<CScript, int64_t>(script, birth_time));
            }
        }
        file.close();
        // We now know whether we are importing private keys, so we can error if
        // private keys are disabled
        if (keys.size() > 0 &&
            pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
            // hide progress dialog in GUI
            uiInterface.ShowProgress("", 100, false);
            throw JSONRPCError(
                RPC_WALLET_ERROR,
                "Importing wallets is disabled when private keys are disabled");
        }
        double total = double(keys.size() + scripts.size());
        double progress = 0;
        for (const auto &key_tuple : keys) {
            uiInterface.ShowProgress(
                "",
                std::max(50, std::min<int>(75, (progress / total) * 100) + 50),
                false);
            const CKey &key = std::get<0>(key_tuple);
            int64_t time = std::get<1>(key_tuple);
            bool has_label = std::get<2>(key_tuple);
            std::string label = std::get<3>(key_tuple);

            CPubKey pubkey = key.GetPubKey();
            assert(key.VerifyPubKey(pubkey));
            CKeyID keyid = pubkey.GetID();
            if (pwallet->HaveKey(keyid)) {
                pwallet->WalletLogPrintf(
                    "Skipping import of %s (key already present)\n",
                    EncodeDestination(keyid, config));
                continue;
            }
            pwallet->WalletLogPrintf("Importing %s...\n",
                                     EncodeDestination(keyid, config));
            if (!pwallet->AddKeyPubKey(key, pubkey)) {
                fGood = false;
                continue;
            }
            pwallet->mapKeyMetadata[keyid].nCreateTime = time;
            if (has_label) {
                pwallet->SetAddressBook(keyid, label, "receive");
            }
            nTimeBegin = std::min(nTimeBegin, time);
            progress++;
        }
        for (const auto &script_pair : scripts) {
            uiInterface.ShowProgress(
                "",
                std::max(50, std::min<int>(75, (progress / total) * 100) + 50),
                false);
            const CScript &script = script_pair.first;
            int64_t time = script_pair.second;
            CScriptID id(script);
            if (pwallet->HaveCScript(id)) {
                pwallet->WalletLogPrintf(
                    "Skipping import of %s (script already present)\n",
                    HexStr(script));
                continue;
            }
            if (!pwallet->AddCScript(script)) {
                pwallet->WalletLogPrintf("Error importing script %s\n",
                                         HexStr(script));
                fGood = false;
                continue;
            }
            if (time > 0) {
                pwallet->m_script_metadata[id].nCreateTime = time;
                nTimeBegin = std::min(nTimeBegin, time);
            }
            progress++;
        }
        // hide progress dialog in GUI
        uiInterface.ShowProgress("", 100, false);
        pwallet->UpdateTimeFirstKey(nTimeBegin);
    }
    // hide progress dialog in GUI
    uiInterface.ShowProgress("", 100, false);<commit_msg>Nits in rpcdump<commit_after>// Copyright (c) 2009-2016 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <chain.h>
#include <config.h>
#include <core_io.h>
#include <key_io.h>
#include <merkleblock.h>
#include <rpc/server.h>
#include <script/script.h>
#include <script/standard.h>
#include <sync.h>
#include <util/system.h>
#include <util/time.h>
#include <validation.h>
#include <wallet/rpcwallet.h>
#include <wallet/wallet.h>

#include <boost/algorithm/string.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

#include <univalue.h>

#include <cstdint>
#include <fstream>
#include <iostream>

static int64_t DecodeDumpTime(const std::string &str) {
    static const boost::posix_time::ptime epoch =
        boost::posix_time::from_time_t(0);
    static const std::locale loc(
        std::locale::classic(),
        new boost::posix_time::time_input_facet("%Y-%m-%dT%H:%M:%SZ"));
    std::istringstream iss(str);
    iss.imbue(loc);
    boost::posix_time::ptime ptime(boost::date_time::not_a_date_time);
    iss >> ptime;
    if (ptime.is_not_a_date_time()) return 0;
    return (ptime - epoch).total_seconds();
}

static std::string EncodeDumpString(const std::string &str) {
    std::stringstream ret;
    for (const uint8_t c : str) {
        if (c <= 32 || c >= 128 || c == '%') {
            ret << '%' << HexStr(&c, &c + 1);
        } else {
            ret << c;
        }
    }
    return ret.str();
}

static std::string DecodeDumpString(const std::string &str) {
    std::stringstream ret;
    for (unsigned int pos = 0; pos < str.length(); pos++) {
        uint8_t c = str[pos];
        if (c == '%' && pos + 2 < str.length()) {
            c = (((str[pos + 1] >> 6) * 9 + ((str[pos + 1] - '0') & 15)) << 4) |
                ((str[pos + 2] >> 6) * 9 + ((str[pos + 2] - '0') & 15));
            pos += 2;
        }
        ret << c;
    }
    return ret.str();
}

bool GetWalletAddressesForKey(const Config &config, CWallet *const pwallet,
                              const CKeyID &keyid, std::string &strAddr,
                              std::string &strLabel) {
    bool fLabelFound = false;
    CKey key;
    pwallet->GetKey(keyid, key);
    for (const auto &dest : GetAllDestinationsForKey(key.GetPubKey())) {
        if (pwallet->mapAddressBook.count(dest)) {
            if (!strAddr.empty()) {
                strAddr += ",";
            }
            strAddr += EncodeDestination(dest, config);
            strLabel = EncodeDumpString(pwallet->mapAddressBook[dest].name);
            fLabelFound = true;
        }
    }
    if (!fLabelFound) {
        strAddr = EncodeDestination(
            GetDestinationForKey(key.GetPubKey(),
                                 pwallet->m_default_address_type),
            config);
    }
    return fLabelFound;
}

static const int64_t TIMESTAMP_MIN = 0;

static void RescanWallet(CWallet &wallet, const WalletRescanReserver &reserver,
                         int64_t time_begin = TIMESTAMP_MIN,
                         bool update = true) {
    int64_t scanned_time = wallet.RescanFromTime(time_begin, reserver, update);
    if (wallet.IsAbortingRescan()) {
        throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted by user.");
    } else if (scanned_time > time_begin) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Rescan was unable to fully rescan the blockchain. "
                           "Some transactions may be missing.");
    }
}

UniValue importprivkey(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() < 1 ||
        request.params.size() > 3) {
        throw std::runtime_error(
            "importprivkey \"privkey\" ( \"label\" ) ( rescan )\n"
            "\nAdds a private key (as returned by dumpprivkey) to your wallet. "
            "Requires a new wallet backup.\n"
            "Hint: use importmulti to import more than one private key.\n"
            "\nArguments:\n"
            "1. \"privkey\"   (string, required) The private key (see "
            "dumpprivkey)\n"
            "2. \"label\"            (string, optional, default=\"\") An "
            "optional label\n"
            "3. rescan               (boolean, optional, default=true) Rescan "
            "the wallet for transactions\n"
            "\nNote: This call can take minutes to complete if rescan is true, "
            "during that time, other rpc calls\n"
            "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"
            "\nExamples:\n"
            "\nDump a private key\n" +
            HelpExampleCli("dumpprivkey", "\"myaddress\"") +
            "\nImport the private key with rescan\n" +
            HelpExampleCli("importprivkey", "\"mykey\"") +
            "\nImport using a label and without rescan\n" +
            HelpExampleCli("importprivkey", "\"mykey\" \"testing\" false") +
            "\nImport using default blank label and without rescan\n" +
            HelpExampleCli("importprivkey", "\"mykey\" \"\" false") +
            "\nAs a JSON-RPC call\n" +
            HelpExampleRpc("importprivkey", "\"mykey\", \"testing\", false"));
    }

    if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Cannot import private keys to a wallet with "
                           "private keys disabled");
    }

    WalletRescanReserver reserver(pwallet);
    bool fRescan = true;
    {
        LOCK2(cs_main, pwallet->cs_wallet);

        EnsureWalletIsUnlocked(pwallet);

        std::string strSecret = request.params[0].get_str();
        std::string strLabel = "";
        if (!request.params[1].isNull()) {
            strLabel = request.params[1].get_str();
        }

        // Whether to perform rescan after import
        if (!request.params[2].isNull()) {
            fRescan = request.params[2].get_bool();
        }

        if (fRescan && fPruneMode) {
            throw JSONRPCError(RPC_WALLET_ERROR,
                               "Rescan is disabled in pruned mode");
        }

        if (fRescan && !reserver.reserve()) {
            throw JSONRPCError(RPC_WALLET_ERROR,
                               "Wallet is currently rescanning. Abort existing "
                               "rescan or wait.");
        }

        CKey key = DecodeSecret(strSecret);
        if (!key.IsValid()) {
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                               "Invalid private key encoding");
        }

        CPubKey pubkey = key.GetPubKey();
        assert(key.VerifyPubKey(pubkey));
        CKeyID vchAddress = pubkey.GetID();
        {
            pwallet->MarkDirty();
            // We don't know which corresponding address will be used; label
            // them all
            for (const auto &dest : GetAllDestinationsForKey(pubkey)) {
                pwallet->SetAddressBook(dest, strLabel, "receive");
            }

            // Don't throw error in case a key is already there
            if (pwallet->HaveKey(vchAddress)) {
                return NullUniValue;
            }

            pwallet->LearnAllRelatedScripts(pubkey);

            // whenever a key is imported, we need to scan the whole chain
            pwallet->UpdateTimeFirstKey(1);
            pwallet->mapKeyMetadata[vchAddress].nCreateTime = 1;

            if (!pwallet->AddKeyPubKey(key, pubkey)) {
                throw JSONRPCError(RPC_WALLET_ERROR,
                                   "Error adding key to wallet");
            }
        }
    }
    if (fRescan) {
        RescanWallet(*pwallet, reserver);
    }

    return NullUniValue;
}

UniValue abortrescan(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() > 0) {
        throw std::runtime_error("abortrescan\n"
                                 "\nStops current wallet rescan triggered by "
                                 "an RPC call, e.g. by an importprivkey call.\n"
                                 "\nExamples:\n"
                                 "\nImport a private key\n" +
                                 HelpExampleCli("importprivkey", "\"mykey\"") +
                                 "\nAbort the running wallet rescan\n" +
                                 HelpExampleCli("abortrescan", "") +
                                 "\nAs a JSON-RPC call\n" +
                                 HelpExampleRpc("abortrescan", ""));
    }

    if (!pwallet->IsScanning() || pwallet->IsAbortingRescan()) {
        return false;
    }
    pwallet->AbortRescan();
    return true;
}

static void ImportAddress(CWallet *, const CTxDestination &dest,
                          const std::string &strLabel);
static void ImportScript(CWallet *const pwallet, const CScript &script,
                         const std::string &strLabel, bool isRedeemScript)
    EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
    if (!isRedeemScript && ::IsMine(*pwallet, script) == ISMINE_SPENDABLE) {
        throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the "
                                             "private key for this address or "
                                             "script");
    }

    pwallet->MarkDirty();

    if (!pwallet->HaveWatchOnly(script) &&
        !pwallet->AddWatchOnly(script, 0 /* nCreateTime */)) {
        throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet");
    }

    if (isRedeemScript) {
        if (!pwallet->HaveCScript(script) && !pwallet->AddCScript(script)) {
            throw JSONRPCError(RPC_WALLET_ERROR,
                               "Error adding p2sh redeemScript to wallet");
        }
        ImportAddress(pwallet, CScriptID(script), strLabel);
    } else {
        CTxDestination destination;
        if (ExtractDestination(script, destination)) {
            pwallet->SetAddressBook(destination, strLabel, "receive");
        }
    }
}

static void ImportAddress(CWallet *const pwallet, const CTxDestination &dest,
                          const std::string &strLabel)
    EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) {
    CScript script = GetScriptForDestination(dest);
    ImportScript(pwallet, script, strLabel, false);
    // add to address book or update label
    if (IsValidDestination(dest)) {
        pwallet->SetAddressBook(dest, strLabel, "receive");
    }
}

UniValue importaddress(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() < 1 ||
        request.params.size() > 4) {
        throw std::runtime_error(
            "importaddress \"address\" ( \"label\" rescan p2sh )\n"
            "\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"
            "\nArguments:\n"
            "1. \"address\"          (string, required) The Bitcoin address "
            "(or hex-encoded script)\n"
            "2. \"label\"            (string, optional, default=\"\") An "
            "optional label\n"
            "3. rescan               (boolean, optional, default=true) Rescan "
            "the wallet for transactions\n"
            "4. p2sh                 (boolean, optional, default=false) Add "
            "the P2SH version of the script as well\n"
            "\nNote: This call can take minutes to complete if rescan is true, "
            "during that time, other rpc calls\n"
            "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"
            "If you have the full public key, you should call importpubkey "
            "instead of this.\n"
            "\nNote: If you import a non-standard raw script in hex form, "
            "outputs sending to it will be treated\n"
            "as change, and not show up in many RPCs.\n"
            "\nExamples:\n"
            "\nImport an address with rescan\n" +
            HelpExampleCli("importaddress", "\"myaddress\"") +
            "\nImport using a label without rescan\n" +
            HelpExampleCli("importaddress", "\"myaddress\" \"testing\" false") +
            "\nAs a JSON-RPC call\n" +
            HelpExampleRpc("importaddress",
                           "\"myaddress\", \"testing\", false"));
    }

    std::string strLabel;
    if (!request.params[1].isNull()) {
        strLabel = request.params[1].get_str();
    }

    // Whether to perform rescan after import
    bool fRescan = true;
    if (!request.params[2].isNull()) {
        fRescan = request.params[2].get_bool();
    }

    if (fRescan && fPruneMode) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Rescan is disabled in pruned mode");
    }

    WalletRescanReserver reserver(pwallet);
    if (fRescan && !reserver.reserve()) {
        throw JSONRPCError(
            RPC_WALLET_ERROR,
            "Wallet is currently rescanning. Abort existing rescan or wait.");
    }

    // Whether to import a p2sh version, too
    bool fP2SH = false;
    if (!request.params[3].isNull()) {
        fP2SH = request.params[3].get_bool();
    }

    {
        LOCK2(cs_main, pwallet->cs_wallet);

        CTxDestination dest = DecodeDestination(request.params[0].get_str(),
                                                config.GetChainParams());
        if (IsValidDestination(dest)) {
            if (fP2SH) {
                throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                                   "Cannot use the p2sh flag with an address - "
                                   "use a script instead");
            }
            ImportAddress(pwallet, dest, strLabel);
        } else if (IsHex(request.params[0].get_str())) {
            std::vector<uint8_t> data(ParseHex(request.params[0].get_str()));
            ImportScript(pwallet, CScript(data.begin(), data.end()), strLabel,
                         fP2SH);
        } else {
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                               "Invalid Bitcoin address or script");
        }
    }
    if (fRescan) {
        RescanWallet(*pwallet, reserver);
        pwallet->ReacceptWalletTransactions();
    }

    return NullUniValue;
}

UniValue importprunedfunds(const Config &config,
                           const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() != 2) {
        throw std::runtime_error(
            "importprunedfunds\n"
            "\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"
            "\nArguments:\n"
            "1. \"rawtransaction\" (string, required) A raw transaction in hex "
            "funding an already-existing address in wallet\n"
            "2. \"txoutproof\"     (string, required) The hex output from "
            "gettxoutproof that contains the transaction\n");
    }

    CMutableTransaction tx;
    if (!DecodeHexTx(tx, request.params[0].get_str())) {
        throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
    }
    uint256 txid = tx.GetId();
    CWalletTx wtx(pwallet, MakeTransactionRef(std::move(tx)));

    CDataStream ssMB(ParseHexV(request.params[1], "proof"), SER_NETWORK,
                     PROTOCOL_VERSION);
    CMerkleBlock merkleBlock;
    ssMB >> merkleBlock;

    // Search partial merkle tree in proof for our transaction and index in
    // valid block
    std::vector<uint256> vMatch;
    std::vector<size_t> vIndex;
    size_t txnIndex = 0;
    if (merkleBlock.txn.ExtractMatches(vMatch, vIndex) ==
        merkleBlock.header.hashMerkleRoot) {
        LOCK(cs_main);
        const CBlockIndex *pindex =
            LookupBlockIndex(merkleBlock.header.GetHash());
        if (!pindex || !chainActive.Contains(pindex)) {
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                               "Block not found in chain");
        }

        std::vector<uint256>::const_iterator it;
        if ((it = std::find(vMatch.begin(), vMatch.end(), txid)) ==
            vMatch.end()) {
            throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                               "Transaction given doesn't exist in proof");
        }

        txnIndex = vIndex[it - vMatch.begin()];
    } else {
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                           "Something wrong with merkleblock");
    }

    wtx.nIndex = txnIndex;
    wtx.hashBlock = merkleBlock.header.GetHash();

    LOCK2(cs_main, pwallet->cs_wallet);

    if (pwallet->IsMine(*wtx.tx)) {
        pwallet->AddToWallet(wtx, false);
        return NullUniValue;
    }

    throw JSONRPCError(
        RPC_INVALID_ADDRESS_OR_KEY,
        "No addresses in wallet correspond to included transaction");
}

UniValue removeprunedfunds(const Config &config,
                           const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() != 1) {
        throw std::runtime_error(
            "removeprunedfunds \"txid\"\n"
            "\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"
            "\nArguments:\n"
            "1. \"txid\"           (string, required) The hex-encoded id of "
            "the transaction you are deleting\n"
            "\nExamples:\n" +
            HelpExampleCli("removeprunedfunds", "\"<KEY>"
                                                "ce581bebf46446a512166eae762873"
                                                "4ea0a5\"") +
            "\nAs a JSON-RPC call\n" +
            HelpExampleRpc("removeprunedfunds",
                           "\"<KEY>6"
                           "eae7628734ea0a5\""));
    }

    LOCK2(cs_main, pwallet->cs_wallet);

    TxId txid;
    txid.SetHex(request.params[0].get_str());
    std::vector<TxId> txIds;
    txIds.push_back(txid);
    std::vector<TxId> txIdsOut;

    if (pwallet->ZapSelectTx(txIds, txIdsOut) != DBErrors::LOAD_OK) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Could not properly delete the transaction.");
    }

    if (txIdsOut.empty()) {
        throw JSONRPCError(RPC_INVALID_PARAMETER,
                           "Transaction does not exist in wallet.");
    }

    return NullUniValue;
}

UniValue importpubkey(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() < 1 ||
        request.params.size() > 4) {
        throw std::runtime_error(
            "importpubkey \"pubkey\" ( \"label\" rescan )\n"
            "\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"
            "\nArguments:\n"
            "1. \"pubkey\"           (string, required) The hex-encoded public "
            "key\n"
            "2. \"label\"            (string, optional, default=\"\") An "
            "optional label\n"
            "3. rescan               (boolean, optional, default=true) Rescan "
            "the wallet for transactions\n"
            "\nNote: This call can take minutes to complete if rescan is true, "
            "during that time, other rpc calls\n"
            "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"
            "\nExamples:\n"
            "\nImport a public key with rescan\n" +
            HelpExampleCli("importpubkey", "\"mypubkey\"") +
            "\nImport using a label without rescan\n" +
            HelpExampleCli("importpubkey", "\"mypubkey\" \"testing\" false") +
            "\nAs a JSON-RPC call\n" +
            HelpExampleRpc("importpubkey", "\"mypubkey\", \"testing\", false"));
    }

    std::string strLabel;
    if (!request.params[1].isNull()) {
        strLabel = request.params[1].get_str();
    }

    // Whether to perform rescan after import
    bool fRescan = true;
    if (!request.params[2].isNull()) {
        fRescan = request.params[2].get_bool();
    }

    if (fRescan && fPruneMode) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Rescan is disabled in pruned mode");
    }

    WalletRescanReserver reserver(pwallet);
    if (fRescan && !reserver.reserve()) {
        throw JSONRPCError(
            RPC_WALLET_ERROR,
            "Wallet is currently rescanning. Abort existing rescan or wait.");
    }

    if (!IsHex(request.params[0].get_str())) {
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                           "Pubkey must be a hex string");
    }
    std::vector<uint8_t> data(ParseHex(request.params[0].get_str()));
    CPubKey pubKey(data.begin(), data.end());
    if (!pubKey.IsFullyValid()) {
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY,
                           "Pubkey is not a valid public key");
    }

    {
        LOCK2(cs_main, pwallet->cs_wallet);

        for (const auto &dest : GetAllDestinationsForKey(pubKey)) {
            ImportAddress(pwallet, dest, strLabel);
        }
        ImportScript(pwallet, GetScriptForRawPubKey(pubKey), strLabel, false);
        pwallet->LearnAllRelatedScripts(pubKey);
    }
    if (fRescan) {
        RescanWallet(*pwallet, reserver);
        pwallet->ReacceptWalletTransactions();
    }

    return NullUniValue;
}

UniValue importwallet(const Config &config, const JSONRPCRequest &request) {
    std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request);
    CWallet *const pwallet = wallet.get();
    if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) {
        return NullUniValue;
    }

    if (request.fHelp || request.params.size() != 1) {
        throw std::runtime_error(
            "importwallet \"filename\"\n"
            "\nImports keys from a wallet dump file (see dumpwallet). Requires "
            "a new wallet backup to include imported keys.\n"
            "\nArguments:\n"
            "1. \"filename\"    (string, required) The wallet file\n"
            "\nExamples:\n"
            "\nDump the wallet\n" +
            HelpExampleCli("dumpwallet", "\"test\"") + "\nImport the wallet\n" +
            HelpExampleCli("importwallet", "\"test\"") +
            "\nImport using the json rpc call\n" +
            HelpExampleRpc("importwallet", "\"test\""));
    }

    if (fPruneMode) {
        throw JSONRPCError(RPC_WALLET_ERROR,
                           "Importing wallets is disabled in pruned mode");
    }

    WalletRescanReserver reserver(pwallet);
    if (!reserver.reserve()) {
        throw JSONRPCError(
            RPC_WALLET_ERROR,
            "Wallet is currently rescanning. Abort existing rescan or wait.");
    }

    int64_t nTimeBegin = 0;
    bool fGood = true;
    {
        LOCK2(cs_main, pwallet->cs_wallet);

        EnsureWalletIsUnlocked(pwallet);

        std::ifstream file;
        file.open(request.params[0].get_str().c_str(),
                  std::ios::in | std::ios::ate);
        if (!file.is_open()) {
            throw JSONRPCError(RPC_INVALID_PARAMETER,
                               "Cannot open wallet dump file");
        }
        nTimeBegin = chainActive.Tip()->GetBlockTime();

        int64_t nFilesize = std::max<int64_t>(1, file.tellg());
        file.seekg(0, file.beg);

        // Use uiInterface.ShowProgress instead of pwallet.ShowProgress because
        // pwallet.ShowProgress has a cancel button tied to AbortRescan which we
        // don't want for this progress bar showing the import progress.
        // uiInterface.ShowProgress does not have a cancel button.

        // show progress dialog in GUI
        uiInterface.ShowProgress(
            strprintf("%s " + _("Importing..."), pwallet->GetDisplayName()), 0,
            false);
        std::vector<std::tuple<CKey, int64_t, bool, std::string>> keys;
        std::vector<std::pair<CScript, int64_t>> scripts;
        while (file.good()) {
            uiInterface.ShowProgress(
                "",
                std::max(1, std::min<int>(50, 100 * double(file.tellg()) /
                                                  double(nFilesize))),
                false);
            std::string line;
            std::getline(file, line);
            if (line.empty() || line[0] == '#') {
                continue;
            }

            std::vector<std::string> vstr;
            boost::split(vstr, line, boost::is_any_of(" "));
            if (vstr.size() < 2) {
                continue;
            }
            CKey key = DecodeSecret(vstr[0]);
            if (key.IsValid()) {
                int64_t nTime = DecodeDumpTime(vstr[1]);
                std::string strLabel;
                bool fLabel = true;
                for (size_t nStr = 2; nStr < vstr.size(); nStr++) {
                    if (vstr[nStr].front() == '#') {
                        break;
                    }
                    if (vstr[nStr] == "change=1") {
                        fLabel = false;
                    }
                    if (vstr[nStr] == "reserve=1") {
                        fLabel = false;
                    }
                    if (vstr[nStr].substr(0, 6) == "label=") {
                        strLabel = DecodeDumpString(vstr[nStr].substr(6));
                        fLabel = true;
                    }
                }
                keys.push_back(std::make_tuple(key, nTime, fLabel, strLabel));
            } else if (IsHex(vstr[0])) {
                std::vector<uint8_t> vData(ParseHex(vstr[0]));
                CScript script = CScript(vData.begin(), vData.end());
                int64_t birth_time = DecodeDumpTime(vstr[1]);
                scripts.push_back(
                    std::pair<CScript, int64_t>(script, birth_time));
            }
        }
        file.close();
        // We now know whether we are importing private keys, so we can error if
        // private keys are disabled
        if (keys.size() > 0 &&
            pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) {
            // hide progress dialog in GUI
            uiInterface.ShowProgress("", 100, false);
            throw JSONRPCError(
                RPC_WALLET_ERROR,
                "Importing wallets is disabled when private keys are disabled");
        }
        double total = double(keys.size() + scripts.size());
        double progress = 0;
        for (const auto &key_tuple : keys) {
            uiInterface.ShowProgress(
                "",
                std::max(50, std::min<int>(75, 100 * progress / total) + 50),
                false);
            const CKey &key = std::get<0>(key_tuple);
            int64_t time = std::get<1>(key_tuple);
            bool has_label = std::get<2>(key_tuple);
            std::string label = std::get<3>(key_tuple);

            CPubKey pubkey = key.GetPubKey();
            assert(key.VerifyPubKey(pubkey));
            CKeyID keyid = pubkey.GetID();
            if (pwallet->HaveKey(keyid)) {
                pwallet->WalletLogPrintf(
                    "Skipping import of %s (key already present)\n",
                    EncodeDestination(keyid, config));
                continue;
            }
            pwallet->WalletLogPrintf("Importing %s...\n",
                                     EncodeDestination(keyid, config));
            if (!pwallet->AddKeyPubKey(key, pubkey)) {
                fGood = false;
                continue;
            }
            pwallet->mapKeyMetadata[keyid].nCreateTime = time;
            if (has_label) {
                pwallet->SetAddressBook(keyid, label, "receive");
            }
            nTimeBegin = std::min(nTimeBegin, time);
            progress++;
        }
        for (const auto &script_pair : scripts) {
            uiInterface.ShowProgress(
                "",
                std::max(50, std::min<int>(75, 100 * progress / total) + 50),
                false);
            const CScript &script = script_pair.first;
            int64_t time = script_pair.second;
            CScriptID id(script);
            if (pwallet->HaveCScript(id)) {
                pwallet->WalletLogPrintf(
                    "Skipping import of %s (script already present)\n",
                    HexStr(script));
                continue;
            }
            if (!pwallet->AddCScript(script)) {
                pwallet->WalletLogPrintf("Error importing script %s\n",
                                         HexStr(script));
                fGood = false;
                continue;
            }
            if (time > 0) {
                pwallet->m_script_metadata[id].nCreateTime = time;
                nTimeBegin = std::min(nTimeBegin, time);
            }
            progress++;
        }
        // hide progress dialog in GUI
        uiInterface.ShowProgress("", 100, false);
        pwallet->UpdateTimeFirstKey(nTimeBegin);
    }
    // hide progress dialog in GUI
    uiInterface.ShowProgress("", 100, false);
{'fraction_non_alphanumeric': 0.11556569665611492, 'fraction_numerical': 0.008371764176562274, 'mean_word_length': 1.8915114064024532, 'pattern_counts': {'":': 0, '<': 138, '<?xml version=': 0, '>': 236, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 56, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5412041', 'n_tokens_mistral': 18258, 'n_tokens_neox': 17990, 'n_words': 4269}