diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 62d0d7b53..e1976c59d 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,4728 +1,4728 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2019 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 <amount.h> #include <chainparams.h> // for GetConsensus. #include <coins.h> #include <config.h> #include <consensus/validation.h> #include <core_io.h> #include <interfaces/chain.h> #include <key_io.h> #include <node/context.h> #include <outputtype.h> #include <policy/fees.h> #include <rpc/rawtransaction_util.h> #include <rpc/server.h> #include <rpc/util.h> #include <script/descriptor.h> #include <util/bip32.h> #include <util/error.h> #include <util/moneystr.h> #include <util/system.h> #include <util/url.h> #include <util/validation.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/psbtwallet.h> #include <wallet/rpcwallet.h> #include <wallet/wallet.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> #include <univalue.h> #include <event2/http.h> static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; static inline bool GetAvoidReuseFlag(CWallet *const pwallet, const UniValue ¶m) { bool can_avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); bool avoid_reuse = param.isNull() ? can_avoid_reuse : param.get_bool(); if (avoid_reuse && !can_avoid_reuse) { throw JSONRPCError( RPC_WALLET_ERROR, "wallet does not have the \"avoid reuse\" feature enabled"); } return avoid_reuse; } /** * Checks if a CKey is in the given CWallet compressed or otherwise */ bool HaveKey(const CWallet &wallet, const CKey &key) { CKey key2; key2.Set(key.begin(), key.end(), !key.IsCompressed()); return wallet.HaveKey(key.GetPubKey().GetID()) || wallet.HaveKey(key2.GetPubKey().GetID()); } bool GetWalletNameFromJSONRPCRequest(const JSONRPCRequest &request, std::string &wallet_name) { if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { // wallet endpoint was used wallet_name = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); return true; } return false; } std::shared_ptr<CWallet> GetWalletForJSONRPCRequest(const JSONRPCRequest &request) { std::string wallet_name; if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { std::shared_ptr<CWallet> pwallet = GetWallet(wallet_name); if (!pwallet) { throw JSONRPCError( RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); } return pwallet; } std::vector<std::shared_ptr<CWallet>> wallets = GetWallets(); return wallets.size() == 1 || (request.fHelp && wallets.size() > 0) ? wallets[0] : nullptr; } std::string HelpRequiringPassphrase(const CWallet *pwallet) { return pwallet && pwallet->IsCrypted() ? "\nRequires wallet passphrase to be set with walletpassphrase " "call." : ""; } bool EnsureWalletIsAvailable(const CWallet *pwallet, bool avoidException) { if (pwallet) { return true; } if (avoidException) { return false; } if (!HasWallets()) { throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (wallet method is disabled " "because no wallet is loaded)"); } throw JSONRPCError(RPC_WALLET_NOT_SPECIFIED, "Wallet file not specified (must request wallet RPC " "through /wallet/<filename> uri-path)."); } void EnsureWalletIsUnlocked(const CWallet *pwallet) { if (pwallet->IsLocked()) { throw JSONRPCError(RPC_WALLET_UNLOCK_NEEDED, "Error: Please enter the wallet passphrase with " "walletpassphrase first."); } } static void WalletTxToJSON(interfaces::Chain &chain, interfaces::Chain::Lock &locked_chain, const CWalletTx &wtx, UniValue &entry) { int confirms = wtx.GetDepthInMainChain(locked_chain); entry.pushKV("confirmations", confirms); if (wtx.IsCoinBase()) { entry.pushKV("generated", true); } if (confirms > 0) { entry.pushKV("blockhash", wtx.hashBlock.GetHex()); entry.pushKV("blockindex", wtx.nIndex); int64_t block_time; bool found_block = chain.findBlock(wtx.hashBlock, nullptr /* block */, &block_time); assert(found_block); entry.pushKV("blocktime", block_time); } else { entry.pushKV("trusted", wtx.IsTrusted(locked_chain)); } uint256 hash = wtx.GetId(); entry.pushKV("txid", hash.GetHex()); UniValue conflicts(UniValue::VARR); for (const uint256 &conflict : wtx.GetConflicts()) { conflicts.push_back(conflict.GetHex()); } entry.pushKV("walletconflicts", conflicts); entry.pushKV("time", wtx.GetTxTime()); entry.pushKV("timereceived", (int64_t)wtx.nTimeReceived); for (const std::pair<const std::string, std::string> &item : wtx.mapValue) { entry.pushKV(item.first, item.second); } } static std::string LabelFromValue(const UniValue &value) { std::string label = value.get_str(); if (label == "*") { throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name"); } return label; } static UniValue getnewaddress(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; } RPCHelpMan{ "getnewaddress", "\nReturns a new Bitcoin address for receiving payments.\n" "If 'label' is specified, it is added to the address book \n" "so payments received with the address will be associated with " "'label'.\n", { {"label", RPCArg::Type::STR, /* default */ "null", "The label name for the address to be linked to. If not provided, " "the default label \"\" is used. It can also be set to the empty " "string \"\" to represent the default label. The label does not " "need to exist, it will be created if there is no label by the " "given name."}, {"address_type", RPCArg::Type::STR, /* default */ "set by -addresstype", "The address type to use. Options are \"legacy\"."}, }, RPCResult{"\"address\" (string) The new bitcoin address\n"}, RPCExamples{HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "")}, } .Check(request); LOCK(pwallet->cs_wallet); if (!pwallet->CanGetAddresses()) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); } // Parse the label first so we don't generate a key if there's an error std::string label; if (!request.params[0].isNull()) { label = LabelFromValue(request.params[0]); } OutputType output_type = pwallet->m_default_address_type; if (!request.params[1].isNull()) { if (!ParseOutputType(request.params[1].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[1].get_str())); } } CTxDestination dest; std::string error; if (!pwallet->GetNewDestination(output_type, label, dest, error)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error); } return EncodeDestination(dest, config); } static UniValue getrawchangeaddress(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; } RPCHelpMan{ "getrawchangeaddress", "\nReturns a new Bitcoin address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n", {}, RPCResult{"\"address\" (string) The address\n"}, RPCExamples{HelpExampleCli("getrawchangeaddress", "") + HelpExampleRpc("getrawchangeaddress", "")}, } .Check(request); LOCK(pwallet->cs_wallet); if (!pwallet->CanGetAddresses(true)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); } OutputType output_type = pwallet->m_default_change_type != OutputType::CHANGE_AUTO ? pwallet->m_default_change_type : pwallet->m_default_address_type; if (!request.params[0].isNull()) { if (!ParseOutputType(request.params[0].get_str(), output_type)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Unknown address type '%s'", request.params[0].get_str())); } } CTxDestination dest; std::string error; if (!pwallet->GetNewChangeDestination(output_type, dest, error)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, error); } return EncodeDestination(dest, config); } static UniValue setlabel(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; } RPCHelpMan{ "setlabel", "\nSets the label associated with the given address.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to be associated with a label."}, {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label to assign to the address."}, }, RPCResults{}, RPCExamples{ HelpExampleCli("setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + HelpExampleRpc( "setlabel", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"")}, } .Check(request); LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str(), config.GetChainParams()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } std::string old_label = pwallet->mapAddressBook[dest].name; std::string label = LabelFromValue(request.params[1]); if (IsMine(*pwallet, dest)) { pwallet->SetAddressBook(dest, label, "receive"); } else { pwallet->SetAddressBook(dest, label, "send"); } return NullUniValue; } static CTransactionRef SendMoney(interfaces::Chain::Lock &locked_chain, CWallet *const pwallet, const CTxDestination &address, Amount nValue, bool fSubtractFeeFromAmount, const CCoinControl &coin_control, mapValue_t mapValue) { Amount curBalance = pwallet->GetBalance(0, coin_control.m_avoid_address_reuse) .m_mine_trusted; // Check amount if (nValue <= Amount::zero()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); } if (nValue > curBalance) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); } if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { throw JSONRPCError( RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } // Parse Bitcoin address CScript scriptPubKey = GetScriptForDestination(address); // Create and send the transaction Amount nFeeRequired; std::string strError; std::vector<CRecipient> vecSend; int nChangePosRet = -1; CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); CCoinControl coinControl; CTransactionRef tx; if (!pwallet->CreateTransaction(locked_chain, vecSend, tx, nFeeRequired, nChangePosRet, strError, coinControl)) { if (!fSubtractFeeFromAmount && nValue + nFeeRequired > curBalance) { strError = strprintf("Error: This transaction requires a " "transaction fee of at least %s", FormatMoney(nFeeRequired)); } throw JSONRPCError(RPC_WALLET_ERROR, strError); } TxValidationState state; if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, state)) { strError = strprintf("Error: The transaction was rejected! Reason given: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strError); } return tx; } static UniValue sendtoaddress(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; } RPCHelpMan{ "sendtoaddress", "\nSend an amount to a given address.\n" + HelpRequiringPassphrase(pwallet) + "\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to send to."}, {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount in " + CURRENCY_UNIT + " to send. eg 0.1"}, {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment used to store what the transaction is for.\n" " This is not part of the " "transaction, just kept in your wallet."}, {"comment_to", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment to store the name of the person or organization\n" " to which you're sending the " "transaction. This is not part of the \n" " transaction, just kept in " "your wallet."}, {"subtractfeefromamount", RPCArg::Type::BOOL, /* default */ "false", "The fee will be deducted from the amount being sent.\n" " The recipient will receive " "less bitcoins than you enter in the amount field."}, {"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Avoid " "spending from dirty addresses; addresses are considered\n" " dirty if they have previously " "been used in a transaction."}, }, RPCResult{"\"txid\" (string) The transaction id.\n"}, RPCExamples{ HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay" "dd\" 0.1 \"donation\" \"seans " "outpost\"") + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44" "Jvaydd\" 0.1 \"\" \"\" true") + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay" "dd\", 0.1, \"donation\", \"seans " "outpost\"")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str(), config.GetChainParams()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } // Amount Amount nAmount = AmountFromValue(request.params[1]); if (nAmount <= Amount::zero()) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); } // Wallet comments mapValue_t mapValue; if (!request.params[2].isNull() && !request.params[2].get_str().empty()) { mapValue["comment"] = request.params[2].get_str(); } if (!request.params[3].isNull() && !request.params[3].get_str().empty()) { mapValue["to"] = request.params[3].get_str(); } bool fSubtractFeeFromAmount = false; if (!request.params[4].isNull()) { fSubtractFeeFromAmount = request.params[4].get_bool(); } CCoinControl coin_control; coin_control.m_avoid_address_reuse = GetAvoidReuseFlag(pwallet, request.params[5]); // We also enable partial spend avoidance if reuse avoidance is set. coin_control.m_avoid_partial_spends |= coin_control.m_avoid_address_reuse; EnsureWalletIsUnlocked(pwallet); CTransactionRef tx = SendMoney(*locked_chain, pwallet, dest, nAmount, fSubtractFeeFromAmount, coin_control, std::move(mapValue)); return tx->GetId().GetHex(); } static UniValue listaddressgroupings(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; } RPCHelpMan{ "listaddressgroupings", "\nLists groups of addresses which have had their common ownership\n" "made public by common use as inputs or as the resulting change\n" "in past transactions\n", {}, RPCResult{"[\n" " [\n" " [\n" " \"address\", (string) The bitcoin address\n" " amount, (numeric) The amount in " + CURRENCY_UNIT + "\n" " \"label\" (string, optional) The label\n" " ]\n" " ,...\n" " ]\n" " ,...\n" "]\n"}, RPCExamples{HelpExampleCli("listaddressgroupings", "") + HelpExampleRpc("listaddressgroupings", "")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); std::map<CTxDestination, Amount> balances = pwallet->GetAddressBalances(*locked_chain); for (const std::set<CTxDestination> &grouping : pwallet->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); for (const CTxDestination &address : grouping) { UniValue addressInfo(UniValue::VARR); addressInfo.push_back(EncodeDestination(address, config)); addressInfo.push_back(ValueFromAmount(balances[address])); if (pwallet->mapAddressBook.find(address) != pwallet->mapAddressBook.end()) { addressInfo.push_back( pwallet->mapAddressBook.find(address)->second.name); } jsonGrouping.push_back(addressInfo); } jsonGroupings.push_back(jsonGrouping); } return jsonGroupings; } static UniValue signmessage(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; } RPCHelpMan{ "signmessage", "\nSign a message with the private key of an address" + HelpRequiringPassphrase(pwallet) + "\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the private key."}, {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message to create a signature of."}, }, RPCResult{"\"signature\" (string) The signature of the " "message encoded in base 64\n"}, RPCExamples{ "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" + HelpExampleCli( "signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + "\nVerify the signature\n" + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" " "\"signature\" \"my message\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc( "signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"")}, } .Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); std::string strAddress = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); CTxDestination dest = DecodeDestination(strAddress, config.GetChainParams()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); } const PKHash *pkhash = boost::get<PKHash>(&dest); if (!pkhash) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } CKey key; CKeyID keyID(*pkhash); if (!pwallet->GetKey(keyID, key)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; std::vector<uint8_t> vchSig; if (!key.SignCompact(ss.GetHash(), vchSig)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); } return EncodeBase64(vchSig.data(), vchSig.size()); } static UniValue getreceivedbyaddress(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; } RPCHelpMan{ "getreceivedbyaddress", "\nReturns the total amount received by the given address in " "transactions with at least minconf confirmations.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address for transactions."}, {"minconf", RPCArg::Type::NUM, /* default */ "1", "Only include transactions confirmed at least this many times."}, }, RPCResult{"amount (numeric) The total amount in " + CURRENCY_UNIT + " received at this address.\n"}, RPCExamples{ "\nThe amount from transactions with at least 1 confirmation\n" + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + "\nThe amount including unconfirmed transactions, zero " "confirmations\n" + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 0") + "\nThe amount with at least 6 confirmations\n" + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); // Bitcoin address CTxDestination dest = DecodeDestination(request.params[0].get_str(), config.GetChainParams()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } CScript scriptPubKey = GetScriptForDestination(dest); if (!IsMine(*pwallet, scriptPubKey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Address not found in wallet"); } // Minimum confirmations int nMinDepth = 1; if (!request.params[1].isNull()) { nMinDepth = request.params[1].get_int(); } // Tally Amount nAmount = Amount::zero(); for (const std::pair<const TxId, CWalletTx> &pairWtx : pwallet->mapWallet) { const CWalletTx &wtx = pairWtx.second; TxValidationState state; if (wtx.IsCoinBase() || !locked_chain->contextualCheckTransactionForCurrentBlock( config.GetChainParams().GetConsensus(), *wtx.tx, state)) { continue; } for (const CTxOut &txout : wtx.tx->vout) { if (txout.scriptPubKey == scriptPubKey) { if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) { nAmount += txout.nValue; } } } } return ValueFromAmount(nAmount); } static UniValue getreceivedbylabel(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; } RPCHelpMan{ "getreceivedbylabel", "\nReturns the total amount received by addresses with <label> in " "transactions with at least [minconf] confirmations.\n", { {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The selected label, may be the default label using \"\"."}, {"minconf", RPCArg::Type::NUM, /* default */ "1", "Only include transactions confirmed at least this many times."}, }, RPCResult{"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this label.\n"}, RPCExamples{"\nAmount received by the default label with at least 1 " "confirmation\n" + HelpExampleCli("getreceivedbylabel", "\"\"") + "\nAmount received at the tabby label including " "unconfirmed amounts with zero confirmations\n" + HelpExampleCli("getreceivedbylabel", "\"tabby\" 0") + "\nThe amount with at least 6 confirmations\n" + HelpExampleCli("getreceivedbylabel", "\"tabby\" 6") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getreceivedbylabel", "\"tabby\", 6")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); // Minimum confirmations int nMinDepth = 1; if (!request.params[1].isNull()) { nMinDepth = request.params[1].get_int(); } // Get the set of pub keys assigned to label std::string label = LabelFromValue(request.params[0]); std::set<CTxDestination> setAddress = pwallet->GetLabelAddresses(label); // Tally Amount nAmount = Amount::zero(); for (const std::pair<const TxId, CWalletTx> &pairWtx : pwallet->mapWallet) { const CWalletTx &wtx = pairWtx.second; TxValidationState state; if (wtx.IsCoinBase() || !locked_chain->contextualCheckTransactionForCurrentBlock( config.GetChainParams().GetConsensus(), *wtx.tx, state)) { continue; } for (const CTxOut &txout : wtx.tx->vout) { CTxDestination address; if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) { if (wtx.GetDepthInMainChain(*locked_chain) >= nMinDepth) { nAmount += txout.nValue; } } } } return ValueFromAmount(nAmount); } static UniValue getbalance(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; } RPCHelpMan{ "getbalance", "\nReturns the total available balance.\n" "The available balance is what the wallet considers currently " "spendable, and is\n" "thus affected by options which limit spendability such as " "-spendzeroconfchange.\n", { {"dummy", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Remains for backward compatibility. Must be excluded or set to " "\"*\"."}, {"minconf", RPCArg::Type::NUM, /* default */ "0", "Only include transactions confirmed at least this many times."}, {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Also include balance in watch-only addresses (see " "'importaddress')"}, {"avoid_reuse", RPCArg::Type::BOOL, /* default */ "true", "(only available if avoid_reuse wallet flag is set) Do not " "include balance in dirty outputs; addresses are considered dirty " "if they have previously been used in a transaction."}, }, RPCResult{"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this wallet.\n"}, RPCExamples{ "\nThe total amount in the wallet with 1 or more confirmations\n" + HelpExampleCli("getbalance", "") + "\nThe total amount in the wallet at least 6 blocks confirmed\n" + HelpExampleCli("getbalance", "\"*\" 6") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("getbalance", "\"*\", 6")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); const UniValue &dummy_value = request.params[0]; if (!dummy_value.isNull() && dummy_value.get_str() != "*") { throw JSONRPCError( RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\"."); } int min_depth = 0; if (!request.params[1].isNull()) { min_depth = request.params[1].get_int(); } bool include_watchonly = false; if (!request.params[2].isNull() && request.params[2].get_bool()) { include_watchonly = true; } bool avoid_reuse = GetAvoidReuseFlag(pwallet, request.params[3]); const auto bal = pwallet->GetBalance(min_depth, avoid_reuse); return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : Amount::zero())); } static UniValue getunconfirmedbalance(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; } RPCHelpMan{ "getunconfirmedbalance", "DEPRECATED\nIdentical to getbalances().mine.untrusted_pending\n", {}, RPCResults{}, RPCExamples{""}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); return ValueFromAmount(pwallet->GetBalance().m_mine_untrusted_pending); } static UniValue sendmany(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; } RPCHelpMan{ "sendmany", "\nSend multiple times. Amounts are double-precision " "floating point numbers." + HelpRequiringPassphrase(pwallet) + "\n", { {"dummy", RPCArg::Type::STR, RPCArg::Optional::NO, "Must be set to \"\" for backwards compatibility.", "\"\""}, { "amounts", RPCArg::Type::OBJ, RPCArg::Optional::NO, "A json object with addresses and amounts", { {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The bitcoin address is the key, the numeric amount (can " "be string) in " + CURRENCY_UNIT + " is the value"}, }, }, {"minconf", RPCArg::Type::NUM, /* default */ "1", "Only use the balance confirmed at least this many times."}, {"comment", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A comment"}, { "subtractfeefrom", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array with addresses.\n" " The fee will be equally deducted " "from the amount of each selected address.\n" " Those recipients will receive less " "bitcoins than you enter in their corresponding amount field.\n" " If no addresses are specified " "here, the sender pays the fee.", { {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Subtract fee from this address"}, }, }, }, RPCResult{ "\"txid\" (string) The transaction id for the " "send. Only 1 transaction is created regardless of \n" " the number of addresses.\n"}, RPCExamples{ "\nSend two amounts to two different addresses:\n" + HelpExampleCli( "sendmany", "\"\" " "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + "\nSend two amounts to two different addresses setting the " "confirmation and comment:\n" + HelpExampleCli("sendmany", "\"\" " "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" " "6 \"testing\"") + "\nSend two amounts to two different addresses, subtract fee " "from amount:\n" + HelpExampleCli( "sendmany", "\"\" " "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" " "\"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\"," "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendmany", "\"\", " "\"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01," "\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"," " 6, \"testing\"")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (pwallet->GetBroadcastTransactions() && !pwallet->chain().p2pEnabled()) { throw JSONRPCError( RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Dummy value must be set to \"\""); } UniValue sendTo = request.params[1].get_obj(); mapValue_t mapValue; if (!request.params[3].isNull() && !request.params[3].get_str().empty()) { mapValue["comment"] = request.params[3].get_str(); } UniValue subtractFeeFromAmount(UniValue::VARR); if (!request.params[4].isNull()) { subtractFeeFromAmount = request.params[4].get_array(); } std::set<CTxDestination> destinations; std::vector<CRecipient> vecSend; std::vector<std::string> keys = sendTo.getKeys(); for (const std::string &name_ : keys) { CTxDestination dest = DecodeDestination(name_, config.GetChainParams()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + name_); } if (destinations.count(dest)) { throw JSONRPCError( RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + name_); } destinations.insert(dest); CScript scriptPubKey = GetScriptForDestination(dest); Amount nAmount = AmountFromValue(sendTo[name_]); if (nAmount <= Amount::zero()) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); } bool fSubtractFeeFromAmount = false; for (size_t idx = 0; idx < subtractFeeFromAmount.size(); idx++) { const UniValue &addr = subtractFeeFromAmount[idx]; if (addr.get_str() == name_) { fSubtractFeeFromAmount = true; } } CRecipient recipient = {scriptPubKey, nAmount, fSubtractFeeFromAmount}; vecSend.push_back(recipient); } EnsureWalletIsUnlocked(pwallet); // Shuffle recipient list std::shuffle(vecSend.begin(), vecSend.end(), FastRandomContext()); // Send Amount nFeeRequired = Amount::zero(); int nChangePosRet = -1; std::string strFailReason; CTransactionRef tx; CCoinControl coinControl; bool fCreated = pwallet->CreateTransaction(*locked_chain, vecSend, tx, nFeeRequired, nChangePosRet, strFailReason, coinControl); if (!fCreated) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); } TxValidationState state; if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, state)) { strFailReason = strprintf("Transaction commit failed:: %s", FormatStateMessage(state)); throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } return tx->GetId().GetHex(); } static UniValue addmultisigaddress(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; } RPCHelpMan{ "addmultisigaddress", "\nAdd a nrequired-to-sign multisignature address to the wallet. " "Requires a new wallet backup.\n" "Each key is a Bitcoin address or hex-encoded public key.\n" "If 'label' is specified (DEPRECATED), assign address to that label.\n", { {"nrequired", RPCArg::Type::NUM, RPCArg::Optional::NO, "The number of required signatures out of the n keys or " "addresses."}, { "keys", RPCArg::Type::ARR, RPCArg::Optional::NO, "A json array of bitcoin addresses or hex-encoded public keys", { {"key", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "bitcoin address or hex-encoded public key"}, }, }, {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "A label to assign the addresses to."}, }, RPCResult{"{\n" " \"address\":\"multisigaddress\", (string) The value of " "the new multisig address.\n" " \"redeemScript\":\"script\" (string) The string " "value of the hex-encoded redemption script.\n" "}\n"}, RPCExamples{ "\nAdd a multisig address from 2 addresses\n" + HelpExampleCli("addmultisigaddress", "2 " "\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\"," "\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("addmultisigaddress", "2, " "\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\"," "\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"")}, } .Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); std::string label; if (!request.params[2].isNull()) { label = LabelFromValue(request.params[2]); } int required = request.params[0].get_int(); // Get the public keys const UniValue &keys_or_addrs = request.params[1].get_array(); std::vector<CPubKey> pubkeys; for (size_t i = 0; i < keys_or_addrs.size(); ++i) { if (IsHex(keys_or_addrs[i].get_str()) && (keys_or_addrs[i].get_str().length() == 66 || keys_or_addrs[i].get_str().length() == 130)) { pubkeys.push_back(HexToPubKey(keys_or_addrs[i].get_str())); } else { pubkeys.push_back(AddrToPubKey(config.GetChainParams(), pwallet, keys_or_addrs[i].get_str())); } } OutputType output_type = pwallet->m_default_address_type; // Construct using pay-to-script-hash: CScript inner; CTxDestination dest = AddAndGetMultisigDestination( required, pubkeys, output_type, *pwallet, inner); pwallet->SetAddressBook(dest, label, "send"); UniValue result(UniValue::VOBJ); result.pushKV("address", EncodeDestination(dest, config)); result.pushKV("redeemScript", HexStr(inner.begin(), inner.end())); return result; } struct tallyitem { Amount nAmount{Amount::zero()}; int nConf{std::numeric_limits<int>::max()}; std::vector<uint256> txids; bool fIsWatchonly{false}; tallyitem() {} }; static UniValue ListReceived(const Config &config, interfaces::Chain::Lock &locked_chain, CWallet *const pwallet, const UniValue ¶ms, bool by_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { // Minimum confirmations int nMinDepth = 1; if (!params[0].isNull()) { nMinDepth = params[0].get_int(); } // Whether to include empty labels bool fIncludeEmpty = false; if (!params[1].isNull()) { fIncludeEmpty = params[1].get_bool(); } isminefilter filter = ISMINE_SPENDABLE; if (!params[2].isNull() && params[2].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } bool has_filtered_address = false; CTxDestination filtered_address = CNoDestination(); if (!by_label && params.size() > 3) { if (!IsValidDestinationString(params[3].get_str(), config.GetChainParams())) { throw JSONRPCError(RPC_WALLET_ERROR, "address_filter parameter was invalid"); } filtered_address = DecodeDestination(params[3].get_str(), config.GetChainParams()); has_filtered_address = true; } // Tally std::map<CTxDestination, tallyitem> mapTally; for (const std::pair<const TxId, CWalletTx> &pairWtx : pwallet->mapWallet) { const CWalletTx &wtx = pairWtx.second; TxValidationState state; if (wtx.IsCoinBase() || !locked_chain.contextualCheckTransactionForCurrentBlock( config.GetChainParams().GetConsensus(), *wtx.tx, state)) { continue; } int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < nMinDepth) { continue; } for (const CTxOut &txout : wtx.tx->vout) { CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address)) { continue; } if (has_filtered_address && !(filtered_address == address)) { continue; } isminefilter mine = IsMine(*pwallet, address); if (!(mine & filter)) { continue; } tallyitem &item = mapTally[address]; item.nAmount += txout.nValue; item.nConf = std::min(item.nConf, nDepth); item.txids.push_back(wtx.GetId()); if (mine & ISMINE_WATCH_ONLY) { item.fIsWatchonly = true; } } } // Reply UniValue ret(UniValue::VARR); std::map<std::string, tallyitem> label_tally; // Create mapAddressBook iterator // If we aren't filtering, go from begin() to end() auto start = pwallet->mapAddressBook.begin(); auto end = pwallet->mapAddressBook.end(); // If we are filtering, find() the applicable entry if (has_filtered_address) { start = pwallet->mapAddressBook.find(filtered_address); if (start != end) { end = std::next(start); } } for (auto item_it = start; item_it != end; ++item_it) { const CTxDestination &address = item_it->first; const std::string &label = item_it->second.name; std::map<CTxDestination, tallyitem>::iterator it = mapTally.find(address); if (it == mapTally.end() && !fIncludeEmpty) { continue; } Amount nAmount = Amount::zero(); int nConf = std::numeric_limits<int>::max(); bool fIsWatchonly = false; if (it != mapTally.end()) { nAmount = (*it).second.nAmount; nConf = (*it).second.nConf; fIsWatchonly = (*it).second.fIsWatchonly; } if (by_label) { tallyitem &_item = label_tally[label]; _item.nAmount += nAmount; _item.nConf = std::min(_item.nConf, nConf); _item.fIsWatchonly = fIsWatchonly; } else { UniValue obj(UniValue::VOBJ); if (fIsWatchonly) { obj.pushKV("involvesWatchonly", true); } obj.pushKV("address", EncodeDestination(address, config)); obj.pushKV("amount", ValueFromAmount(nAmount)); obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); obj.pushKV("label", label); UniValue transactions(UniValue::VARR); if (it != mapTally.end()) { for (const uint256 &_item : (*it).second.txids) { transactions.push_back(_item.GetHex()); } } obj.pushKV("txids", transactions); ret.push_back(obj); } } if (by_label) { for (const auto &entry : label_tally) { Amount nAmount = entry.second.nAmount; int nConf = entry.second.nConf; UniValue obj(UniValue::VOBJ); if (entry.second.fIsWatchonly) { obj.pushKV("involvesWatchonly", true); } obj.pushKV("amount", ValueFromAmount(nAmount)); obj.pushKV("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)); obj.pushKV("label", entry.first); ret.push_back(obj); } } return ret; } static UniValue listreceivedbyaddress(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; } RPCHelpMan{ "listreceivedbyaddress", "\nList balances by receiving address.\n", { {"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are " "included."}, {"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include addresses that haven't received any " "payments."}, {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, {"address_filter", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If present, only return information on this address."}, }, RPCResult{ "[\n" " {\n" " \"involvesWatchonly\" : true, (bool) Only returned if " "imported addresses were involved in transaction\n" " \"address\" : \"receivingaddress\", (string) The receiving " "address\n" " \"amount\" : x.xxx, (numeric) The total " "amount in " + CURRENCY_UNIT + " received by the address\n" " \"confirmations\" : n, (numeric) The number of " "confirmations of the most recent transaction included\n" " \"label\" : \"label\", (string) The label of " "the receiving address. The default label is \"\".\n" " \"txids\": [\n" " \"txid\", (string) The ids of " "transactions received with the address \n" " ...\n" " ]\n" " }\n" " ,...\n" "]\n"}, RPCExamples{ HelpExampleCli("listreceivedbyaddress", "") + HelpExampleCli("listreceivedbyaddress", "6 true") + HelpExampleRpc("listreceivedbyaddress", "6, true, true") + HelpExampleRpc( "listreceivedbyaddress", "6, true, true, \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\"")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); return ListReceived(config, *locked_chain, pwallet, request.params, false); } static UniValue listreceivedbylabel(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; } RPCHelpMan{ "listreceivedbylabel", "\nList received transactions by label.\n", { {"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum number of confirmations before payments are " "included."}, {"include_empty", RPCArg::Type::BOOL, /* default */ "false", "Whether to include labels that haven't received any payments."}, {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses (see 'importaddress')."}, }, RPCResult{"[\n" " {\n" " \"involvesWatchonly\" : true, (bool) Only returned if " "imported addresses were involved in transaction\n" " \"amount\" : x.xxx, (numeric) The total " "amount received by addresses with this label\n" " \"confirmations\" : n, (numeric) The number of " "confirmations of the most recent transaction included\n" " \"label\" : \"label\" (string) The label of " "the receiving address. The default label is \"\".\n" " }\n" " ,...\n" "]\n"}, RPCExamples{HelpExampleCli("listreceivedbylabel", "") + HelpExampleCli("listreceivedbylabel", "6 true") + HelpExampleRpc("listreceivedbylabel", "6, true, true")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); return ListReceived(config, *locked_chain, pwallet, request.params, true); } static void MaybePushAddress(UniValue &entry, const CTxDestination &dest) { if (IsValidDestination(dest)) { entry.pushKV("address", EncodeDestination(dest, GetConfig())); } } /** * List transactions based on the given criteria. * * @param pwallet The wallet. * @param wtx The wallet transaction. * @param nMinDepth The minimum confirmation depth. * @param fLong Whether to include the JSON version of the * transaction. * @param ret The UniValue into which the result is stored. * @param filter_ismine The "is mine" filter flags. * @param filter_label Optional label string to filter incoming transactions. */ static void ListTransactions(interfaces::Chain::Lock &locked_chain, CWallet *const pwallet, const CWalletTx &wtx, int nMinDepth, bool fLong, UniValue &ret, const isminefilter &filter_ismine, const std::string *filter_label) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { Amount nFee; std::list<COutputEntry> listReceived; std::list<COutputEntry> listSent; wtx.GetAmounts(listReceived, listSent, nFee, filter_ismine); bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); // Sent if (!filter_label) { for (const COutputEntry &s : listSent) { UniValue entry(UniValue::VOBJ); if (involvesWatchonly || (::IsMine(*pwallet, s.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, s.destination); entry.pushKV("category", "send"); entry.pushKV("amount", ValueFromAmount(-s.amount)); if (pwallet->mapAddressBook.count(s.destination)) { entry.pushKV("label", pwallet->mapAddressBook[s.destination].name); } entry.pushKV("vout", s.vout); entry.pushKV("fee", ValueFromAmount(-1 * nFee)); if (fLong) { WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); } entry.pushKV("abandoned", wtx.isAbandoned()); ret.push_back(entry); } } // Received if (listReceived.size() > 0 && wtx.GetDepthInMainChain(locked_chain) >= nMinDepth) { for (const COutputEntry &r : listReceived) { std::string label; if (pwallet->mapAddressBook.count(r.destination)) { label = pwallet->mapAddressBook[r.destination].name; } if (filter_label && label != *filter_label) { continue; } UniValue entry(UniValue::VOBJ); if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) { entry.pushKV("involvesWatchonly", true); } MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { if (wtx.GetDepthInMainChain(locked_chain) < 1) { entry.pushKV("category", "orphan"); } else if (wtx.IsImmatureCoinBase(locked_chain)) { entry.pushKV("category", "immature"); } else { entry.pushKV("category", "generate"); } } else { entry.pushKV("category", "receive"); } entry.pushKV("amount", ValueFromAmount(r.amount)); if (pwallet->mapAddressBook.count(r.destination)) { entry.pushKV("label", label); } entry.pushKV("vout", r.vout); if (fLong) { WalletTxToJSON(pwallet->chain(), locked_chain, wtx, entry); } ret.push_back(entry); } } } UniValue listtransactions(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; } RPCHelpMan{ "listtransactions", "\nIf a label name is provided, this will return only incoming " "transactions paying to addresses with the specified label.\n" "\nReturns up to 'count' most recent transactions skipping the first " "'from' transactions.\n", { {"label", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If set, should be a valid label name to return only incoming " "transactions with the specified label, or \"*\" to disable " "filtering and return all transactions."}, {"count", RPCArg::Type::NUM, /* default */ "10", "The number of transactions to return"}, {"skip", RPCArg::Type::NUM, /* default */ "0", "The number of transactions to skip"}, {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see " "'importaddress')"}, }, RPCResult{ "[\n" " {\n" " \"address\":\"address\", (string) The bitcoin address of " "the transaction.\n" " \"category\":\"send|receive\", (string) The transaction " "category.\n" " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n" " for the 'receive' " "category,\n" " \"label\": \"label\", (string) A comment for the " "address/transaction, if any\n" " \"vout\": n, (numeric) the vout value\n" " \"fee\": x.xxx, (numeric) The amount of the fee " "in " + CURRENCY_UNIT + ". This is negative and only available for the \n" " 'send' category of " "transactions.\n" " \"confirmations\": n, (numeric) The number of " "confirmations for the transaction. Negative confirmations " "indicate the\n" " transaction conflicts " "with the block chain\n" " \"trusted\": xxx, (bool) Whether we consider the " "outputs of this unconfirmed transaction safe to spend.\n" " \"blockhash\": \"hashvalue\", (string) The block hash " "containing the transaction.\n" " \"blockindex\": n, (numeric) The index of the " "transaction in the block that includes it.\n" " \"blocktime\": xxx, (numeric) The block time in " "seconds since epoch (1 Jan 1970 GMT).\n" " \"txid\": \"transactionid\", (string) The transaction id.\n" " \"time\": xxx, (numeric) The transaction time in " "seconds since epoch (midnight Jan 1 1970 GMT).\n" " \"timereceived\": xxx, (numeric) The time received in " "seconds since epoch (midnight Jan 1 1970 GMT).\n" " \"comment\": \"...\", (string) If a comment is " "associated with the transaction.\n" " \"abandoned\": xxx (bool) 'true' if the transaction " "has been abandoned (inputs are respendable). Only available for " "the \n" " 'send' category of " "transactions.\n" " }\n" "]\n"}, RPCExamples{"\nList the most recent 10 transactions in the systems\n" + HelpExampleCli("listtransactions", "") + "\nList transactions 100 to 120\n" + HelpExampleCli("listtransactions", "\"*\" 20 100") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listtransactions", "\"*\", 20, 100")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); const std::string *filter_label = nullptr; if (!request.params[0].isNull() && request.params[0].get_str() != "*") { filter_label = &request.params[0].get_str(); if (filter_label->empty()) { throw JSONRPCError( RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\"."); } } int nCount = 10; if (!request.params[1].isNull()) { nCount = request.params[1].get_int(); } int nFrom = 0; if (!request.params[2].isNull()) { nFrom = request.params[2].get_int(); } isminefilter filter = ISMINE_SPENDABLE; if (!request.params[3].isNull() && request.params[3].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } if (nCount < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative count"); } if (nFrom < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Negative from"); } UniValue ret(UniValue::VARR); { auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); const CWallet::TxItems &txOrdered = pwallet->wtxOrdered; // iterate backwards until we have nCount items to return: for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = (*it).second; ListTransactions(*locked_chain, pwallet, *pwtx, 0, true, ret, filter, filter_label); if (int(ret.size()) >= (nCount + nFrom)) { break; } } } // ret is newest to oldest if (nFrom > (int)ret.size()) { nFrom = ret.size(); } if ((nFrom + nCount) > (int)ret.size()) { nCount = ret.size() - nFrom; } const std::vector<UniValue> &txs = ret.getValues(); UniValue result{UniValue::VARR}; // Return oldest to newest result.push_backV({txs.rend() - nFrom - nCount, txs.rend() - nFrom}); return result; } static UniValue listsinceblock(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; } RPCHelpMan{ "listsinceblock", "\nGet all transactions in blocks since block [blockhash], or all " "transactions if omitted.\n" "If \"blockhash\" is no longer a part of the main chain, transactions " "from the fork point onward are included.\n" "Additionally, if include_removed is set, transactions affecting the " "wallet which were removed are returned in the \"removed\" array.\n", { {"blockhash", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "If set, the block hash to list transactions since, otherwise " "list all transactions."}, {"target_confirmations", RPCArg::Type::NUM, /* default */ "1", "Return the nth block hash from the main chain. e.g. 1 would mean " "the best block hash. Note: this is not used as a filter, but " "only affects [lastblock] in the return value"}, {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Include transactions to watch-only addresses (see " "'importaddress')"}, {"include_removed", RPCArg::Type::BOOL, /* default */ "true", "Show transactions that were removed due to a reorg in the " "\"removed\" array\n" " (not " "guaranteed to work on pruned nodes)"}, }, RPCResult{ "{\n" " \"transactions\": [\n" " \"address\":\"address\", (string) The bitcoin address of " "the transaction. Not present for move transactions (category = " "move).\n" " \"category\":\"send|receive\", (string) The transaction " "category. 'send' has negative amounts, 'receive' has positive " "amounts.\n" " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the 'move' " "category for moves \n" " outbound. It is " "positive for the 'receive' category, and for the 'move' category " "for inbound funds.\n" " \"vout\" : n, (numeric) the vout value\n" " \"fee\": x.xxx, (numeric) The amount of the fee " "in " + CURRENCY_UNIT + ". This is negative and only available for the 'send' category of " "transactions.\n" " \"confirmations\": n, (numeric) The number of " "confirmations for the transaction. Available for 'send' and " "'receive' category of transactions.\n" " When it's < 0, it means " "the transaction conflicted that many blocks ago.\n" " \"blockhash\": \"hashvalue\", (string) The block hash " "containing the transaction. Available for 'send' and 'receive' " "category of transactions.\n" " \"blockindex\": n, (numeric) The index of the " "transaction in the block that includes it. Available for 'send' " "and 'receive' category of transactions.\n" " \"blocktime\": xxx, (numeric) The block time in " "seconds since epoch (1 Jan 1970 GMT).\n" " \"txid\": \"transactionid\", (string) The transaction id. " "Available for 'send' and 'receive' category of transactions.\n" " \"time\": xxx, (numeric) The transaction time in " "seconds since epoch (Jan 1 1970 GMT).\n" " \"timereceived\": xxx, (numeric) The time received in " "seconds since epoch (Jan 1 1970 GMT). Available for 'send' and " "'receive' category of transactions.\n" " \"abandoned\": xxx, (bool) 'true' if the transaction " "has been abandoned (inputs are respendable). Only available for " "the 'send' category of transactions.\n" " \"comment\": \"...\", (string) If a comment is " "associated with the transaction.\n" " \"label\" : \"label\" (string) A comment for the " "address/transaction, if any\n" " \"to\": \"...\", (string) If a comment to is " "associated with the transaction.\n" " ],\n" " \"removed\": [\n" " <structure is the same as \"transactions\" above, only " "present if include_removed=true>\n" " Note: transactions that were re-added in the active chain " "will appear as-is in this array, and may thus have a positive " "confirmation count.\n" " ],\n" " \"lastblock\": \"lastblockhash\" (string) The hash of the " "block (target_confirmations-1) from the best block on the main " "chain. This is typically used to feed back into listsinceblock " "the next time you call it. So you would generally use a " "target_confirmations of say 6, so you will be continually " "re-notified of transactions until they've reached 6 confirmations " "plus any new ones\n" "}\n"}, RPCExamples{HelpExampleCli("listsinceblock", "") + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a" "7d38571037cdb1a57f663ad\" 6") + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a" "7d38571037cdb1a57f663ad\", 6")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); // The way the 'height' is initialized is just a workaround for the gcc bug // #47679 since version 4.6.0. Height of the specified block or the common // ancestor, if the block provided was in a deactivated chain. Optional<int> height = MakeOptional(false, int()); // Height of the specified block, even if it's in a deactivated chain. Optional<int> altheight; int target_confirms = 1; isminefilter filter = ISMINE_SPENDABLE; BlockHash blockId; if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { blockId = BlockHash(ParseHashV(request.params[0], "blockhash")); height = locked_chain->findFork(blockId, &altheight); if (!height) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } } if (!request.params[1].isNull()) { target_confirms = request.params[1].get_int(); if (target_confirms < 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); } } if (!request.params[2].isNull() && request.params[2].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); const Optional<int> tip_height = locked_chain->getHeight(); int depth = tip_height && height ? (1 + *tip_height - *height) : -1; UniValue transactions(UniValue::VARR); for (const std::pair<const TxId, CWalletTx> &pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; if (depth == -1 || tx.GetDepthInMainChain(*locked_chain) < depth) { ListTransactions(*locked_chain, pwallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); } } // when a reorg'd block is requested, we also list any relevant transactions // in the blocks of the chain that was detached UniValue removed(UniValue::VARR); while (include_removed && altheight && *altheight > *height) { CBlock block; if (!pwallet->chain().findBlock(blockId, &block) || block.IsNull()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); } for (const CTransactionRef &tx : block.vtx) { auto it = pwallet->mapWallet.find(tx->GetId()); if (it != pwallet->mapWallet.end()) { // We want all transactions regardless of confirmation count to // appear here, even negative confirmation ones, hence the big // negative. ListTransactions(*locked_chain, pwallet, it->second, -100000000, true, removed, filter, nullptr /* filter_label */); } } blockId = block.hashPrevBlock; --*altheight; } int last_height = tip_height ? *tip_height + 1 - target_confirms : -1; BlockHash lastblock = last_height >= 0 ? locked_chain->getBlockHash(last_height) : BlockHash(); UniValue ret(UniValue::VOBJ); ret.pushKV("transactions", transactions); if (include_removed) { ret.pushKV("removed", removed); } ret.pushKV("lastblock", lastblock.GetHex()); return ret; } static UniValue gettransaction(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; } RPCHelpMan{ "gettransaction", "\nGet detailed information about in-wallet transaction <txid>\n", { {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"}, {"include_watchonly", RPCArg::Type::BOOL, /* default */ "false", "Whether to include watch-only addresses in balance calculation " "and details[]"}, }, RPCResult{ "{\n" " \"amount\" : x.xxx, (numeric) The transaction amount " "in " + CURRENCY_UNIT + "\n" " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" " 'send' category of transactions.\n" " \"confirmations\" : n, (numeric) The number of " "confirmations\n" " \"blockhash\" : \"hash\", (string) The block hash\n" " \"blockindex\" : xx, (numeric) The index of the " "transaction in the block that includes it\n" " \"blocktime\" : ttt, (numeric) The time in seconds since " "epoch (1 Jan 1970 GMT)\n" " \"txid\" : \"transactionid\", (string) The transaction id.\n" " \"time\" : ttt, (numeric) The transaction time in " "seconds since epoch (1 Jan 1970 GMT)\n" " \"timereceived\" : ttt, (numeric) The time received in " "seconds since epoch (1 Jan 1970 GMT)\n" " \"details\" : [\n" " {\n" " \"address\" : \"address\", (string) The bitcoin " "address involved in the transaction\n" " \"category\" : \"send|receive\", (string) The category, " "either 'send' or 'receive'\n" " \"amount\" : x.xxx, (numeric) The amount " "in " + CURRENCY_UNIT + "\n" " \"label\" : \"label\", (string) A comment for " "the address/transaction, if any\n" " \"vout\" : n, (numeric) the vout " "value\n" " \"fee\": x.xxx, (numeric) The amount of " "the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" " 'send' category of " "transactions.\n" " \"abandoned\": xxx (bool) 'true' if the " "transaction has been abandoned (inputs are respendable). Only " "available for the \n" " 'send' category of " "transactions.\n" " }\n" " ,...\n" " ],\n" " \"hex\" : \"data\" (string) Raw data for transaction\n" "}\n"}, RPCExamples{HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c" "5cf302fc80e9d5fbf5d48d\"") + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c" "5cf302fc80e9d5fbf5d48d\" true") + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c" "5cf302fc80e9d5fbf5d48d\"")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); TxId txid(ParseHashV(request.params[0], "txid")); isminefilter filter = ISMINE_SPENDABLE; if (!request.params[1].isNull() && request.params[1].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } UniValue entry(UniValue::VOBJ); auto it = pwallet->mapWallet.find(txid); if (it == pwallet->mapWallet.end()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); } const CWalletTx &wtx = it->second; Amount nCredit = wtx.GetCredit(*locked_chain, filter); Amount nDebit = wtx.GetDebit(filter); Amount nNet = nCredit - nDebit; Amount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : Amount::zero()); entry.pushKV("amount", ValueFromAmount(nNet - nFee)); if (wtx.IsFromMe(filter)) { entry.pushKV("fee", ValueFromAmount(nFee)); } WalletTxToJSON(pwallet->chain(), *locked_chain, wtx, entry); UniValue details(UniValue::VARR); ListTransactions(*locked_chain, pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); entry.pushKV("details", details); std::string strHex = EncodeHexTx(*wtx.tx, pwallet->chain().rpcSerializationFlags()); entry.pushKV("hex", strHex); return entry; } static UniValue abandontransaction(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; } RPCHelpMan{ "abandontransaction", "\nMark in-wallet transaction <txid> as abandoned\n" "This will mark this transaction and all its in-wallet descendants as " "abandoned which will allow\n" "for their inputs to be respent. It can be used to replace \"stuck\" " "or evicted transactions.\n" "It only works on transactions which are not included in a block and " "are not currently in the mempool.\n" "It has no effect on transactions which are already abandoned.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, }, RPCResults{}, RPCExamples{HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c" "5cf302fc80e9d5fbf5d48d\"") + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c" "5cf302fc80e9d5fbf5d48d\"")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); TxId txid(ParseHashV(request.params[0], "txid")); if (!pwallet->mapWallet.count(txid)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); } if (!pwallet->AbandonTransaction(*locked_chain, txid)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } return NullUniValue; } static UniValue backupwallet(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; } RPCHelpMan{ "backupwallet", "\nSafely copies current wallet file to destination, which can be a " "directory or a path with filename.\n", { {"destination", RPCArg::Type::STR, RPCArg::Optional::NO, "The destination directory or file"}, }, RPCResults{}, RPCExamples{HelpExampleCli("backupwallet", "\"backup.dat\"") + HelpExampleRpc("backupwallet", "\"backup.dat\"")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); std::string strDest = request.params[0].get_str(); if (!pwallet->BackupWallet(strDest)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!"); } return NullUniValue; } static UniValue keypoolrefill(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; } RPCHelpMan{ "keypoolrefill", "\nFills the keypool." + HelpRequiringPassphrase(pwallet) + "\n", { {"newsize", RPCArg::Type::NUM, /* default */ "100", "The new keypool size"}, }, RPCResults{}, RPCExamples{HelpExampleCli("keypoolrefill", "") + HelpExampleRpc("keypoolrefill", "")}, } .Check(request); if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Private keys are disabled for this wallet"); } auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by // -keypool unsigned int kpSize = 0; if (!request.params[0].isNull()) { if (request.params[0].get_int() < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size."); } kpSize = (unsigned int)request.params[0].get_int(); } EnsureWalletIsUnlocked(pwallet); pwallet->TopUpKeyPool(kpSize); if (pwallet->GetKeyPoolSize() < kpSize) { throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool."); } return NullUniValue; } static UniValue walletpassphrase(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; } RPCHelpMan{ "walletpassphrase", "\nStores the wallet decryption key in memory for 'timeout' seconds.\n" "This is needed prior to performing transactions related to private " "keys such as sending bitcoins\n" "\nNote:\n" "Issuing the walletpassphrase command while the wallet is already " "unlocked will set a new unlock\n" "time that overrides the old one.\n", { {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet passphrase"}, {"timeout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The time to keep the decryption key in seconds; capped at " "100000000 (~3 years)."}, }, RPCResults{}, RPCExamples{ "\nUnlock the wallet for 60 seconds\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + "\nLock the wallet again (before 60 seconds)\n" + HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")}, } .Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but " "walletpassphrase was called."); } // Note that the walletpassphrase is stored in request.params[0] which is // not mlock()ed SecureString strWalletPass; strWalletPass.reserve(100); // TODO: get rid of this .c_str() by implementing // SecureString::operator=(std::string) // Alternately, find a way to make request.params[0] mlock()'d to begin // with. strWalletPass = request.params[0].get_str().c_str(); // Get the timeout int64_t nSleepTime = request.params[1].get_int64(); // Timeout cannot be negative, otherwise it will relock immediately if (nSleepTime < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Timeout cannot be negative."); } // Clamp timeout // larger values trigger a macos/libevent bug? constexpr int64_t MAX_SLEEP_TIME = 100000000; if (nSleepTime > MAX_SLEEP_TIME) { nSleepTime = MAX_SLEEP_TIME; } if (strWalletPass.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty"); } if (!pwallet->Unlock(strWalletPass)) { throw JSONRPCError( RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); } pwallet->TopUpKeyPool(); pwallet->nRelockTime = GetTime() + nSleepTime; // Keep a weak pointer to the wallet so that it is possible to unload the // wallet before the following callback is called. If a valid shared pointer // is acquired in the callback then the wallet is still loaded. std::weak_ptr<CWallet> weak_wallet = wallet; pwallet->chain().rpcRunLater( strprintf("lockwallet(%s)", pwallet->GetName()), [weak_wallet] { if (auto shared_wallet = weak_wallet.lock()) { LOCK(shared_wallet->cs_wallet); shared_wallet->Lock(); shared_wallet->nRelockTime = 0; } }, nSleepTime); return NullUniValue; } static UniValue walletpassphrasechange(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; } RPCHelpMan{ "walletpassphrasechange", "\nChanges the wallet passphrase from 'oldpassphrase' to " "'newpassphrase'.\n", { {"oldpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The current passphrase"}, {"newpassphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The new passphrase"}, }, RPCResults{}, RPCExamples{HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")}, } .Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but " "walletpassphrasechange was called."); } // TODO: get rid of these .c_str() calls by implementing // SecureString::operator=(std::string) // Alternately, find a way to make request.params[0] mlock()'d to begin // with. SecureString strOldWalletPass; strOldWalletPass.reserve(100); strOldWalletPass = request.params[0].get_str().c_str(); SecureString strNewWalletPass; strNewWalletPass.reserve(100); strNewWalletPass = request.params[1].get_str().c_str(); if (strOldWalletPass.empty() || strNewWalletPass.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty"); } if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) { throw JSONRPCError( RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); } return NullUniValue; } static UniValue walletlock(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; } RPCHelpMan{ "walletlock", "\nRemoves the wallet encryption key from memory, locking the wallet.\n" "After calling this method, you will need to call walletpassphrase " "again\n" "before being able to call any methods which require the wallet to be " "unlocked.\n", {}, RPCResults{}, RPCExamples{ "\nSet the passphrase for 2 minutes to perform a transaction\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 120") + "\nPerform a send (requires passphrase set)\n" + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 1.0") + "\nClear the passphrase since we are done before 2 minutes is " "up\n" + HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("walletlock", "")}, } .Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but " "walletlock was called."); } pwallet->Lock(); pwallet->nRelockTime = 0; return NullUniValue; } static UniValue encryptwallet(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; } RPCHelpMan{ "encryptwallet", "\nEncrypts the wallet with 'passphrase'. This is for first time " "encryption.\n" "After this, any calls that interact with private keys such as sending " "or signing \n" "will require the passphrase to be set prior the making these calls.\n" "Use the walletpassphrase call for this, and then walletlock call.\n" "If the wallet is already encrypted, use the walletpassphrasechange " "call.\n", { {"passphrase", RPCArg::Type::STR, RPCArg::Optional::NO, "The pass phrase to encrypt the wallet with. It must be at least " "1 character, but should be long."}, }, RPCResults{}, RPCExamples{ "\nEncrypt your wallet\n" + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + "\nNow set the passphrase to use the wallet, such as for signing " "or sending bitcoin\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") + "\nNow we can do something like sign\n" + HelpExampleCli("signmessage", "\"address\" \"test message\"") + "\nNow lock the wallet again by removing the passphrase\n" + HelpExampleCli("walletlock", "") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("encryptwallet", "\"my pass phrase\"")}, } .Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an encrypted wallet, but " "encryptwallet was called."); } // TODO: get rid of this .c_str() by implementing // SecureString::operator=(std::string) // Alternately, find a way to make request.params[0] mlock()'d to begin // with. SecureString strWalletPass; strWalletPass.reserve(100); strWalletPass = request.params[0].get_str().c_str(); if (strWalletPass.empty()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "passphrase can not be empty"); } if (!pwallet->EncryptWallet(strWalletPass)) { throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); } return "wallet encrypted; The keypool has been flushed and a new HD seed " "was generated (if you are using HD). You need to make a new " "backup."; } static UniValue lockunspent(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; } RPCHelpMan{ "lockunspent", "\nUpdates list of temporarily unspendable outputs.\n" "Temporarily lock (unlock=false) or unlock (unlock=true) specified " "transaction outputs.\n" "If no transaction outputs are specified when unlocking then all " "current locked transaction outputs are unlocked.\n" "A locked transaction output will not be chosen by automatic coin " "selection, when spending bitcoins.\n" "Locks are stored in memory only. Nodes start with zero locked " "outputs, and the locked output list\n" "is always cleared (by virtue of process exit) when a node stops or " "fails.\n" "Also see the listunspent call\n", { {"unlock", RPCArg::Type::BOOL, RPCArg::Optional::NO, "Whether to unlock (true) or lock (false) the specified " "transactions"}, { "transactions", RPCArg::Type::ARR, /* default */ "empty array", "A json array of objects. Each object the txid (string) vout " "(numeric).", { { "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, }, }, }, }, }, RPCResult{"true|false (boolean) Whether the command was successful " "or not\n"}, RPCExamples{ "\nList the unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nLock an unspent transaction\n" + HelpExampleCli("lockunspent", "false " "\"[{\\\"txid\\\":" "\\\"a08e6907dbbd3d809776dbfc5d82e371" "b764ed838b5655e72f463568df1aadf0\\\"" ",\\\"vout\\\":1}]\"") + "\nList the locked transactions\n" + HelpExampleCli("listlockunspent", "") + "\nUnlock the transaction again\n" + HelpExampleCli("lockunspent", "true " "\"[{\\\"txid\\\":" "\\\"a08e6907dbbd3d809776dbfc5d82e371" "b764ed838b5655e72f463568df1aadf0\\\"" ",\\\"vout\\\":1}]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("lockunspent", "false, " "\"[{\\\"txid\\\":" "\\\"a08e6907dbbd3d809776dbfc5d82e371" "b764ed838b5655e72f463568df1aadf0\\\"" ",\\\"vout\\\":1}]\"")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); RPCTypeCheckArgument(request.params[0], UniValue::VBOOL); bool fUnlock = request.params[0].get_bool(); if (request.params[1].isNull()) { if (fUnlock) { pwallet->UnlockAllCoins(); } return true; } RPCTypeCheckArgument(request.params[1], UniValue::VARR); const UniValue &output_params = request.params[1]; // Create and validate the COutPoints first. std::vector<COutPoint> outputs; outputs.reserve(output_params.size()); for (size_t idx = 0; idx < output_params.size(); idx++) { const UniValue &o = output_params[idx].get_obj(); RPCTypeCheckObj(o, { {"txid", UniValueType(UniValue::VSTR)}, {"vout", UniValueType(UniValue::VNUM)}, }); const int nOutput = find_value(o, "vout").get_int(); if (nOutput < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); } const TxId txid(ParseHashO(o, "txid")); const auto it = pwallet->mapWallet.find(txid); if (it == pwallet->mapWallet.end()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, unknown transaction"); } const COutPoint output(txid, nOutput); const CWalletTx &trans = it->second; if (output.GetN() >= trans.tx->vout.size()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout index out of bounds"); } if (pwallet->IsSpent(*locked_chain, output)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected unspent output"); } const bool is_locked = pwallet->IsLockedCoin(output); if (fUnlock && !is_locked) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected locked output"); } if (!fUnlock && is_locked) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, output already locked"); } outputs.push_back(output); } // Atomically set (un)locked status for the outputs. for (const COutPoint &output : outputs) { if (fUnlock) { pwallet->UnlockCoin(output); } else { pwallet->LockCoin(output); } } return true; } static UniValue listlockunspent(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; } RPCHelpMan{ "listlockunspent", "\nReturns list of temporarily unspendable outputs.\n" "See the lockunspent call to lock and unlock transactions for " "spending.\n", {}, RPCResult{ "[\n" " {\n" " \"txid\" : \"transactionid\", (string) The transaction id " "locked\n" " \"vout\" : n (numeric) The vout value\n" " }\n" " ,...\n" "]\n"}, RPCExamples{ "\nList the unspent transactions\n" + HelpExampleCli("listunspent", "") + "\nLock an unspent transaction\n" + HelpExampleCli("lockunspent", "false " "\"[{\\\"txid\\\":" "\\\"a08e6907dbbd3d809776dbfc5d82e371" "b764ed838b5655e72f463568df1aadf0\\\"" ",\\\"vout\\\":1}]\"") + "\nList the locked transactions\n" + HelpExampleCli("listlockunspent", "") + "\nUnlock the transaction again\n" + HelpExampleCli("lockunspent", "true " "\"[{\\\"txid\\\":" "\\\"a08e6907dbbd3d809776dbfc5d82e371" "b764ed838b5655e72f463568df1aadf0\\\"" ",\\\"vout\\\":1}]\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlockunspent", "")}, } .Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); std::vector<COutPoint> vOutpts; pwallet->ListLockedCoins(vOutpts); UniValue ret(UniValue::VARR); for (const COutPoint &output : vOutpts) { UniValue o(UniValue::VOBJ); o.pushKV("txid", output.GetTxId().GetHex()); o.pushKV("vout", int(output.GetN())); ret.push_back(o); } return ret; } static UniValue settxfee(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; } RPCHelpMan{ "settxfee", "\nSet the transaction fee per kB for this wallet. Overrides the " "global -paytxfee command line parameter.\n", { {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The transaction fee in " + CURRENCY_UNIT + "/kB"}, }, RPCResult{"true|false (boolean) Returns true if successful\n"}, RPCExamples{HelpExampleCli("settxfee", "0.00001") + HelpExampleRpc("settxfee", "0.00001")}, } .Check(request); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); Amount nAmount = AmountFromValue(request.params[0]); CFeeRate tx_fee_rate(nAmount, 1000); if (tx_fee_rate == CFeeRate()) { // automatic selection } else if (tx_fee_rate < pwallet->chain().relayMinFee()) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("txfee cannot be less than min relay tx fee (%s)", pwallet->chain().relayMinFee().ToString())); } else if (tx_fee_rate < pwallet->m_min_fee) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("txfee cannot be less than wallet min fee (%s)", pwallet->m_min_fee.ToString())); } pwallet->m_pay_tx_fee = tx_fee_rate; return true; } static UniValue getbalances(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const rpc_wallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(rpc_wallet.get(), request.fHelp)) { return NullUniValue; } CWallet &wallet = *rpc_wallet; RPCHelpMan{ "getbalances", "Returns an object with all balances in " + CURRENCY_UNIT + ".\n", {}, RPCResult{ "{\n" " \"mine\": { (object) balances from " "outputs that the wallet can sign\n" " \"trusted\": xxx (numeric) trusted balance " "(outputs created by the wallet or confirmed outputs)\n" " \"untrusted_pending\": xxx (numeric) untrusted " "pending balance (outputs created by others that are in the " "mempool)\n" " \"immature\": xxx (numeric) balance from " "immature coinbase outputs\n" " \"used\": xxx (numeric) (only present if " "avoid_reuse is set) balance from coins sent to addresses that " "were previously spent from (potentially privacy violating)\n" " },\n" " \"watchonly\": { (object) watchonly " "balances (not present if wallet does not watch anything)\n" " \"trusted\": xxx (numeric) trusted balance " "(outputs created by the wallet or confirmed outputs)\n" " \"untrusted_pending\": xxx (numeric) untrusted " "pending balance (outputs created by others that are in the " "mempool)\n" " \"immature\": xxx (numeric) balance from " "immature coinbase outputs\n" " },\n" "}\n"}, RPCExamples{HelpExampleCli("getbalances", "") + HelpExampleRpc("getbalances", "")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now wallet.BlockUntilSyncedToCurrentChain(); auto locked_chain = wallet.chain().lock(); LOCK(wallet.cs_wallet); UniValue obj(UniValue::VOBJ); const auto bal = wallet.GetBalance(); UniValue balances{UniValue::VOBJ}; { UniValue balances_mine{UniValue::VOBJ}; balances_mine.pushKV("trusted", ValueFromAmount(bal.m_mine_trusted)); balances_mine.pushKV("untrusted_pending", ValueFromAmount(bal.m_mine_untrusted_pending)); balances_mine.pushKV("immature", ValueFromAmount(bal.m_mine_immature)); if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) { // If the AVOID_REUSE flag is set, bal has been set to just the // un-reused address balance. Get the total balance, and then // subtract bal to get the reused address balance. const auto full_bal = wallet.GetBalance(0, false); balances_mine.pushKV( "used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending)); } balances.pushKV("mine", balances_mine); } if (wallet.HaveWatchOnly()) { UniValue balances_watchonly{UniValue::VOBJ}; balances_watchonly.pushKV("trusted", ValueFromAmount(bal.m_watchonly_trusted)); balances_watchonly.pushKV( "untrusted_pending", ValueFromAmount(bal.m_watchonly_untrusted_pending)); balances_watchonly.pushKV("immature", ValueFromAmount(bal.m_watchonly_immature)); balances.pushKV("watchonly", balances_watchonly); } return balances; } static UniValue getwalletinfo(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; } RPCHelpMan{ "getwalletinfo", "Returns an object containing various wallet state info.\n", {}, RPCResult{ "{\n" " \"walletname\": xxxxx, (string) the wallet name\n" " \"walletversion\": xxxxx, (numeric) the wallet " "version\n" " \"balance\": xxxxxxx, (numeric) DEPRECATED. " "Identical to getbalances().mine.trusted\n" " \"unconfirmed_balance\": xxx, (numeric) DEPRECATED. " "Identical to getbalances().mine.untrusted_pending\n" " \"immature_balance\": xxxxxx, (numeric) DEPRECATED. " "Identical to getbalances().mine.immature\n" " \"txcount\": xxxxxxx, (numeric) the total number " "of transactions in the wallet\n" " \"keypoololdest\": xxxxxx, (numeric) the timestamp " "(seconds since Unix epoch) of the oldest pre-generated key in the " "key pool\n" " \"keypoolsize\": xxxx, (numeric) how many new keys " "are pre-generated (only counts external keys)\n" " \"keypoolsize_hd_internal\": xxxx, (numeric) how many new keys " "are pre-generated for internal use (used for change outputs, only " "appears if the wallet is using this feature, otherwise external " "keys are used)\n" " \"unlocked_until\": ttt, (numeric) the timestamp in " "seconds since epoch (midnight Jan 1 1970 GMT) that the wallet is " "unlocked for transfers, or 0 if the wallet is locked\n" " \"paytxfee\": x.xxxx, (numeric) the transaction " "fee configuration, set in " + CURRENCY_UNIT + "/kB\n" " \"hdseedid\": \"<hash160>\" (string, optional) the " "Hash160 of the HD seed (only present when HD is enabled)\n" " \"private_keys_enabled\": true|false (boolean) false if " "privatekeys are disabled for this wallet (enforced watch-only " "wallet)\n" " \"scanning\": (json object) current " "scanning details, or false if no scan is in progress\n" " {\n" " \"duration\" : xxxx (numeric) elapsed seconds " "since scan start\n" " \"progress\" : x.xxxx, (numeric) scanning " "progress percentage [0.0, 1.0]\n" " }\n" " \"avoid_reuse\": true|false (boolean) whether this " "wallet tracks clean/dirty coins in terms of reuse\n" "}\n"}, RPCExamples{HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "")}, } .Check(request); // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); UniValue obj(UniValue::VOBJ); size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); const auto bal = pwallet->GetBalance(); obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletversion", pwallet->GetVersion()); obj.pushKV("balance", ValueFromAmount(bal.m_mine_trusted)); obj.pushKV("unconfirmed_balance", ValueFromAmount(bal.m_mine_untrusted_pending)); obj.pushKV("immature_balance", ValueFromAmount(bal.m_mine_immature)); obj.pushKV("txcount", (int)pwallet->mapWallet.size()); obj.pushKV("keypoololdest", pwallet->GetOldestKeyPoolTime()); obj.pushKV("keypoolsize", (int64_t)kpExternalSize); CKeyID seed_id = pwallet->GetHDChain().seed_id; if (pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { obj.pushKV("keypoolsize_hd_internal", int64_t(pwallet->GetKeyPoolSize() - kpExternalSize)); } if (pwallet->IsCrypted()) { obj.pushKV("unlocked_until", pwallet->nRelockTime); } obj.pushKV("paytxfee", ValueFromAmount(pwallet->m_pay_tx_fee.GetFeePerK())); if (!seed_id.IsNull()) { obj.pushKV("hdseedid", seed_id.GetHex()); } obj.pushKV("private_keys_enabled", !pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); if (pwallet->IsScanning()) { UniValue scanning(UniValue::VOBJ); scanning.pushKV("duration", pwallet->ScanningDuration() / 1000); scanning.pushKV("progress", pwallet->ScanningProgress()); obj.pushKV("scanning", scanning); } else { obj.pushKV("scanning", false); } obj.pushKV("avoid_reuse", pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)); return obj; } static UniValue listwalletdir(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "listwalletdir", "Returns a list of wallets in the wallet directory.\n", {}, RPCResult{ "{\n" " \"wallets\" : [ (json array of objects)\n" " {\n" " \"name\" : \"name\" (string) The wallet name\n" " }\n" " ,...\n" " ]\n" "}\n"}, RPCExamples{HelpExampleCli("listwalletdir", "") + HelpExampleRpc("listwalletdir", "")}, } .Check(request); UniValue wallets(UniValue::VARR); for (const auto &path : ListWalletDir()) { UniValue wallet(UniValue::VOBJ); wallet.pushKV("name", path.string()); wallets.push_back(wallet); } UniValue result(UniValue::VOBJ); result.pushKV("wallets", wallets); return result; } static UniValue listwallets(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "listwallets", "Returns a list of currently loaded wallets.\n" "For full information on the wallet, use \"getwalletinfo\"\n", {}, RPCResult{"[ (json array of strings)\n" " \"walletname\" (string) the wallet name\n" " ...\n" "]\n"}, RPCExamples{HelpExampleCli("listwallets", "") + HelpExampleRpc("listwallets", "")}, } .Check(request); UniValue obj(UniValue::VARR); for (const std::shared_ptr<CWallet> &wallet : GetWallets()) { if (!EnsureWalletIsAvailable(wallet.get(), request.fHelp)) { return NullUniValue; } LOCK(wallet->cs_wallet); obj.push_back(wallet->GetName()); } return obj; } static UniValue loadwallet(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "loadwallet", "\nLoads a wallet from a wallet file or directory." "\nNote that all wallet command-line options used when starting " "bitcoind will be" "\napplied to the new wallet (eg -zapwallettxes, upgradewallet, " "rescan, etc).\n", { {"filename", RPCArg::Type::STR, RPCArg::Optional::NO, "The wallet directory or .dat file."}, }, RPCResult{"{\n" " \"name\" : <wallet_name>, (string) The wallet " "name if loaded successfully.\n" " \"warning\" : <warning>, (string) Warning " "message if wallet was not loaded cleanly.\n" "}\n"}, RPCExamples{HelpExampleCli("loadwallet", "\"test.dat\"") + HelpExampleRpc("loadwallet", "\"test.dat\"")}, } .Check(request); const CChainParams &chainParams = config.GetChainParams(); WalletLocation location(request.params[0].get_str()); if (!location.Exists()) { throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Wallet " + location.GetName() + " not found."); } else if (fs::is_directory(location.GetPath())) { // The given filename is a directory. Check that there's a wallet.dat // file. fs::path wallet_dat_file = location.GetPath() / "wallet.dat"; if (fs::symlink_status(wallet_dat_file).type() == fs::file_not_found) { throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Directory " + location.GetName() + " does not contain a wallet.dat file."); } } std::string error, warning; std::shared_ptr<CWallet> const wallet = LoadWallet(chainParams, *g_rpc_chain, location, error, warning); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, error); } UniValue obj(UniValue::VOBJ); obj.pushKV("name", wallet->GetName()); obj.pushKV("warning", warning); return obj; } static UniValue setwalletflag(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; } std::string flags = ""; for (auto &it : WALLET_FLAG_MAP) { if (it.second & MUTABLE_WALLET_FLAGS) { flags += (flags == "" ? "" : ", ") + it.first; } } RPCHelpMan{ "setwalletflag", "\nChange the state of the given wallet flag for a wallet.\n", { {"flag", RPCArg::Type::STR, RPCArg::Optional::NO, "The name of the flag to change. Current available flags: " + flags}, {"value", RPCArg::Type::BOOL, /* default */ "true", "The new state."}, }, RPCResult{ "{\n" " \"flag_name\": string (string) The name of the flag that " "was modified\n" " \"flag_state\": bool (bool) The new state of the flag\n" " \"warnings\": string (string) Any warnings associated with " "the change\n" "}\n"}, RPCExamples{HelpExampleCli("setwalletflag", "avoid_reuse") + HelpExampleRpc("setwalletflag", "\"avoid_reuse\"")}, } .Check(request); std::string flag_str = request.params[0].get_str(); bool value = request.params[1].isNull() || request.params[1].get_bool(); if (!WALLET_FLAG_MAP.count(flag_str)) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Unknown wallet flag: %s", flag_str)); } auto flag = WALLET_FLAG_MAP.at(flag_str); if (!(flag & MUTABLE_WALLET_FLAGS)) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is immutable: %s", flag_str)); } UniValue res(UniValue::VOBJ); if (pwallet->IsWalletFlagSet(flag) == value) { throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Wallet flag is already set to %s: %s", value ? "true" : "false", flag_str)); } res.pushKV("flag_name", flag_str); res.pushKV("flag_state", value); if (value) { pwallet->SetWalletFlag(flag); } else { pwallet->UnsetWalletFlag(flag); } if (flag && value && WALLET_FLAG_CAVEATS.count(flag)) { res.pushKV("warnings", WALLET_FLAG_CAVEATS.at(flag)); } return res; } static UniValue createwallet(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "createwallet", "\nCreates and loads a new wallet.\n", { {"wallet_name", RPCArg::Type::STR, RPCArg::Optional::NO, "The name for the new wallet. If this is a path, the wallet will " "be created at the path location."}, {"disable_private_keys", RPCArg::Type::BOOL, /* default */ "false", "Disable the possibility of private keys (only watchonlys are " "possible in this mode)."}, {"blank", RPCArg::Type::BOOL, /* default */ "false", "Create a blank wallet. A blank wallet has no keys or HD seed. " "One can be set using sethdseed."}, {"passphrase", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "Encrypt the wallet with this passphrase."}, {"avoid_reuse", RPCArg::Type::BOOL, /* default */ "false", "Keep track of coin reuse, and treat dirty and clean coins " "differently with privacy considerations in mind."}, }, RPCResult{"{\n" " \"name\" : <wallet_name>, (string) The wallet " "name if created successfully. If the wallet was created " "using a full path, the wallet_name will be the full path.\n" " \"warning\" : <warning>, (string) Warning " "message if wallet was not loaded cleanly.\n" "}\n"}, RPCExamples{HelpExampleCli("createwallet", "\"testwallet\"") + HelpExampleRpc("createwallet", "\"testwallet\"")}, } .Check(request); uint64_t flags = 0; if (!request.params[1].isNull() && request.params[1].get_bool()) { flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; } if (!request.params[2].isNull() && request.params[2].get_bool()) { flags |= WALLET_FLAG_BLANK_WALLET; } SecureString passphrase; passphrase.reserve(100); if (!request.params[3].isNull()) { passphrase = request.params[3].get_str().c_str(); if (passphrase.empty()) { // Empty string is invalid throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Cannot encrypt a wallet with a blank password"); } } if (!request.params[4].isNull() && request.params[4].get_bool()) { flags |= WALLET_FLAG_AVOID_REUSE; } std::string error; std::string warning; WalletCreationStatus status; - std::shared_ptr<CWallet> wallet = CreateWallet( - config.GetChainParams(), *g_rpc_chain, request.params[0].get_str(), - error, warning, status, passphrase, flags); + std::shared_ptr<CWallet> wallet = + CreateWallet(config.GetChainParams(), *g_rpc_chain, passphrase, flags, + request.params[0].get_str(), error, warning, status); if (status == WalletCreationStatus::CREATION_FAILED) { throw JSONRPCError(RPC_WALLET_ERROR, error); } else if (status == WalletCreationStatus::ENCRYPTION_FAILED) { throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, error); } else if (status != WalletCreationStatus::SUCCESS) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed"); } UniValue obj(UniValue::VOBJ); obj.pushKV("name", wallet->GetName()); obj.pushKV("warning", warning); return obj; } static UniValue unloadwallet(const Config &config, const JSONRPCRequest &request) { RPCHelpMan{ "unloadwallet", "Unloads the wallet referenced by the request endpoint otherwise " "unloads the wallet specified in the argument.\n" "Specifying the wallet name on a wallet endpoint is invalid.", { {"wallet_name", RPCArg::Type::STR, /* default */ "the wallet name from the RPC request", "The name of the wallet to unload."}, }, RPCResults{}, RPCExamples{HelpExampleCli("unloadwallet", "wallet_name") + HelpExampleRpc("unloadwallet", "wallet_name")}, } .Check(request); std::string wallet_name; if (GetWalletNameFromJSONRPCRequest(request, wallet_name)) { if (!request.params[0].isNull()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot unload the requested wallet"); } } else { wallet_name = request.params[0].get_str(); } std::shared_ptr<CWallet> wallet = GetWallet(wallet_name); if (!wallet) { throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); } // Release the "main" shared pointer and prevent further notifications. // Note that any attempt to load the same wallet would fail until the wallet // is destroyed (see CheckUniqueFileid). if (!RemoveWallet(wallet)) { throw JSONRPCError(RPC_MISC_ERROR, "Requested wallet already unloaded"); } UnloadWallet(std::move(wallet)); return NullUniValue; } static UniValue listunspent(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; } RPCHelpMan{ "listunspent", "\nReturns array of unspent transaction outputs\n" "with between minconf and maxconf (inclusive) confirmations.\n" "Optionally filter to only include txouts paid to specified " "addresses.\n", { {"minconf", RPCArg::Type::NUM, /* default */ "1", "The minimum confirmations to filter"}, {"maxconf", RPCArg::Type::NUM, /* default */ "9999999", "The maximum confirmations to filter"}, { "addresses", RPCArg::Type::ARR, /* default */ "empty array", "A json array of bitcoin addresses to filter", { {"address", RPCArg::Type::STR, RPCArg::Optional::OMITTED, "bitcoin address"}, }, }, {"include_unsafe", RPCArg::Type::BOOL, /* default */ "true", "Include outputs that are not safe to spend\n" " See description of \"safe\" attribute below."}, {"query_options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "JSON with query options", { {"minimumAmount", RPCArg::Type::AMOUNT, /* default */ "0", "Minimum value of each UTXO in " + CURRENCY_UNIT + ""}, {"maximumAmount", RPCArg::Type::AMOUNT, /* default */ "unlimited", "Maximum value of each UTXO in " + CURRENCY_UNIT + ""}, {"maximumCount", RPCArg::Type::NUM, /* default */ "unlimited", "Maximum number of UTXOs"}, {"minimumSumAmount", RPCArg::Type::AMOUNT, /* default */ "unlimited", "Minimum sum value of all UTXOs in " + CURRENCY_UNIT + ""}, }, "query_options"}, }, RPCResult{ "[ (array of json object)\n" " {\n" " \"txid\" : \"txid\", (string) the transaction id\n" " \"vout\" : n, (numeric) the vout value\n" " \"address\" : \"address\", (string) the bitcoin address\n" " \"label\" : \"label\", (string) The associated label, or " "\"\" for the default label\n" " \"scriptPubKey\" : \"key\", (string) the script key\n" " \"amount\" : x.xxx, (numeric) the transaction output " "amount in " + CURRENCY_UNIT + "\n" " \"confirmations\" : n, (numeric) The number of " "confirmations\n" " \"redeemScript\" : n (string) The redeemScript if " "scriptPubKey is P2SH\n" " \"spendable\" : xxx, (bool) Whether we have the " "private keys to spend this output\n" " \"solvable\" : xxx, (bool) Whether we know how to " "spend this output, ignoring the lack of keys\n" " \"reused\" : xxx, (bool) (only present if " "avoid_reuse is set) Whether this output is reused/dirty (sent to " "an address that was previously spent from)\n" " \"desc\" : xxx, (string, only when solvable) A " "descriptor for spending this output\n" " \"safe\" : xxx (bool) Whether this output is " "considered safe to spend. Unconfirmed transactions\n" " from outside keys are considered " "unsafe and are not eligible for spending by\n" " fundrawtransaction and " "sendtoaddress.\n" " }\n" " ,...\n" "]\n"}, RPCExamples{ HelpExampleCli("listunspent", "") + HelpExampleCli("listunspent", "6 9999999 " "\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\"," "\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleRpc("listunspent", "6, 9999999 " "\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\"," "\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleCli( "listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") + HelpExampleRpc( "listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ")}, } .Check(request); int nMinDepth = 1; if (!request.params[0].isNull()) { RPCTypeCheckArgument(request.params[0], UniValue::VNUM); nMinDepth = request.params[0].get_int(); } int nMaxDepth = 9999999; if (!request.params[1].isNull()) { RPCTypeCheckArgument(request.params[1], UniValue::VNUM); nMaxDepth = request.params[1].get_int(); } std::set<CTxDestination> destinations; if (!request.params[2].isNull()) { RPCTypeCheckArgument(request.params[2], UniValue::VARR); UniValue inputs = request.params[2].get_array(); for (size_t idx = 0; idx < inputs.size(); idx++) { const UniValue &input = inputs[idx]; CTxDestination dest = DecodeDestination(input.get_str(), config.GetChainParams()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ") + input.get_str()); } if (!destinations.insert(dest).second) { throw JSONRPCError( RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ") + input.get_str()); } } } bool include_unsafe = true; if (!request.params[3].isNull()) { RPCTypeCheckArgument(request.params[3], UniValue::VBOOL); include_unsafe = request.params[3].get_bool(); } Amount nMinimumAmount = Amount::zero(); Amount nMaximumAmount = MAX_MONEY; Amount nMinimumSumAmount = MAX_MONEY; uint64_t nMaximumCount = 0; if (!request.params[4].isNull()) { const UniValue &options = request.params[4].get_obj(); if (options.exists("minimumAmount")) { nMinimumAmount = AmountFromValue(options["minimumAmount"]); } if (options.exists("maximumAmount")) { nMaximumAmount = AmountFromValue(options["maximumAmount"]); } if (options.exists("minimumSumAmount")) { nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]); } if (options.exists("maximumCount")) { nMaximumCount = options["maximumCount"].get_int64(); } } // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); UniValue results(UniValue::VARR); std::vector<COutput> vecOutputs; { CCoinControl cctl; cctl.m_avoid_address_reuse = false; auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); } LOCK(pwallet->cs_wallet); const bool avoid_reuse = pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); for (const COutput &out : vecOutputs) { CTxDestination address; const CScript &scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, address); bool reused = avoid_reuse && pwallet->IsUsedDestination(address); if (destinations.size() && (!fValidAddress || !destinations.count(address))) { continue; } UniValue entry(UniValue::VOBJ); entry.pushKV("txid", out.tx->GetId().GetHex()); entry.pushKV("vout", out.i); if (fValidAddress) { entry.pushKV("address", EncodeDestination(address, config)); auto i = pwallet->mapAddressBook.find(address); if (i != pwallet->mapAddressBook.end()) { entry.pushKV("label", i->second.name); } if (scriptPubKey.IsPayToScriptHash()) { const CScriptID &hash = CScriptID(boost::get<ScriptHash>(address)); CScript redeemScript; if (pwallet->GetCScript(hash, redeemScript)) { entry.pushKV("redeemScript", HexStr(redeemScript.begin(), redeemScript.end())); } } } entry.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); entry.pushKV("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue)); entry.pushKV("confirmations", out.nDepth); entry.pushKV("spendable", out.fSpendable); entry.pushKV("solvable", out.fSolvable); if (out.fSolvable) { auto descriptor = InferDescriptor(scriptPubKey, *pwallet); entry.pushKV("desc", descriptor->ToString()); } if (avoid_reuse) { entry.pushKV("reused", reused); } entry.pushKV("safe", out.fSafe); results.push_back(entry); } return results; } void FundTransaction(CWallet *const pwallet, CMutableTransaction &tx, Amount &fee_out, int &change_position, UniValue options) { // Make sure the results are valid at least up to the most recent block // the user could have gotten from another RPC command prior to now pwallet->BlockUntilSyncedToCurrentChain(); CCoinControl coinControl; change_position = -1; bool lockUnspents = false; UniValue subtractFeeFromOutputs; std::set<int> setSubtractFeeFromOutputs; if (!options.isNull()) { if (options.type() == UniValue::VBOOL) { // backward compatibility bool only fallback coinControl.fAllowWatchOnly = options.get_bool(); } else { RPCTypeCheckArgument(options, UniValue::VOBJ); RPCTypeCheckObj( options, { {"changeAddress", UniValueType(UniValue::VSTR)}, {"changePosition", UniValueType(UniValue::VNUM)}, {"includeWatching", UniValueType(UniValue::VBOOL)}, {"lockUnspents", UniValueType(UniValue::VBOOL)}, // will be checked below {"feeRate", UniValueType()}, {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, }, true, true); if (options.exists("changeAddress")) { CTxDestination dest = DecodeDestination( options["changeAddress"].get_str(), pwallet->chainParams); if (!IsValidDestination(dest)) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address"); } coinControl.destChange = dest; } if (options.exists("changePosition")) { change_position = options["changePosition"].get_int(); } if (options.exists("includeWatching")) { coinControl.fAllowWatchOnly = options["includeWatching"].get_bool(); } if (options.exists("lockUnspents")) { lockUnspents = options["lockUnspents"].get_bool(); } if (options.exists("feeRate")) { coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"])); coinControl.fOverrideFeeRate = true; } if (options.exists("subtractFeeFromOutputs")) { subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array(); } } } if (tx.vout.size() == 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); } if (change_position != -1 && (change_position < 0 || (unsigned int)change_position > tx.vout.size())) { throw JSONRPCError(RPC_INVALID_PARAMETER, "changePosition out of bounds"); } for (size_t idx = 0; idx < subtractFeeFromOutputs.size(); idx++) { int pos = subtractFeeFromOutputs[idx].get_int(); if (setSubtractFeeFromOutputs.count(pos)) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Invalid parameter, duplicated position: %d", pos)); } if (pos < 0) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Invalid parameter, negative position: %d", pos)); } if (pos >= int(tx.vout.size())) { throw JSONRPCError( RPC_INVALID_PARAMETER, strprintf("Invalid parameter, position too large: %d", pos)); } setSubtractFeeFromOutputs.insert(pos); } std::string strFailReason; if (!pwallet->FundTransaction(tx, fee_out, change_position, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } } static UniValue fundrawtransaction(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; } RPCHelpMan{ "fundrawtransaction", "\nAdd inputs to a transaction until it has enough in value to meet " "its out value.\n" "This will not modify existing inputs, and will add at most one change " "output to the outputs.\n" "No existing outputs will be modified unless " "\"subtractFeeFromOutputs\" is specified.\n" "Note that inputs which were signed may need to be resigned after " "completion since in/outputs have been added.\n" "The inputs added will not be signed, use signrawtransactionwithkey or " "signrawtransactionwithwallet for that.\n" "Note that all existing inputs must have their previous output " "transaction be in the wallet.\n" "Note that all inputs selected must be of standard form and P2SH " "scripts must be\n" "in the wallet using importaddress or addmultisigaddress (to calculate " "fees).\n" "You can see whether this is the case by checking the \"solvable\" " "field in the listunspent output.\n" "Only pay-to-pubkey, multisig, and P2SH versions thereof are currently " "supported for watch-only\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hex string of the raw transaction"}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "for backward compatibility: passing in a true instead of an " "object will result in {\"includeWatching\":true}", { {"changeAddress", RPCArg::Type::STR, /* default */ "pool address", "The bitcoin address to receive the change"}, {"changePosition", RPCArg::Type::NUM, /* default */ "", "The index of the change output"}, {"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"}, {"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"}, {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, { "subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n" " The fee will be equally " "deducted from the amount of each specified output.\n" " Those recipients will " "receive less bitcoins than you enter in their " "corresponding amount field.\n" " If no outputs are " "specified here, the sender pays the fee.", { {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output " "is added."}, }, }, }, "options"}, }, RPCResult{"{\n" " \"hex\": \"value\", (string) The resulting raw " "transaction (hex-encoded string)\n" " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" " \"changepos\": n (numeric) The position of the " "added change output, or -1\n" "}\n"}, RPCExamples{ "\nCreate a transaction with no inputs\n" + HelpExampleCli("createrawtransaction", "\"[]\" \"{\\\"myaddress\\\":0.01}\"") + "\nAdd sufficient unsigned inputs to meet the output value\n" + HelpExampleCli("fundrawtransaction", "\"rawtransactionhex\"") + "\nSign the transaction\n" + HelpExampleCli("signrawtransactionwithwallet", "\"fundedtransactionhex\"") + "\nSend the transaction\n" + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")}, } .Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValueType()}); // parse hex string from parameter CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } Amount fee; int change_position; FundTransaction(pwallet, tx, fee, change_position, request.params[1]); UniValue result(UniValue::VOBJ); result.pushKV("hex", EncodeHexTx(CTransaction(tx))); result.pushKV("fee", ValueFromAmount(fee)); result.pushKV("changepos", change_position); return result; } UniValue signrawtransactionwithwallet(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; } RPCHelpMan{ "signrawtransactionwithwallet", "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second optional argument (may be null) is an array of previous " "transaction outputs that\n" "this transaction depends on but may not yet be in the block chain.\n" + HelpRequiringPassphrase(pwallet) + "\n", { {"hexstring", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction hex string"}, { "prevtxs", RPCArg::Type::ARR, RPCArg::Optional::OMITTED_NAMED_ARG, "A json array of previous dependent transaction outputs", { { "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, {"scriptPubKey", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "script key"}, {"redeemScript", RPCArg::Type::STR_HEX, RPCArg::Optional::OMITTED, "(required for P2SH)"}, {"amount", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "The amount spent"}, }, }, }, }, {"sighashtype", RPCArg::Type::STR, /* default */ "ALL|FORKID", "The signature hash type. Must be one of\n" " \"ALL|FORKID\"\n" " \"NONE|FORKID\"\n" " \"SINGLE|FORKID\"\n" " \"ALL|FORKID|ANYONECANPAY\"\n" " \"NONE|FORKID|ANYONECANPAY\"\n" " \"SINGLE|FORKID|ANYONECANPAY\""}, }, RPCResult{"{\n" " \"hex\" : \"value\", (string) The hex-encoded " "raw transaction with signature(s)\n" " \"complete\" : true|false, (boolean) If the " "transaction has a complete set of signatures\n" " \"errors\" : [ (json array of objects) " "Script verification errors (if there are any)\n" " {\n" " \"txid\" : \"hash\", (string) The hash of the " "referenced, previous transaction\n" " \"vout\" : n, (numeric) The index of " "the output to spent and used as input\n" " \"scriptSig\" : \"hex\", (string) The hex-encoded " "signature script\n" " \"sequence\" : n, (numeric) Script sequence " "number\n" " \"error\" : \"text\" (string) Verification or " "signing error related to the input\n" " }\n" " ,...\n" " ]\n" "}\n"}, RPCExamples{ HelpExampleCli("signrawtransactionwithwallet", "\"myhex\"") + HelpExampleRpc("signrawtransactionwithwallet", "\"myhex\"")}, } .Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VSTR}, true); CMutableTransaction mtx; if (!DecodeHexTx(mtx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } // Sign the transaction auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); EnsureWalletIsUnlocked(pwallet); // Fetch previous transactions (inputs): std::map<COutPoint, Coin> coins; for (const CTxIn &txin : mtx.vin) { // Create empty map entry keyed by prevout. coins[txin.prevout]; } pwallet->chain().findCoins(coins); return SignTransaction(mtx, request.params[1], pwallet, coins, false, request.params[2]); } UniValue rescanblockchain(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; } RPCHelpMan{ "rescanblockchain", "\nRescan the local blockchain for wallet related transactions.\n", { {"start_height", RPCArg::Type::NUM, /* default */ "0", "block height where the rescan should start"}, {"stop_height", RPCArg::Type::NUM, RPCArg::Optional::OMITTED_NAMED_ARG, "the last block height that should be scanned"}, }, RPCResult{"{\n" " \"start_height\" (numeric) The block height where the " "rescan started (the requested height or 0\n" " \"stop_height\" (numeric) The height of the last " "rescanned block. May be null in rare cases if there was a " "reorg and the call didn't scan any blocks because they were " "already scanned in the background.\n" "}\n"}, RPCExamples{HelpExampleCli("rescanblockchain", "100000 120000") + HelpExampleRpc("rescanblockchain", "100000, 120000")}, } .Check(request); WalletRescanReserver reserver(pwallet); if (!reserver.reserve()) { throw JSONRPCError( RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } int start_height = 0; BlockHash start_block, stop_block; { auto locked_chain = pwallet->chain().lock(); Optional<int> tip_height = locked_chain->getHeight(); if (!request.params[0].isNull()) { start_height = request.params[0].get_int(); if (start_height < 0 || !tip_height || start_height > *tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid start_height"); } } Optional<int> stop_height; if (!request.params[1].isNull()) { stop_height = request.params[1].get_int(); if (*stop_height < 0 || !tip_height || *stop_height > *tip_height) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid stop_height"); } else if (*stop_height < start_height) { throw JSONRPCError( RPC_INVALID_PARAMETER, "stop_height must be greater than start_height"); } } // We can't rescan beyond non-pruned blocks, stop and throw an error if (locked_chain->findPruned(start_height, stop_height)) { throw JSONRPCError( RPC_MISC_ERROR, "Can't rescan beyond pruned data. Use RPC call " "getblockchaininfo to determine your pruned height."); } if (tip_height) { start_block = locked_chain->getBlockHash(start_height); // If called with a stop_height, set the stop_height here to // trigger a rescan to that height. // If called without a stop height, leave stop_height as null here // so rescan continues to the tip (even if the tip advances during // rescan). if (stop_height) { stop_block = locked_chain->getBlockHash(*stop_height); } } } CWallet::ScanResult result = pwallet->ScanForWalletTransactions( start_block, stop_block, reserver, true /* fUpdate */); switch (result.status) { case CWallet::ScanResult::SUCCESS: break; case CWallet::ScanResult::FAILURE: throw JSONRPCError( RPC_MISC_ERROR, "Rescan failed. Potentially corrupted data files."); case CWallet::ScanResult::USER_ABORT: throw JSONRPCError(RPC_MISC_ERROR, "Rescan aborted."); // no default case, so the compiler can warn about missing cases } UniValue response(UniValue::VOBJ); response.pushKV("start_height", start_height); response.pushKV("stop_height", result.last_scanned_height ? *result.last_scanned_height : UniValue()); return response; } class DescribeWalletAddressVisitor : public boost::static_visitor<UniValue> { public: CWallet *const pwallet; void ProcessSubScript(const CScript &subscript, UniValue &obj) const { // Always present: script type and redeemscript std::vector<std::vector<uint8_t>> solutions_data; txnouttype which_type = Solver(subscript, solutions_data); obj.pushKV("script", GetTxnOutputType(which_type)); obj.pushKV("hex", HexStr(subscript.begin(), subscript.end())); CTxDestination embedded; if (ExtractDestination(subscript, embedded)) { // Only when the script corresponds to an address. UniValue subobj(UniValue::VOBJ); UniValue detail = DescribeAddress(embedded); subobj.pushKVs(detail); UniValue wallet_detail = boost::apply_visitor(*this, embedded); subobj.pushKVs(wallet_detail); subobj.pushKV("address", EncodeDestination(embedded, GetConfig())); subobj.pushKV("scriptPubKey", HexStr(subscript.begin(), subscript.end())); // Always report the pubkey at the top level, so that // `getnewaddress()['pubkey']` always works. if (subobj.exists("pubkey")) { obj.pushKV("pubkey", subobj["pubkey"]); } obj.pushKV("embedded", std::move(subobj)); } else if (which_type == TX_MULTISIG) { // Also report some information on multisig scripts (which do not // have a corresponding address). // TODO: abstract out the common functionality between this logic // and ExtractDestinations. obj.pushKV("sigsrequired", solutions_data[0][0]); UniValue pubkeys(UniValue::VARR); for (size_t i = 1; i < solutions_data.size() - 1; ++i) { CPubKey key(solutions_data[i].begin(), solutions_data[i].end()); pubkeys.push_back(HexStr(key.begin(), key.end())); } obj.pushKV("pubkeys", std::move(pubkeys)); } } explicit DescribeWalletAddressVisitor(CWallet *_pwallet) : pwallet(_pwallet) {} UniValue operator()(const CNoDestination &dest) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const PKHash &pkhash) const { CKeyID keyID(pkhash); UniValue obj(UniValue::VOBJ); CPubKey vchPubKey; if (pwallet && pwallet->GetPubKey(keyID, vchPubKey)) { obj.pushKV("pubkey", HexStr(vchPubKey)); obj.pushKV("iscompressed", vchPubKey.IsCompressed()); } return obj; } UniValue operator()(const ScriptHash &scripthash) const { CScriptID scriptID(scripthash); UniValue obj(UniValue::VOBJ); CScript subscript; if (pwallet && pwallet->GetCScript(scriptID, subscript)) { ProcessSubScript(subscript, obj); } return obj; } }; static UniValue DescribeWalletAddress(CWallet *pwallet, const CTxDestination &dest) { UniValue ret(UniValue::VOBJ); UniValue detail = DescribeAddress(dest); ret.pushKVs(detail); ret.pushKVs( boost::apply_visitor(DescribeWalletAddressVisitor(pwallet), dest)); return ret; } /** Convert CAddressBookData to JSON record. */ static UniValue AddressBookDataToJSON(const CAddressBookData &data, const bool verbose) { UniValue ret(UniValue::VOBJ); if (verbose) { ret.pushKV("name", data.name); } ret.pushKV("purpose", data.purpose); return ret; } UniValue getaddressinfo(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; } RPCHelpMan{ "getaddressinfo", "\nReturn information about the given bitcoin address. Some " "information requires the address\n" "to be in the wallet.\n", { {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to get the information of."}, }, RPCResult{ "{\n" " \"address\" : \"address\", (string) The bitcoin address " "validated\n" " \"scriptPubKey\" : \"hex\", (string) The hex-encoded " "scriptPubKey generated by the address\n" " \"ismine\" : true|false, (boolean) If the address is " "yours or not\n" " \"iswatchonly\" : true|false, (boolean) If the address is " "watchonly\n" " \"solvable\" : true|false, (boolean) Whether we know how " "to spend coins sent to this address, ignoring the possible lack " "of private keys\n" " \"desc\" : \"desc\", (string, optional) A descriptor " "for spending coins sent to this address (only when solvable)\n" " \"isscript\" : true|false, (boolean) If the key is a " "script\n" " \"ischange\" : true|false, (boolean) If the address was " "used for change output\n" " \"script\" : \"type\" (string, optional) The output " "script type. Only if \"isscript\" is true and the redeemscript is " "known. Possible types: nonstandard, pubkey, pubkeyhash, " "scripthash, multisig, nulldata\n" " \"hex\" : \"hex\", (string, optional) The " "redeemscript for the p2sh address\n" " \"pubkeys\" (string, optional) Array of " "pubkeys associated with the known redeemscript (only if " "\"script\" is \"multisig\")\n" " [\n" " \"pubkey\"\n" " ,...\n" " ]\n" " \"sigsrequired\" : xxxxx (numeric, optional) Number of " "signatures required to spend multisig output (only if \"script\" " "is \"multisig\")\n" " \"pubkey\" : \"publickeyhex\", (string, optional) The hex " "value of the raw public key, for single-key addresses (possibly " "embedded in P2SH)\n" " \"embedded\" : {...}, (object, optional) Information " "about the address embedded in P2SH, if relevant and known. It " "includes all getaddressinfo output fields for the embedded " "address, excluding metadata (\"timestamp\", \"hdkeypath\", " "\"hdseedid\") and relation to the wallet (\"ismine\", " "\"iswatchonly\").\n" " \"iscompressed\" : true|false, (boolean) If the address is " "compressed\n" " \"label\" : \"label\" (string) The label associated " "with the address, \"\" is the default label\n" " \"timestamp\" : timestamp, (number, optional) The creation " "time of the key if available in seconds since epoch (Jan 1 1970 " "GMT)\n" " \"hdkeypath\" : \"keypath\" (string, optional) The HD " "keypath if the key is HD and available\n" " \"hdseedid\" : \"<hash160>\" (string, optional) The Hash160 " "of the HD seed\n" " \"hdmasterfingerprint\" : \"<hash160>\" (string, optional) The " "fingperint of the master key.\n" " \"labels\" (object) Array of labels " "associated with the address.\n" " [\n" " { (json object of label data)\n" " \"name\": \"labelname\" (string) The label\n" " \"purpose\": \"string\" (string) Purpose of address " "(\"send\" for sending address, \"receive\" for receiving " "address)\n" " },...\n" " ]\n" "}\n"}, RPCExamples{HelpExampleCli("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"") + HelpExampleRpc("getaddressinfo", "\"1PSSGeFHDnKNxiEyFrD1wcEaHr9hrQDDWc\"")}, } .Check(request); LOCK(pwallet->cs_wallet); UniValue ret(UniValue::VOBJ); CTxDestination dest = DecodeDestination(request.params[0].get_str(), config.GetChainParams()); // Make sure the destination is valid if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } std::string currentAddress = EncodeDestination(dest, config); ret.pushKV("address", currentAddress); CScript scriptPubKey = GetScriptForDestination(dest); ret.pushKV("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end())); isminetype mine = IsMine(*pwallet, dest); ret.pushKV("ismine", bool(mine & ISMINE_SPENDABLE)); bool solvable = IsSolvable(*pwallet, scriptPubKey); ret.pushKV("solvable", solvable); if (solvable) { ret.pushKV("desc", InferDescriptor(scriptPubKey, *pwallet)->ToString()); } ret.pushKV("iswatchonly", bool(mine & ISMINE_WATCH_ONLY)); UniValue detail = DescribeWalletAddress(pwallet, dest); ret.pushKVs(detail); if (pwallet->mapAddressBook.count(dest)) { ret.pushKV("label", pwallet->mapAddressBook[dest].name); } ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); const CKeyMetadata *meta = nullptr; CKeyID key_id = GetKeyForDestination(*pwallet, dest); if (!key_id.IsNull()) { auto it = pwallet->mapKeyMetadata.find(key_id); if (it != pwallet->mapKeyMetadata.end()) { meta = &it->second; } } if (!meta) { auto it = pwallet->m_script_metadata.find(CScriptID(scriptPubKey)); if (it != pwallet->m_script_metadata.end()) { meta = &it->second; } } if (meta) { ret.pushKV("timestamp", meta->nCreateTime); if (meta->has_key_origin) { ret.pushKV("hdkeypath", WriteHDKeypath(meta->key_origin.path)); ret.pushKV("hdseedid", meta->hd_seed_id.GetHex()); ret.pushKV("hdmasterfingerprint", HexStr(meta->key_origin.fingerprint, meta->key_origin.fingerprint + 4)); } } // Currently only one label can be associated with an address, return an // array so the API remains stable if we allow multiple labels to be // associated with an address. UniValue labels(UniValue::VARR); std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(dest); if (mi != pwallet->mapAddressBook.end()) { labels.push_back(AddressBookDataToJSON(mi->second, true)); } ret.pushKV("labels", std::move(labels)); return ret; } UniValue getaddressesbylabel(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; } RPCHelpMan{ "getaddressesbylabel", "\nReturns the list of addresses assigned the specified label.\n", { {"label", RPCArg::Type::STR, RPCArg::Optional::NO, "The label."}, }, RPCResult{ "{ (json object with addresses as keys)\n" " \"address\": { (json object with information about address)\n" " \"purpose\": \"string\" (string) Purpose of address " "(\"send\" for sending address, \"receive\" for receiving " "address)\n" " },...\n" "}\n"}, RPCExamples{HelpExampleCli("getaddressesbylabel", "\"tabby\"") + HelpExampleRpc("getaddressesbylabel", "\"tabby\"")}, } .Check(request); LOCK(pwallet->cs_wallet); std::string label = LabelFromValue(request.params[0]); // Find all addresses that have the given label UniValue ret(UniValue::VOBJ); std::set<std::string> addresses; for (const std::pair<const CTxDestination, CAddressBookData> &item : pwallet->mapAddressBook) { if (item.second.name == label) { std::string address = EncodeDestination(item.first, config); // CWallet::mapAddressBook is not expected to contain duplicate // address strings, but build a separate set as a precaution just in // case it does. bool unique = addresses.emplace(address).second; assert(unique); // UniValue::pushKV checks if the key exists in O(N) // and since duplicate addresses are unexpected (checked with // std::set in O(log(N))), UniValue::__pushKV is used instead, // which currently is O(1). ret.__pushKV(address, AddressBookDataToJSON(item.second, false)); } } if (ret.empty()) { throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, std::string("No addresses with label " + label)); } return ret; } UniValue listlabels(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; } RPCHelpMan{ "listlabels", "\nReturns the list of all labels, or labels that are assigned to " "addresses with a specific purpose.\n", { {"purpose", RPCArg::Type::STR, RPCArg::Optional::OMITTED_NAMED_ARG, "Address purpose to list labels for ('send','receive'). An empty " "string is the same as not providing this argument."}, }, RPCResult{"[ (json array of string)\n" " \"label\", (string) Label name\n" " ...\n" "]\n"}, RPCExamples{"\nList all labels\n" + HelpExampleCli("listlabels", "") + "\nList labels that have receiving addresses\n" + HelpExampleCli("listlabels", "receive") + "\nList labels that have sending addresses\n" + HelpExampleCli("listlabels", "send") + "\nAs a JSON-RPC call\n" + HelpExampleRpc("listlabels", "receive")}, } .Check(request); LOCK(pwallet->cs_wallet); std::string purpose; if (!request.params[0].isNull()) { purpose = request.params[0].get_str(); } // Add to a set to sort by label name, then insert into Univalue array std::set<std::string> label_set; for (const std::pair<const CTxDestination, CAddressBookData> &entry : pwallet->mapAddressBook) { if (purpose.empty() || entry.second.purpose == purpose) { label_set.insert(entry.second.name); } } UniValue ret(UniValue::VARR); for (const std::string &name : label_set) { ret.push_back(name); } return ret; } static UniValue sethdseed(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; } RPCHelpMan{ "sethdseed", "\nSet or generate a new HD wallet seed. Non-HD wallets will not be " "upgraded to being a HD wallet. Wallets that are already\n" "HD will have a new HD seed set so that new keys added to the keypool " "will be derived from this new seed.\n" "\nNote that you will need to MAKE A NEW BACKUP of your wallet after " "setting the HD wallet seed.\n" + HelpRequiringPassphrase(pwallet) + "\n", { {"newkeypool", RPCArg::Type::BOOL, /* default */ "true", "Whether to flush old unused addresses, including change " "addresses, from the keypool and regenerate it.\n" " If true, the next address from " "getnewaddress and change address from getrawchangeaddress will " "be from this new seed.\n" " If false, addresses (including " "change addresses if the wallet already had HD Chain Split " "enabled) from the existing\n" " keypool will be used until it has " "been depleted."}, {"seed", RPCArg::Type::STR, /* default */ "random seed", "The WIF private key to use as the new HD seed.\n" " The seed value can be retrieved " "using the dumpwallet command. It is the private key marked " "hdseed=1"}, }, RPCResults{}, RPCExamples{HelpExampleCli("sethdseed", "") + HelpExampleCli("sethdseed", "false") + HelpExampleCli("sethdseed", "true \"wifkey\"") + HelpExampleRpc("sethdseed", "true, \"wifkey\"")}, } .Check(request); if (pwallet->chain().isInitialBlockDownload()) { throw JSONRPCError( RPC_CLIENT_IN_INITIAL_DOWNLOAD, "Cannot set a new HD seed while still in Initial Block Download"); } if (pwallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { throw JSONRPCError( RPC_WALLET_ERROR, "Cannot set a HD seed to a wallet with private keys disabled"); } auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); // Do not do anything to non-HD wallets if (!pwallet->CanSupportFeature(FEATURE_HD)) { throw JSONRPCError( RPC_WALLET_ERROR, "Cannot set a HD seed on a non-HD wallet. Start with " "-upgradewallet in order to upgrade a non-HD wallet to HD"); } EnsureWalletIsUnlocked(pwallet); bool flush_key_pool = true; if (!request.params[0].isNull()) { flush_key_pool = request.params[0].get_bool(); } CPubKey master_pub_key; if (request.params[1].isNull()) { master_pub_key = pwallet->GenerateNewSeed(); } else { CKey key = DecodeSecret(request.params[1].get_str()); if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); } if (HaveKey(*pwallet, key)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key (either as an HD seed or " "as a loose private key)"); } master_pub_key = pwallet->DeriveNewSeed(key); } pwallet->SetHDSeed(master_pub_key); if (flush_key_pool) { pwallet->NewKeyPool(); } return NullUniValue; } static UniValue walletprocesspsbt(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; } RPCHelpMan{ "walletprocesspsbt", "\nUpdate a PSBT with input information from our wallet and then sign " "inputs that we can sign for." + HelpRequiringPassphrase(pwallet) + "\n", { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction base64 string"}, {"sign", RPCArg::Type::BOOL, /* default */ "true", "Also sign the transaction when updating"}, {"sighashtype", RPCArg::Type::STR, /* default */ "ALL|FORKID", "The signature hash type to sign with if not specified by " "the PSBT. Must be one of\n" " \"ALL|FORKID\"\n" " \"NONE|FORKID\"\n" " \"SINGLE|FORKID\"\n" " \"ALL|FORKID|ANYONECANPAY\"\n" " \"NONE|FORKID|ANYONECANPAY\"\n" " \"SINGLE|FORKID|ANYONECANPAY\""}, {"bip32derivs", RPCArg::Type::BOOL, /* default */ "false", "If true, includes the BIP 32 derivation paths for public keys if " "we know them"}, }, RPCResult{"{\n" " \"psbt\" : \"value\", (string) The " "base64-encoded partially signed transaction\n" " \"complete\" : true|false, (boolean) If the transaction " "has a complete set of signatures\n" " ]\n" "}\n"}, RPCExamples{HelpExampleCli("walletprocesspsbt", "\"psbt\"")}, } .Check(request); RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VSTR}); // Unserialize the transaction PartiallySignedTransaction psbtx; std::string error; if (!DecodeBase64PSBT(psbtx, request.params[0].get_str(), error)) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, strprintf("TX decode failed %s", error)); } // Get the sighash type SigHashType nHashType = ParseSighashString(request.params[2]); if (!nHashType.hasForkId()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Signature must use SIGHASH_FORKID"); } // Fill transaction with our data and also sign bool sign = request.params[1].isNull() ? true : request.params[1].get_bool(); bool bip32derivs = request.params[3].isNull() ? false : request.params[3].get_bool(); bool complete = true; const TransactionError err = FillPSBT(pwallet, psbtx, complete, nHashType, sign, bip32derivs); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } UniValue result(UniValue::VOBJ); CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; result.pushKV("psbt", EncodeBase64(ssTx.str())); result.pushKV("complete", complete); return result; } static UniValue walletcreatefundedpsbt(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; } RPCHelpMan{ "walletcreatefundedpsbt", "\nCreates and funds a transaction in the Partially Signed Transaction " "format. Inputs will be added if supplied inputs are not enough\n" "Implements the Creator and Updater roles.\n", { { "inputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "A json array of json objects", { { "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id"}, {"vout", RPCArg::Type::NUM, RPCArg::Optional::NO, "The output number"}, {"sequence", RPCArg::Type::NUM, RPCArg::Optional::NO, "The sequence number"}, }, }, }, }, { "outputs", RPCArg::Type::ARR, RPCArg::Optional::NO, "a json array with outputs (key-value pairs).\n" "For compatibility reasons, a dictionary, which holds the " "key-value pairs directly, is also\n" " accepted as second parameter.", { { "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { {"address", RPCArg::Type::AMOUNT, RPCArg::Optional::NO, "A key-value pair. The key (string) is the " "bitcoin address, the value (float or string) is " "the amount in " + CURRENCY_UNIT + ""}, }, }, { "", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED, "", { {"data", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "A key-value pair. The key must be \"data\", the " "value is hex-encoded data"}, }, }, }, }, {"locktime", RPCArg::Type::NUM, /* default */ "0", "Raw locktime. Non-0 value also locktime-activates inputs\n" " Allows this transaction to be " "replaced by a transaction with higher fees. If provided, it is " "an error if explicit sequence numbers are incompatible."}, {"options", RPCArg::Type::OBJ, RPCArg::Optional::OMITTED_NAMED_ARG, "", { {"changeAddress", RPCArg::Type::STR_HEX, /* default */ "pool address", "The bitcoin address to receive the change"}, {"changePosition", RPCArg::Type::NUM, /* default */ "random", "The index of the change output"}, {"includeWatching", RPCArg::Type::BOOL, /* default */ "false", "Also select inputs which are watch only"}, {"lockUnspents", RPCArg::Type::BOOL, /* default */ "false", "Lock selected unspent outputs"}, {"feeRate", RPCArg::Type::AMOUNT, /* default */ "not set: makes wallet determine the fee", "Set a specific fee rate in " + CURRENCY_UNIT + "/kB"}, { "subtractFeeFromOutputs", RPCArg::Type::ARR, /* default */ "empty array", "A json array of integers.\n" " The fee will be equally " "deducted from the amount of each specified output.\n" " Those recipients will " "receive less bitcoins than you enter in their " "corresponding amount field.\n" " If no outputs are " "specified here, the sender pays the fee.", { {"vout_index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "The zero-based output index, before a change output " "is added."}, }, }, }, "options"}, {"bip32derivs", RPCArg::Type::BOOL, /* default */ "false", "If true, includes the BIP 32 derivation paths for public keys if " "we know them"}, }, RPCResult{"{\n" " \"psbt\": \"value\", (string) The resulting raw " "transaction (base64-encoded string)\n" " \"fee\": n, (numeric) Fee in " + CURRENCY_UNIT + " the resulting transaction pays\n" " \"changepos\": n (numeric) The position of the " "added change output, or -1\n" "}\n"}, RPCExamples{ "\nCreate a transaction with no inputs\n" + HelpExampleCli("walletcreatefundedpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" " "\"[{\\\"data\\\":\\\"00010203\\\"}]\"")}, } .Check(request); RPCTypeCheck(request.params, {UniValue::VARR, UniValueType(), // ARR or OBJ, checked later UniValue::VNUM, UniValue::VOBJ}, true); Amount fee; int change_position; CMutableTransaction rawTx = ConstructTransaction(config.GetChainParams(), request.params[0], request.params[1], request.params[2]); FundTransaction(pwallet, rawTx, fee, change_position, request.params[3]); // Make a blank psbt PartiallySignedTransaction psbtx(rawTx); // Fill transaction with out data but don't sign bool bip32derivs = request.params[4].isNull() ? false : request.params[4].get_bool(); bool complete = true; const TransactionError err = FillPSBT(pwallet, psbtx, complete, SigHashType().withForkId(), false, bip32derivs); if (err != TransactionError::OK) { throw JSONRPCTransactionError(err); } // Serialize the PSBT CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; UniValue result(UniValue::VOBJ); result.pushKV("psbt", EncodeBase64(ssTx.str())); result.pushKV("fee", ValueFromAmount(fee)); result.pushKV("changepos", change_position); return result; } // clang-format off static const CRPCCommand commands[] = { // category name actor (function) argNames // ------------------- ------------------------ ---------------------- ---------- { "rawtransactions", "fundrawtransaction", fundrawtransaction, {"hexstring","options"} }, { "wallet", "abandontransaction", abandontransaction, {"txid"} }, { "wallet", "addmultisigaddress", addmultisigaddress, {"nrequired","keys","label"} }, { "wallet", "backupwallet", backupwallet, {"destination"} }, { "wallet", "createwallet", createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase", "avoid_reuse"} }, { "wallet", "encryptwallet", encryptwallet, {"passphrase"} }, { "wallet", "getaddressesbylabel", getaddressesbylabel, {"label"} }, { "wallet", "getaddressinfo", getaddressinfo, {"address"} }, { "wallet", "getbalance", getbalance, {"dummy","minconf","include_watchonly","avoid_reuse"} }, { "wallet", "getnewaddress", getnewaddress, {"label", "address_type"} }, { "wallet", "getrawchangeaddress", getrawchangeaddress, {"address_type"} }, { "wallet", "getreceivedbyaddress", getreceivedbyaddress, {"address","minconf"} }, { "wallet", "getreceivedbylabel", getreceivedbylabel, {"label","minconf"} }, { "wallet", "gettransaction", gettransaction, {"txid","include_watchonly"} }, { "wallet", "getunconfirmedbalance", getunconfirmedbalance, {} }, { "wallet", "getbalances", getbalances, {} }, { "wallet", "getwalletinfo", getwalletinfo, {} }, { "wallet", "keypoolrefill", keypoolrefill, {"newsize"} }, { "wallet", "listaddressgroupings", listaddressgroupings, {} }, { "wallet", "listlabels", listlabels, {"purpose"} }, { "wallet", "listlockunspent", listlockunspent, {} }, { "wallet", "listreceivedbyaddress", listreceivedbyaddress, {"minconf","include_empty","include_watchonly","address_filter"} }, { "wallet", "listreceivedbylabel", listreceivedbylabel, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listsinceblock", listsinceblock, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, { "wallet", "listtransactions", listtransactions, {"label|dummy","count","skip","include_watchonly"} }, { "wallet", "listunspent", listunspent, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, { "wallet", "listwalletdir", listwalletdir, {} }, { "wallet", "listwallets", listwallets, {} }, { "wallet", "loadwallet", loadwallet, {"filename"} }, { "wallet", "lockunspent", lockunspent, {"unlock","transactions"} }, { "wallet", "rescanblockchain", rescanblockchain, {"start_height", "stop_height"} }, { "wallet", "sendmany", sendmany, {"dummy","amounts","minconf","comment","subtractfeefrom"} }, { "wallet", "sendtoaddress", sendtoaddress, {"address","amount","comment","comment_to","subtractfeefromamount","avoid_reuse"} }, { "wallet", "sethdseed", sethdseed, {"newkeypool","seed"} }, { "wallet", "setlabel", setlabel, {"address","label"} }, { "wallet", "settxfee", settxfee, {"amount"} }, { "wallet", "setwalletflag", setwalletflag, {"flag","value"} }, { "wallet", "signmessage", signmessage, {"address","message"} }, { "wallet", "signrawtransactionwithwallet", signrawtransactionwithwallet, {"hextring","prevtxs","sighashtype"} }, { "wallet", "unloadwallet", unloadwallet, {"wallet_name"} }, { "wallet", "walletcreatefundedpsbt", walletcreatefundedpsbt, {"inputs","outputs","locktime","options","bip32derivs"} }, { "wallet", "walletlock", walletlock, {} }, { "wallet", "walletpassphrase", walletpassphrase, {"passphrase","timeout"} }, { "wallet", "walletpassphrasechange", walletpassphrasechange, {"oldpassphrase","newpassphrase"} }, { "wallet", "walletprocesspsbt", walletprocesspsbt, {"psbt","sign","sighashtype","bip32derivs"} }, }; // clang-format on void RegisterWalletRPCCommands( interfaces::Chain &chain, std::vector<std::unique_ptr<interfaces::Handler>> &handlers) { for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { handlers.emplace_back(chain.handleRpc(commands[vcidx])); } } interfaces::Chain *g_rpc_chain = nullptr; diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 951241c2e..99a9c92c7 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1,5434 +1,5434 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2019 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 <wallet/wallet.h> #include <chain.h> #include <chainparams.h> #include <config.h> #include <consensus/consensus.h> #include <consensus/validation.h> #include <fs.h> #include <interfaces/wallet.h> #include <key.h> #include <key_io.h> #include <policy/mempool.h> #include <policy/policy.h> #include <primitives/block.h> #include <primitives/transaction.h> #include <random.h> #include <script/descriptor.h> #include <script/script.h> #include <script/sighashtype.h> #include <script/sign.h> #include <script/signingprovider.h> #include <util/bip32.h> #include <util/error.h> #include <util/moneystr.h> #include <util/translation.h> #include <util/validation.h> #include <validation.h> #include <wallet/coincontrol.h> #include <wallet/fees.h> #include <boost/algorithm/string/replace.hpp> #include <cassert> const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS{ {WALLET_FLAG_AVOID_REUSE, "You need to rescan the blockchain in order to correctly mark used " "destinations in the past. Until this is done, some destinations may " "be considered unused, even if the opposite is the case."}, }; static RecursiveMutex cs_wallets; static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets); bool AddWallet(const std::shared_ptr<CWallet> &wallet) { LOCK(cs_wallets); assert(wallet); std::vector<std::shared_ptr<CWallet>>::const_iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); if (i != vpwallets.end()) { return false; } vpwallets.push_back(wallet); return true; } bool RemoveWallet(const std::shared_ptr<CWallet> &wallet) { assert(wallet); // Unregister with the validation interface which also drops shared ponters. wallet->m_chain_notifications_handler.reset(); LOCK(cs_wallets); std::vector<std::shared_ptr<CWallet>>::iterator i = std::find(vpwallets.begin(), vpwallets.end(), wallet); if (i == vpwallets.end()) { return false; } vpwallets.erase(i); return true; } bool HasWallets() { LOCK(cs_wallets); return !vpwallets.empty(); } std::vector<std::shared_ptr<CWallet>> GetWallets() { LOCK(cs_wallets); return vpwallets; } std::shared_ptr<CWallet> GetWallet(const std::string &name) { LOCK(cs_wallets); for (const std::shared_ptr<CWallet> &wallet : vpwallets) { if (wallet->GetName() == name) { return wallet; } } return nullptr; } static Mutex g_wallet_release_mutex; static std::condition_variable g_wallet_release_cv; static std::set<std::string> g_unloading_wallet_set; // Custom deleter for shared_ptr<CWallet>. static void ReleaseWallet(CWallet *wallet) { const std::string name = wallet->GetName(); wallet->WalletLogPrintf("Releasing wallet\n"); wallet->Flush(); delete wallet; // Wallet is now released, notify UnloadWallet, if any. { LOCK(g_wallet_release_mutex); if (g_unloading_wallet_set.erase(name) == 0) { // UnloadWallet was not called for this wallet, all done. return; } } g_wallet_release_cv.notify_all(); } void UnloadWallet(std::shared_ptr<CWallet> &&wallet) { // Mark wallet for unloading. const std::string name = wallet->GetName(); { LOCK(g_wallet_release_mutex); auto it = g_unloading_wallet_set.insert(name); assert(it.second); } // The wallet can be in use so it's not possible to explicitly unload here. // Notify the unload intent so that all remaining shared pointers are // released. wallet->NotifyUnload(); // Time to ditch our shared_ptr and wait for ReleaseWallet call. wallet.reset(); { WAIT_LOCK(g_wallet_release_mutex, lock); while (g_unloading_wallet_set.count(name) == 1) { g_wallet_release_cv.wait(lock); } } } static const size_t OUTPUT_GROUP_MAX_ENTRIES = 10; std::shared_ptr<CWallet> LoadWallet(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, std::string &error, std::string &warning) { if (!CWallet::Verify(chainParams, chain, location, false, error, warning)) { error = "Wallet file verification failed: " + error; return nullptr; } std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile(chainParams, chain, location); if (!wallet) { error = "Wallet loading failed."; return nullptr; } AddWallet(wallet); wallet->postInitProcess(); return wallet; } std::shared_ptr<CWallet> LoadWallet(const CChainParams &chainParams, interfaces::Chain &chain, const std::string &name, std::string &error, std::string &warning) { return LoadWallet(chainParams, chain, WalletLocation(name), error, warning); } std::shared_ptr<CWallet> CreateWallet(const CChainParams ¶ms, interfaces::Chain &chain, + const SecureString &passphrase, uint64_t wallet_creation_flags, const std::string &name, std::string &error, std::string &warning, - WalletCreationStatus &status, const SecureString &passphrase, - uint64_t wallet_creation_flags) { + WalletCreationStatus &status) { // Indicate that the wallet is actually supposed to be blank and not just // blank to make it encrypted bool create_blank = (wallet_creation_flags & WALLET_FLAG_BLANK_WALLET); // Born encrypted wallets need to be created blank first. if (!passphrase.empty()) { wallet_creation_flags |= WALLET_FLAG_BLANK_WALLET; } // Check the wallet file location WalletLocation location(name); if (location.Exists()) { error = "Wallet " + location.GetName() + " already exists."; status = WalletCreationStatus::CREATION_FAILED; return nullptr; } // Wallet::Verify will check if we're trying to create a wallet with a // duplicate name. std::string wallet_error; if (!CWallet::Verify(params, chain, location, false, wallet_error, warning)) { error = "Wallet file verification failed: " + wallet_error; status = WalletCreationStatus::CREATION_FAILED; return nullptr; } // Make the wallet std::shared_ptr<CWallet> wallet = CWallet::CreateWalletFromFile( params, chain, location, wallet_creation_flags); if (!wallet) { error = "Wallet creation failed"; status = WalletCreationStatus::CREATION_FAILED; return nullptr; } // Encrypt the wallet if (!passphrase.empty() && !(wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (!wallet->EncryptWallet(passphrase)) { error = "Error: Wallet created but failed to encrypt."; status = WalletCreationStatus::ENCRYPTION_FAILED; return nullptr; } if (!create_blank) { // Unlock the wallet if (!wallet->Unlock(passphrase)) { error = "Error: Wallet was encrypted but could not be unlocked"; status = WalletCreationStatus::ENCRYPTION_FAILED; return nullptr; } // Set a seed for the wallet CPubKey master_pub_key = wallet->GenerateNewSeed(); wallet->SetHDSeed(master_pub_key); wallet->NewKeyPool(); // Relock the wallet wallet->Lock(); } } AddWallet(wallet); wallet->postInitProcess(); status = WalletCreationStatus::SUCCESS; return wallet; } const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000; const BlockHash CMerkleTx::ABANDON_HASH(uint256S( "0000000000000000000000000000000000000000000000000000000000000001")); /** @defgroup mapWallet * * @{ */ std::string COutput::ToString() const { return strprintf("COutput(%s, %d, %d) [%s]", tx->GetId().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); } std::vector<CKeyID> GetAffectedKeys(const CScript &spk, const SigningProvider &provider) { std::vector<CScript> dummy; FlatSigningProvider out; InferDescriptor(spk, provider) ->Expand(0, DUMMY_SIGNING_PROVIDER, dummy, out); std::vector<CKeyID> ret; for (const auto &entry : out.pubkeys) { ret.push_back(entry.first); } return ret; } const CWalletTx *CWallet::GetWalletTx(const TxId &txid) const { LOCK(cs_wallet); std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid); if (it == mapWallet.end()) { return nullptr; } return &(it->second); } CPubKey CWallet::GenerateNewKey(WalletBatch &batch, bool internal) { assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); assert(!IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET)); AssertLockHeld(cs_wallet); // default to compressed public keys if we want 0.6.0 wallets bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY); CKey secret; // Create new metadata int64_t nCreationTime = GetTime(); CKeyMetadata metadata(nCreationTime); // use HD key derivation if HD was enabled during wallet creation and a seed // is present if (IsHDEnabled()) { DeriveNewChildKey( batch, metadata, secret, (CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false)); } else { secret.MakeNewKey(fCompressed); } // Compressed public keys were introduced in version 0.6.0 if (fCompressed) { SetMinVersion(FEATURE_COMPRPUBKEY); } CPubKey pubkey = secret.GetPubKey(); assert(secret.VerifyPubKey(pubkey)); mapKeyMetadata[pubkey.GetID()] = metadata; UpdateTimeFirstKey(nCreationTime); if (!AddKeyPubKeyWithDB(batch, secret, pubkey)) { throw std::runtime_error(std::string(__func__) + ": AddKey failed"); } return pubkey; } void CWallet::DeriveNewChildKey(WalletBatch &batch, CKeyMetadata &metadata, CKey &secret, bool internal) { // for now we use a fixed keypath scheme of m/0'/0'/k // seed (256bit) CKey seed; // hd master key CExtKey masterKey; // key at m/0' CExtKey accountKey; // key at m/0'/0' (external) or m/0'/1' (internal) CExtKey chainChildKey; // key at m/0'/0'/<n>' CExtKey childKey; // try to get the seed if (!GetKey(hdChain.seed_id, seed)) { throw std::runtime_error(std::string(__func__) + ": seed not found"); } masterKey.SetSeed(seed.begin(), seed.size()); // derive m/0' // use hardened derivation (child keys >= 0x80000000 are hardened after // bip32) masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT); // derive m/0'/0' (external chain) OR m/0'/1' (internal chain) assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true); accountKey.Derive(chainChildKey, BIP32_HARDENED_KEY_LIMIT + (internal ? 1 : 0)); // derive child key at next index, skip keys already known to the wallet do { // always derive hardened keys // childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened // child-index-range // example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649 if (internal) { chainChildKey.Derive(childKey, hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/1'/" + std::to_string(hdChain.nInternalChainCounter) + "'"; metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); metadata.key_origin.path.push_back(1 | BIP32_HARDENED_KEY_LIMIT); metadata.key_origin.path.push_back(hdChain.nInternalChainCounter | BIP32_HARDENED_KEY_LIMIT); hdChain.nInternalChainCounter++; } else { chainChildKey.Derive(childKey, hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); metadata.hdKeypath = "m/0'/0'/" + std::to_string(hdChain.nExternalChainCounter) + "'"; metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); metadata.key_origin.path.push_back(0 | BIP32_HARDENED_KEY_LIMIT); metadata.key_origin.path.push_back(hdChain.nExternalChainCounter | BIP32_HARDENED_KEY_LIMIT); hdChain.nExternalChainCounter++; } } while (HaveKey(childKey.key.GetPubKey().GetID())); secret = childKey.key; metadata.hd_seed_id = hdChain.seed_id; CKeyID master_id = masterKey.key.GetPubKey().GetID(); std::copy(master_id.begin(), master_id.begin() + 4, metadata.key_origin.fingerprint); metadata.has_key_origin = true; // update the chain model in the database if (!batch.WriteHDChain(hdChain)) { throw std::runtime_error(std::string(__func__) + ": Writing HD chain model failed"); } } bool CWallet::AddKeyPubKeyWithDB(WalletBatch &batch, const CKey &secret, const CPubKey &pubkey) { AssertLockHeld(cs_wallet); // Make sure we aren't adding private keys to private key disabled wallets assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); // FillableSigningProvider has no concept of wallet databases, but calls // AddCryptedKey which is overridden below. To avoid flushes, the database // handle is tunneled through to it. bool needsDB = !encrypted_batch; if (needsDB) { encrypted_batch = &batch; } if (!AddKeyPubKeyInner(secret, pubkey)) { if (needsDB) { encrypted_batch = nullptr; } return false; } if (needsDB) { encrypted_batch = nullptr; } // Check if we need to remove from watch-only. CScript script; script = GetScriptForDestination(PKHash(pubkey)); if (HaveWatchOnly(script)) { RemoveWatchOnly(script); } script = GetScriptForRawPubKey(pubkey); if (HaveWatchOnly(script)) { RemoveWatchOnly(script); } if (!IsCrypted()) { return batch.WriteKey(pubkey, secret.GetPrivKey(), mapKeyMetadata[pubkey.GetID()]); } UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); return true; } bool CWallet::AddKeyPubKey(const CKey &secret, const CPubKey &pubkey) { WalletBatch batch(*database); return CWallet::AddKeyPubKeyWithDB(batch, secret, pubkey); } bool CWallet::AddCryptedKey(const CPubKey &vchPubKey, const std::vector<uint8_t> &vchCryptedSecret) { if (!AddCryptedKeyInner(vchPubKey, vchCryptedSecret)) { return false; } LOCK(cs_wallet); if (encrypted_batch) { return encrypted_batch->WriteCryptedKey( vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]); } return WalletBatch(*database).WriteCryptedKey( vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]); } void CWallet::LoadKeyMetadata(const CKeyID &keyID, const CKeyMetadata &meta) { AssertLockHeld(cs_wallet); UpdateTimeFirstKey(meta.nCreateTime); mapKeyMetadata[keyID] = meta; } void CWallet::LoadScriptMetadata(const CScriptID &script_id, const CKeyMetadata &meta) { AssertLockHeld(cs_wallet); UpdateTimeFirstKey(meta.nCreateTime); m_script_metadata[script_id] = meta; } void CWallet::UpgradeKeyMetadata() { AssertLockHeld(cs_wallet); if (IsLocked() || IsWalletFlagSet(WALLET_FLAG_KEY_ORIGIN_METADATA)) { return; } auto batch = std::make_unique<WalletBatch>(*database); for (auto &meta_pair : mapKeyMetadata) { CKeyMetadata &meta = meta_pair.second; // If the hdKeypath is "s", that's the seed and it doesn't have a key // origin if (!meta.hd_seed_id.IsNull() && !meta.has_key_origin && meta.hdKeypath != "s") { CKey key; GetKey(meta.hd_seed_id, key); CExtKey masterKey; masterKey.SetSeed(key.begin(), key.size()); // Add to map CKeyID master_id = masterKey.key.GetPubKey().GetID(); std::copy(master_id.begin(), master_id.begin() + 4, meta.key_origin.fingerprint); if (!ParseHDKeypath(meta.hdKeypath, meta.key_origin.path)) { throw std::runtime_error("Invalid stored hdKeypath"); } meta.has_key_origin = true; if (meta.nVersion < CKeyMetadata::VERSION_WITH_KEY_ORIGIN) { meta.nVersion = CKeyMetadata::VERSION_WITH_KEY_ORIGIN; } // Write meta to wallet CPubKey pubkey; if (GetPubKey(meta_pair.first, pubkey)) { batch->WriteKeyMetadata(meta, pubkey, true); } } } // write before setting the flag batch.reset(); SetWalletFlag(WALLET_FLAG_KEY_ORIGIN_METADATA); } bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<uint8_t> &vchCryptedSecret) { return AddCryptedKeyInner(vchPubKey, vchCryptedSecret); } /** * Update wallet first key creation time. This should be called whenever keys * are added to the wallet, with the oldest key creation time. */ void CWallet::UpdateTimeFirstKey(int64_t nCreateTime) { AssertLockHeld(cs_wallet); if (nCreateTime <= 1) { // Cannot determine birthday information, so set the wallet birthday to // the beginning of time. nTimeFirstKey = 1; } else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) { nTimeFirstKey = nCreateTime; } } bool CWallet::AddCScript(const CScript &redeemScript) { WalletBatch batch(*database); return AddCScriptWithDB(batch, redeemScript); } bool CWallet::AddCScriptWithDB(WalletBatch &batch, const CScript &redeemScript) { if (!FillableSigningProvider::AddCScript(redeemScript)) { return false; } if (batch.WriteCScript(Hash160(redeemScript), redeemScript)) { UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); return true; } return false; } bool CWallet::LoadCScript(const CScript &redeemScript) { /** * A sanity check was added in pull #3843 to avoid adding redeemScripts that * never can be redeemed. However, old wallets may still contain these. Do * not add them to the wallet and warn. */ if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) { std::string strAddr = EncodeDestination(ScriptHash(redeemScript), GetConfig()); WalletLogPrintf("%s: Warning: This wallet contains a redeemScript " "of size %i which exceeds maximum size %i thus can " "never be redeemed. Do not use address %s.\n", __func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE, strAddr); return true; } return FillableSigningProvider::AddCScript(redeemScript); } static bool ExtractPubKey(const CScript &dest, CPubKey &pubKeyOut) { std::vector<std::vector<uint8_t>> solutions; return Solver(dest, solutions) == TX_PUBKEY && (pubKeyOut = CPubKey(solutions[0])).IsFullyValid(); } bool CWallet::AddWatchOnlyInMem(const CScript &dest) { LOCK(cs_KeyStore); setWatchOnly.insert(dest); CPubKey pubKey; if (ExtractPubKey(dest, pubKey)) { mapWatchKeys[pubKey.GetID()] = pubKey; } return true; } bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest) { if (!AddWatchOnlyInMem(dest)) { return false; } const CKeyMetadata &meta = m_script_metadata[CScriptID(dest)]; UpdateTimeFirstKey(meta.nCreateTime); NotifyWatchonlyChanged(true); if (batch.WriteWatchOnly(dest, meta)) { UnsetWalletFlagWithDB(batch, WALLET_FLAG_BLANK_WALLET); return true; } return false; } bool CWallet::AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest, int64_t create_time) { m_script_metadata[CScriptID(dest)].nCreateTime = create_time; return AddWatchOnlyWithDB(batch, dest); } bool CWallet::AddWatchOnly(const CScript &dest) { WalletBatch batch(*database); return AddWatchOnlyWithDB(batch, dest); } bool CWallet::AddWatchOnly(const CScript &dest, int64_t nCreateTime) { m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime; return AddWatchOnly(dest); } bool CWallet::RemoveWatchOnly(const CScript &dest) { AssertLockHeld(cs_wallet); { LOCK(cs_KeyStore); setWatchOnly.erase(dest); CPubKey pubKey; if (ExtractPubKey(dest, pubKey)) { mapWatchKeys.erase(pubKey.GetID()); } } if (!HaveWatchOnly()) { NotifyWatchonlyChanged(false); } return WalletBatch(*database).EraseWatchOnly(dest); } bool CWallet::LoadWatchOnly(const CScript &dest) { return AddWatchOnlyInMem(dest); } bool CWallet::HaveWatchOnly(const CScript &dest) const { LOCK(cs_KeyStore); return setWatchOnly.count(dest) > 0; } bool CWallet::HaveWatchOnly() const { LOCK(cs_KeyStore); return (!setWatchOnly.empty()); } bool CWallet::Unlock(const SecureString &strWalletPassphrase, bool accept_no_keys) { CCrypter crypter; CKeyingMaterial _vMasterKey; { LOCK(cs_wallet); for (const MasterKeyMap::value_type &pMasterKey : mapMasterKeys) { if (!crypter.SetKeyFromPassphrase( strWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) { return false; } if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) { // try another master key continue; } if (Unlock(_vMasterKey, accept_no_keys)) { // Now that we've unlocked, upgrade the key metadata UpgradeKeyMetadata(); return true; } } } return false; } bool CWallet::ChangeWalletPassphrase( const SecureString &strOldWalletPassphrase, const SecureString &strNewWalletPassphrase) { bool fWasLocked = IsLocked(); LOCK(cs_wallet); Lock(); CCrypter crypter; CKeyingMaterial _vMasterKey; for (MasterKeyMap::value_type &pMasterKey : mapMasterKeys) { if (!crypter.SetKeyFromPassphrase( strOldWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) { return false; } if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) { return false; } if (Unlock(_vMasterKey)) { int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); pMasterKey.second.nDeriveIterations = static_cast<unsigned int>( pMasterKey.second.nDeriveIterations * (100 / ((double)(GetTimeMillis() - nStartTime)))); nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod); pMasterKey.second.nDeriveIterations = (pMasterKey.second.nDeriveIterations + static_cast<unsigned int>( pMasterKey.second.nDeriveIterations * 100 / double(GetTimeMillis() - nStartTime))) / 2; if (pMasterKey.second.nDeriveIterations < 25000) { pMasterKey.second.nDeriveIterations = 25000; } WalletLogPrintf( "Wallet passphrase changed to an nDeriveIterations of %i\n", pMasterKey.second.nDeriveIterations); if (!crypter.SetKeyFromPassphrase( strNewWalletPassphrase, pMasterKey.second.vchSalt, pMasterKey.second.nDeriveIterations, pMasterKey.second.nDerivationMethod)) { return false; } if (!crypter.Encrypt(_vMasterKey, pMasterKey.second.vchCryptedKey)) { return false; } WalletBatch(*database).WriteMasterKey(pMasterKey.first, pMasterKey.second); if (fWasLocked) { Lock(); } return true; } } return false; } void CWallet::ChainStateFlushed(const CBlockLocator &loc) { WalletBatch batch(*database); batch.WriteBestBlock(loc); } void CWallet::SetMinVersion(enum WalletFeature nVersion, WalletBatch *batch_in, bool fExplicit) { LOCK(cs_wallet); if (nWalletVersion >= nVersion) { return; } // When doing an explicit upgrade, if we pass the max version permitted, // upgrade all the way. if (fExplicit && nVersion > nWalletMaxVersion) { nVersion = FEATURE_LATEST; } nWalletVersion = nVersion; if (nVersion > nWalletMaxVersion) { nWalletMaxVersion = nVersion; } WalletBatch *batch = batch_in ? batch_in : new WalletBatch(*database); if (nWalletVersion > 40000) { batch->WriteMinVersion(nWalletVersion); } if (!batch_in) { delete batch; } } bool CWallet::SetMaxVersion(int nVersion) { LOCK(cs_wallet); // Cannot downgrade below current version if (nWalletVersion > nVersion) { return false; } nWalletMaxVersion = nVersion; return true; } std::set<TxId> CWallet::GetConflicts(const TxId &txid) const { std::set<TxId> result; AssertLockHeld(cs_wallet); std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid); if (it == mapWallet.end()) { return result; } const CWalletTx &wtx = it->second; std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range; for (const CTxIn &txin : wtx.tx->vin) { if (mapTxSpends.count(txin.prevout) <= 1) { // No conflict if zero or one spends. continue; } range = mapTxSpends.equal_range(txin.prevout); for (TxSpends::const_iterator _it = range.first; _it != range.second; ++_it) { result.insert(_it->second); } } return result; } bool CWallet::HasWalletSpend(const TxId &txid) const { AssertLockHeld(cs_wallet); auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0)); return (iter != mapTxSpends.end() && iter->first.GetTxId() == txid); } void CWallet::Flush(bool shutdown) { database->Flush(shutdown); } void CWallet::SyncMetaData( std::pair<TxSpends::iterator, TxSpends::iterator> range) { // We want all the wallet transactions in range to have the same metadata as // the oldest (smallest nOrderPos). // So: find smallest nOrderPos: int nMinOrderPos = std::numeric_limits<int>::max(); const CWalletTx *copyFrom = nullptr; for (TxSpends::iterator it = range.first; it != range.second; ++it) { const CWalletTx *wtx = &mapWallet.at(it->second); if (wtx->nOrderPos < nMinOrderPos) { nMinOrderPos = wtx->nOrderPos; copyFrom = wtx; } } if (!copyFrom) { return; } // Now copy data from copyFrom to rest: for (TxSpends::iterator it = range.first; it != range.second; ++it) { const TxId &txid = it->second; CWalletTx *copyTo = &mapWallet.at(txid); if (copyFrom == copyTo) { continue; } assert( copyFrom && "Oldest wallet transaction in range assumed to have been found."); if (!copyFrom->IsEquivalentTo(*copyTo)) { continue; } copyTo->mapValue = copyFrom->mapValue; copyTo->vOrderForm = copyFrom->vOrderForm; // fTimeReceivedIsTxTime not copied on purpose nTimeReceived not copied // on purpose. copyTo->nTimeSmart = copyFrom->nTimeSmart; copyTo->fFromMe = copyFrom->fFromMe; // nOrderPos not copied on purpose cached members not copied on purpose. } } /** * Outpoint is spent if any non-conflicted transaction, spends it: */ bool CWallet::IsSpent(interfaces::Chain::Lock &locked_chain, const COutPoint &outpoint) const { std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(outpoint); for (TxSpends::const_iterator it = range.first; it != range.second; ++it) { const TxId &wtxid = it->second; std::map<TxId, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); if (mit != mapWallet.end()) { int depth = mit->second.GetDepthInMainChain(locked_chain); if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) { // Spent return true; } } } return false; } void CWallet::AddToSpends(const COutPoint &outpoint, const TxId &wtxid) { mapTxSpends.insert(std::make_pair(outpoint, wtxid)); setLockedCoins.erase(outpoint); std::pair<TxSpends::iterator, TxSpends::iterator> range; range = mapTxSpends.equal_range(outpoint); SyncMetaData(range); } void CWallet::AddToSpends(const TxId &wtxid) { auto it = mapWallet.find(wtxid); assert(it != mapWallet.end()); CWalletTx &thisTx = it->second; // Coinbases don't spend anything! if (thisTx.IsCoinBase()) { return; } for (const CTxIn &txin : thisTx.tx->vin) { AddToSpends(txin.prevout, wtxid); } } bool CWallet::EncryptWallet(const SecureString &strWalletPassphrase) { if (IsCrypted()) { return false; } CKeyingMaterial _vMasterKey; _vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE); GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE); CMasterKey kMasterKey; kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE); GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE); CCrypter crypter; int64_t nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000, kMasterKey.nDerivationMethod); kMasterKey.nDeriveIterations = static_cast<unsigned int>( 2500000 / double(GetTimeMillis() - nStartTime)); nStartTime = GetTimeMillis(); crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod); kMasterKey.nDeriveIterations = (kMasterKey.nDeriveIterations + static_cast<unsigned int>(kMasterKey.nDeriveIterations * 100 / double(GetTimeMillis() - nStartTime))) / 2; if (kMasterKey.nDeriveIterations < 25000) { kMasterKey.nDeriveIterations = 25000; } WalletLogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n", kMasterKey.nDeriveIterations); if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, kMasterKey.nDeriveIterations, kMasterKey.nDerivationMethod)) { return false; } if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey)) { return false; } { LOCK(cs_wallet); mapMasterKeys[++nMasterKeyMaxID] = kMasterKey; assert(!encrypted_batch); encrypted_batch = new WalletBatch(*database); if (!encrypted_batch->TxnBegin()) { delete encrypted_batch; encrypted_batch = nullptr; return false; } encrypted_batch->WriteMasterKey(nMasterKeyMaxID, kMasterKey); if (!EncryptKeys(_vMasterKey)) { encrypted_batch->TxnAbort(); delete encrypted_batch; encrypted_batch = nullptr; // We now probably have half of our keys encrypted in memory, and // half not... die and let the user reload the unencrypted wallet. assert(false); } // Encryption was introduced in version 0.4.0 SetMinVersion(FEATURE_WALLETCRYPT, encrypted_batch, true); if (!encrypted_batch->TxnCommit()) { delete encrypted_batch; encrypted_batch = nullptr; // We now have keys encrypted in memory, but not on disk... // die to avoid confusion and let the user reload the unencrypted // wallet. assert(false); } delete encrypted_batch; encrypted_batch = nullptr; Lock(); Unlock(strWalletPassphrase); // If we are using HD, replace the HD seed with a new one if (IsHDEnabled()) { SetHDSeed(GenerateNewSeed()); } NewKeyPool(); Lock(); // Need to completely rewrite the wallet file; if we don't, bdb might // keep bits of the unencrypted private key in slack space in the // database file. database->Rewrite(); // BDB seems to have a bad habit of writing old data into // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: database->ReloadDbEnv(); } NotifyStatusChanged(this); return true; } DBErrors CWallet::ReorderTransactions() { LOCK(cs_wallet); WalletBatch batch(*database); // Old wallets didn't have any defined order for transactions. Probably a // bad idea to change the output of this. // First: get all CWalletTx into a sorted-by-time // multimap. TxItems txByTime; for (auto &entry : mapWallet) { CWalletTx *wtx = &entry.second; txByTime.insert(std::make_pair(wtx->nTimeReceived, wtx)); } nOrderPosNext = 0; std::vector<int64_t> nOrderPosOffsets; for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) { CWalletTx *const pwtx = (*it).second; int64_t &nOrderPos = pwtx->nOrderPos; if (nOrderPos == -1) { nOrderPos = nOrderPosNext++; nOrderPosOffsets.push_back(nOrderPos); if (!batch.WriteTx(*pwtx)) { return DBErrors::LOAD_FAIL; } } else { int64_t nOrderPosOff = 0; for (const int64_t &nOffsetStart : nOrderPosOffsets) { if (nOrderPos >= nOffsetStart) { ++nOrderPosOff; } } nOrderPos += nOrderPosOff; nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1); if (!nOrderPosOff) { continue; } // Since we're changing the order, write it back. if (!batch.WriteTx(*pwtx)) { return DBErrors::LOAD_FAIL; } } } batch.WriteOrderPosNext(nOrderPosNext); return DBErrors::LOAD_OK; } int64_t CWallet::IncOrderPosNext(WalletBatch *batch) { AssertLockHeld(cs_wallet); int64_t nRet = nOrderPosNext++; if (batch) { batch->WriteOrderPosNext(nOrderPosNext); } else { WalletBatch(*database).WriteOrderPosNext(nOrderPosNext); } return nRet; } void CWallet::MarkDirty() { LOCK(cs_wallet); for (std::pair<const TxId, CWalletTx> &item : mapWallet) { item.second.MarkDirty(); } } void CWallet::SetUsedDestinationState(const TxId &hash, unsigned int n, bool used) { const CWalletTx *srctx = GetWalletTx(hash); if (!srctx) { return; } CTxDestination dst; if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) { if (::IsMine(*this, dst)) { LOCK(cs_wallet); if (used && !GetDestData(dst, "used", nullptr)) { // p for "present", opposite of absent (null) AddDestData(dst, "used", "p"); } else if (!used && GetDestData(dst, "used", nullptr)) { EraseDestData(dst, "used"); } } } } bool CWallet::IsUsedDestination(const CTxDestination &dst) const { LOCK(cs_wallet); return ::IsMine(*this, dst) && GetDestData(dst, "used", nullptr); } bool CWallet::IsUsedDestination(const TxId &txid, unsigned int n) const { CTxDestination dst; const CWalletTx *srctx = GetWalletTx(txid); return srctx && ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst) && IsUsedDestination(dst); } bool CWallet::AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose) { LOCK(cs_wallet); WalletBatch batch(*database, "r+", fFlushOnClose); const TxId &txid = wtxIn.GetId(); if (IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) { // Mark used destinations for (const CTxIn &txin : wtxIn.tx->vin) { const COutPoint &op = txin.prevout; SetUsedDestinationState(op.GetTxId(), op.GetN(), true); } } // Inserts only if not already there, returns tx inserted or tx found. std::pair<std::map<TxId, CWalletTx>::iterator, bool> ret = mapWallet.insert(std::make_pair(txid, wtxIn)); CWalletTx &wtx = (*ret.first).second; wtx.BindWallet(this); bool fInsertedNew = ret.second; if (fInsertedNew) { wtx.nTimeReceived = chain().getAdjustedTime(); wtx.nOrderPos = IncOrderPosNext(&batch); wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); wtx.nTimeSmart = ComputeTimeSmart(wtx); AddToSpends(txid); } bool fUpdated = false; if (!fInsertedNew) { // Merge if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) { wtx.hashBlock = wtxIn.hashBlock; fUpdated = true; } // If no longer abandoned, update if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned()) { wtx.hashBlock = wtxIn.hashBlock; fUpdated = true; } if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex)) { wtx.nIndex = wtxIn.nIndex; fUpdated = true; } if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) { wtx.fFromMe = wtxIn.fFromMe; fUpdated = true; } } //// debug print WalletLogPrintf("AddToWallet %s %s%s\n", wtxIn.GetId().ToString(), (fInsertedNew ? "new" : ""), (fUpdated ? "update" : "")); // Write to disk if ((fInsertedNew || fUpdated) && !batch.WriteTx(wtx)) { return false; } // Break debit/credit balance caches: wtx.MarkDirty(); // Notify UI of new or updated transaction. NotifyTransactionChanged(this, txid, fInsertedNew ? CT_NEW : CT_UPDATED); // Notify an external script when a wallet transaction comes in or is // updated. std::string strCmd = gArgs.GetArg("-walletnotify", ""); if (!strCmd.empty()) { boost::replace_all(strCmd, "%s", wtxIn.GetId().GetHex()); std::thread t(runCommand, strCmd); // Thread runs free. t.detach(); } return true; } void CWallet::LoadToWallet(const CWalletTx &wtxIn) { const TxId &txid = wtxIn.GetId(); const auto &ins = mapWallet.emplace(txid, wtxIn); CWalletTx &wtx = ins.first->second; wtx.BindWallet(this); if (/* insertion took place */ ins.second) { wtx.m_it_wtxOrdered = wtxOrdered.insert(std::make_pair(wtx.nOrderPos, &wtx)); } AddToSpends(txid); for (const CTxIn &txin : wtx.tx->vin) { auto it = mapWallet.find(txin.prevout.GetTxId()); if (it != mapWallet.end()) { CWalletTx &prevtx = it->second; if (prevtx.nIndex == -1 && !prevtx.hashUnset()) { MarkConflicted(prevtx.hashBlock, wtx.GetId()); } } } } bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx, const BlockHash &block_hash, int posInBlock, bool fUpdate) { const CTransaction &tx = *ptx; AssertLockHeld(cs_wallet); if (!block_hash.IsNull()) { for (const CTxIn &txin : tx.vin) { std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout); while (range.first != range.second) { if (range.first->second != tx.GetId()) { WalletLogPrintf( "Transaction %s (in block %s) conflicts with wallet " "transaction %s (both spend %s:%i)\n", tx.GetId().ToString(), block_hash.ToString(), range.first->second.ToString(), range.first->first.GetTxId().ToString(), range.first->first.GetN()); MarkConflicted(block_hash, range.first->second); } range.first++; } } } bool fExisted = mapWallet.count(tx.GetId()) != 0; if (fExisted && !fUpdate) { return false; } if (fExisted || IsMine(tx) || IsFromMe(tx)) { /** * Check if any keys in the wallet keypool that were supposed to be * unused have appeared in a new transaction. If so, remove those keys * from the keypool. This can happen when restoring an old wallet backup * that does not contain the mostly recently created transactions from * newer versions of the wallet. */ // loop though all outputs for (const CTxOut &txout : tx.vout) { // extract addresses and check if they match with an unused keypool // key for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *this)) { std::map<CKeyID, int64_t>::const_iterator mi = m_pool_key_to_index.find(keyid); if (mi != m_pool_key_to_index.end()) { WalletLogPrintf("%s: Detected a used keypool key, mark all " "keypool key up to this key as used\n", __func__); MarkReserveKeysAsUsed(mi->second); if (!TopUpKeyPool()) { WalletLogPrintf( "%s: Topping up keypool failed (locked wallet)\n", __func__); } } } } CWalletTx wtx(this, ptx); // Get merkle branch if transaction was found in a block if (!block_hash.IsNull()) { wtx.SetMerkleBranch(block_hash, posInBlock); } return AddToWallet(wtx, false); } return false; } bool CWallet::TransactionCanBeAbandoned(const TxId &txid) const { auto locked_chain = chain().lock(); LOCK(cs_wallet); const CWalletTx *wtx = GetWalletTx(txid); return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain(*locked_chain) == 0 && !wtx->InMempool(); } void CWallet::MarkInputsDirty(const CTransactionRef &tx) { for (const CTxIn &txin : tx->vin) { auto it = mapWallet.find(txin.prevout.GetTxId()); if (it != mapWallet.end()) { it->second.MarkDirty(); } } } bool CWallet::AbandonTransaction(interfaces::Chain::Lock &locked_chain, const TxId &txid) { // Temporary. Removed in upcoming lock cleanup auto locked_chain_recursive = chain().lock(); LOCK(cs_wallet); WalletBatch batch(*database, "r+"); std::set<TxId> todo; std::set<TxId> done; // Can't mark abandoned if confirmed or in mempool auto it = mapWallet.find(txid); assert(it != mapWallet.end()); CWalletTx &origtx = it->second; if (origtx.GetDepthInMainChain(locked_chain) != 0 || origtx.InMempool()) { return false; } todo.insert(txid); while (!todo.empty()) { const TxId now = *todo.begin(); todo.erase(now); done.insert(now); it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx &wtx = it->second; int currentconfirm = wtx.GetDepthInMainChain(locked_chain); // If the orig tx was not in block, none of its spends can be. assert(currentconfirm <= 0); // If (currentconfirm < 0) {Tx and spends are already conflicted, no // need to abandon} if (currentconfirm == 0 && !wtx.isAbandoned()) { // If the orig tx was not in block/mempool, none of its spends can // be in mempool. assert(!wtx.InMempool()); wtx.nIndex = -1; wtx.setAbandoned(); wtx.MarkDirty(); batch.WriteTx(wtx); NotifyTransactionChanged(this, wtx.GetId(), CT_UPDATED); // Iterate over all its outputs, and mark transactions in the wallet // that spend them abandoned too. TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) { if (!done.count(iter->second)) { todo.insert(iter->second); } iter++; } // If a transaction changes 'conflicted' state, that changes the // balance available of the outputs it spends. So force those to be // recomputed. MarkInputsDirty(wtx.tx); } } return true; } void CWallet::MarkConflicted(const BlockHash &hashBlock, const TxId &txid) { auto locked_chain = chain().lock(); LOCK(cs_wallet); int conflictconfirms = -locked_chain->getBlockDepth(hashBlock); // If number of conflict confirms cannot be determined, this means that the // block is still unknown or not yet part of the main chain, for example // when loading the wallet during a reindex. Do nothing in that case. if (conflictconfirms >= 0) { return; } // Do not flush the wallet here for performance reasons. WalletBatch batch(*database, "r+", false); std::set<TxId> todo; std::set<TxId> done; todo.insert(txid); while (!todo.empty()) { const TxId now = *todo.begin(); todo.erase(now); done.insert(now); auto it = mapWallet.find(now); assert(it != mapWallet.end()); CWalletTx &wtx = it->second; int currentconfirm = wtx.GetDepthInMainChain(*locked_chain); if (conflictconfirms < currentconfirm) { // Block is 'more conflicted' than current confirm; update. // Mark transaction as conflicted with this block. wtx.nIndex = -1; wtx.hashBlock = hashBlock; wtx.MarkDirty(); batch.WriteTx(wtx); // Iterate over all its outputs, and mark transactions in the wallet // that spend them conflicted too. TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) { if (!done.count(iter->second)) { todo.insert(iter->second); } iter++; } // If a transaction changes 'conflicted' state, that changes the // balance available of the outputs it spends. So force those to be // recomputed. MarkInputsDirty(wtx.tx); } } } void CWallet::SyncTransaction(const CTransactionRef &ptx, const BlockHash &block_hash, int posInBlock, bool update_tx) { if (!AddToWalletIfInvolvingMe(ptx, block_hash, posInBlock, update_tx)) { // Not one of ours return; } // If a transaction changes 'conflicted' state, that changes the balance // available of the outputs it spends. So force those to be // recomputed, also: MarkInputsDirty(ptx); } void CWallet::TransactionAddedToMempool(const CTransactionRef &ptx) { auto locked_chain = chain().lock(); LOCK(cs_wallet); SyncTransaction(ptx, BlockHash(), 0 /* position in block */); auto it = mapWallet.find(ptx->GetId()); if (it != mapWallet.end()) { it->second.fInMempool = true; } } void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) { LOCK(cs_wallet); auto it = mapWallet.find(ptx->GetId()); if (it != mapWallet.end()) { it->second.fInMempool = false; } } void CWallet::BlockConnected( const CBlock &block, const std::vector<CTransactionRef> &vtxConflicted) { const BlockHash &block_hash = block.GetHash(); auto locked_chain = chain().lock(); LOCK(cs_wallet); // TODO: Temporarily ensure that mempool removals are notified before // connected transactions. This shouldn't matter, but the abandoned state of // transactions in our wallet is currently cleared when we receive another // notification and there is a race condition where notification of a // connected conflict might cause an outside process to abandon a // transaction and then have it inadvertently cleared by the notification // that the conflicted transaction was evicted. for (const CTransactionRef &ptx : vtxConflicted) { SyncTransaction(ptx, BlockHash(), 0 /* position in block */); TransactionRemovedFromMempool(ptx); } for (size_t i = 0; i < block.vtx.size(); i++) { SyncTransaction(block.vtx[i], block_hash, i); TransactionRemovedFromMempool(block.vtx[i]); } m_last_block_processed = block_hash; } void CWallet::BlockDisconnected(const CBlock &block) { auto locked_chain = chain().lock(); LOCK(cs_wallet); for (const CTransactionRef &ptx : block.vtx) { SyncTransaction(ptx, BlockHash(), 0 /* position in block */); } } void CWallet::UpdatedBlockTip() { m_best_block_time = GetTime(); } void CWallet::BlockUntilSyncedToCurrentChain() { AssertLockNotHeld(cs_wallet); // Skip the queue-draining stuff if we know we're caught up with // chainActive.Tip(), otherwise put a callback in the validation interface // queue and wait for the queue to drain enough to execute it (indicating we // are caught up at least with the time we entered this function). const BlockHash last_block_hash = WITH_LOCK(cs_wallet, return m_last_block_processed); chain().waitForNotificationsIfNewBlocksConnected(last_block_hash); } isminetype CWallet::IsMine(const CTxIn &txin) const { LOCK(cs_wallet); std::map<TxId, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.GetTxId()); if (mi != mapWallet.end()) { const CWalletTx &prev = (*mi).second; if (txin.prevout.GetN() < prev.tx->vout.size()) { return IsMine(prev.tx->vout[txin.prevout.GetN()]); } } return ISMINE_NO; } // Note that this function doesn't distinguish between a 0-valued input, and a // not-"is mine" (according to the filter) input. Amount CWallet::GetDebit(const CTxIn &txin, const isminefilter &filter) const { LOCK(cs_wallet); std::map<TxId, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.GetTxId()); if (mi != mapWallet.end()) { const CWalletTx &prev = (*mi).second; if (txin.prevout.GetN() < prev.tx->vout.size()) { if (IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter) { return prev.tx->vout[txin.prevout.GetN()].nValue; } } } return Amount::zero(); } isminetype CWallet::IsMine(const CTxOut &txout) const { return ::IsMine(*this, txout.scriptPubKey); } Amount CWallet::GetCredit(const CTxOut &txout, const isminefilter &filter) const { if (!MoneyRange(txout.nValue)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } return (IsMine(txout) & filter) ? txout.nValue : Amount::zero(); } bool CWallet::IsChange(const CTxOut &txout) const { return IsChange(txout.scriptPubKey); } bool CWallet::IsChange(const CScript &script) const { // TODO: fix handling of 'change' outputs. The assumption is that any // payment to a script that is ours, but is not in the address book is // change. That assumption is likely to break when we implement // multisignature wallets that return change back into a // multi-signature-protected address; a better way of identifying which // outputs are 'the send' and which are 'the change' will need to be // implemented (maybe extend CWalletTx to remember which output, if any, was // change). if (::IsMine(*this, script)) { CTxDestination address; if (!ExtractDestination(script, address)) { return true; } LOCK(cs_wallet); if (!mapAddressBook.count(address)) { return true; } } return false; } Amount CWallet::GetChange(const CTxOut &txout) const { if (!MoneyRange(txout.nValue)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } return (IsChange(txout) ? txout.nValue : Amount::zero()); } bool CWallet::IsMine(const CTransaction &tx) const { for (const CTxOut &txout : tx.vout) { if (IsMine(txout)) { return true; } } return false; } bool CWallet::IsFromMe(const CTransaction &tx) const { return GetDebit(tx, ISMINE_ALL) > Amount::zero(); } Amount CWallet::GetDebit(const CTransaction &tx, const isminefilter &filter) const { Amount nDebit = Amount::zero(); for (const CTxIn &txin : tx.vin) { nDebit += GetDebit(txin, filter); if (!MoneyRange(nDebit)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } } return nDebit; } bool CWallet::IsAllFromMe(const CTransaction &tx, const isminefilter &filter) const { LOCK(cs_wallet); for (const CTxIn &txin : tx.vin) { auto mi = mapWallet.find(txin.prevout.GetTxId()); if (mi == mapWallet.end()) { // Any unknown inputs can't be from us. return false; } const CWalletTx &prev = (*mi).second; if (txin.prevout.GetN() >= prev.tx->vout.size()) { // Invalid input! return false; } if (!(IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter)) { return false; } } return true; } Amount CWallet::GetCredit(const CTransaction &tx, const isminefilter &filter) const { Amount nCredit = Amount::zero(); for (const CTxOut &txout : tx.vout) { nCredit += GetCredit(txout, filter); if (!MoneyRange(nCredit)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } } return nCredit; } Amount CWallet::GetChange(const CTransaction &tx) const { Amount nChange = Amount::zero(); for (const CTxOut &txout : tx.vout) { nChange += GetChange(txout); if (!MoneyRange(nChange)) { throw std::runtime_error(std::string(__func__) + ": value out of range"); } } return nChange; } CPubKey CWallet::GenerateNewSeed() { assert(!IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)); CKey key; key.MakeNewKey(true); return DeriveNewSeed(key); } CPubKey CWallet::DeriveNewSeed(const CKey &key) { int64_t nCreationTime = GetTime(); CKeyMetadata metadata(nCreationTime); // Calculate the seed CPubKey seed = key.GetPubKey(); assert(key.VerifyPubKey(seed)); // Set the hd keypath to "s" -> Seed, refers the seed to itself metadata.hdKeypath = "s"; metadata.has_key_origin = false; metadata.hd_seed_id = seed.GetID(); LOCK(cs_wallet); // mem store the metadata mapKeyMetadata[seed.GetID()] = metadata; // Write the key&metadata to the database if (!AddKeyPubKey(key, seed)) { throw std::runtime_error(std::string(__func__) + ": AddKeyPubKey failed"); } return seed; } void CWallet::SetHDSeed(const CPubKey &seed) { LOCK(cs_wallet); // Store the keyid (hash160) together with the child index counter in the // database as a hdchain object. CHDChain newHdChain; newHdChain.nVersion = CanSupportFeature(FEATURE_HD_SPLIT) ? CHDChain::VERSION_HD_CHAIN_SPLIT : CHDChain::VERSION_HD_BASE; newHdChain.seed_id = seed.GetID(); SetHDChain(newHdChain, false); NotifyCanGetAddressesChanged(); UnsetWalletFlag(WALLET_FLAG_BLANK_WALLET); } void CWallet::SetHDChain(const CHDChain &chain, bool memonly) { LOCK(cs_wallet); if (!memonly && !WalletBatch(*database).WriteHDChain(chain)) { throw std::runtime_error(std::string(__func__) + ": writing chain failed"); } hdChain = chain; } bool CWallet::IsHDEnabled() const { return !hdChain.seed_id.IsNull(); } bool CWallet::CanGenerateKeys() { // A wallet can generate keys if it has an HD seed (IsHDEnabled) or it is a // non-HD wallet (pre FEATURE_HD) LOCK(cs_wallet); return IsHDEnabled() || !CanSupportFeature(FEATURE_HD); } bool CWallet::CanGetAddresses(bool internal) { LOCK(cs_wallet); // Check if the keypool has keys bool keypool_has_keys; if (internal && CanSupportFeature(FEATURE_HD_SPLIT)) { keypool_has_keys = setInternalKeyPool.size() > 0; } else { keypool_has_keys = KeypoolCountExternalKeys() > 0; } // If the keypool doesn't have keys, check if we can generate them if (!keypool_has_keys) { return CanGenerateKeys(); } return keypool_has_keys; } void CWallet::SetWalletFlag(uint64_t flags) { LOCK(cs_wallet); m_wallet_flags |= flags; if (!WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) { throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } } void CWallet::UnsetWalletFlag(uint64_t flag) { WalletBatch batch(*database); UnsetWalletFlagWithDB(batch, flag); } void CWallet::UnsetWalletFlagWithDB(WalletBatch &batch, uint64_t flag) { LOCK(cs_wallet); m_wallet_flags &= ~flag; if (!batch.WriteWalletFlags(m_wallet_flags)) { throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } } bool CWallet::IsWalletFlagSet(uint64_t flag) const { return (m_wallet_flags & flag); } bool CWallet::SetWalletFlags(uint64_t overwriteFlags, bool memonly) { LOCK(cs_wallet); m_wallet_flags = overwriteFlags; if (((overwriteFlags & KNOWN_WALLET_FLAGS) >> 32) ^ (overwriteFlags >> 32)) { // contains unknown non-tolerable wallet flags return false; } if (!memonly && !WalletBatch(*database).WriteWalletFlags(m_wallet_flags)) { throw std::runtime_error(std::string(__func__) + ": writing wallet flags failed"); } return true; } int64_t CWalletTx::GetTxTime() const { int64_t n = nTimeSmart; return n ? n : nTimeReceived; } // Helper for producing a max-sized low-S low-R signature (eg 71 bytes) // or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) const { // Fill in dummy signatures for fee calculation. const CScript &scriptPubKey = txout.scriptPubKey; SignatureData sigdata; if (!ProduceSignature(*this, use_max_sig ? DUMMY_MAXIMUM_SIGNATURE_CREATOR : DUMMY_SIGNATURE_CREATOR, scriptPubKey, sigdata)) { return false; } UpdateInput(tx_in, sigdata); return true; } // Helper for producing a bunch of max-sized low-S low-R signatures (eg 71 // bytes) bool CWallet::DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig) const { // Fill in dummy signatures for fee calculation. int nIn = 0; for (const auto &txout : txouts) { if (!DummySignInput(txNew.vin[nIn], txout, use_max_sig)) { return false; } nIn++; } return true; } bool CWallet::ImportScripts(const std::set<CScript> scripts, int64_t timestamp) { WalletBatch batch(*database); for (const auto &entry : scripts) { CScriptID id(entry); if (HaveCScript(id)) { WalletLogPrintf("Already have script %s, skipping\n", HexStr(entry)); continue; } if (!AddCScriptWithDB(batch, entry)) { return false; } if (timestamp > 0) { m_script_metadata[CScriptID(entry)].nCreateTime = timestamp; } } if (timestamp > 0) { UpdateTimeFirstKey(timestamp); } return true; } bool CWallet::ImportPrivKeys(const std::map<CKeyID, CKey> &privkey_map, const int64_t timestamp) { WalletBatch batch(*database); for (const auto &entry : privkey_map) { const CKey &key = entry.second; CPubKey pubkey = key.GetPubKey(); const CKeyID &id = entry.first; assert(key.VerifyPubKey(pubkey)); // Skip if we already have the key if (HaveKey(id)) { WalletLogPrintf("Already have key with pubkey %s, skipping\n", HexStr(pubkey)); continue; } mapKeyMetadata[id].nCreateTime = timestamp; // If the private key is not present in the wallet, insert it. if (!AddKeyPubKeyWithDB(batch, key, pubkey)) { return false; } UpdateTimeFirstKey(timestamp); } return true; } bool CWallet::ImportPubKeys( const std::vector<CKeyID> &ordered_pubkeys, const std::map<CKeyID, CPubKey> &pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> &key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) { WalletBatch batch(*database); for (const auto &entry : key_origins) { AddKeyOriginWithDB(batch, entry.second.first, entry.second.second); } for (const CKeyID &id : ordered_pubkeys) { auto entry = pubkey_map.find(id); if (entry == pubkey_map.end()) { continue; } const CPubKey &pubkey = entry->second; CPubKey temp; if (GetPubKey(id, temp)) { // Already have pubkey, skipping WalletLogPrintf("Already have pubkey %s, skipping\n", HexStr(temp)); continue; } if (!AddWatchOnlyWithDB(batch, GetScriptForRawPubKey(pubkey), timestamp)) { return false; } mapKeyMetadata[id].nCreateTime = timestamp; // Add to keypool only works with pubkeys if (add_keypool) { AddKeypoolPubkeyWithDB(pubkey, internal, batch); NotifyCanGetAddressesChanged(); } } return true; } bool CWallet::ImportScriptPubKeys(const std::string &label, const std::set<CScript> &script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) { WalletBatch batch(*database); for (const CScript &script : script_pub_keys) { if (!have_solving_data || !::IsMine(*this, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that // watch timestamp gets updated if (!AddWatchOnlyWithDB(batch, script, timestamp)) { return false; } } CTxDestination dest; ExtractDestination(script, dest); if (apply_label && IsValidDestination(dest)) { SetAddressBookWithDB(batch, dest, label, "receive"); } } return true; } int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) { std::vector<CTxOut> txouts; // Look up the inputs. We should have already checked that this transaction // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our // wallet, with a valid index into the vout array, and the ability to sign. for (auto &input : tx.vin) { const auto mi = wallet->mapWallet.find(input.prevout.GetTxId()); if (mi == wallet->mapWallet.end()) { return -1; } assert(input.prevout.GetN() < mi->second.tx->vout.size()); txouts.emplace_back(mi->second.tx->vout[input.prevout.GetN()]); } return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig); } // txouts needs to be in the order of tx.vin int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut> &txouts, bool use_max_sig) { CMutableTransaction txNew(tx); if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) // implies that we can sign for every input. return -1; } return GetSerializeSize(txNew, PROTOCOL_VERSION); } int CalculateMaximumSignedInputSize(const CTxOut &txout, const CWallet *wallet, bool use_max_sig) { CMutableTransaction txn; txn.vin.push_back(CTxIn(COutPoint())); if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) { // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) // implies that we can sign for every input. return -1; } return GetSerializeSize(txn.vin[0], PROTOCOL_VERSION); } void CWalletTx::GetAmounts(std::list<COutputEntry> &listReceived, std::list<COutputEntry> &listSent, Amount &nFee, const isminefilter &filter) const { nFee = Amount::zero(); listReceived.clear(); listSent.clear(); // Compute fee: Amount nDebit = GetDebit(filter); // debit>0 means we signed/sent this transaction. if (nDebit > Amount::zero()) { Amount nValueOut = tx->GetValueOut(); nFee = (nDebit - nValueOut); } // Sent/received. for (unsigned int i = 0; i < tx->vout.size(); ++i) { const CTxOut &txout = tx->vout[i]; isminetype fIsMine = pwallet->IsMine(txout); // Only need to handle txouts if AT LEAST one of these is true: // 1) they debit from us (sent) // 2) the output is to us (received) if (nDebit > Amount::zero()) { // Don't report 'change' txouts if (pwallet->IsChange(txout)) { continue; } } else if (!(fIsMine & filter)) { continue; } // In either case, we need to get the destination address. CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) { pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown " "transaction type found, txid %s\n", this->GetId().ToString()); address = CNoDestination(); } COutputEntry output = {address, txout.nValue, (int)i}; // If we are debited by the transaction, add the output as a "sent" // entry. if (nDebit > Amount::zero()) { listSent.push_back(output); } // If we are receiving the output, add it as a "received" entry. if (fIsMine & filter) { listReceived.push_back(output); } } } /** * Scan active chain for relevant transactions after importing keys. This should * be called whenever new keys are added to the wallet, with the oldest key * creation time. * * @return Earliest timestamp that could be successfully scanned from. Timestamp * returned will be higher than startTime if relevant blocks could not be read. */ int64_t CWallet::RescanFromTime(int64_t startTime, const WalletRescanReserver &reserver, bool update) { // Find starting block. May be null if nCreateTime is greater than the // highest blockchain timestamp, in which case there is nothing that needs // to be scanned. BlockHash start_block; { auto locked_chain = chain().lock(); const Optional<int> start_height = locked_chain->findFirstBlockWithTimeAndHeight( startTime - TIMESTAMP_WINDOW, 0, &start_block); const Optional<int> tip_height = locked_chain->getHeight(); WalletLogPrintf( "%s: Rescanning last %i blocks\n", __func__, tip_height && start_height ? *tip_height - *start_height + 1 : 0); } if (!start_block.IsNull()) { // TODO: this should take into account failure by ScanResult::USER_ABORT ScanResult result = ScanForWalletTransactions(start_block, BlockHash(), reserver, update); if (result.status == ScanResult::FAILURE) { int64_t time_max; if (!chain().findBlock(result.last_failed_block, nullptr /* block */, nullptr /* time */, &time_max)) { throw std::logic_error( "ScanForWalletTransactions returned invalid block hash"); } return time_max + TIMESTAMP_WINDOW + 1; } } return startTime; } /** * Scan the block chain (starting in start_block) for transactions from or to * us. If fUpdate is true, found transactions that already exist in the wallet * will be updated. * * @param[in] start_block Scan starting block. If block is not on the active * chain, the scan will return SUCCESS immediately. * @param[in] stop_block Scan ending block. If block is not on the active * chain, the scan will continue until it reaches the * chain tip. * * @return ScanResult returning scan information and indicating success or * failure. Return status will be set to SUCCESS if scan was * successful. FAILURE if a complete rescan was not possible (due to * pruning or corruption). USER_ABORT if the rescan was aborted before * it could complete. * * @pre Caller needs to make sure start_block (and the optional stop_block) are * on the main chain after to the addition of any new keys you want to detect * transactions for. */ CWallet::ScanResult CWallet::ScanForWalletTransactions( const BlockHash &start_block, const BlockHash &stop_block, const WalletRescanReserver &reserver, bool fUpdate) { int64_t nNow = GetTime(); int64_t start_time = GetTimeMillis(); assert(reserver.isReserved()); BlockHash block_hash = start_block; ScanResult result; WalletLogPrintf("Rescan started from block %s...\n", start_block.ToString()); fAbortRescan = false; // Show rescan progress in GUI as dialog or on splashscreen, if -rescan // on startup. ShowProgress( strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 0); BlockHash tip_hash; // The way the 'block_height' is initialized is just a workaround for // the gcc bug #47679 since version 4.6.0. Optional<int> block_height = MakeOptional(false, int()); double progress_begin; double progress_end; { auto locked_chain = chain().lock(); if (Optional<int> tip_height = locked_chain->getHeight()) { tip_hash = locked_chain->getBlockHash(*tip_height); } block_height = locked_chain->getBlockHeight(block_hash); progress_begin = chain().guessVerificationProgress(block_hash); progress_end = chain().guessVerificationProgress( stop_block.IsNull() ? tip_hash : stop_block); } double progress_current = progress_begin; while (block_height && !fAbortRescan && !chain().shutdownRequested()) { m_scanning_progress = (progress_current - progress_begin) / (progress_end - progress_begin); if (*block_height % 100 == 0 && progress_end - progress_begin > 0.0) { ShowProgress( strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), std::max(1, std::min(99, int(m_scanning_progress * 100)))); } if (GetTime() >= nNow + 60) { nNow = GetTime(); WalletLogPrintf("Still rescanning. At block %d. Progress=%f\n", *block_height, progress_current); } CBlock block; if (chain().findBlock(block_hash, &block) && !block.IsNull()) { auto locked_chain = chain().lock(); LOCK(cs_wallet); if (!locked_chain->getBlockHeight(block_hash)) { // Abort scan if current block is no longer active, to // prevent marking transactions as coming from the wrong // block. // TODO: This should return success instead of failure, see // https://github.com/bitcoin/bitcoin/pull/14711#issuecomment-458342518 result.last_failed_block = block_hash; result.status = ScanResult::FAILURE; break; } for (size_t posInBlock = 0; posInBlock < block.vtx.size(); ++posInBlock) { SyncTransaction(block.vtx[posInBlock], block_hash, posInBlock, fUpdate); } // scan succeeded, record block as most recent successfully // scanned result.last_scanned_block = block_hash; result.last_scanned_height = *block_height; } else { // could not scan block, keep scanning but record this block as // the most recent failure result.last_failed_block = block_hash; result.status = ScanResult::FAILURE; } if (block_hash == stop_block) { break; } { auto locked_chain = chain().lock(); Optional<int> tip_height = locked_chain->getHeight(); if (!tip_height || *tip_height <= block_height || !locked_chain->getBlockHeight(block_hash)) { // break successfully when rescan has reached the tip, or // previous block is no longer on the chain due to a reorg break; } // increment block and verification progress block_hash = locked_chain->getBlockHash(++*block_height); progress_current = chain().guessVerificationProgress(block_hash); // handle updated tip hash const BlockHash prev_tip_hash = tip_hash; tip_hash = locked_chain->getBlockHash(*tip_height); if (stop_block.IsNull() && prev_tip_hash != tip_hash) { // in case the tip has changed, update progress max progress_end = chain().guessVerificationProgress(tip_hash); } } } // Hide progress dialog in GUI. ShowProgress( strprintf("%s " + _("Rescanning...").translated, GetDisplayName()), 100); if (block_height && fAbortRescan) { WalletLogPrintf("Rescan aborted at block %d. Progress=%f\n", *block_height, progress_current); result.status = ScanResult::USER_ABORT; } else if (block_height && chain().shutdownRequested()) { WalletLogPrintf("Rescan interrupted by shutdown request at block " "%d. Progress=%f\n", *block_height, progress_current); result.status = ScanResult::USER_ABORT; } else { WalletLogPrintf("Rescan completed in %15dms\n", GetTimeMillis() - start_time); } return result; } void CWallet::ReacceptWalletTransactions( interfaces::Chain::Lock &locked_chain) { // If transactions aren't being broadcasted, don't let them into local // mempool either. if (!fBroadcastTransactions) { return; } std::map<int64_t, CWalletTx *> mapSorted; // Sort pending wallet transactions based on their initial wallet insertion // order. for (std::pair<const TxId, CWalletTx> &item : mapWallet) { const TxId &wtxid = item.first; CWalletTx &wtx = item.second; assert(wtx.GetId() == wtxid); int nDepth = wtx.GetDepthInMainChain(locked_chain); if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); } } // Try to add wallet transactions to memory pool. for (const std::pair<const int64_t, CWalletTx *> &item : mapSorted) { CWalletTx &wtx = *(item.second); std::string unused_err_string; wtx.SubmitMemoryPoolAndRelay(unused_err_string, false); } } bool CWalletTx::SubmitMemoryPoolAndRelay(std::string &err_string, bool relay) { // Can't relay if wallet is not broadcasting if (!pwallet->GetBroadcastTransactions()) { return false; } // Don't relay abandoned transactions if (isAbandoned()) { return false; } // Submit transaction to mempool for relay pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetId().ToString()); // We must set fInMempool here - while it will be re-set to true by the // entered-mempool callback, if we did not there would be a race where a // user could call sendmoney in a loop and hit spurious out of funds errors // because we think that this newly generated transaction's change is // unavailable as we're not yet aware that it is in the mempool. // // Irrespective of the failure reason, un-marking fInMempool // out-of-order is incorrect - it should be unmarked when // TransactionRemovedFromMempool fires. bool ret = pwallet->chain().broadcastTransaction( GetConfig(), tx, err_string, pwallet->m_default_max_tx_fee, relay); fInMempool |= ret; return ret; } std::set<TxId> CWalletTx::GetConflicts() const { std::set<TxId> result; if (pwallet != nullptr) { const TxId &txid = GetId(); result = pwallet->GetConflicts(txid); result.erase(txid); } return result; } Amount CWalletTx::GetCachableAmount(AmountType type, const isminefilter &filter, bool recalculate) const { auto &amount = m_amounts[type]; if (recalculate || !amount.m_cached[filter]) { amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter)); } return amount.m_value[filter]; } Amount CWalletTx::GetDebit(const isminefilter &filter) const { if (tx->vin.empty()) { return Amount::zero(); } Amount debit = Amount::zero(); if (filter & ISMINE_SPENDABLE) { debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE); } if (filter & ISMINE_WATCH_ONLY) { debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY); } return debit; } Amount CWalletTx::GetCredit(interfaces::Chain::Lock &locked_chain, const isminefilter &filter) const { // Must wait until coinbase is safely deep enough in the chain before // valuing it. if (IsImmatureCoinBase(locked_chain)) { return Amount::zero(); } Amount credit = Amount::zero(); if (filter & ISMINE_SPENDABLE) { // GetBalance can assume transactions in mapWallet won't change. credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE); } if (filter & ISMINE_WATCH_ONLY) { credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY); } return credit; } Amount CWalletTx::GetImmatureCredit(interfaces::Chain::Lock &locked_chain, bool fUseCache) const { if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); } return Amount::zero(); } Amount CWalletTx::GetAvailableCredit(interfaces::Chain::Lock &locked_chain, bool fUseCache, const isminefilter &filter) const { if (pwallet == nullptr) { return Amount::zero(); } // Avoid caching ismine for NO or ALL cases (could remove this check and // simplify in the future). bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL; // Must wait until coinbase is safely deep enough in the chain before // valuing it. if (IsImmatureCoinBase(locked_chain)) { return Amount::zero(); } if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { return m_amounts[AVAILABLE_CREDIT].m_value[filter]; } bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); Amount nCredit = Amount::zero(); const TxId &txid = GetId(); for (uint32_t i = 0; i < tx->vout.size(); i++) { if (!pwallet->IsSpent(locked_chain, COutPoint(txid, i)) && (allow_used_addresses || !pwallet->IsUsedDestination(txid, i))) { const CTxOut &txout = tx->vout[i]; nCredit += pwallet->GetCredit(txout, filter); if (!MoneyRange(nCredit)) { throw std::runtime_error(std::string(__func__) + " : value out of range"); } } } if (allow_cache) { m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit); } return nCredit; } Amount CWalletTx::GetImmatureWatchOnlyCredit(interfaces::Chain::Lock &locked_chain, const bool fUseCache) const { if (IsImmatureCoinBase(locked_chain) && IsInMainChain(locked_chain)) { return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); } return Amount::zero(); } Amount CWalletTx::GetChange() const { if (fChangeCached) { return nChangeCached; } nChangeCached = pwallet->GetChange(*tx); fChangeCached = true; return nChangeCached; } bool CWalletTx::InMempool() const { return fInMempool; } bool CWalletTx::IsTrusted(interfaces::Chain::Lock &locked_chain) const { // Quick answer in most cases TxValidationState state; if (!locked_chain.contextualCheckTransactionForCurrentBlock( Params().GetConsensus(), *tx, state)) { return false; } int nDepth = GetDepthInMainChain(locked_chain); if (nDepth >= 1) { return true; } if (nDepth < 0) { return false; } // using wtx's cached debit if (!pwallet->m_spend_zero_conf_change || !IsFromMe(ISMINE_ALL)) { return false; } // Don't trust unconfirmed transactions from us unless they are in the // mempool. if (!InMempool()) { return false; } // Trusted if all inputs are from us and are in the mempool: for (const CTxIn &txin : tx->vin) { // Transactions not sent by us: not trusted const CWalletTx *parent = pwallet->GetWalletTx(txin.prevout.GetTxId()); if (parent == nullptr) { return false; } const CTxOut &parentOut = parent->tx->vout[txin.prevout.GetN()]; if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) { return false; } } return true; } bool CWalletTx::IsEquivalentTo(const CWalletTx &_tx) const { CMutableTransaction tx1{*this->tx}; CMutableTransaction tx2{*_tx.tx}; for (auto &txin : tx1.vin) { txin.scriptSig = CScript(); } for (auto &txin : tx2.vin) { txin.scriptSig = CScript(); } return CTransaction(tx1) == CTransaction(tx2); } // Rebroadcast transactions from the wallet. We do this on a random timer // to slightly obfuscate which transactions come from our wallet. // // Ideally, we'd only resend transactions that we think should have been // mined in the most recent block. Any transaction that wasn't in the top // blockweight of transactions in the mempool shouldn't have been mined, // and so is probably just sitting in the mempool waiting to be confirmed. // Rebroadcasting does nothing to speed up confirmation and only damages // privacy. void CWallet::ResendWalletTransactions() { // During reindex, importing and IBD, old wallet transactions become // unconfirmed. Don't resend them as that would spam other nodes. if (!chain().isReadyToBroadcast()) { return; } // Do this infrequently and randomly to avoid giving away that these are our // transactions. if (GetTime() < nNextResend || !fBroadcastTransactions) { return; } bool fFirst = (nNextResend == 0); nNextResend = GetTime() + GetRand(30 * 60); if (fFirst) { return; } // Only do it if there's been a new block since last time if (m_best_block_time < nLastResend) { return; } nLastResend = GetTime(); int submitted_tx_count = 0; { // locked_chain and cs_wallet scope auto locked_chain = chain().lock(); LOCK(cs_wallet); // Relay transactions for (std::pair<const TxId, CWalletTx> &item : mapWallet) { CWalletTx &wtx = item.second; // only rebroadcast unconfirmed txes older than 5 minutes before the // last block was found if (wtx.nTimeReceived > m_best_block_time - 5 * 60) { continue; } std::string unused_err_string; if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) { ++submitted_tx_count; } } } // locked_chain and cs_wallet if (submitted_tx_count > 0) { WalletLogPrintf("%s: resubmit %u unconfirmed transactions\n", __func__, submitted_tx_count); } } /** @} */ // end of mapWallet void MaybeResendWalletTxs() { for (const std::shared_ptr<CWallet> &pwallet : GetWallets()) { pwallet->ResendWalletTransactions(); } } /** * @defgroup Actions * * @{ */ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const { Balance ret; isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; auto locked_chain = chain().lock(); LOCK(cs_wallet); for (const auto &entry : mapWallet) { const CWalletTx &wtx = entry.second; const bool is_trusted{wtx.IsTrusted(*locked_chain)}; const int tx_depth{wtx.GetDepthInMainChain(*locked_chain)}; const Amount tx_credit_mine{ wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; const Amount tx_credit_watchonly{ wtx.GetAvailableCredit(*locked_chain, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; if (is_trusted && tx_depth >= min_depth) { ret.m_mine_trusted += tx_credit_mine; ret.m_watchonly_trusted += tx_credit_watchonly; } if (!is_trusted && tx_depth == 0 && wtx.InMempool()) { ret.m_mine_untrusted_pending += tx_credit_mine; ret.m_watchonly_untrusted_pending += tx_credit_watchonly; } ret.m_mine_immature += wtx.GetImmatureCredit(*locked_chain); ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(*locked_chain); } return ret; } Amount CWallet::GetAvailableBalance(const CCoinControl *coinControl) const { auto locked_chain = chain().lock(); LOCK(cs_wallet); Amount balance = Amount::zero(); std::vector<COutput> vCoins; AvailableCoins(*locked_chain, vCoins, true, coinControl); for (const COutput &out : vCoins) { if (out.fSpendable) { balance += out.tx->tx->vout[out.i].nValue; } } return balance; } void CWallet::AvailableCoins(interfaces::Chain::Lock &locked_chain, std::vector<COutput> &vCoins, bool fOnlySafe, const CCoinControl *coinControl, const Amount nMinimumAmount, const Amount nMaximumAmount, const Amount nMinimumSumAmount, const uint64_t nMaximumCount, const int nMinDepth, const int nMaxDepth) const { AssertLockHeld(cs_wallet); vCoins.clear(); Amount nTotal = Amount::zero(); // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we // always allow), or we default to avoiding, and only in the case where a // coin control object is provided, and has the avoid address reuse flag set // to false, do we allow already used addresses bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); const Consensus::Params params = Params().GetConsensus(); for (const auto &entry : mapWallet) { const TxId &wtxid = entry.first; const CWalletTx &wtx = entry.second; TxValidationState state; if (!locked_chain.contextualCheckTransactionForCurrentBlock( params, *wtx.tx, state)) { continue; } if (wtx.IsImmatureCoinBase(locked_chain)) { continue; } int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < 0) { continue; } // We should not consider coins which aren't at least in our mempool. // It's possible for these to be conflicted via ancestors which we may // never be able to detect. if (nDepth == 0 && !wtx.InMempool()) { continue; } bool safeTx = wtx.IsTrusted(locked_chain); // Bitcoin-ABC: Removed check that prevents consideration of coins from // transactions that are replacing other transactions. This check based // on wtx.mapValue.count("replaces_txid") which was not being set // anywhere. // Similarly, we should not consider coins from transactions that have // been replaced. In the example above, we would want to prevent // creation of a transaction A' spending an output of A, because if // transaction B were initially confirmed, conflicting with A and A', we // wouldn't want to the user to create a transaction D intending to // replace A', but potentially resulting in a scenario where A, A', and // D could all be accepted (instead of just B and D, or just A and A' // like the user would want). // Bitcoin-ABC: retained this check as 'replaced_by_txid' is still set // in the wallet code. if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) { safeTx = false; } if (fOnlySafe && !safeTx) { continue; } if (nDepth < nMinDepth || nDepth > nMaxDepth) { continue; } for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) { if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) { continue; } const COutPoint outpoint(wtxid, i); if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(outpoint)) { continue; } if (IsLockedCoin(outpoint)) { continue; } if (IsSpent(locked_chain, outpoint)) { continue; } isminetype mine = IsMine(wtx.tx->vout[i]); if (mine == ISMINE_NO) { continue; } if (!allow_used_addresses && IsUsedDestination(wtxid, i)) { continue; } bool solvable = IsSolvable(*this, wtx.tx->vout[i].scriptPubKey); bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); vCoins.push_back( COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); // Checks the sum amount of all UTXO's. if (nMinimumSumAmount != MAX_MONEY) { nTotal += wtx.tx->vout[i].nValue; if (nTotal >= nMinimumSumAmount) { return; } } // Checks the maximum number of UTXO's. if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { return; } } } } std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins(interfaces::Chain::Lock &locked_chain) const { AssertLockHeld(cs_wallet); std::map<CTxDestination, std::vector<COutput>> result; std::vector<COutput> availableCoins; AvailableCoins(locked_chain, availableCoins); for (const auto &coin : availableCoins) { CTxDestination address; if (coin.fSpendable && ExtractDestination( FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { result[address].emplace_back(std::move(coin)); } } std::vector<COutPoint> lockedCoins; ListLockedCoins(lockedCoins); for (const auto &output : lockedCoins) { auto it = mapWallet.find(output.GetTxId()); if (it != mapWallet.end()) { int depth = it->second.GetDepthInMainChain(locked_chain); if (depth >= 0 && output.GetN() < it->second.tx->vout.size() && IsMine(it->second.tx->vout[output.GetN()]) == ISMINE_SPENDABLE) { CTxDestination address; if (ExtractDestination( FindNonChangeParentOutput(*it->second.tx, output.GetN()) .scriptPubKey, address)) { result[address].emplace_back( &it->second, output.GetN(), depth, true /* spendable */, true /* solvable */, false /* safe */); } } } } return result; } const CTxOut &CWallet::FindNonChangeParentOutput(const CTransaction &tx, int output) const { const CTransaction *ptx = &tx; int n = output; while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) { const COutPoint &prevout = ptx->vin[0].prevout; auto it = mapWallet.find(prevout.GetTxId()); if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.GetN() || !IsMine(it->second.tx->vout[prevout.GetN()])) { break; } ptx = it->second.tx.get(); n = prevout.GetN(); } return ptx->vout[n]; } bool CWallet::SelectCoinsMinConf( const Amount nTargetValue, const CoinEligibilityFilter &eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, const CoinSelectionParams &coin_selection_params, bool &bnb_used) const { setCoinsRet.clear(); nValueRet = Amount::zero(); std::vector<OutputGroup> utxo_pool; if (coin_selection_params.use_bnb) { // Get long term estimate CCoinControl temp; temp.m_confirm_target = 1008; CFeeRate long_term_feerate = GetMinimumFeeRate(*this, temp); // Calculate cost of change Amount cost_of_change = chain().relayDustFee().GetFee( coin_selection_params.change_spend_size) + coin_selection_params.effective_fee.GetFee( coin_selection_params.change_output_size); // Filter by the min conf specs and add to utxo_pool and calculate // effective value for (OutputGroup &group : groups) { if (!group.EligibleForSpending(eligibility_filter)) { continue; } group.fee = Amount::zero(); group.long_term_fee = Amount::zero(); group.effective_value = Amount::zero(); for (auto it = group.m_outputs.begin(); it != group.m_outputs.end();) { const CInputCoin &coin = *it; Amount effective_value = coin.txout.nValue - (coin.m_input_bytes < 0 ? Amount::zero() : coin_selection_params.effective_fee.GetFee( coin.m_input_bytes)); // Only include outputs that are positive effective value (i.e. // not dust) if (effective_value > Amount::zero()) { group.fee += coin.m_input_bytes < 0 ? Amount::zero() : coin_selection_params.effective_fee.GetFee( coin.m_input_bytes); group.long_term_fee += coin.m_input_bytes < 0 ? Amount::zero() : long_term_feerate.GetFee(coin.m_input_bytes); group.effective_value += effective_value; ++it; } else { it = group.Discard(coin); } } if (group.effective_value > Amount::zero()) { utxo_pool.push_back(group); } } // Calculate the fees for things that aren't inputs Amount not_input_fees = coin_selection_params.effective_fee.GetFee( coin_selection_params.tx_noinputs_size); bnb_used = true; return SelectCoinsBnB(utxo_pool, nTargetValue, cost_of_change, setCoinsRet, nValueRet, not_input_fees); } else { // Filter by the min conf specs and add to utxo_pool for (const OutputGroup &group : groups) { if (!group.EligibleForSpending(eligibility_filter)) { continue; } utxo_pool.push_back(group); } bnb_used = false; return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet); } } bool CWallet::SelectCoins(const std::vector<COutput> &vAvailableCoins, const Amount nTargetValue, std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, const CCoinControl &coin_control, CoinSelectionParams &coin_selection_params, bool &bnb_used) const { std::vector<COutput> vCoins(vAvailableCoins); // coin control -> return all selected outputs (we want all selected to go // into the transaction for sure) if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) { // We didn't use BnB here, so set it to false. bnb_used = false; for (const COutput &out : vCoins) { if (!out.fSpendable) { continue; } nValueRet += out.tx->tx->vout[out.i].nValue; setCoinsRet.insert(out.GetInputCoin()); } return (nValueRet >= nTargetValue); } // Calculate value from preset inputs and store them. std::set<CInputCoin> setPresetCoins; Amount nValueFromPresetInputs = Amount::zero(); std::vector<COutPoint> vPresetInputs; coin_control.ListSelected(vPresetInputs); for (const COutPoint &outpoint : vPresetInputs) { // For now, don't use BnB if preset inputs are selected. TODO: Enable // this later bnb_used = false; coin_selection_params.use_bnb = false; std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(outpoint.GetTxId()); if (it == mapWallet.end()) { // TODO: Allow non-wallet inputs return false; } const CWalletTx &wtx = it->second; // Clearly invalid input, fail. if (wtx.tx->vout.size() <= outpoint.GetN()) { return false; } // Just to calculate the marginal byte size nValueFromPresetInputs += wtx.tx->vout[outpoint.GetN()].nValue; setPresetCoins.insert(CInputCoin(wtx.tx, outpoint.GetN())); } // Remove preset inputs from vCoins for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) { if (setPresetCoins.count(it->GetInputCoin())) { it = vCoins.erase(it); } else { ++it; } } // form groups from remaining coins; note that preset coins will not // automatically have their associated (same address) coins included if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) { // Cases where we have 11+ outputs all pointing to the same destination // may result in privacy leaks as they will potentially be // deterministically sorted. We solve that by explicitly shuffling the // outputs before processing Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext()); } std::vector<OutputGroup> groups = GroupOutputs(vCoins, !coin_control.m_avoid_partial_spends); size_t max_ancestors = std::max<size_t>( 1, gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT)); size_t max_descendants = std::max<size_t>( 1, gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT)); bool fRejectLongChains = gArgs.GetBoolArg( "-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); bool res = nTargetValue <= nValueFromPresetInputs || SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 6, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(1, 1, 0), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used) || (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && SelectCoinsMinConf( nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::min<size_t>(4, max_ancestors / 3), std::min<size_t>(4, max_descendants / 3)), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors / 2, max_descendants / 2), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, max_ancestors - 1, max_descendants - 1), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)) || (m_spend_zero_conf_change && !fRejectLongChains && SelectCoinsMinConf( nTargetValue - nValueFromPresetInputs, CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max()), groups, setCoinsRet, nValueRet, coin_selection_params, bnb_used)); // Because SelectCoinsMinConf clears the setCoinsRet, we now add the // possible inputs to the coinset. util::insert(setCoinsRet, setPresetCoins); // Add preset inputs to the total value selected. nValueRet += nValueFromPresetInputs; return res; } bool CWallet::SignTransaction(CMutableTransaction &tx) { AssertLockHeld(cs_wallet); // sign the new tx int nIn = 0; for (CTxIn &input : tx.vin) { auto mi = mapWallet.find(input.prevout.GetTxId()); if (mi == mapWallet.end() || input.prevout.GetN() >= mi->second.tx->vout.size()) { return false; } const CScript &scriptPubKey = mi->second.tx->vout[input.prevout.GetN()].scriptPubKey; const Amount amount = mi->second.tx->vout[input.prevout.GetN()].nValue; SignatureData sigdata; SigHashType sigHashType = SigHashType().withForkId(); if (!ProduceSignature(*this, MutableTransactionSignatureCreator( &tx, nIn, amount, sigHashType), scriptPubKey, sigdata)) { return false; } UpdateInput(input, sigdata); nIn++; } return true; } bool CWallet::FundTransaction(CMutableTransaction &tx, Amount &nFeeRet, int &nChangePosInOut, std::string &strFailReason, bool lockUnspents, const std::set<int> &setSubtractFeeFromOutputs, CCoinControl coinControl) { std::vector<CRecipient> vecSend; // Turn the txout set into a CRecipient vector. for (size_t idx = 0; idx < tx.vout.size(); idx++) { const CTxOut &txOut = tx.vout[idx]; CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1}; vecSend.push_back(recipient); } coinControl.fAllowOtherInputs = true; for (const CTxIn &txin : tx.vin) { coinControl.Select(txin.prevout); } // Acquire the locks to prevent races to the new locked unspents between the // CreateTransaction call and LockCoin calls (when lockUnspents is true). auto locked_chain = chain().lock(); LOCK(cs_wallet); CTransactionRef tx_new; if (!CreateTransaction(*locked_chain, vecSend, tx_new, nFeeRet, nChangePosInOut, strFailReason, coinControl, false)) { return false; } if (nChangePosInOut != -1) { tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); } // Copy output sizes from new transaction; they may have had the fee // subtracted from them. for (size_t idx = 0; idx < tx.vout.size(); idx++) { tx.vout[idx].nValue = tx_new->vout[idx].nValue; } // Add new txins (keeping original txin scriptSig/order) for (const CTxIn &txin : tx_new->vin) { if (!coinControl.IsSelected(txin.prevout)) { tx.vin.push_back(txin); if (lockUnspents) { LockCoin(txin.prevout); } } } return true; } static bool IsCurrentForAntiFeeSniping(interfaces::Chain &chain, interfaces::Chain::Lock &locked_chain) { if (chain.isInitialBlockDownload()) { return false; } // in seconds constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; if (locked_chain.getBlockTime(*locked_chain.getHeight()) < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) { return false; } return true; } /** * Return a height-based locktime for new transactions (uses the height of the * current chain tip unless we are not synced with the current chain */ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain &chain, interfaces::Chain::Lock &locked_chain) { uint32_t const height = locked_chain.getHeight().value_or(-1); uint32_t locktime; // Discourage fee sniping. // // For a large miner the value of the transactions in the best block and // the mempool can exceed the cost of deliberately attempting to mine two // blocks to orphan the current best block. By setting nLockTime such that // only the next block can include the transaction, we discourage this // practice as the height restricted and limited blocksize gives miners // considering fee sniping fewer options for pulling off this attack. // // A simple way to think about this is from the wallet's point of view we // always want the blockchain to move forward. By setting nLockTime this // way we're basically making the statement that we only want this // transaction to appear in the next block; we don't want to potentially // encourage reorgs by allowing transactions to appear at lower heights // than the next block in forks of the best chain. // // Of course, the subsidy is high enough, and transaction volume low // enough, that fee sniping isn't a problem yet, but by implementing a fix // now we ensure code won't be written that makes assumptions about // nLockTime that preclude a fix later. if (IsCurrentForAntiFeeSniping(chain, locked_chain)) { locktime = height; // Secondly occasionally randomly pick a nLockTime even further back, so // that transactions that are delayed after signing for whatever reason, // e.g. high-latency mix networks and some CoinJoin implementations, // have better privacy. if (GetRandInt(10) == 0) { locktime = std::max(0, int(locktime) - GetRandInt(100)); } } else { // If our chain is lagging behind, we can't discourage fee sniping nor // help the privacy of high-latency transactions. To avoid leaking a // potentially unique "nLockTime fingerprint", set nLockTime to a // constant. locktime = 0; } assert(locktime <= height); assert(locktime < LOCKTIME_THRESHOLD); return locktime; } OutputType CWallet::TransactionChangeType(OutputType change_type, const std::vector<CRecipient> &vecSend) { // If -changetype is specified, always use that change type. if (change_type != OutputType::CHANGE_AUTO) { return change_type; } // if m_default_address_type is legacy, use legacy address as change. if (m_default_address_type == OutputType::LEGACY) { return OutputType::LEGACY; } // else use m_default_address_type for change return m_default_address_type; } bool CWallet::CreateTransaction(interfaces::Chain::Lock &locked_chainIn, const std::vector<CRecipient> &vecSend, CTransactionRef &tx, Amount &nFeeRet, int &nChangePosInOut, std::string &strFailReason, const CCoinControl &coinControl, bool sign) { Amount nValue = Amount::zero(); ReserveDestination reservedest(this); int nChangePosRequest = nChangePosInOut; unsigned int nSubtractFeeFromAmount = 0; for (const auto &recipient : vecSend) { if (nValue < Amount::zero() || recipient.nAmount < Amount::zero()) { strFailReason = _("Transaction amounts must not be negative").translated; return false; } nValue += recipient.nAmount; if (recipient.fSubtractFeeFromAmount) { nSubtractFeeFromAmount++; } } if (vecSend.empty()) { strFailReason = _("Transaction must have at least one recipient").translated; return false; } CMutableTransaction txNew; txNew.nLockTime = GetLocktimeForNewTransaction(chain(), locked_chainIn); { std::set<CInputCoin> setCoins; auto locked_chain = chain().lock(); LOCK(cs_wallet); std::vector<COutput> vAvailableCoins; AvailableCoins(*locked_chain, vAvailableCoins, true, &coinControl); // Parameters for coin selection, init with dummy CoinSelectionParams coin_selection_params; // Create change script that will be used if we need change // TODO: pass in scriptChange instead of reservedest so // change transaction isn't always pay-to-bitcoin-address CScript scriptChange; // coin control: send change to custom address if (!boost::get<CNoDestination>(&coinControl.destChange)) { scriptChange = GetScriptForDestination(coinControl.destChange); // no coin control: send change to newly generated address } else { // Note: We use a new key here to keep it from being obvious // which side is the change. // The drawback is that by not reusing a previous key, the // change may be lost if a backup is restored, if the backup // doesn't have the new private key for the change. If we // reused the old key, it would be possible to add code to look // for and rediscover unknown transactions that were written // with keys of ours to recover post-backup change. // Reserve a new key pair from key pool if (!CanGetAddresses(true)) { strFailReason = _("Can't generate a change-address key. No keys in the " "internal keypool and can't generate any keys.") .translated; return false; } CTxDestination dest; const OutputType change_type = TransactionChangeType( coinControl.m_change_type ? *coinControl.m_change_type : m_default_change_type, vecSend); bool ret = reservedest.GetReservedDestination(change_type, dest, true); if (!ret) { strFailReason = _("Keypool ran out, please call keypoolrefill first") .translated; return false; } scriptChange = GetScriptForDestination(dest); } CTxOut change_prototype_txout(Amount::zero(), scriptChange); coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); // Get the fee rate to use effective values in coin selection CFeeRate nFeeRateNeeded = GetMinimumFeeRate(*this, coinControl); nFeeRet = Amount::zero(); bool pick_new_inputs = true; Amount nValueIn = Amount::zero(); // BnB selector is the only selector used when this is true. // That should only happen on the first pass through the loop. // If we are doing subtract fee from recipient, then don't use BnB coin_selection_params.use_bnb = nSubtractFeeFromAmount == 0; // Start with no fee and loop until there is enough fee while (true) { nChangePosInOut = nChangePosRequest; txNew.vin.clear(); txNew.vout.clear(); bool fFirst = true; Amount nValueToSelect = nValue; if (nSubtractFeeFromAmount == 0) { nValueToSelect += nFeeRet; } // Static size overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 // input count, 1 output count coin_selection_params.tx_noinputs_size = 10; // vouts to the payees for (const auto &recipient : vecSend) { CTxOut txout(recipient.nAmount, recipient.scriptPubKey); if (recipient.fSubtractFeeFromAmount) { assert(nSubtractFeeFromAmount != 0); // Subtract fee equally from each selected recipient. txout.nValue -= nFeeRet / int(nSubtractFeeFromAmount); // First receiver pays the remainder not divisible by output // count. if (fFirst) { fFirst = false; txout.nValue -= nFeeRet % int(nSubtractFeeFromAmount); } } // Include the fee cost for outputs. Note this is only used for // BnB right now coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); if (IsDust(txout, chain().relayDustFee())) { if (recipient.fSubtractFeeFromAmount && nFeeRet > Amount::zero()) { if (txout.nValue < Amount::zero()) { strFailReason = _("The transaction amount is too " "small to pay the fee") .translated; } else { strFailReason = _("The transaction amount is too small to " "send after the fee has been deducted") .translated; } } else { strFailReason = _("Transaction amount too small").translated; } return false; } txNew.vout.push_back(txout); } // Choose coins to use bool bnb_used; if (pick_new_inputs) { nValueIn = Amount::zero(); setCoins.clear(); coin_selection_params.change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this); coin_selection_params.effective_fee = nFeeRateNeeded; if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins, nValueIn, coinControl, coin_selection_params, bnb_used)) { // If BnB was used, it was the first pass. No longer the // first pass and continue loop with knapsack. if (bnb_used) { coin_selection_params.use_bnb = false; continue; } else { strFailReason = _("Insufficient funds").translated; return false; } } } const Amount nChange = nValueIn - nValueToSelect; if (nChange > Amount::zero()) { // Fill a vout to ourself. CTxOut newTxOut(nChange, scriptChange); // Never create dust outputs; if we would, just add the dust to // the fee. // The nChange when BnB is used is always going to go to fees. if (IsDust(newTxOut, chain().relayDustFee()) || bnb_used) { nChangePosInOut = -1; nFeeRet += nChange; } else { if (nChangePosInOut == -1) { // Insert change txn at random position: nChangePosInOut = GetRandInt(txNew.vout.size() + 1); } else if ((unsigned int)nChangePosInOut > txNew.vout.size()) { strFailReason = _("Change index out of range").translated; return false; } std::vector<CTxOut>::iterator position = txNew.vout.begin() + nChangePosInOut; txNew.vout.insert(position, newTxOut); } } else { nChangePosInOut = -1; } // Dummy fill vin for maximum size estimation // for (const auto &coin : setCoins) { txNew.vin.push_back(CTxIn(coin.outpoint, CScript())); } CTransaction txNewConst(txNew); int nBytes = CalculateMaximumSignedTxSize( txNewConst, this, coinControl.fAllowWatchOnly); if (nBytes < 0) { strFailReason = _("Signing transaction failed").translated; return false; } Amount nFeeNeeded = GetMinimumFee(*this, nBytes, coinControl); if (nFeeRet >= nFeeNeeded) { // Reduce fee to only the needed amount if possible. This // prevents potential overpayment in fees if the coins selected // to meet nFeeNeeded result in a transaction that requires less // fee than the prior iteration. // If we have no change and a big enough excess fee, then try to // construct transaction again only without picking new inputs. // We now know we only need the smaller fee (because of reduced // tx size) and so we should add a change output. Only try this // once. if (nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 && pick_new_inputs) { // Add 2 as a buffer in case increasing # of outputs changes // compact size unsigned int tx_size_with_change = nBytes + coin_selection_params.change_output_size + 2; Amount fee_needed_with_change = GetMinimumFee(*this, tx_size_with_change, coinControl); Amount minimum_value_for_change = GetDustThreshold( change_prototype_txout, chain().relayDustFee()); if (nFeeRet >= fee_needed_with_change + minimum_value_for_change) { pick_new_inputs = false; nFeeRet = fee_needed_with_change; continue; } } // If we have change output already, just increase it if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { Amount extraFeePaid = nFeeRet - nFeeNeeded; std::vector<CTxOut>::iterator change_position = txNew.vout.begin() + nChangePosInOut; change_position->nValue += extraFeePaid; nFeeRet -= extraFeePaid; } // Done, enough fee included. break; } else if (!pick_new_inputs) { // This shouldn't happen, we should have had enough excess fee // to pay for the new output and still meet nFeeNeeded. // Or we should have just subtracted fee from recipients and // nFeeNeeded should not have changed. strFailReason = _("Transaction fee and change calculation failed") .translated; return false; } // Try to reduce change to include necessary fee. if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) { Amount additionalFeeNeeded = nFeeNeeded - nFeeRet; std::vector<CTxOut>::iterator change_position = txNew.vout.begin() + nChangePosInOut; // Only reduce change if remaining amount is still a large // enough output. if (change_position->nValue >= MIN_FINAL_CHANGE + additionalFeeNeeded) { change_position->nValue -= additionalFeeNeeded; nFeeRet += additionalFeeNeeded; // Done, able to increase fee from change. break; } } // If subtracting fee from recipients, we now know what fee we // need to subtract, we have no reason to reselect inputs. if (nSubtractFeeFromAmount > 0) { pick_new_inputs = false; } // Include more fee and try again. nFeeRet = nFeeNeeded; coin_selection_params.use_bnb = false; continue; } // Shuffle selected coins and fill in final vin txNew.vin.clear(); std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end()); Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext()); // Note how the sequence number is set to non-maxint so that // the nLockTime set above actually works. for (const auto &coin : selected_coins) { txNew.vin.push_back( CTxIn(coin.outpoint, CScript(), std::numeric_limits<uint32_t>::max() - 1)); } if (sign) { SigHashType sigHashType = SigHashType().withForkId(); int nIn = 0; for (const auto &coin : selected_coins) { const CScript &scriptPubKey = coin.txout.scriptPubKey; SignatureData sigdata; if (!ProduceSignature( *this, MutableTransactionSignatureCreator( &txNew, nIn, coin.txout.nValue, sigHashType), scriptPubKey, sigdata)) { strFailReason = _("Signing transaction failed").translated; return false; } UpdateInput(txNew.vin.at(nIn), sigdata); nIn++; } } // Return the constructed transaction data. tx = MakeTransactionRef(std::move(txNew)); // Limit size. if (tx->GetTotalSize() > MAX_STANDARD_TX_SIZE) { strFailReason = _("Transaction too large").translated; return false; } } if (nFeeRet > m_default_max_tx_fee) { strFailReason = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); return false; } if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { // Lastly, ensure this tx will pass the mempool's chain limits if (!chain().checkChainLimits(tx)) { strFailReason = _("Transaction has too long of a mempool chain").translated; return false; } } // Before we return success, we assume any change key will be used to // prevent accidental re-use. reservedest.KeepDestination(); return true; } /** * Call after CreateTransaction unless you want to abort */ bool CWallet::CommitTransaction( CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, TxValidationState &state) { auto locked_chain = chain().lock(); LOCK(cs_wallet); CWalletTx wtxNew(this, std::move(tx)); wtxNew.mapValue = std::move(mapValue); wtxNew.vOrderForm = std::move(orderForm); wtxNew.fTimeReceivedIsTxTime = true; wtxNew.fFromMe = true; WalletLogPrintfToBeContinued("CommitTransaction:\n%s", wtxNew.tx->ToString()); // Add tx to wallet, because if it has change it's also ours, otherwise just // for transaction history. AddToWallet(wtxNew); // Notify that old coins are spent. for (const CTxIn &txin : wtxNew.tx->vin) { CWalletTx &coin = mapWallet.at(txin.prevout.GetTxId()); coin.BindWallet(this); NotifyTransactionChanged(this, coin.GetId(), CT_UPDATED); } // Get the inserted-CWalletTx from mapWallet so that the // fInMempool flag is cached properly CWalletTx &wtx = mapWallet.at(wtxNew.GetId()); if (fBroadcastTransactions) { std::string err_string; if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) { WalletLogPrintf("CommitTransaction(): Transaction cannot be " "broadcast immediately, %s\n", err_string); // TODO: if we expect the failure to be long term or permanent, // instead delete wtx from the wallet and return failure. } } return true; } DBErrors CWallet::LoadWallet(bool &fFirstRunRet) { LOCK(cs_wallet); fFirstRunRet = false; DBErrors nLoadWalletRet = WalletBatch(*database, "cr+").LoadWallet(this); if (nLoadWalletRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { setInternalKeyPool.clear(); setExternalKeyPool.clear(); m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. } } { LOCK(cs_KeyStore); // This wallet is in its first run if all of these are empty fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() && mapWatchKeys.empty() && setWatchOnly.empty() && mapScripts.empty() && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && !IsWalletFlagSet(WALLET_FLAG_BLANK_WALLET); } if (nLoadWalletRet != DBErrors::LOAD_OK) { return nLoadWalletRet; } return DBErrors::LOAD_OK; } DBErrors CWallet::ZapSelectTx(std::vector<TxId> &txIdsIn, std::vector<TxId> &txIdsOut) { AssertLockHeld(cs_wallet); DBErrors nZapSelectTxRet = WalletBatch(*database, "cr+").ZapSelectTx(txIdsIn, txIdsOut); for (const TxId &txid : txIdsOut) { const auto &it = mapWallet.find(txid); wtxOrdered.erase(it->second.m_it_wtxOrdered); mapWallet.erase(it); } if (nZapSelectTxRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { setInternalKeyPool.clear(); setExternalKeyPool.clear(); m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. } } if (nZapSelectTxRet != DBErrors::LOAD_OK) { return nZapSelectTxRet; } MarkDirty(); return DBErrors::LOAD_OK; } DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx> &vWtx) { DBErrors nZapWalletTxRet = WalletBatch(*database, "cr+").ZapWalletTx(vWtx); if (nZapWalletTxRet == DBErrors::NEED_REWRITE) { if (database->Rewrite("\x04pool")) { LOCK(cs_wallet); setInternalKeyPool.clear(); setExternalKeyPool.clear(); m_pool_key_to_index.clear(); // Note: can't top-up keypool here, because wallet is locked. // User will be prompted to unlock wallet the next operation // that requires a new key. } } if (nZapWalletTxRet != DBErrors::LOAD_OK) { return nZapWalletTxRet; } return DBErrors::LOAD_OK; } bool CWallet::SetAddressBookWithDB(WalletBatch &batch, const CTxDestination &address, const std::string &strName, const std::string &strPurpose) { bool fUpdated = false; { LOCK(cs_wallet); std::map<CTxDestination, CAddressBookData>::iterator mi = mapAddressBook.find(address); fUpdated = mi != mapAddressBook.end(); mapAddressBook[address].name = strName; // Update purpose only if requested. if (!strPurpose.empty()) { mapAddressBook[address].purpose = strPurpose; } } NotifyAddressBookChanged(this, address, strName, ::IsMine(*this, address) != ISMINE_NO, strPurpose, (fUpdated ? CT_UPDATED : CT_NEW)); if (!strPurpose.empty() && !batch.WritePurpose(address, strPurpose)) { return false; } return batch.WriteName(address, strName); } bool CWallet::SetAddressBook(const CTxDestination &address, const std::string &strName, const std::string &strPurpose) { WalletBatch batch(*database); return SetAddressBookWithDB(batch, address, strName, strPurpose); } bool CWallet::DelAddressBook(const CTxDestination &address) { { LOCK(cs_wallet); // Delete destdata tuples associated with address. for (const std::pair<const std::string, std::string> &item : mapAddressBook[address].destdata) { WalletBatch(*database).EraseDestData(address, item.first); } mapAddressBook.erase(address); } NotifyAddressBookChanged(this, address, "", ::IsMine(*this, address) != ISMINE_NO, "", CT_DELETED); WalletBatch(*database).ErasePurpose(address); return WalletBatch(*database).EraseName(address); } const std::string &CWallet::GetLabelName(const CScript &scriptPubKey) const { CTxDestination address; if (ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable()) { auto mi = mapAddressBook.find(address); if (mi != mapAddressBook.end()) { return mi->second.name; } } // A scriptPubKey that doesn't have an entry in the address book is // associated with the default label (""). const static std::string DEFAULT_LABEL_NAME; return DEFAULT_LABEL_NAME; } /** * Mark old keypool keys as used, and generate all new keys. */ bool CWallet::NewKeyPool() { if (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { return false; } LOCK(cs_wallet); WalletBatch batch(*database); for (const int64_t nIndex : setInternalKeyPool) { batch.ErasePool(nIndex); } setInternalKeyPool.clear(); for (const int64_t nIndex : setExternalKeyPool) { batch.ErasePool(nIndex); } setExternalKeyPool.clear(); for (int64_t nIndex : set_pre_split_keypool) { batch.ErasePool(nIndex); } set_pre_split_keypool.clear(); m_pool_key_to_index.clear(); if (!TopUpKeyPool()) { return false; } WalletLogPrintf("CWallet::NewKeyPool rewrote keypool\n"); return true; } size_t CWallet::KeypoolCountExternalKeys() { AssertLockHeld(cs_wallet); return setExternalKeyPool.size() + set_pre_split_keypool.size(); } void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) { AssertLockHeld(cs_wallet); if (keypool.m_pre_split) { set_pre_split_keypool.insert(nIndex); } else if (keypool.fInternal) { setInternalKeyPool.insert(nIndex); } else { setExternalKeyPool.insert(nIndex); } m_max_keypool_index = std::max(m_max_keypool_index, nIndex); m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex; // If no metadata exists yet, create a default with the pool key's // creation time. Note that this may be overwritten by actually // stored metadata for that key later, which is fine. CKeyID keyid = keypool.vchPubKey.GetID(); if (mapKeyMetadata.count(keyid) == 0) { mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); } } bool CWallet::TopUpKeyPool(unsigned int kpSize) { if (!CanGenerateKeys()) { return false; } { LOCK(cs_wallet); if (IsLocked()) { return false; } // Top up key pool unsigned int nTargetSize; if (kpSize > 0) { nTargetSize = kpSize; } else { nTargetSize = std::max<int64_t>( gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), 0); } // count amount of available keys (internal, external) // make sure the keypool of external and internal keys fits the user // selected target (-keypool) int64_t missingExternal = std::max<int64_t>( std::max<int64_t>(nTargetSize, 1) - setExternalKeyPool.size(), 0); int64_t missingInternal = std::max<int64_t>( std::max<int64_t>(nTargetSize, 1) - setInternalKeyPool.size(), 0); if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) { // don't create extra internal keys missingInternal = 0; } bool internal = false; WalletBatch batch(*database); for (int64_t i = missingInternal + missingExternal; i--;) { if (i < missingInternal) { internal = true; } CPubKey pubkey(GenerateNewKey(batch, internal)); AddKeypoolPubkeyWithDB(pubkey, internal, batch); } if (missingInternal + missingExternal > 0) { WalletLogPrintf( "keypool added %d keys (%d internal), size=%u (%u internal)\n", missingInternal + missingExternal, missingInternal, setInternalKeyPool.size() + setExternalKeyPool.size() + set_pre_split_keypool.size(), setInternalKeyPool.size()); } } NotifyCanGetAddressesChanged(); return true; } void CWallet::AddKeypoolPubkeyWithDB(const CPubKey &pubkey, const bool internal, WalletBatch &batch) { LOCK(cs_wallet); // How in the hell did you use so many keys? assert(m_max_keypool_index < std::numeric_limits<int64_t>::max()); int64_t index = ++m_max_keypool_index; if (!batch.WritePool(index, CKeyPool(pubkey, internal))) { throw std::runtime_error(std::string(__func__) + ": writing imported pubkey failed"); } if (internal) { setInternalKeyPool.insert(index); } else { setExternalKeyPool.insert(index); } m_pool_key_to_index[pubkey.GetID()] = index; } bool CWallet::ReserveKeyFromKeyPool(int64_t &nIndex, CKeyPool &keypool, bool fRequestedInternal) { nIndex = -1; keypool.vchPubKey = CPubKey(); { LOCK(cs_wallet); TopUpKeyPool(); bool fReturningInternal = fRequestedInternal; fReturningInternal &= (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) || IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); bool use_split_keypool = set_pre_split_keypool.empty(); std::set<int64_t> &setKeyPool = use_split_keypool ? (fReturningInternal ? setInternalKeyPool : setExternalKeyPool) : set_pre_split_keypool; // Get the oldest key if (setKeyPool.empty()) { return false; } WalletBatch batch(*database); auto it = setKeyPool.begin(); nIndex = *it; setKeyPool.erase(it); if (!batch.ReadPool(nIndex, keypool)) { throw std::runtime_error(std::string(__func__) + ": read failed"); } CPubKey pk; if (!GetPubKey(keypool.vchPubKey.GetID(), pk)) { throw std::runtime_error(std::string(__func__) + ": unknown key in key pool"); } // If the key was pre-split keypool, we don't care about what type it is if (use_split_keypool && keypool.fInternal != fReturningInternal) { throw std::runtime_error(std::string(__func__) + ": keypool entry misclassified"); } if (!keypool.vchPubKey.IsValid()) { throw std::runtime_error(std::string(__func__) + ": keypool entry invalid"); } m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); WalletLogPrintf("keypool reserve %d\n", nIndex); } NotifyCanGetAddressesChanged(); return true; } void CWallet::KeepKey(int64_t nIndex) { // Remove from key pool. WalletBatch batch(*database); batch.ErasePool(nIndex); WalletLogPrintf("keypool keep %d\n", nIndex); } void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey &pubkey) { // Return to key pool { LOCK(cs_wallet); if (fInternal) { setInternalKeyPool.insert(nIndex); } else if (!set_pre_split_keypool.empty()) { set_pre_split_keypool.insert(nIndex); } else { setExternalKeyPool.insert(nIndex); } m_pool_key_to_index[pubkey.GetID()] = nIndex; NotifyCanGetAddressesChanged(); } WalletLogPrintf("keypool return %d\n", nIndex); } bool CWallet::GetKeyFromPool(CPubKey &result, bool internal) { if (!CanGetAddresses(internal)) { return false; } CKeyPool keypool; LOCK(cs_wallet); int64_t nIndex; if (!ReserveKeyFromKeyPool(nIndex, keypool, internal) && !IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { if (IsLocked()) { return false; } WalletBatch batch(*database); result = GenerateNewKey(batch, internal); return true; } KeepKey(nIndex); result = keypool.vchPubKey; return true; } bool CWallet::GetNewDestination(const OutputType type, const std::string label, CTxDestination &dest, std::string &error) { LOCK(cs_wallet); error.clear(); TopUpKeyPool(); // Generate a new key that is added to wallet CPubKey new_key; if (!GetKeyFromPool(new_key)) { error = "Error: Keypool ran out, please call keypoolrefill first"; return false; } LearnRelatedScripts(new_key, type); dest = GetDestinationForKey(new_key, type); SetAddressBook(dest, label, "receive"); return true; } bool CWallet::GetNewChangeDestination(const OutputType type, CTxDestination &dest, std::string &error) { error.clear(); TopUpKeyPool(); ReserveDestination reservedest(this); if (!reservedest.GetReservedDestination(type, dest, true)) { error = "Error: Keypool ran out, please call keypoolrefill first"; return false; } reservedest.KeepDestination(); return true; } static int64_t GetOldestKeyTimeInPool(const std::set<int64_t> &setKeyPool, WalletBatch &batch) { if (setKeyPool.empty()) { return GetTime(); } CKeyPool keypool; int64_t nIndex = *(setKeyPool.begin()); if (!batch.ReadPool(nIndex, keypool)) { throw std::runtime_error(std::string(__func__) + ": read oldest key in keypool failed"); } assert(keypool.vchPubKey.IsValid()); return keypool.nTime; } int64_t CWallet::GetOldestKeyPoolTime() { LOCK(cs_wallet); WalletBatch batch(*database); // load oldest key from keypool, get time and return int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, batch); if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) { oldestKey = std::max(GetOldestKeyTimeInPool(setInternalKeyPool, batch), oldestKey); if (!set_pre_split_keypool.empty()) { oldestKey = std::max(GetOldestKeyTimeInPool(set_pre_split_keypool, batch), oldestKey); } } return oldestKey; } std::map<CTxDestination, Amount> CWallet::GetAddressBalances(interfaces::Chain::Lock &locked_chain) { std::map<CTxDestination, Amount> balances; LOCK(cs_wallet); for (const auto &walletEntry : mapWallet) { const CWalletTx &wtx = walletEntry.second; if (!wtx.IsTrusted(locked_chain)) { continue; } if (wtx.IsImmatureCoinBase(locked_chain)) { continue; } int nDepth = wtx.GetDepthInMainChain(locked_chain); if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) { continue; } for (uint32_t i = 0; i < wtx.tx->vout.size(); i++) { CTxDestination addr; if (!IsMine(wtx.tx->vout[i])) { continue; } if (!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) { continue; } Amount n = IsSpent(locked_chain, COutPoint(walletEntry.first, i)) ? Amount::zero() : wtx.tx->vout[i].nValue; if (!balances.count(addr)) { balances[addr] = Amount::zero(); } balances[addr] += n; } } return balances; } std::set<std::set<CTxDestination>> CWallet::GetAddressGroupings() { AssertLockHeld(cs_wallet); std::set<std::set<CTxDestination>> groupings; std::set<CTxDestination> grouping; for (const auto &walletEntry : mapWallet) { const CWalletTx &wtx = walletEntry.second; if (wtx.tx->vin.size() > 0) { bool any_mine = false; // Group all input addresses with each other. for (const auto &txin : wtx.tx->vin) { CTxDestination address; // If this input isn't mine, ignore it. if (!IsMine(txin)) { continue; } if (!ExtractDestination(mapWallet.at(txin.prevout.GetTxId()) .tx->vout[txin.prevout.GetN()] .scriptPubKey, address)) { continue; } grouping.insert(address); any_mine = true; } // Group change with input addresses. if (any_mine) { for (const auto &txout : wtx.tx->vout) { if (IsChange(txout)) { CTxDestination txoutAddr; if (!ExtractDestination(txout.scriptPubKey, txoutAddr)) { continue; } grouping.insert(txoutAddr); } } } if (grouping.size() > 0) { groupings.insert(grouping); grouping.clear(); } } // Group lone addrs by themselves. for (const auto &txout : wtx.tx->vout) { if (IsMine(txout)) { CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address)) { continue; } grouping.insert(address); groupings.insert(grouping); grouping.clear(); } } } // A set of pointers to groups of addresses. std::set<std::set<CTxDestination> *> uniqueGroupings; // Map addresses to the unique group containing it. std::map<CTxDestination, std::set<CTxDestination> *> setmap; for (std::set<CTxDestination> _grouping : groupings) { // Make a set of all the groups hit by this new group. std::set<std::set<CTxDestination> *> hits; std::map<CTxDestination, std::set<CTxDestination> *>::iterator it; for (const CTxDestination &address : _grouping) { if ((it = setmap.find(address)) != setmap.end()) { hits.insert((*it).second); } } // Merge all hit groups into a new single group and delete old groups. std::set<CTxDestination> *merged = new std::set<CTxDestination>(_grouping); for (std::set<CTxDestination> *hit : hits) { merged->insert(hit->begin(), hit->end()); uniqueGroupings.erase(hit); delete hit; } uniqueGroupings.insert(merged); // Update setmap. for (const CTxDestination &element : *merged) { setmap[element] = merged; } } std::set<std::set<CTxDestination>> ret; for (const std::set<CTxDestination> *uniqueGrouping : uniqueGroupings) { ret.insert(*uniqueGrouping); delete uniqueGrouping; } return ret; } std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string &label) const { LOCK(cs_wallet); std::set<CTxDestination> result; for (const std::pair<const CTxDestination, CAddressBookData> &item : mapAddressBook) { const CTxDestination &address = item.first; const std::string &strName = item.second.name; if (strName == label) { result.insert(address); } } return result; } bool ReserveDestination::GetReservedDestination(const OutputType type, CTxDestination &dest, bool internal) { if (!pwallet->CanGetAddresses(internal)) { return false; } if (nIndex == -1) { CKeyPool keypool; if (!pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal)) { return false; } vchPubKey = keypool.vchPubKey; fInternal = keypool.fInternal; } assert(vchPubKey.IsValid()); pwallet->LearnRelatedScripts(vchPubKey, type); address = GetDestinationForKey(vchPubKey, type); dest = address; return true; } void ReserveDestination::KeepDestination() { if (nIndex != -1) { pwallet->KeepKey(nIndex); } nIndex = -1; vchPubKey = CPubKey(); address = CNoDestination(); } void ReserveDestination::ReturnDestination() { if (nIndex != -1) { pwallet->ReturnKey(nIndex, fInternal, vchPubKey); } nIndex = -1; vchPubKey = CPubKey(); address = CNoDestination(); } void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) { AssertLockHeld(cs_wallet); bool internal = setInternalKeyPool.count(keypool_id); if (!internal) { assert(setExternalKeyPool.count(keypool_id) || set_pre_split_keypool.count(keypool_id)); } std::set<int64_t> *setKeyPool = internal ? &setInternalKeyPool : (set_pre_split_keypool.empty() ? &setExternalKeyPool : &set_pre_split_keypool); auto it = setKeyPool->begin(); WalletBatch batch(*database); while (it != std::end(*setKeyPool)) { const int64_t &index = *(it); if (index > keypool_id) { // set*KeyPool is ordered break; } CKeyPool keypool; if (batch.ReadPool(index, keypool)) { // TODO: This should be unnecessary m_pool_key_to_index.erase(keypool.vchPubKey.GetID()); } LearnAllRelatedScripts(keypool.vchPubKey); batch.ErasePool(index); WalletLogPrintf("keypool index %d removed\n", index); it = setKeyPool->erase(it); } } void CWallet::LockCoin(const COutPoint &output) { AssertLockHeld(cs_wallet); setLockedCoins.insert(output); } void CWallet::UnlockCoin(const COutPoint &output) { AssertLockHeld(cs_wallet); setLockedCoins.erase(output); } void CWallet::UnlockAllCoins() { AssertLockHeld(cs_wallet); setLockedCoins.clear(); } bool CWallet::IsLockedCoin(const COutPoint &outpoint) const { AssertLockHeld(cs_wallet); return setLockedCoins.count(outpoint) > 0; } void CWallet::ListLockedCoins(std::vector<COutPoint> &vOutpts) const { AssertLockHeld(cs_wallet); for (COutPoint outpoint : setLockedCoins) { vOutpts.push_back(outpoint); } } /** @} */ // end of Actions void CWallet::GetKeyBirthTimes(interfaces::Chain::Lock &locked_chain, std::map<CKeyID, int64_t> &mapKeyBirth) const { AssertLockHeld(cs_wallet); mapKeyBirth.clear(); // Get birth times for keys with metadata. for (const auto &entry : mapKeyMetadata) { if (entry.second.nCreateTime) { mapKeyBirth[entry.first] = entry.second.nCreateTime; } } // Map in which we'll infer heights of other keys const Optional<int> tip_height = locked_chain.getHeight(); // the tip can be reorganized; use a 144-block safety margin const int max_height = tip_height && *tip_height > 144 ? *tip_height - 144 : 0; std::map<CKeyID, int> mapKeyFirstBlock; for (const CKeyID &keyid : GetKeys()) { if (mapKeyBirth.count(keyid) == 0) { mapKeyFirstBlock[keyid] = max_height; } } // If there are no such keys, we're done. if (mapKeyFirstBlock.empty()) { return; } // Find first block that affects those keys, if there are any left. for (const auto &entry : mapWallet) { // iterate over all wallet transactions... const CWalletTx &wtx = entry.second; if (Optional<int> height = locked_chain.getBlockHeight(wtx.hashBlock)) { // ... which are already in a block for (const CTxOut &txout : wtx.tx->vout) { // Iterate over all their outputs... for (const auto &keyid : GetAffectedKeys(txout.scriptPubKey, *this)) { // ... and all their affected keys. std::map<CKeyID, int>::iterator rit = mapKeyFirstBlock.find(keyid); if (rit != mapKeyFirstBlock.end() && *height < rit->second) { rit->second = *height; } } } } } // Extract block timestamps for those keys. for (const auto &entry : mapKeyFirstBlock) { // block times can be 2h off mapKeyBirth[entry.first] = locked_chain.getBlockTime(entry.second) - TIMESTAMP_WINDOW; } } /** * Compute smart timestamp for a transaction being added to the wallet. * * Logic: * - If sending a transaction, assign its timestamp to the current time. * - If receiving a transaction outside a block, assign its timestamp to the * current time. * - If receiving a block with a future timestamp, assign all its (not already * known) transactions' timestamps to the current time. * - If receiving a block with a past timestamp, before the most recent known * transaction (that we care about), assign all its (not already known) * transactions' timestamps to the same timestamp as that most-recent-known * transaction. * - If receiving a block with a past timestamp, but after the most recent known * transaction, assign all its (not already known) transactions' timestamps to * the block time. * * For more information see CWalletTx::nTimeSmart, * https://bitcointalk.org/?topic=54527, or * https://github.com/bitcoin/bitcoin/pull/1393. */ unsigned int CWallet::ComputeTimeSmart(const CWalletTx &wtx) const { unsigned int nTimeSmart = wtx.nTimeReceived; if (!wtx.hashUnset()) { int64_t blocktime; if (chain().findBlock(wtx.hashBlock, nullptr /* block */, &blocktime)) { int64_t latestNow = wtx.nTimeReceived; int64_t latestEntry = 0; // Tolerate times up to the last timestamp in the wallet not more // than 5 minutes into the future int64_t latestTolerated = latestNow + 300; const TxItems &txOrdered = wtxOrdered; for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) { CWalletTx *const pwtx = it->second; if (pwtx == &wtx) { continue; } int64_t nSmartTime; nSmartTime = pwtx->nTimeSmart; if (!nSmartTime) { nSmartTime = pwtx->nTimeReceived; } if (nSmartTime <= latestTolerated) { latestEntry = nSmartTime; if (nSmartTime > latestNow) { latestNow = nSmartTime; } break; } } nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow)); } else { WalletLogPrintf("%s: found %s in block %s not in index\n", __func__, wtx.GetId().ToString(), wtx.hashBlock.ToString()); } } return nTimeSmart; } bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value) { if (boost::get<CNoDestination>(&dest)) { return false; } mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); return WalletBatch(*database).WriteDestData(dest, key, value); } bool CWallet::EraseDestData(const CTxDestination &dest, const std::string &key) { if (!mapAddressBook[dest].destdata.erase(key)) { return false; } return WalletBatch(*database).EraseDestData(dest, key); } void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) { mapAddressBook[dest].destdata.insert(std::make_pair(key, value)); } bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const { std::map<CTxDestination, CAddressBookData>::const_iterator i = mapAddressBook.find(dest); if (i != mapAddressBook.end()) { CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key); if (j != i->second.destdata.end()) { if (value) { *value = j->second; } return true; } } return false; } std::vector<std::string> CWallet::GetDestValues(const std::string &prefix) const { std::vector<std::string> values; for (const auto &address : mapAddressBook) { for (const auto &data : address.second.destdata) { if (!data.first.compare(0, prefix.size(), prefix)) { values.emplace_back(data.second); } } } return values; } bool CWallet::Verify(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, bool salvage_wallet, std::string &error_string, std::string &warning_string) { // Do some checking on wallet path. It should be either a: // // 1. Path where a directory can be created. // 2. Path to an existing directory. // 3. Path to a symlink to a directory. // 4. For backwards compatibility, the name of a data file in -walletdir. LOCK(cs_wallets); const fs::path &wallet_path = location.GetPath(); fs::file_type path_type = fs::symlink_status(wallet_path).type(); if (!(path_type == fs::file_not_found || path_type == fs::directory_file || (path_type == fs::symlink_file && fs::is_directory(wallet_path)) || (path_type == fs::regular_file && fs::path(location.GetName()).filename() == location.GetName()))) { error_string = strprintf("Invalid -wallet path '%s'. -wallet path should point to " "a directory where wallet.dat and " "database/log.?????????? files can be stored, a location " "where such a directory could be created, " "or (for backwards compatibility) the name of an " "existing data file in -walletdir (%s)", location.GetName(), GetWalletDir()); return false; } // Make sure that the wallet path doesn't clash with an existing wallet path if (IsWalletLoaded(wallet_path)) { error_string = strprintf( "Error loading wallet %s. Duplicate -wallet filename specified.", location.GetName()); return false; } // Keep same database environment instance across Verify/Recover calls // below. std::unique_ptr<WalletDatabase> database = WalletDatabase::Create(wallet_path); try { if (!WalletBatch::VerifyEnvironment(wallet_path, error_string)) { return false; } } catch (const fs::filesystem_error &e) { error_string = strprintf("Error loading wallet %s. %s", location.GetName(), fsbridge::get_filesystem_error_message(e)); return false; } if (salvage_wallet) { // Recover readable keypairs: CWallet dummyWallet(chainParams, &chain, WalletLocation(), WalletDatabase::CreateDummy()); std::string backup_filename; if (!WalletBatch::Recover( wallet_path, static_cast<void *>(&dummyWallet), WalletBatch::RecoverKeysOnlyFilter, backup_filename)) { return false; } } return WalletBatch::VerifyDatabaseFile(wallet_path, warning_string, error_string); } void CWallet::MarkPreSplitKeys() { WalletBatch batch(*database); for (auto it = setExternalKeyPool.begin(); it != setExternalKeyPool.end();) { int64_t index = *it; CKeyPool keypool; if (!batch.ReadPool(index, keypool)) { throw std::runtime_error(std::string(__func__) + ": read keypool entry failed"); } keypool.m_pre_split = true; if (!batch.WritePool(index, keypool)) { throw std::runtime_error(std::string(__func__) + ": writing modified keypool entry failed"); } set_pre_split_keypool.insert(index); it = setExternalKeyPool.erase(it); } } std::shared_ptr<CWallet> CWallet::CreateWalletFromFile( const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, uint64_t wallet_creation_flags) { const std::string &walletFile = WalletDataFilePath(location.GetPath()).string(); // Needed to restore wallet transaction meta data after -zapwallettxes std::vector<CWalletTx> vWtx; if (gArgs.GetBoolArg("-zapwallettxes", false)) { chain.initMessage( _("Zapping all transactions from wallet...").translated); std::unique_ptr<CWallet> tempWallet = std::make_unique<CWallet>( chainParams, &chain, location, WalletDatabase::Create(location.GetPath())); DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx); if (nZapWalletRet != DBErrors::LOAD_OK) { chain.initError( strprintf(_("Error loading %s: Wallet corrupted").translated, walletFile)); return nullptr; } } chain.initMessage(_("Loading wallet...").translated); int64_t nStart = GetTimeMillis(); bool fFirstRun = true; // TODO: Can't use std::make_shared because we need a custom deleter but // should be possible to use std::allocate_shared. std::shared_ptr<CWallet> walletInstance( new CWallet(chainParams, &chain, location, WalletDatabase::Create(location.GetPath())), ReleaseWallet); DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun); if (nLoadWalletRet != DBErrors::LOAD_OK) { if (nLoadWalletRet == DBErrors::CORRUPT) { chain.initError( strprintf(_("Error loading %s: Wallet corrupted").translated, walletFile)); return nullptr; } if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR) { chain.initError(strprintf( _("Error reading %s! All keys read correctly, but transaction " "data or address book entries might be missing or incorrect.") .translated, walletFile)); } else if (nLoadWalletRet == DBErrors::TOO_NEW) { chain.initError(strprintf( _("Error loading %s: Wallet requires newer version of %s") .translated, walletFile, PACKAGE_NAME)); return nullptr; } else if (nLoadWalletRet == DBErrors::NEED_REWRITE) { chain.initError(strprintf( _("Wallet needed to be rewritten: restart %s to complete") .translated, PACKAGE_NAME)); return nullptr; } else { chain.initError( strprintf(_("Error loading %s").translated, walletFile)); return nullptr; } } int prev_version = walletInstance->GetVersion(); if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) { int nMaxVersion = gArgs.GetArg("-upgradewallet", 0); // The -upgradewallet without argument case if (nMaxVersion == 0) { walletInstance->WalletLogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST); nMaxVersion = FEATURE_LATEST; // permanently upgrade the wallet immediately walletInstance->SetMinVersion(FEATURE_LATEST); } else { walletInstance->WalletLogPrintf( "Allowing wallet upgrade up to %i\n", nMaxVersion); } if (nMaxVersion < walletInstance->GetVersion()) { chain.initError(_("Cannot downgrade wallet").translated); return nullptr; } walletInstance->SetMaxVersion(nMaxVersion); } // Upgrade to HD if explicit upgrade if (gArgs.GetBoolArg("-upgradewallet", false)) { LOCK(walletInstance->cs_wallet); // Do not upgrade versions to any version between HD_SPLIT and // FEATURE_PRE_SPLIT_KEYPOOL unless already supporting HD_SPLIT int max_version = walletInstance->GetVersion(); if (!walletInstance->CanSupportFeature(FEATURE_HD_SPLIT) && max_version >= FEATURE_HD_SPLIT && max_version < FEATURE_PRE_SPLIT_KEYPOOL) { chain.initError( _("Cannot upgrade a non HD split wallet without upgrading to " "support pre split keypool. Please use -upgradewallet=200300 " "or -upgradewallet with no version specified.") .translated); return nullptr; } bool hd_upgrade = false; bool split_upgrade = false; if (walletInstance->CanSupportFeature(FEATURE_HD) && !walletInstance->IsHDEnabled()) { walletInstance->WalletLogPrintf("Upgrading wallet to HD\n"); walletInstance->SetMinVersion(FEATURE_HD); // generate a new master key CPubKey masterPubKey = walletInstance->GenerateNewSeed(); walletInstance->SetHDSeed(masterPubKey); hd_upgrade = true; } // Upgrade to HD chain split if necessary if (walletInstance->CanSupportFeature(FEATURE_HD_SPLIT)) { walletInstance->WalletLogPrintf( "Upgrading wallet to use HD chain split\n"); walletInstance->SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL); split_upgrade = FEATURE_HD_SPLIT > prev_version; } // Mark all keys currently in the keypool as pre-split if (split_upgrade) { walletInstance->MarkPreSplitKeys(); } // Regenerate the keypool if upgraded to HD if (hd_upgrade) { if (!walletInstance->TopUpKeyPool()) { chain.initError(_("Unable to generate keys").translated); return nullptr; } } } if (fFirstRun) { // Ensure this wallet.dat can only be opened by clients supporting // HD with chain split and expects no default key. walletInstance->SetMinVersion(FEATURE_LATEST); walletInstance->SetWalletFlags(wallet_creation_flags, false); if (!(wallet_creation_flags & (WALLET_FLAG_DISABLE_PRIVATE_KEYS | WALLET_FLAG_BLANK_WALLET))) { // generate a new seed CPubKey seed = walletInstance->GenerateNewSeed(); walletInstance->SetHDSeed(seed); } // Top up the keypool if (walletInstance->CanGenerateKeys() && !walletInstance->TopUpKeyPool()) { chain.initError(_("Unable to generate initial keys").translated); return nullptr; } auto locked_chain = chain.lock(); walletInstance->ChainStateFlushed(locked_chain->getTipLocator()); } else if (wallet_creation_flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS) { // Make it impossible to disable private keys after creation chain.initError( strprintf(_("Error loading %s: Private keys can only be " "disabled during creation") .translated, walletFile)); return nullptr; } else if (walletInstance->IsWalletFlagSet( WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { LOCK(walletInstance->cs_KeyStore); if (!walletInstance->mapKeys.empty() || !walletInstance->mapCryptedKeys.empty()) { chain.initWarning( strprintf(_("Warning: Private keys detected in wallet " "{%s} with disabled private keys") .translated, walletFile)); } } if (gArgs.IsArgSet("-mintxfee")) { Amount n = Amount::zero(); if (!ParseMoney(gArgs.GetArg("-mintxfee", ""), n) || n == Amount::zero()) { chain.initError( AmountErrMsg("mintxfee", gArgs.GetArg("-mintxfee", ""))); return nullptr; } if (n > HIGH_TX_FEE_PER_KB) { chain.initWarning( AmountHighWarn("-mintxfee") + " " + _("This is the minimum transaction fee you pay on " "every transaction.") .translated); } walletInstance->m_min_fee = CFeeRate(n); } if (gArgs.IsArgSet("-fallbackfee")) { Amount nFeePerK = Amount::zero(); if (!ParseMoney(gArgs.GetArg("-fallbackfee", ""), nFeePerK)) { chain.initError(strprintf( _("Invalid amount for -fallbackfee=<amount>: '%s'").translated, gArgs.GetArg("-fallbackfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { chain.initWarning( AmountHighWarn("-fallbackfee") + " " + _("This is the transaction fee you may pay when fee " "estimates are not available.") .translated); } walletInstance->m_fallback_fee = CFeeRate(nFeePerK); // disable fallback fee in case value was set to 0, enable if non-null // value walletInstance->m_allow_fallback_fee = (nFeePerK != Amount::zero()); } if (gArgs.IsArgSet("-paytxfee")) { Amount nFeePerK = Amount::zero(); if (!ParseMoney(gArgs.GetArg("-paytxfee", ""), nFeePerK)) { chain.initError( AmountErrMsg("paytxfee", gArgs.GetArg("-paytxfee", ""))); return nullptr; } if (nFeePerK > HIGH_TX_FEE_PER_KB) { chain.initWarning( AmountHighWarn("-paytxfee") + " " + _("This is the transaction fee you will pay if you " "send a transaction.") .translated); } walletInstance->m_pay_tx_fee = CFeeRate(nFeePerK, 1000); if (walletInstance->m_pay_tx_fee < chain.relayMinFee()) { chain.initError(strprintf( _("Invalid amount for -paytxfee=<amount>: '%s' " "(must be at least %s)") .translated, gArgs.GetArg("-paytxfee", ""), chain.relayMinFee().ToString())); return nullptr; } } if (gArgs.IsArgSet("-maxtxfee")) { Amount nMaxFee = Amount::zero(); if (!ParseMoney(gArgs.GetArg("-maxtxfee", ""), nMaxFee)) { chain.initError( AmountErrMsg("maxtxfee", gArgs.GetArg("-maxtxfee", ""))); return nullptr; } if (nMaxFee > HIGH_MAX_TX_FEE) { chain.initWarning(_("-maxtxfee is set very high! Fees this large " "could be paid on a single transaction.") .translated); } if (CFeeRate(nMaxFee, 1000) < chain.relayMinFee()) { chain.initError(strprintf( _("Invalid amount for -maxtxfee=<amount>: '%s' (must be at " "least the minrelay fee of %s to prevent stuck transactions)") .translated, gArgs.GetArg("-maxtxfee", ""), chain.relayMinFee().ToString())); return nullptr; } walletInstance->m_default_max_tx_fee = nMaxFee; } if (chain.relayMinFee().GetFeePerK() > HIGH_TX_FEE_PER_KB) { chain.initWarning( AmountHighWarn("-minrelaytxfee") + " " + _("The wallet will avoid paying less than the minimum relay fee.") .translated); } walletInstance->m_spend_zero_conf_change = gArgs.GetBoolArg("-spendzeroconfchange", DEFAULT_SPEND_ZEROCONF_CHANGE); walletInstance->m_default_address_type = DEFAULT_ADDRESS_TYPE; walletInstance->m_default_change_type = DEFAULT_CHANGE_TYPE; walletInstance->WalletLogPrintf("Wallet completed loading in %15dms\n", GetTimeMillis() - nStart); // Try to top up keypool. No-op if the wallet is locked. walletInstance->TopUpKeyPool(); auto locked_chain = chain.lock(); LOCK(walletInstance->cs_wallet); int rescan_height = 0; if (!gArgs.GetBoolArg("-rescan", false)) { WalletBatch batch(*walletInstance->database); CBlockLocator locator; if (batch.ReadBestBlock(locator)) { if (const Optional<int> fork_height = locked_chain->findLocatorFork(locator)) { rescan_height = *fork_height; } } } const Optional<int> tip_height = locked_chain->getHeight(); if (tip_height) { walletInstance->m_last_block_processed = locked_chain->getBlockHash(*tip_height); } else { walletInstance->m_last_block_processed.SetNull(); } if (tip_height && *tip_height != rescan_height) { // We can't rescan beyond non-pruned blocks, stop and throw an error. // This might happen if a user uses an old wallet within a pruned node // or if they ran -disablewallet for a longer time, then decided to // re-enable if (chain.havePruned()) { // Exit early and print an error. // If a block is pruned after this check, we will load the wallet, // but fail the rescan with a generic error. int block_height = *tip_height; while (block_height > 0 && locked_chain->haveBlockOnDisk(block_height - 1) && rescan_height != block_height) { --block_height; } if (rescan_height != block_height) { chain.initError( _("Prune: last wallet synchronisation goes beyond " "pruned data. You need to -reindex (download the " "whole blockchain again in case of pruned node)") .translated); return nullptr; } } chain.initMessage(_("Rescanning...").translated); walletInstance->WalletLogPrintf( "Rescanning last %i blocks (from block %i)...\n", *tip_height - rescan_height, rescan_height); // No need to read and scan block if block was created before our wallet // birthday (as adjusted for block time variability) if (walletInstance->nTimeFirstKey) { if (Optional<int> first_block = locked_chain->findFirstBlockWithTimeAndHeight( walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW, rescan_height, nullptr)) { rescan_height = *first_block; } } { WalletRescanReserver reserver(walletInstance.get()); if (!reserver.reserve() || (ScanResult::SUCCESS != walletInstance ->ScanForWalletTransactions( locked_chain->getBlockHash(rescan_height), BlockHash(), reserver, true /* update */) .status)) { chain.initError( _("Failed to rescan the wallet during initialization") .translated); return nullptr; } } walletInstance->ChainStateFlushed(locked_chain->getTipLocator()); walletInstance->database->IncrementUpdateCounter(); // Restore wallet transaction metadata after -zapwallettxes=1 if (gArgs.GetBoolArg("-zapwallettxes", false) && gArgs.GetArg("-zapwallettxes", "1") != "2") { WalletBatch batch(*walletInstance->database); for (const CWalletTx &wtxOld : vWtx) { const TxId txid = wtxOld.GetId(); std::map<TxId, CWalletTx>::iterator mi = walletInstance->mapWallet.find(txid); if (mi != walletInstance->mapWallet.end()) { const CWalletTx *copyFrom = &wtxOld; CWalletTx *copyTo = &mi->second; copyTo->mapValue = copyFrom->mapValue; copyTo->vOrderForm = copyFrom->vOrderForm; copyTo->nTimeReceived = copyFrom->nTimeReceived; copyTo->nTimeSmart = copyFrom->nTimeSmart; copyTo->fFromMe = copyFrom->fFromMe; copyTo->nOrderPos = copyFrom->nOrderPos; batch.WriteTx(*copyTo); } } } } chain.loadWallet(interfaces::MakeWallet(walletInstance)); // Register with the validation interface. It's ok to do this after rescan // since we're still holding locked_chain. walletInstance->m_chain_notifications_handler = walletInstance->chain().handleNotifications(walletInstance); walletInstance->SetBroadcastTransactions( gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST)); walletInstance->WalletLogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize()); walletInstance->WalletLogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size()); walletInstance->WalletLogPrintf("mapAddressBook.size() = %u\n", walletInstance->mapAddressBook.size()); return walletInstance; } void CWallet::postInitProcess() { auto locked_chain = chain().lock(); LOCK(cs_wallet); // Add wallet transactions that aren't already in a block to mempool. // Do this here as mempool requires genesis block to be loaded. ReacceptWalletTransactions(*locked_chain); // Update wallet transactions with current mempool transactions. chain().requestMempoolTransactions(*this); } bool CWallet::BackupWallet(const std::string &strDest) { return database->Backup(strDest); } CKeyPool::CKeyPool() { nTime = GetTime(); fInternal = false; m_pre_split = false; } CKeyPool::CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn) { nTime = GetTime(); vchPubKey = vchPubKeyIn; fInternal = internalIn; m_pre_split = false; } void CMerkleTx::SetMerkleBranch(const BlockHash &block_hash, int posInBlock) { // Update the tx's hashBlock hashBlock = block_hash; // Set the position of the transaction in the block. nIndex = posInBlock; } int CMerkleTx::GetDepthInMainChain( interfaces::Chain::Lock &locked_chain) const { if (hashUnset()) { return 0; } return locked_chain.getBlockDepth(hashBlock) * (nIndex == -1 ? -1 : 1); } int CMerkleTx::GetBlocksToMaturity( interfaces::Chain::Lock &locked_chain) const { if (!IsCoinBase()) { return 0; } int chain_depth = GetDepthInMainChain(locked_chain); // coinbase tx should not be conflicted assert(chain_depth >= 0); return std::max(0, (COINBASE_MATURITY + 1) - chain_depth); } bool CMerkleTx::IsImmatureCoinBase( interfaces::Chain::Lock &locked_chain) const { // note GetBlocksToMaturity is 0 for non-coinbase tx return GetBlocksToMaturity(locked_chain) > 0; } void CWallet::LearnRelatedScripts(const CPubKey &key, OutputType type) { // Nothing to do... } void CWallet::LearnAllRelatedScripts(const CPubKey &key) { // Nothing to do... } std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput> &outputs, bool single_coin) const { std::vector<OutputGroup> groups; std::map<CTxDestination, OutputGroup> gmap; CTxDestination dst; for (const auto &output : outputs) { if (output.fSpendable) { CInputCoin input_coin = output.GetInputCoin(); size_t ancestors, descendants; chain().getTransactionAncestry(output.tx->GetId(), ancestors, descendants); if (!single_coin && ExtractDestination(output.tx->tx->vout[output.i].scriptPubKey, dst)) { // Limit output groups to no more than 10 entries, to protect // against inadvertently creating a too-large transaction // when using -avoidpartialspends if (gmap[dst].m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) { groups.push_back(gmap[dst]); gmap.erase(dst); } gmap[dst].Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants); } else { groups.emplace_back(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants); } } } if (!single_coin) { for (const auto &it : gmap) { groups.push_back(it.second); } } return groups; } bool CWallet::GetKeyOrigin(const CKeyID &keyID, KeyOriginInfo &info) const { CKeyMetadata meta; { LOCK(cs_wallet); auto it = mapKeyMetadata.find(keyID); if (it != mapKeyMetadata.end()) { meta = it->second; } } if (meta.has_key_origin) { std::copy(meta.key_origin.fingerprint, meta.key_origin.fingerprint + 4, info.fingerprint); info.path = meta.key_origin.path; } else { // Single pubkeys get the master fingerprint of themselves std::copy(keyID.begin(), keyID.begin() + 4, info.fingerprint); } return true; } bool CWallet::AddKeyOriginWithDB(WalletBatch &batch, const CPubKey &pubkey, const KeyOriginInfo &info) { LOCK(cs_wallet); std::copy(info.fingerprint, info.fingerprint + 4, mapKeyMetadata[pubkey.GetID()].key_origin.fingerprint); mapKeyMetadata[pubkey.GetID()].key_origin.path = info.path; mapKeyMetadata[pubkey.GetID()].has_key_origin = true; mapKeyMetadata[pubkey.GetID()].hdKeypath = WriteHDKeypath(info.path); return batch.WriteKeyMetadata(mapKeyMetadata[pubkey.GetID()], pubkey, true); } bool CWallet::SetCrypted() { LOCK(cs_KeyStore); if (fUseCrypto) { return true; } if (!mapKeys.empty()) { return false; } fUseCrypto = true; return true; } bool CWallet::IsLocked() const { if (!IsCrypted()) { return false; } LOCK(cs_KeyStore); return vMasterKey.empty(); } bool CWallet::Lock() { if (!SetCrypted()) { return false; } { LOCK(cs_KeyStore); vMasterKey.clear(); } NotifyStatusChanged(this); return true; } bool CWallet::Unlock(const CKeyingMaterial &vMasterKeyIn, bool accept_no_keys) { { LOCK(cs_KeyStore); if (!SetCrypted()) { return false; } // Always pass when there are no encrypted keys bool keyPass = mapCryptedKeys.empty(); bool keyFail = false; CryptedKeyMap::const_iterator mi = mapCryptedKeys.begin(); for (; mi != mapCryptedKeys.end(); ++mi) { const CPubKey &vchPubKey = (*mi).second.first; const std::vector<uint8_t> &vchCryptedSecret = (*mi).second.second; CKey key; if (!DecryptKey(vMasterKeyIn, vchCryptedSecret, vchPubKey, key)) { keyFail = true; break; } keyPass = true; if (fDecryptionThoroughlyChecked) { break; } } if (keyPass && keyFail) { LogPrintf("The wallet is probably corrupted: Some keys decrypt but " "not all.\n"); assert(false); } if (keyFail || (!keyPass && !accept_no_keys)) { return false; } vMasterKey = vMasterKeyIn; fDecryptionThoroughlyChecked = true; } NotifyStatusChanged(this); return true; } bool CWallet::HaveKey(const CKeyID &address) const { LOCK(cs_KeyStore); if (!IsCrypted()) { return FillableSigningProvider::HaveKey(address); } return mapCryptedKeys.count(address) > 0; } bool CWallet::GetKey(const CKeyID &address, CKey &keyOut) const { LOCK(cs_KeyStore); if (!IsCrypted()) { return FillableSigningProvider::GetKey(address, keyOut); } CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); if (mi != mapCryptedKeys.end()) { const CPubKey &vchPubKey = (*mi).second.first; const std::vector<uint8_t> &vchCryptedSecret = (*mi).second.second; return DecryptKey(vMasterKey, vchCryptedSecret, vchPubKey, keyOut); } return false; } bool CWallet::GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const { LOCK(cs_KeyStore); WatchKeyMap::const_iterator it = mapWatchKeys.find(address); if (it != mapWatchKeys.end()) { pubkey_out = it->second; return true; } return false; } bool CWallet::GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const { LOCK(cs_KeyStore); if (!IsCrypted()) { if (!FillableSigningProvider::GetPubKey(address, vchPubKeyOut)) { return GetWatchPubKey(address, vchPubKeyOut); } return true; } CryptedKeyMap::const_iterator mi = mapCryptedKeys.find(address); if (mi != mapCryptedKeys.end()) { vchPubKeyOut = (*mi).second.first; return true; } // Check for watch-only pubkeys return GetWatchPubKey(address, vchPubKeyOut); } std::set<CKeyID> CWallet::GetKeys() const { LOCK(cs_KeyStore); if (!IsCrypted()) { return FillableSigningProvider::GetKeys(); } std::set<CKeyID> set_address; for (const auto &mi : mapCryptedKeys) { set_address.insert(mi.first); } return set_address; } bool CWallet::EncryptKeys(CKeyingMaterial &vMasterKeyIn) { LOCK(cs_KeyStore); if (!mapCryptedKeys.empty() || IsCrypted()) { return false; } fUseCrypto = true; for (const KeyMap::value_type &mKey : mapKeys) { const CKey &key = mKey.second; CPubKey vchPubKey = key.GetPubKey(); CKeyingMaterial vchSecret(key.begin(), key.end()); std::vector<uint8_t> vchCryptedSecret; if (!EncryptSecret(vMasterKeyIn, vchSecret, vchPubKey.GetHash(), vchCryptedSecret)) { return false; } if (!AddCryptedKey(vchPubKey, vchCryptedSecret)) { return false; } } mapKeys.clear(); return true; } bool CWallet::AddKeyPubKeyInner(const CKey &key, const CPubKey &pubkey) { LOCK(cs_KeyStore); if (!IsCrypted()) { return FillableSigningProvider::AddKeyPubKey(key, pubkey); } if (IsLocked()) { return false; } std::vector<uint8_t> vchCryptedSecret; CKeyingMaterial vchSecret(key.begin(), key.end()); if (!EncryptSecret(vMasterKey, vchSecret, pubkey.GetHash(), vchCryptedSecret)) { return false; } if (!AddCryptedKey(pubkey, vchCryptedSecret)) { return false; } return true; } bool CWallet::AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<uint8_t> &vchCryptedSecret) { LOCK(cs_KeyStore); if (!SetCrypted()) { return false; } mapCryptedKeys[vchPubKey.GetID()] = make_pair(vchPubKey, vchCryptedSecret); return true; } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 7e3a09bd6..3fc0fefd2 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1,1691 +1,1691 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Copyright (c) 2018-2020 The Bitcoin developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_WALLET_H #define BITCOIN_WALLET_WALLET_H #include <amount.h> #include <interfaces/chain.h> #include <interfaces/handler.h> #include <outputtype.h> #include <primitives/blockhash.h> #include <streams.h> #include <tinyformat.h> #include <ui_interface.h> #include <util/strencodings.h> #include <util/system.h> #include <validationinterface.h> #include <wallet/coinselection.h> #include <wallet/crypter.h> #include <wallet/ismine.h> #include <wallet/rpcwallet.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> #include <algorithm> #include <atomic> #include <cstdint> #include <map> #include <memory> #include <set> #include <stdexcept> #include <string> #include <utility> #include <vector> #include <boost/signals2/signal.hpp> //! Explicitly unload and delete the wallet. //! Blocks the current thread after signaling the unload intent so that all //! wallet clients release the wallet. //! Note that, when blocking is not required, the wallet is implicitly unloaded //! by the shared pointer deleter. void UnloadWallet(std::shared_ptr<CWallet> &&wallet); bool AddWallet(const std::shared_ptr<CWallet> &wallet); bool RemoveWallet(const std::shared_ptr<CWallet> &wallet); bool HasWallets(); std::vector<std::shared_ptr<CWallet>> GetWallets(); std::shared_ptr<CWallet> GetWallet(const std::string &name); std::shared_ptr<CWallet> LoadWallet(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, std::string &error, std::string &warning); enum class WalletCreationStatus { SUCCESS, CREATION_FAILED, ENCRYPTION_FAILED }; std::shared_ptr<CWallet> CreateWallet(const CChainParams ¶ms, interfaces::Chain &chain, + const SecureString &passphrase, uint64_t wallet_creation_flags, const std::string &name, std::string &error, std::string &warning, - WalletCreationStatus &status, const SecureString &passphrase, - uint64_t wallet_creation_flags); + WalletCreationStatus &status); //! Default for -keypool static const unsigned int DEFAULT_KEYPOOL_SIZE = 1000; //! -paytxfee default constexpr Amount DEFAULT_PAY_TX_FEE = Amount::zero(); //! -fallbackfee default static const Amount DEFAULT_FALLBACK_FEE(20000 * SATOSHI); //! -mintxfee default static const Amount DEFAULT_TRANSACTION_MINFEE_PER_KB = 1000 * SATOSHI; //! minimum recommended increment for BIP 125 replacement txs static const Amount WALLET_INCREMENTAL_RELAY_FEE(5000 * SATOSHI); //! Default for -spendzeroconfchange static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; //! Default for -walletrejectlongchains static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false; //! Default for -avoidpartialspends static const bool DEFAULT_AVOIDPARTIALSPENDS = false; static const bool DEFAULT_WALLETBROADCAST = true; static const bool DEFAULT_DISABLE_WALLET = false; //! -maxtxfee default constexpr Amount DEFAULT_TRANSACTION_MAXFEE{COIN / 10}; //! Discourage users to set fees higher than this amount (in satoshis) per kB constexpr Amount HIGH_TX_FEE_PER_KB{COIN / 100}; //! -maxtxfee will warn if called with a higher fee than this amount (in //! satoshis) constexpr Amount HIGH_MAX_TX_FEE{100 * HIGH_TX_FEE_PER_KB}; class CChainParams; class CCoinControl; class COutput; class CScript; class CTxMemPool; class CWalletTx; class ReserveDestination; /** (client) version numbers for particular wallet features */ enum WalletFeature { // the earliest version new wallets supports (only useful for // getwalletinfo's clientversion output) FEATURE_BASE = 10500, // wallet encryption FEATURE_WALLETCRYPT = 40000, // compressed public keys FEATURE_COMPRPUBKEY = 60000, // Hierarchical key derivation after BIP32 (HD Wallet) FEATURE_HD = 130000, // Wallet with HD chain split (change outputs will use m/0'/1'/k) FEATURE_HD_SPLIT = 160300, // Wallet without a default key written FEATURE_NO_DEFAULT_KEY = 190700, // Upgraded to HD SPLIT and can have a pre-split keypool FEATURE_PRE_SPLIT_KEYPOOL = 200300, FEATURE_LATEST = FEATURE_PRE_SPLIT_KEYPOOL, }; //! Default for -addresstype constexpr OutputType DEFAULT_ADDRESS_TYPE{OutputType::LEGACY}; //! Default for -changetype constexpr OutputType DEFAULT_CHANGE_TYPE{OutputType::CHANGE_AUTO}; enum WalletFlags : uint64_t { // Wallet flags in the upper section (> 1 << 31) will lead to not opening // the wallet if flag is unknown. // Unknown wallet flags in the lower section <= (1 << 31) will be tolerated. // will categorize coins as clean (not reused) and dirty (reused), and // handle // them with privacy considerations in mind WALLET_FLAG_AVOID_REUSE = (1ULL << 0), // Indicates that the metadata has already been upgraded to contain key // origins WALLET_FLAG_KEY_ORIGIN_METADATA = (1ULL << 1), // Will enforce the rule that the wallet can't contain any private keys // (only watch-only/pubkeys). WALLET_FLAG_DISABLE_PRIVATE_KEYS = (1ULL << 32), //! Flag set when a wallet contains no HD seed and no private keys, scripts, //! addresses, and other watch only things, and is therefore "blank." //! //! The only function this flag serves is to distinguish a blank wallet from //! a newly created wallet when the wallet database is loaded, to avoid //! initialization that should only happen on first run. //! //! This flag is also a mandatory flag to prevent previous versions of //! bitcoin from opening the wallet, thinking it was newly created, and //! then improperly reinitializing it. WALLET_FLAG_BLANK_WALLET = (1ULL << 33), }; static constexpr uint64_t KNOWN_WALLET_FLAGS = WALLET_FLAG_AVOID_REUSE | WALLET_FLAG_BLANK_WALLET | WALLET_FLAG_KEY_ORIGIN_METADATA | WALLET_FLAG_DISABLE_PRIVATE_KEYS; static constexpr uint64_t MUTABLE_WALLET_FLAGS = WALLET_FLAG_AVOID_REUSE; static const std::map<std::string, WalletFlags> WALLET_FLAG_MAP{ {"avoid_reuse", WALLET_FLAG_AVOID_REUSE}, {"blank", WALLET_FLAG_BLANK_WALLET}, {"key_origin_metadata", WALLET_FLAG_KEY_ORIGIN_METADATA}, {"disable_private_keys", WALLET_FLAG_DISABLE_PRIVATE_KEYS}, }; extern const std::map<uint64_t, std::string> WALLET_FLAG_CAVEATS; /** * A key from a CWallet's keypool * * The wallet holds one (for pre HD-split wallets) or several keypools. These * are sets of keys that have not yet been used to provide addresses or receive * change. * * The Bitcoin ABC wallet was originally a collection of unrelated private * keys with their associated addresses. If a non-HD wallet generated a * key/address, gave that address out and then restored a backup from before * that key's generation, then any funds sent to that address would be * lost definitively. * * The keypool was implemented to avoid this scenario (commit: 10384941). The * wallet would generate a set of keys (100 by default). When a new public key * was required, either to give out as an address or to use in a change output, * it would be drawn from the keypool. The keypool would then be topped up to * maintain 100 keys. This ensured that as long as the wallet hadn't used more * than 100 keys since the previous backup, all funds would be safe, since a * restored wallet would be able to scan for all owned addresses. * * A keypool also allowed encrypted wallets to give out addresses without * having to be decrypted to generate a new private key. * * With the introduction of HD wallets (commit: f1902510), the keypool * essentially became an address look-ahead pool. Restoring old backups can no * longer definitively lose funds as long as the addresses used were from the * wallet's HD seed (since all private keys can be rederived from the seed). * However, if many addresses were used since the backup, then the wallet may * not know how far ahead in the HD chain to look for its addresses. The * keypool is used to implement a 'gap limit'. The keypool maintains a set of * keys (by default 1000) ahead of the last used key and scans for the * addresses of those keys. This avoids the risk of not seeing transactions * involving the wallet's addresses, or of re-using the same address. * * The HD-split wallet feature added a second keypool (commit: 02592f4c). There * is an external keypool (for addresses to hand out) and an internal keypool * (for change addresses). * * Keypool keys are stored in the wallet/keystore's keymap. The keypool data is * stored as sets of indexes in the wallet (setInternalKeyPool, * setExternalKeyPool and set_pre_split_keypool), and a map from the key to the * index (m_pool_key_to_index). The CKeyPool object is used to * serialize/deserialize the pool data to/from the database. */ class CKeyPool { public: //! The time at which the key was generated. Set in AddKeypoolPubKeyWithDB int64_t nTime; //! The public key CPubKey vchPubKey; //! Whether this keypool entry is in the internal keypool (for change //! outputs) bool fInternal; //! Whether this key was generated for a keypool before the wallet was //! upgraded to HD-split bool m_pre_split; CKeyPool(); CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn); ADD_SERIALIZE_METHODS; template <typename Stream, typename Operation> inline void SerializationOp(Stream &s, Operation ser_action) { int nVersion = s.GetVersion(); if (!(s.GetType() & SER_GETHASH)) { READWRITE(nVersion); } READWRITE(nTime); READWRITE(vchPubKey); if (ser_action.ForRead()) { try { READWRITE(fInternal); } catch (std::ios_base::failure &) { /** * flag as external address if we can't read the internal * boolean (this will be the case for any wallet before the HD * chain split version) */ fInternal = false; } try { READWRITE(m_pre_split); } catch (std::ios_base::failure &) { /** * flag as postsplit address if we can't read the m_pre_split * boolean (this will be the case for any wallet that upgrades * to HD chain split) */ m_pre_split = false; } } else { READWRITE(fInternal); READWRITE(m_pre_split); } } }; /** * A wrapper to reserve an address from a wallet * * ReserveDestination is used to reserve an address. * It is currently only used inside of CreateTransaction. * * Instantiating a ReserveDestination does not reserve an address. To do so, * GetReservedDestination() needs to be called on the object. Once an address * has been reserved, call KeepDestination() on the ReserveDestination object to * make sure it is not returned. Call ReturnDestination() to return the address * so it can be re-used (for example, if the address was used in a new * transaction and that transaction was not completed and needed to be aborted). * * If an address is reserved and KeepDestination() is not called, then the * address will be returned when the ReserveDestination goes out of scope. */ class ReserveDestination { protected: //! The wallet to reserve from CWallet *pwallet; //! The index of the address's key in the keypool int64_t nIndex{-1}; //! The public key for the address CPubKey vchPubKey; //! The destination CTxDestination address; //! Whether this is from the internal (change output) keypool bool fInternal{false}; public: //! Construct a ReserveDestination object. This does NOT reserve an address //! yet explicit ReserveDestination(CWallet *pwalletIn) { pwallet = pwalletIn; } ReserveDestination(const ReserveDestination &) = delete; ReserveDestination &operator=(const ReserveDestination &) = delete; //! Destructor. If a key has been reserved and not KeepKey'ed, it will be //! returned to the keypool ~ReserveDestination() { ReturnDestination(); } //! Reserve an address bool GetReservedDestination(const OutputType type, CTxDestination &pubkey, bool internal); //! Return reserved address void ReturnDestination(); //! Keep the address. Do not return it's key to the keypool when this object //! goes out of scope void KeepDestination(); }; /** Address book data */ class CAddressBookData { public: std::string name; std::string purpose; CAddressBookData() : purpose("unknown") {} typedef std::map<std::string, std::string> StringMap; StringMap destdata; }; struct CRecipient { CScript scriptPubKey; Amount nAmount; bool fSubtractFeeFromAmount; }; typedef std::map<std::string, std::string> mapValue_t; static inline void ReadOrderPos(int64_t &nOrderPos, mapValue_t &mapValue) { if (!mapValue.count("n")) { // TODO: calculate elsewhere nOrderPos = -1; return; } nOrderPos = atoi64(mapValue["n"].c_str()); } static inline void WriteOrderPos(const int64_t &nOrderPos, mapValue_t &mapValue) { if (nOrderPos == -1) { return; } mapValue["n"] = i64tostr(nOrderPos); } struct COutputEntry { CTxDestination destination; Amount amount; int vout; }; /** A transaction with a merkle branch linking it to the block chain. */ class CMerkleTx { private: /** Constant used in hashBlock to indicate tx has been abandoned */ static const BlockHash ABANDON_HASH; public: CTransactionRef tx; BlockHash hashBlock; /** * An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest * block in the chain we know this or any in-wallet dependency conflicts * with. Older clients interpret nIndex == -1 as unconfirmed for backward * compatibility. */ int nIndex; CMerkleTx() { SetTx(MakeTransactionRef()); Init(); } explicit CMerkleTx(CTransactionRef arg) { SetTx(std::move(arg)); Init(); } void Init() { hashBlock = BlockHash(); nIndex = -1; } void SetTx(CTransactionRef arg) { tx = std::move(arg); } ADD_SERIALIZE_METHODS; template <typename Stream, typename Operation> inline void SerializationOp(Stream &s, Operation ser_action) { // For compatibility with older versions. std::vector<uint256> vMerkleBranch; READWRITE(tx); READWRITE(hashBlock); READWRITE(vMerkleBranch); READWRITE(nIndex); } void SetMerkleBranch(const BlockHash &block_hash, int posInBlock); /** * Return depth of transaction in blockchain: * <0 : conflicts with a transaction this deep in the blockchain * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ int GetDepthInMainChain(interfaces::Chain::Lock &locked_chain) const; bool IsInMainChain(interfaces::Chain::Lock &locked_chain) const { return GetDepthInMainChain(locked_chain) > 0; } /** * @return number of blocks to maturity for this transaction: * 0 : is not a coinbase transaction, or is a mature coinbase transaction * >0 : is a coinbase transaction which matures in this many blocks */ int GetBlocksToMaturity(interfaces::Chain::Lock &locked_chain) const; bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } void setAbandoned() { hashBlock = ABANDON_HASH; } TxId GetId() const { return tx->GetId(); } bool IsCoinBase() const { return tx->IsCoinBase(); } bool IsImmatureCoinBase(interfaces::Chain::Lock &locked_chain) const; }; // Get the marginal bytes of spending the specified output int CalculateMaximumSignedInputSize(const CTxOut &txout, const CWallet *pwallet, bool use_max_sig = false); /** * A transaction with a bunch of additional info that only the owner cares * about. It includes any unrecorded transactions needed to link it back to the * block chain. */ class CWalletTx : public CMerkleTx { private: const CWallet *pwallet; public: /** * Key/value map with information about the transaction. * * The following keys can be read and written through the map and are * serialized in the wallet database: * * "comment", "to" - comment strings provided to sendtoaddress, * and sendmany wallet RPCs * "replaces_txid" - txid (as HexStr) of transaction replaced by * bumpfee on transaction created by bumpfee * "replaced_by_txid" - txid (as HexStr) of transaction created by * bumpfee on transaction replaced by bumpfee * "from", "message" - obsolete fields that could be set in UI prior to * 2011 (removed in commit 4d9b223) * * The following keys are serialized in the wallet database, but shouldn't * be read or written through the map (they will be temporarily added and * removed from the map during serialization): * * "fromaccount" - serialized strFromAccount value * "n" - serialized nOrderPos value * "timesmart" - serialized nTimeSmart value * "spent" - serialized vfSpent value that existed prior to * 2014 (removed in commit 93a18a3) */ mapValue_t mapValue; std::vector<std::pair<std::string, std::string>> vOrderForm; unsigned int fTimeReceivedIsTxTime; //! time received by this node unsigned int nTimeReceived; /** * Stable timestamp that never changes, and reflects the order a transaction * was added to the wallet. Timestamp is based on the block time for a * transaction added as part of a block, or else the time when the * transaction was received if it wasn't part of a block, with the timestamp * adjusted in both cases so timestamp order matches the order transactions * were added to the wallet. More details can be found in * CWallet::ComputeTimeSmart(). */ unsigned int nTimeSmart; /** * From me flag is set to 1 for transactions that were created by the wallet * on this bitcoin node, and set to 0 for transactions that were created * externally and came in through the network or sendrawtransaction RPC. */ char fFromMe; //! position in ordered transaction list int64_t nOrderPos; std::multimap<int64_t, CWalletTx *>::const_iterator m_it_wtxOrdered; // memory only enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; Amount GetCachableAmount(AmountType type, const isminefilter &filter, bool recalculate = false) const; mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS]; mutable bool fChangeCached; mutable bool fInMempool; mutable Amount nChangeCached; CWalletTx(const CWallet *pwalletIn, CTransactionRef arg) : CMerkleTx(std::move(arg)) { Init(pwalletIn); } void Init(const CWallet *pwalletIn) { pwallet = pwalletIn; mapValue.clear(); vOrderForm.clear(); fTimeReceivedIsTxTime = false; nTimeReceived = 0; nTimeSmart = 0; fFromMe = false; fChangeCached = false; fInMempool = false; nChangeCached = Amount::zero(); nOrderPos = -1; } template <typename Stream> void Serialize(Stream &s) const { char fSpent = false; mapValue_t mapValueCopy = mapValue; mapValueCopy["fromaccount"] = ""; WriteOrderPos(nOrderPos, mapValueCopy); if (nTimeSmart) { mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart); } s << static_cast<const CMerkleTx &>(*this); //! Used to be vtxPrev std::vector<CMerkleTx> vUnused; s << vUnused << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << fSpent; } template <typename Stream> void Unserialize(Stream &s) { Init(nullptr); char fSpent; s >> static_cast<CMerkleTx &>(*this); //! Used to be vtxPrev std::vector<CMerkleTx> vUnused; s >> vUnused >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> fSpent; ReadOrderPos(nOrderPos, mapValue); nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; mapValue.erase("fromaccount"); mapValue.erase("spent"); mapValue.erase("n"); mapValue.erase("timesmart"); } //! make sure balances are recalculated void MarkDirty() { m_amounts[DEBIT].Reset(); m_amounts[CREDIT].Reset(); m_amounts[IMMATURE_CREDIT].Reset(); m_amounts[AVAILABLE_CREDIT].Reset(); fChangeCached = false; } void BindWallet(CWallet *pwalletIn) { pwallet = pwalletIn; MarkDirty(); } //! filter decides which addresses will count towards the debit Amount GetDebit(const isminefilter &filter) const; Amount GetCredit(interfaces::Chain::Lock &locked_chain, const isminefilter &filter) const; Amount GetImmatureCredit(interfaces::Chain::Lock &locked_chain, bool fUseCache = true) const; // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid // having to resolve the issue of member access into incomplete type // CWallet. Amount GetAvailableCredit(interfaces::Chain::Lock &locked_chain, bool fUseCache = true, const isminefilter &filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; Amount GetImmatureWatchOnlyCredit(interfaces::Chain::Lock &locked_chain, const bool fUseCache = true) const; Amount GetChange() const; // Get the marginal bytes if spending the specified output from this // transaction int GetSpendSize(unsigned int out, bool use_max_sig = false) const { return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig); } void GetAmounts(std::list<COutputEntry> &listReceived, std::list<COutputEntry> &listSent, Amount &nFee, const isminefilter &filter) const; bool IsFromMe(const isminefilter &filter) const { return GetDebit(filter) > Amount::zero(); } // True if only scriptSigs are different bool IsEquivalentTo(const CWalletTx &tx) const; bool InMempool() const; bool IsTrusted(interfaces::Chain::Lock &locked_chain) const; int64_t GetTxTime() const; // Pass this transaction to node for mempool insertion and relay to peers if // flag set to true bool SubmitMemoryPoolAndRelay(std::string &err_string, bool relay); // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to // resolve the issue of member access into incomplete type CWallet. Note // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" // in place. std::set<TxId> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; }; class COutput { public: const CWalletTx *tx; int i; int nDepth; /** * Pre-computed estimated size of this output as a fully-signed input in a * transaction. Can be -1 if it could not be calculated. */ int nInputBytes; /** Whether we have the private keys to spend this output */ bool fSpendable; /** Whether we know how to spend this output, ignoring the lack of keys */ bool fSolvable; /** * Whether to use the maximum sized, 72 byte signature when calculating the * size of the input spend. This should only be set when watch-only outputs * are allowed. */ bool use_max_sig; /** * Whether this output is considered safe to spend. Unconfirmed transactions * from outside keys are considered unsafe and will not be used to fund new * spending transactions. */ bool fSafe; COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) { tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in; // If known and signable by the given wallet, compute nInputBytes // Failure will keep this value -1 if (fSpendable && tx) { nInputBytes = tx->GetSpendSize(i, use_max_sig); } } std::string ToString() const; inline CInputCoin GetInputCoin() const { return CInputCoin(tx->tx, i, nInputBytes); } }; struct CoinSelectionParams { bool use_bnb = true; size_t change_output_size = 0; size_t change_spend_size = 0; CFeeRate effective_fee = CFeeRate(Amount::zero()); size_t tx_noinputs_size = 0; CoinSelectionParams(bool use_bnb_, size_t change_output_size_, size_t change_spend_size_, CFeeRate effective_fee_, size_t tx_noinputs_size_) : use_bnb(use_bnb_), change_output_size(change_output_size_), change_spend_size(change_spend_size_), effective_fee(effective_fee_), tx_noinputs_size(tx_noinputs_size_) {} CoinSelectionParams() {} }; // forward declarations for ScanForWalletTransactions/RescanFromTime class WalletRescanReserver; /** * A CWallet is an extension of a keystore, which also maintains a set of * transactions and balances, and provides the ability to create new * transactions. */ class CWallet final : public FillableSigningProvider, public interfaces::Chain::Notifications { private: CKeyingMaterial vMasterKey GUARDED_BY(cs_KeyStore); //! if fUseCrypto is true, mapKeys must be empty //! if fUseCrypto is false, vMasterKey must be empty std::atomic<bool> fUseCrypto; //! keeps track of whether Unlock has run a thorough check before bool fDecryptionThoroughlyChecked; using CryptedKeyMap = std::map<CKeyID, std::pair<CPubKey, std::vector<uint8_t>>>; using WatchOnlySet = std::set<CScript>; using WatchKeyMap = std::map<CKeyID, CPubKey>; bool SetCrypted(); //! will encrypt previously unencrypted keys bool EncryptKeys(CKeyingMaterial &vMasterKeyIn); bool Unlock(const CKeyingMaterial &vMasterKeyIn, bool accept_no_keys = false); CryptedKeyMap mapCryptedKeys GUARDED_BY(cs_KeyStore); WatchOnlySet setWatchOnly GUARDED_BY(cs_KeyStore); WatchKeyMap mapWatchKeys GUARDED_BY(cs_KeyStore); bool AddCryptedKeyInner(const CPubKey &vchPubKey, const std::vector<uint8_t> &vchCryptedSecret); bool AddKeyPubKeyInner(const CKey &key, const CPubKey &pubkey); std::atomic<bool> fAbortRescan{false}; // controlled by WalletRescanReserver std::atomic<bool> fScanningWallet{false}; std::atomic<int64_t> m_scanning_start{0}; std::atomic<double> m_scanning_progress{0}; std::mutex mutexScanning; friend class WalletRescanReserver; WalletBatch *encrypted_batch GUARDED_BY(cs_wallet) = nullptr; //! the current wallet version: clients below this version are not able to //! load the wallet int nWalletVersion GUARDED_BY(cs_wallet) = FEATURE_BASE; //! the maximum wallet format version: memory-only variable that specifies //! to what version this wallet may be upgraded int nWalletMaxVersion GUARDED_BY(cs_wallet) = FEATURE_BASE; int64_t nNextResend = 0; int64_t nLastResend = 0; bool fBroadcastTransactions = false; // Local time that the tip block was received. Used to schedule wallet // rebroadcasts. std::atomic<int64_t> m_best_block_time{0}; /** * Used to keep track of spent outpoints, and detect and report conflicts * (double-spends or mutated transactions where the mutant gets mined). */ typedef std::multimap<COutPoint, TxId> TxSpends; TxSpends mapTxSpends GUARDED_BY(cs_wallet); void AddToSpends(const COutPoint &outpoint, const TxId &wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void AddToSpends(const TxId &wtxid) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Add a transaction to the wallet, or update it. pIndex and posInBlock * should be set when the transaction was known to be included in a * block. When *pIndex == nullptr, then wallet state is not updated in * AddToWallet, but notifications happen and cached balances are marked * dirty. * * If fUpdate is true, existing transactions will be updated. * TODO: One exception to this is that the abandoned state is cleared under * the assumption that any further notification of a transaction that was * considered abandoned is an indication that it is not safe to be * considered abandoned. Abandoned state should probably be more carefully * tracked via different posInBlock signals or by checking mempool presence * when necessary. */ bool AddToWalletIfInvolvingMe(const CTransactionRef &tx, const BlockHash &block_hash, int posInBlock, bool fUpdate) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Mark a transaction (and its in-wallet descendants) as conflicting with a * particular block. */ void MarkConflicted(const BlockHash &hashBlock, const TxId &txid); /** * Mark a transaction's inputs dirty, thus forcing the outputs to be * recomputed */ void MarkInputsDirty(const CTransactionRef &tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Used by * TransactionAddedToMemorypool/BlockConnected/Disconnected/ScanForWalletTransactions. * Should be called with non-zero block_hash and posInBlock if this is for a * transaction that is included in a block. */ void SyncTransaction(const CTransactionRef &tx, const BlockHash &block_hash, int posInBlock = 0, bool update_tx = true) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* the HD chain data model (external chain counters) */ CHDChain hdChain; /* HD derive new child key (on internal or external chain) */ void DeriveNewChildKey(WalletBatch &batch, CKeyMetadata &metadata, CKey &secret, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::set<int64_t> setInternalKeyPool GUARDED_BY(cs_wallet); std::set<int64_t> setExternalKeyPool GUARDED_BY(cs_wallet); std::set<int64_t> set_pre_split_keypool GUARDED_BY(cs_wallet); int64_t m_max_keypool_index GUARDED_BY(cs_wallet) = 0; std::map<CKeyID, int64_t> m_pool_key_to_index; std::atomic<uint64_t> m_wallet_flags{0}; int64_t nTimeFirstKey GUARDED_BY(cs_wallet) = 0; /** * Private version of AddWatchOnly method which does not accept a timestamp, * and which will reset the wallet's nTimeFirstKey value to 1 if the watch * key did not previously have a timestamp associated with it. Because this * is an inherited virtual method, it is accessible despite being marked * private, but it is marked private anyway to encourage use of the other * AddWatchOnly which accepts a timestamp and sets nTimeFirstKey more * intelligently for more efficient rescans. */ bool AddWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool AddWatchOnlyInMem(const CScript &dest); /** Add a KeyOriginInfo to the wallet */ bool AddKeyOriginWithDB(WalletBatch &batch, const CPubKey &pubkey, const KeyOriginInfo &info); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKeyWithDB(WalletBatch &batch, const CKey &key, const CPubKey &pubkey) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a watch-only address to the store, and saves it to disk. bool AddWatchOnlyWithDB(WalletBatch &batch, const CScript &dest, int64_t create_time) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void AddKeypoolPubkeyWithDB(const CPubKey &pubkey, const bool internal, WalletBatch &batch); bool SetAddressBookWithDB(WalletBatch &batch, const CTxDestination &address, const std::string &strName, const std::string &strPurpose); //! Adds a script to the store and saves it to disk bool AddCScriptWithDB(WalletBatch &batch, const CScript &script); //! Unsets a wallet flag and saves it to disk void UnsetWalletFlagWithDB(WalletBatch &batch, uint64_t flag); /** Interface for accessing chain state. */ interfaces::Chain *m_chain; /** * Wallet location which includes wallet name (see WalletLocation). */ WalletLocation m_location; /** Internal database handle. */ std::unique_ptr<WalletDatabase> database; /** * The following is used to keep track of how far behind the wallet is * from the chain sync, and to allow clients to block on us being caught up. * * Note that this is *not* how far we've processed, we may need some rescan * to have seen all transactions in the chain, but is only used to track * live BlockConnected callbacks. */ BlockHash m_last_block_processed GUARDED_BY(cs_wallet); //! Fetches a key from the keypool bool GetKeyFromPool(CPubKey &key, bool internal = false); public: const CChainParams &chainParams; /* * Main wallet lock. * This lock protects all the fields added by CWallet. */ mutable RecursiveMutex cs_wallet; /** * Get database handle used by this wallet. Ideally this function would not * be necessary. */ WalletDatabase &GetDBHandle() { return *database; } /** * Select a set of coins such that nValueRet >= nTargetValue and at least * all coins from coinControl are selected; Never select unconfirmed coins * if they are not ours. */ bool SelectCoins(const std::vector<COutput> &vAvailableCoins, const Amount nTargetValue, std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, const CCoinControl &coin_control, CoinSelectionParams &coin_selection_params, bool &bnb_used) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); const WalletLocation &GetLocation() const { return m_location; } /** * Get a name for this wallet for logging/debugging purposes. */ const std::string &GetName() const { return m_location.GetName(); } void LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void MarkPreSplitKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // Map from Key ID to key metadata. std::map<CKeyID, CKeyMetadata> mapKeyMetadata GUARDED_BY(cs_wallet); // Map from Script ID to key metadata (for watch-only keys). std::map<CScriptID, CKeyMetadata> m_script_metadata GUARDED_BY(cs_wallet); typedef std::map<unsigned int, CMasterKey> MasterKeyMap; MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID = 0; /** Construct wallet with specified name and database implementation. */ CWallet(const CChainParams &chainParamsIn, interfaces::Chain *chain, const WalletLocation &location, std::unique_ptr<WalletDatabase> databaseIn) : fUseCrypto(false), fDecryptionThoroughlyChecked(false), m_chain(chain), m_location(location), database(std::move(databaseIn)), chainParams(chainParamsIn) {} ~CWallet() { // Should not have slots connected at this point. assert(NotifyUnload.empty()); delete encrypted_batch; encrypted_batch = nullptr; } bool IsCrypted() const { return fUseCrypto; } bool IsLocked() const; bool Lock(); std::map<TxId, CWalletTx> mapWallet GUARDED_BY(cs_wallet); typedef std::multimap<int64_t, CWalletTx *> TxItems; TxItems wtxOrdered; int64_t nOrderPosNext GUARDED_BY(cs_wallet) = 0; uint64_t nAccountingEntryNumber = 0; std::map<CTxDestination, CAddressBookData> mapAddressBook GUARDED_BY(cs_wallet); std::set<COutPoint> setLockedCoins GUARDED_BY(cs_wallet); /** Registered interfaces::Chain::Notifications handler. */ std::unique_ptr<interfaces::Handler> m_chain_notifications_handler; /** Interface for accessing chain state. */ interfaces::Chain &chain() const { assert(m_chain); return *m_chain; } const CWalletTx *GetWalletTx(const TxId &txid) const; //! check whether we are allowed to upgrade (or already support) to the //! named feature bool CanSupportFeature(enum WalletFeature wf) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } /** * populate vCoins with vector of available COutputs. */ void AvailableCoins(interfaces::Chain::Lock &locked_chain, std::vector<COutput> &vCoins, bool fOnlySafe = true, const CCoinControl *coinControl = nullptr, const Amount nMinimumAmount = SATOSHI, const Amount nMaximumAmount = MAX_MONEY, const Amount nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0, const int nMinDepth = 0, const int nMaxDepth = 9999999) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Return list of available coins and locked coins grouped by non-change * output address. */ std::map<CTxDestination, std::vector<COutput>> ListCoins(interfaces::Chain::Lock &locked_chain) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Find non-change parent output. */ const CTxOut &FindNonChangeParentOutput(const CTransaction &tx, int output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Shuffle and select coins until nTargetValue is reached while avoiding * small change; This method is stochastic for some inputs and upon * completion the coin set and corresponding actual target value is * assembled. */ bool SelectCoinsMinConf(const Amount nTargetValue, const CoinEligibilityFilter &eligibility_filter, std::vector<OutputGroup> groups, std::set<CInputCoin> &setCoinsRet, Amount &nValueRet, const CoinSelectionParams &coin_selection_params, bool &bnb_used) const; bool IsSpent(interfaces::Chain::Lock &locked_chain, const COutPoint &outpoint) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); // Whether this or any UTXO with the same CTxDestination has been spent. bool IsUsedDestination(const CTxDestination &dst) const; bool IsUsedDestination(const TxId &txid, unsigned int n) const; void SetUsedDestinationState(const TxId &hash, unsigned int n, bool used); std::vector<OutputGroup> GroupOutputs(const std::vector<COutput> &outputs, bool single_coin) const; bool IsLockedCoin(const COutPoint &outpoint) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void LockCoin(const COutPoint &output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void UnlockCoin(const COutPoint &output) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void UnlockAllCoins() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ListLockedCoins(std::vector<COutPoint> &vOutpts) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /* * Rescan abort properties */ void AbortRescan() { fAbortRescan = true; } bool IsAbortingRescan() { return fAbortRescan; } bool IsScanning() { return fScanningWallet; } int64_t ScanningDuration() const { return fScanningWallet ? GetTimeMillis() - m_scanning_start : 0; } double ScanningProgress() const { return fScanningWallet ? double(m_scanning_progress) : 0; } /** * keystore implementation * Generate a new key */ CPubKey GenerateNewKey(WalletBatch &batch, bool internal = false) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a key to the store, without saving it to disk (used by LoadWallet) bool LoadKey(const CKey &key, const CPubKey &pubkey) { return AddKeyPubKeyInner(key, pubkey); } //! Load metadata (used by LoadWallet) void LoadKeyMetadata(const CKeyID &keyID, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void LoadScriptMetadata(const CScriptID &script_id, const CKeyMetadata &metadata) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Upgrade stored CKeyMetadata objects to store key origin info as //! KeyOriginInfo void UpgradeKeyMetadata() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } void UpdateTimeFirstKey(int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds an encrypted key to the store, and saves it to disk. bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector<uint8_t> &vchCryptedSecret); //! Adds an encrypted key to the store, without saving it to disk (used by //! LoadWallet) bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector<uint8_t> &vchCryptedSecret); bool GetKey(const CKeyID &address, CKey &keyOut) const override; bool GetPubKey(const CKeyID &address, CPubKey &vchPubKeyOut) const override; bool HaveKey(const CKeyID &address) const override; std::set<CKeyID> GetKeys() const override; bool AddCScript(const CScript &redeemScript) override; bool LoadCScript(const CScript &redeemScript); //! Adds a destination data tuple to the store, and saves it to disk bool AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Erases a destination data tuple in the store and on disk bool EraseDestData(const CTxDestination &dest, const std::string &key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a destination data tuple to the store, without saving it to disk void LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Look up a destination data tuple in the store, return true if found //! false otherwise bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Get all destination values matching a prefix. std::vector<std::string> GetDestValues(const std::string &prefix) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a watch-only address to the store, and saves it to disk. bool AddWatchOnly(const CScript &dest, int64_t nCreateTime) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool RemoveWatchOnly(const CScript &dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a watch-only address to the store, without saving it to disk (used //! by LoadWallet) bool LoadWatchOnly(const CScript &dest); //! Returns whether the watch-only script is in the wallet bool HaveWatchOnly(const CScript &dest) const; //! Returns whether there are any watch-only things in the wallet bool HaveWatchOnly() const; //! Fetches a pubkey from mapWatchKeys if it exists there bool GetWatchPubKey(const CKeyID &address, CPubKey &pubkey_out) const; //! Holds a timestamp at which point the wallet is scheduled (externally) to //! be relocked. Caller must arrange for actual relocking to occur via //! Lock(). int64_t nRelockTime = 0; bool Unlock(const SecureString &strWalletPassphrase, bool accept_no_keys = false); bool ChangeWalletPassphrase(const SecureString &strOldWalletPassphrase, const SecureString &strNewWalletPassphrase); bool EncryptWallet(const SecureString &strWalletPassphrase); void GetKeyBirthTimes(interfaces::Chain::Lock &locked_chain, std::map<CKeyID, int64_t> &mapKeyBirth) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int ComputeTimeSmart(const CWalletTx &wtx) const; /** * Increment the next transaction order id * @return next transaction order id */ int64_t IncOrderPosNext(WalletBatch *batch = nullptr) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); DBErrors ReorderTransactions(); void MarkDirty(); bool AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose = true); void LoadToWallet(const CWalletTx &wtxIn) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void TransactionAddedToMempool(const CTransactionRef &tx) override; void BlockConnected(const CBlock &block, const std::vector<CTransactionRef> &vtxConflicted) override; void BlockDisconnected(const CBlock &block) override; void UpdatedBlockTip() override; int64_t RescanFromTime(int64_t startTime, const WalletRescanReserver &reserver, bool update); struct ScanResult { enum { SUCCESS, FAILURE, USER_ABORT } status = SUCCESS; //! Hash and height of most recent block that was successfully scanned. //! Unset if no blocks were scanned due to read errors or the chain //! being empty. BlockHash last_scanned_block; Optional<int> last_scanned_height; //! Hash of the most recent block that could not be scanned due to //! read errors or pruning. Will be set if status is FAILURE, unset if //! status is SUCCESS, and may or may not be set if status is //! USER_ABORT. BlockHash last_failed_block; }; ScanResult ScanForWalletTransactions(const BlockHash &first_block, const BlockHash &last_block, const WalletRescanReserver &reserver, bool fUpdate); void TransactionRemovedFromMempool(const CTransactionRef &ptx) override; void ReacceptWalletTransactions(interfaces::Chain::Lock &locked_chain) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ResendWalletTransactions(); struct Balance { //! Trusted, at depth=GetBalance.min_depth or more Amount m_mine_trusted{Amount::zero()}; //! Untrusted, but in mempool (pending) Amount m_mine_untrusted_pending{Amount::zero()}; //! Immature coinbases in the main chain Amount m_mine_immature{Amount::zero()}; Amount m_watchonly_trusted{Amount::zero()}; Amount m_watchonly_untrusted_pending{Amount::zero()}; Amount m_watchonly_immature{Amount::zero()}; }; Balance GetBalance(int min_depth = 0, bool avoid_reuse = true) const; Amount GetAvailableBalance(const CCoinControl *coinControl = nullptr) const; OutputType TransactionChangeType(OutputType change_type, const std::vector<CRecipient> &vecSend); /** * Insert additional inputs into the transaction by calling * CreateTransaction(); */ bool FundTransaction(CMutableTransaction &tx, Amount &nFeeRet, int &nChangePosInOut, std::string &strFailReason, bool lockUnspents, const std::set<int> &setSubtractFeeFromOutputs, CCoinControl coinControl); bool SignTransaction(CMutableTransaction &tx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); /** * Create a new transaction paying the recipients with a set of coins * selected by SelectCoins(); Also create the change output, when needed * @note passing nChangePosInOut as -1 will result in setting a random * position */ bool CreateTransaction(interfaces::Chain::Lock &locked_chain, const std::vector<CRecipient> &vecSend, CTransactionRef &tx, Amount &nFeeRet, int &nChangePosInOut, std::string &strFailReason, const CCoinControl &coin_control, bool sign = true); bool CommitTransaction( CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm, TxValidationState &state); bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const { std::vector<CTxOut> v_txouts(txouts.size()); std::copy(txouts.begin(), txouts.end(), v_txouts.begin()); return DummySignTx(txNew, v_txouts, use_max_sig); } bool DummySignTx(CMutableTransaction &txNew, const std::vector<CTxOut> &txouts, bool use_max_sig = false) const; bool DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig = false) const; bool ImportScripts(const std::set<CScript> scripts, int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportPrivKeys(const std::map<CKeyID, CKey> &privkey_map, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportPubKeys( const std::vector<CKeyID> &ordered_pubkeys, const std::map<CKeyID, CPubKey> &pubkey_map, const std::map<CKeyID, std::pair<CPubKey, KeyOriginInfo>> &key_origins, const bool add_keypool, const bool internal, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool ImportScriptPubKeys(const std::string &label, const std::set<CScript> &script_pub_keys, const bool have_solving_data, const bool apply_label, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); CFeeRate m_pay_tx_fee{DEFAULT_PAY_TX_FEE}; bool m_spend_zero_conf_change{DEFAULT_SPEND_ZEROCONF_CHANGE}; // will be defined via chainparams bool m_allow_fallback_fee{true}; // Override with -mintxfee CFeeRate m_min_fee{DEFAULT_TRANSACTION_MINFEE_PER_KB}; /** * If fee estimation does not have enough data to provide estimates, use * this fee instead. Has no effect if not using fee estimation Override with * -fallbackfee */ CFeeRate m_fallback_fee{DEFAULT_FALLBACK_FEE}; OutputType m_default_address_type{DEFAULT_ADDRESS_TYPE}; OutputType m_default_change_type{DEFAULT_CHANGE_TYPE}; /** * Absolute maximum transaction fee (in satoshis) used by default for the * wallet. */ Amount m_default_max_tx_fee{DEFAULT_TRANSACTION_MAXFEE}; bool NewKeyPool(); size_t KeypoolCountExternalKeys() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool TopUpKeyPool(unsigned int kpSize = 0); /** * Reserves a key from the keypool and sets nIndex to its index * * @param[out] nIndex the index of the key in keypool * @param[out] keypool the keypool the key was drawn from, which could be * the the pre-split pool if present, or the internal or external pool * @param fRequestedInternal true if the caller would like the key drawn * from the internal keypool, false if external is preferred * * @return true if succeeded, false if failed due to empty keypool * @throws std::runtime_error if keypool read failed, key was invalid, * was not found in the wallet, or was misclassified in the internal * or external keypool */ bool ReserveKeyFromKeyPool(int64_t &nIndex, CKeyPool &keypool, bool fRequestedInternal); void KeepKey(int64_t nIndex); void ReturnKey(int64_t nIndex, bool fInternal, const CPubKey &pubkey); int64_t GetOldestKeyPoolTime(); /** * Marks all keys in the keypool up to and including reserve_key as used. */ void MarkReserveKeysAsUsed(int64_t keypool_id) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); const std::map<CKeyID, int64_t> &GetAllReserveKeys() const { return m_pool_key_to_index; } std::set<std::set<CTxDestination>> GetAddressGroupings() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); std::map<CTxDestination, Amount> GetAddressBalances(interfaces::Chain::Lock &locked_chain); std::set<CTxDestination> GetLabelAddresses(const std::string &label) const; bool GetNewDestination(const OutputType type, const std::string label, CTxDestination &dest, std::string &error); bool GetNewChangeDestination(const OutputType type, CTxDestination &dest, std::string &error); isminetype IsMine(const CTxIn &txin) const; /** * Returns amount of debit if the input matches the filter, otherwise * returns 0 */ Amount GetDebit(const CTxIn &txin, const isminefilter &filter) const; isminetype IsMine(const CTxOut &txout) const; Amount GetCredit(const CTxOut &txout, const isminefilter &filter) const; bool IsChange(const CTxOut &txout) const; bool IsChange(const CScript &script) const; Amount GetChange(const CTxOut &txout) const; bool IsMine(const CTransaction &tx) const; /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction &tx) const; Amount GetDebit(const CTransaction &tx, const isminefilter &filter) const; /** Returns whether all of the inputs match the filter */ bool IsAllFromMe(const CTransaction &tx, const isminefilter &filter) const; Amount GetCredit(const CTransaction &tx, const isminefilter &filter) const; Amount GetChange(const CTransaction &tx) const; void ChainStateFlushed(const CBlockLocator &loc) override; DBErrors LoadWallet(bool &fFirstRunRet); DBErrors ZapWalletTx(std::vector<CWalletTx> &vWtx); DBErrors ZapSelectTx(std::vector<TxId> &txIdsIn, std::vector<TxId> &txIdsOut) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool SetAddressBook(const CTxDestination &address, const std::string &strName, const std::string &purpose); bool DelAddressBook(const CTxDestination &address); const std::string &GetLabelName(const CScript &scriptPubKey) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); unsigned int GetKeyPoolSize() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return setInternalKeyPool.size() + setExternalKeyPool.size(); } //! signify that a particular wallet feature is now used. this may change //! nWalletVersion and nWalletMaxVersion if those are lower void SetMinVersion(enum WalletFeature, WalletBatch *batch_in = nullptr, bool fExplicit = false); //! change which version we're allowed to upgrade to (note that this does //! not immediately imply upgrading to that format) bool SetMaxVersion(int nVersion); //! get the current wallet format (the oldest client version guaranteed to //! understand this wallet) int GetVersion() { LOCK(cs_wallet); return nWalletVersion; } //! Get wallet transactions that conflict with given transaction (spend same //! outputs) std::set<TxId> GetConflicts(const TxId &txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Check if a given transaction has any of its outputs spent by another //! transaction in the wallet bool HasWalletSpend(const TxId &txid) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Flush wallet (bitdb flush) void Flush(bool shutdown = false); /** Wallet is about to be unloaded */ boost::signals2::signal<void()> NotifyUnload; /** * Address book entry changed. * @note called with lock cs_wallet held. */ boost::signals2::signal<void(CWallet *wallet, const CTxDestination &address, const std::string &label, bool isMine, const std::string &purpose, ChangeType status)> NotifyAddressBookChanged; /** * Wallet transaction added, removed or updated. * @note called with lock cs_wallet held. */ boost::signals2::signal<void(CWallet *wallet, const TxId &txid, ChangeType status)> NotifyTransactionChanged; /** Show progress e.g. for rescan */ boost::signals2::signal<void(const std::string &title, int nProgress)> ShowProgress; /** Watch-only address added */ boost::signals2::signal<void(bool fHaveWatchOnly)> NotifyWatchonlyChanged; /** Keypool has new keys */ boost::signals2::signal<void()> NotifyCanGetAddressesChanged; /** * Wallet status (encrypted, locked) changed. * Note: Called without locks held. */ boost::signals2::signal<void(CWallet *wallet)> NotifyStatusChanged; /** Inquire whether this wallet broadcasts transactions. */ bool GetBroadcastTransactions() const { return fBroadcastTransactions; } /** Set whether this wallet broadcasts transactions. */ void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; } /** Return whether transaction can be abandoned */ bool TransactionCanBeAbandoned(const TxId &txid) const; /** * Mark a transaction (and it in-wallet descendants) as abandoned so its * inputs may be respent. */ bool AbandonTransaction(interfaces::Chain::Lock &locked_chain, const TxId &txid); //! Verify wallet naming and perform salvage on the wallet if required static bool Verify(const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, bool salvage_wallet, std::string &error_string, std::string &warning_string); /** * Initializes the wallet, returns a new CWallet instance or a null pointer * in case of an error. */ static std::shared_ptr<CWallet> CreateWalletFromFile( const CChainParams &chainParams, interfaces::Chain &chain, const WalletLocation &location, uint64_t wallet_creation_flags = 0); /** * Wallet post-init setup * Gives the wallet a chance to register repetitive tasks and complete * post-init tasks */ void postInitProcess(); bool BackupWallet(const std::string &strDest); /* Set the HD chain model (chain child index counters) */ void SetHDChain(const CHDChain &chain, bool memonly); const CHDChain &GetHDChain() const { return hdChain; } /* Returns true if HD is enabled */ bool IsHDEnabled() const; /* Returns true if the wallet can generate new keys */ bool CanGenerateKeys(); /** * Returns true if the wallet can give out new addresses. This means it has * keys in the keypool or can generate new keys. */ bool CanGetAddresses(bool internal = false); /* Generates a new HD seed (will not be activated) */ CPubKey GenerateNewSeed(); /** * Derives a new HD seed (will not be activated) */ CPubKey DeriveNewSeed(const CKey &key); /** * Set the current HD seed (will reset the chain child index counters) * Sets the seed's version based on the current wallet version (so the * caller must ensure the current wallet version is correct before calling * this function). */ void SetHDSeed(const CPubKey &key); /** * Blocks until the wallet state is up-to-date to /at least/ the current * chain at the time this function is entered. * Obviously holding cs_main/cs_wallet when going into this call may cause * deadlock */ void BlockUntilSyncedToCurrentChain() LOCKS_EXCLUDED(cs_main, cs_wallet); /** * Explicitly make the wallet learn the related scripts for outputs to the * given key. This is purely to make the wallet file compatible with older * software, as FillableSigningProvider automatically does this implicitly * for all keys now. */ void LearnRelatedScripts(const CPubKey &key, OutputType); /** * Same as LearnRelatedScripts, but when the OutputType is not known (and * could be anything). */ void LearnAllRelatedScripts(const CPubKey &key); /** * Set a single wallet flag. */ void SetWalletFlag(uint64_t flags); /** * Unsets a single wallet flag. */ void UnsetWalletFlag(uint64_t flag); /** * Check if a certain wallet flag is set. */ bool IsWalletFlagSet(uint64_t flag) const; /** * Overwrite all flags by the given uint64_t. * Returns false if unknown, non-tolerable flags are present. */ bool SetWalletFlags(uint64_t overwriteFlags, bool memOnly); /** * Returns a bracketed wallet name for displaying in logs, will return * [default wallet] if the wallet has no name. */ const std::string GetDisplayName() const { std::string wallet_name = GetName().length() == 0 ? "default wallet" : GetName(); return strprintf("[%s]", wallet_name); }; /** * Prepends the wallet name in logging output to ease debugging in * multi-wallet use cases. */ template <typename... Params> void WalletLogPrintf(std::string fmt, Params... parameters) const { LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; template <typename... Params> void WalletLogPrintfToBeContinued(std::string fmt, Params... parameters) const { LogPrintfToBeContinued(("%s " + fmt).c_str(), GetDisplayName(), parameters...); }; /** * Implement lookup of key origin information through wallet key metadata. */ bool GetKeyOrigin(const CKeyID &keyid, KeyOriginInfo &info) const override; }; /** * Called periodically by the schedule thread. Prompts individual wallets to * resend their transactions. Actual rebroadcast schedule is managed by the * wallets themselves. */ void MaybeResendWalletTxs(); /** RAII object to check and reserve a wallet rescan */ class WalletRescanReserver { private: CWallet *m_wallet; bool m_could_reserve; public: explicit WalletRescanReserver(CWallet *w) : m_wallet(w), m_could_reserve(false) {} bool reserve() { assert(!m_could_reserve); std::lock_guard<std::mutex> lock(m_wallet->mutexScanning); if (m_wallet->fScanningWallet) { return false; } m_wallet->m_scanning_start = GetTimeMillis(); m_wallet->m_scanning_progress = 0; m_wallet->fScanningWallet = true; m_could_reserve = true; return true; } bool isReserved() const { return (m_could_reserve && m_wallet->fScanningWallet); } ~WalletRescanReserver() { std::lock_guard<std::mutex> lock(m_wallet->mutexScanning); if (m_could_reserve) { m_wallet->fScanningWallet = false; } } }; // Calculate the size of the transaction assuming all signatures are max size // Use DummySignatureCreator, which inserts 71 byte signatures everywhere. // NOTE: this requires that all inputs must be in mapWallet (eg the tx should // be IsAllFromMe). int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet); int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut> &txouts, bool use_max_sig = false); #endif // BITCOIN_WALLET_WALLET_H