diff --git a/doc/release-notes.md b/doc/release-notes.md index 3be26adad..c82682126 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -1,17 +1,22 @@ Bitcoin ABC version 0.21.9 is now available from: <https://download.bitcoinabc.org/0.21.9/> This release includes the following features and fixes: - Improve management of maxfee by the wallet. Wallet changes -------------- When creating a transaction with a fee above `-maxtxfee` (default 0.1 BCH), the RPC commands `walletcreatefundedpsbt` and `fundrawtransaction` will now fail instead of rounding down the fee. Beware that the `feeRate` argument is specified in BCH per kilobyte, not satoshi per byte. RPC changes ----------- The `getblockstats` RPC is faster for fee calculation by using BlockUndo data. Also, `-txindex` is no longer required and `getblockstats` works for all non-pruned blocks. + +Miscellaneous RPC changes +------------ + +- `createwallet` can now create encrypted wallets if a non-empty passphrase is specified. diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 2c9f5b960..daa048b75 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,4806 +1,4850 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2018 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 <node/transaction.h> #include <outputtype.h> #include <policy/fees.h> #include <rpc/mining.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 <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> #include <functional> static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; [[maybe_unused]] 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; } 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(CWallet *const pwallet) { return pwallet && pwallet->IsCrypted() ? "\nRequires wallet passphrase to be set with walletpassphrase " "call." : ""; } bool EnsureWalletIsAvailable(CWallet *const 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(CWallet *const 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; } if (request.fHelp || request.params.size() > 2) { throw std::runtime_error(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."}, }, RPCResult{"\"address\" (string) The new bitcoin address\n"}, RPCExamples{HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "")}, } .ToString()); } 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())); } } if (!pwallet->IsLocked()) { pwallet->TopUpKeyPool(); } // Generate a new key that is added to wallet CPubKey newKey; if (!pwallet->GetKeyFromPool(newKey)) { throw JSONRPCError( RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } pwallet->LearnRelatedScripts(newKey, output_type); CTxDestination dest = GetDestinationForKey(newKey, output_type); pwallet->SetAddressBook(dest, label, "receive"); 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; } if (request.fHelp || request.params.size() > 1) { throw std::runtime_error(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", "")}, } .ToString()); } LOCK(pwallet->cs_wallet); if (!pwallet->CanGetAddresses(true)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: This wallet has no available keys"); } if (!pwallet->IsLocked()) { pwallet->TopUpKeyPool(); } 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())); } } CReserveKey reservekey(pwallet); CPubKey vchPubKey; if (!reservekey.GetReservedKey(vchPubKey, true)) { throw JSONRPCError( RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } reservekey.KeepKey(); pwallet->LearnRelatedScripts(vchPubKey, output_type); CTxDestination dest = GetDestinationForKey(vchPubKey, output_type); 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; } if (request.fHelp || request.params.size() != 2) { throw std::runtime_error(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\"")}, } .ToString()); } 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().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 CReserveKey reservekey(pwallet); 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, reservekey, 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); } CValidationState state; if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, reservekey, 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; } if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) { throw std::runtime_error(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."}, }, 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\"")}, } .ToString()); } // 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; 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; } if (request.fHelp || request.params.size() != 0) { throw std::runtime_error(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", "")}, } .ToString()); } // 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; } if (request.fHelp || request.params.size() != 2) { throw std::runtime_error(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", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4" "XX\" \"signature\" \"my " "message\"") + "\nAs a JSON-RPC call\n" + HelpExampleRpc( "signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error(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")}, } .ToString()); } // 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; CValidationState 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; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error(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")}, } .ToString()); } // 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; CValidationState 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; } if (request.fHelp || (request.params.size() > 3)) { throw std::runtime_error(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')"}, }, 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")}, } .ToString()); } // 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; } const auto bal = pwallet->GetBalance(min_depth); 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; } if (request.fHelp || request.params.size() > 0) { throw std::runtime_error(RPCHelpMan{ "getunconfirmedbalance", "Returns the server's total unconfirmed balance\n", {}, RPCResults{}, RPCExamples{""}, } .ToString()); } // 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; } const RPCHelpMan help{ "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\"")}, }; if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { throw std::runtime_error(help.ToString()); } // 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 CReserveKey keyChange(pwallet); Amount nFeeRequired = Amount::zero(); int nChangePosRet = -1; std::string strFailReason; CTransactionRef tx; CCoinControl coinControl; bool fCreated = pwallet->CreateTransaction( *locked_chain, vecSend, tx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coinControl); if (!fCreated) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); } CValidationState state; if (!pwallet->CommitTransaction(tx, std::move(mapValue), {} /* orderForm */, keyChange, 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; } if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { std::string msg = 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\\\"]\"")}, } .ToString(); throw std::runtime_error(msg); } 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 = CreateMultisigRedeemscript(required, pubkeys); CTxDestination dest = AddAndGetDestinationForScript(*pwallet, inner, output_type); 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; CValidationState 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; } if (request.fHelp || request.params.size() > 4) { throw std::runtime_error(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\"")}, } .ToString()); } // 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; } if (request.fHelp || request.params.size() > 3) { throw std::runtime_error(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")}, } .ToString()); } // 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; } if (request.fHelp || request.params.size() > 4) { throw std::runtime_error(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")}, } .ToString()); } // 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; } std::vector<UniValue> arrTmp = ret.getValues(); std::vector<UniValue>::iterator first = arrTmp.begin(); std::advance(first, nFrom); std::vector<UniValue>::iterator last = arrTmp.begin(); std::advance(last, nFrom + nCount); if (last != arrTmp.end()) { arrTmp.erase(last, arrTmp.end()); } if (first != arrTmp.begin()) { arrTmp.erase(arrTmp.begin(), first); } // Return oldest to newest std::reverse(arrTmp.begin(), arrTmp.end()); ret.clear(); ret.setArray(); ret.push_backV(arrTmp); return ret; } 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; } if (request.fHelp || request.params.size() > 4) { throw std::runtime_error(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", "\"000000000000000bacf66f7497b7dc4" "5ef753ee9a7d38571037cdb1a57f663ad" "\" 6") + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc4" "5ef753ee9a7d38571037cdb1a57f663ad" "\", 6")}, } .ToString()); } // 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; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error(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" " \"bip125-replaceable\": \"yes|no|unknown\", (string) " "Whether this transaction could be replaced due to BIP125 " "(replace-by-fee);\n" " may be " "unknown for unconfirmed transactions not in the mempool\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", "\"1075db55d416d3ca199f55b6084e211" "5b9345e16c5cf302fc80e9d5fbf5d48d" "\"") + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e211" "5b9345e16c5cf302fc80e9d5fbf5d48d" "\" true") + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e211" "5b9345e16c5cf302fc80e9d5fbf5d48d" "\"")}, } .ToString()); } // 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; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error(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", "\"1075db55d416d3ca199f55b6084" "e2115b9345e16c5cf302fc80e9d5f" "bf5d48d\"") + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084" "e2115b9345e16c5cf302fc80e9d5f" "bf5d48d\"")}, } .ToString()); } // 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; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error(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\"")}, } .ToString()); } // 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; } if (request.fHelp || request.params.size() > 1) { throw std::runtime_error(RPCHelpMan{ "keypoolrefill", "\nFills the keypool." + HelpRequiringPassphrase(pwallet) + "\n", { {"newsize", RPCArg::Type::NUM, /* default */ "100", "The new keypool size"}, }, RPCResults{}, RPCExamples{HelpExampleCli("keypoolrefill", "") + HelpExampleRpc("keypoolrefill", "")}, } .ToString()); } 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; } if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 2)) { throw std::runtime_error(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")}, } .ToString()); } auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (request.fHelp) { return true; } 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; } if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 2)) { throw std::runtime_error(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\"")}, } .ToString()); } auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (request.fHelp) { return true; } 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; } if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 0)) { throw std::runtime_error(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", "")}, } .ToString()); } auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (request.fHelp) { return true; } 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; } if (!pwallet->IsCrypted() && (request.fHelp || request.params.size() != 1)) { throw std::runtime_error(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\"")}, } .ToString()); } auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); if (request.fHelp) { return true; } 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; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error(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}]\"")}, } .ToString()); } // 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; } if (request.fHelp || request.params.size() > 0) { throw std::runtime_error(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", "")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) { throw std::runtime_error(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")}, } .ToString()); } 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 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; } if (request.fHelp || request.params.size() != 0) { throw std::runtime_error(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) the total " "confirmed balance of the wallet in " + CURRENCY_UNIT + "\n" " \"unconfirmed_balance\": xxx, (numeric) the total " "unconfirmed balance of the wallet in " + CURRENCY_UNIT + "\n" " \"immature_balance\": xxxxxx, (numeric) the total " "immature balance of the wallet in " + CURRENCY_UNIT + "\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" "}\n"}, RPCExamples{HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "")}, } .ToString()); } // 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)); return obj; } static UniValue listwalletdir(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error(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", "")}, } .ToString()); } 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) { if (request.fHelp || request.params.size() != 0) { throw std::runtime_error(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", "")}, } .ToString()); } 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) { if (request.fHelp || request.params.size() != 1) { throw std::runtime_error(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\"")}, } .ToString()); } 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_node->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 createwallet(const Config &config, const JSONRPCRequest &request) { - if (request.fHelp || request.params.size() < 1 || - request.params.size() > 3) { - throw std::runtime_error(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.\n"}, - }, - 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\"")}, - } - .ToString()); + const RPCHelpMan help{ + "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."}, + }, + 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\"")}, + }; + + if (request.fHelp || !help.IsValidNumArgs(request.params.size())) { + throw std::runtime_error(help.ToString()); } const CChainParams &chainParams = config.GetChainParams(); std::string error; std::string warning; uint64_t flags = 0; if (!request.params[1].isNull() && request.params[1].get_bool()) { flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; } + // Indicate that the wallet is actually supposed to be blank and not just + // blank to make it encrypted + bool create_blank = false; + if (!request.params[2].isNull() && request.params[2].get_bool()) { + create_blank = true; + 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"); + } + // Born encrypted wallets need to be blank first so that wallet creation + // doesn't make any unencrypted keys flags |= WALLET_FLAG_BLANK_WALLET; } WalletLocation location(request.params[0].get_str()); if (location.Exists()) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet " + location.GetName() + " already exists."); } // Wallet::Verify will check if we're trying to create a wallet with a // duplicate name. if (!CWallet::Verify(chainParams, *g_rpc_node->chain, location, false, error, warning)) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet file verification failed: " + error); } std::shared_ptr<CWallet> const wallet = CWallet::CreateWalletFromFile( chainParams, *g_rpc_node->chain, location, flags); if (!wallet) { throw JSONRPCError(RPC_WALLET_ERROR, "Wallet creation failed."); } + + // Encrypt the wallet if there's a passphrase + if (!passphrase.empty() && !(flags & WALLET_FLAG_DISABLE_PRIVATE_KEYS)) { + if (!wallet->EncryptWallet(passphrase)) { + throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, + "Error: Wallet created but failed to encrypt."); + } + + if (!create_blank) { + // Unlock the wallet + if (!wallet->Unlock(passphrase)) { + throw JSONRPCError( + RPC_WALLET_ENCRYPTION_FAILED, + "Error: Wallet was encrypted but could not be unlocked"); + } + + // 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(); UniValue obj(UniValue::VOBJ); obj.pushKV("name", wallet->GetName()); obj.pushKV("warning", warning); return obj; } static UniValue unloadwallet(const Config &config, const JSONRPCRequest &request) { if (request.fHelp || request.params.size() > 1) { throw std::runtime_error(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")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() > 5) { throw std::runtime_error(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" " \"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 } ")}, } .ToString()); } 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; { auto locked_chain = pwallet->chain().lock(); LOCK(pwallet->cs_wallet); pwallet->AvailableCoins(*locked_chain, vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); } LOCK(pwallet->cs_wallet); for (const COutput &out : vecOutputs) { CTxDestination address; const CScript &scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, 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()); } 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; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error(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\"")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) { throw std::runtime_error(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\"")}, } .ToString()); } 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 generate(const Config &config, const JSONRPCRequest &request) { std::shared_ptr<CWallet> const wallet = GetWalletForJSONRPCRequest(request); CWallet *const pwallet = wallet.get(); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error(RPCHelpMan{ "generate", "\nMine up to nblocks blocks immediately (before the RPC call " "returns) to an address in the wallet.\n", { {"nblocks", RPCArg::Type::NUM, RPCArg::Optional::NO, "How many blocks are generated immediately."}, {"maxtries", RPCArg::Type::NUM, /* default */ "1000000", "How many iterations to try."}, }, RPCResult{ "[ blockhashes ] (array) hashes of blocks generated\n"}, RPCExamples{"\nGenerate 11 blocks\n" + HelpExampleCli("generate", "11")}} .ToString()); } if (!IsDeprecatedRPCEnabled(gArgs, "generate")) { throw JSONRPCError(RPC_METHOD_DEPRECATED, "The wallet generate rpc method is deprecated and " "will be fully removed in v0.22. " "To use generate in v0.21, restart bitcoind with " "-deprecatedrpc=generate.\n" "Clients should transition to using the node rpc " "method generatetoaddress\n"); } int num_generate = request.params[0].get_int(); uint64_t max_tries = 1000000; if (!request.params[1].isNull()) { max_tries = request.params[1].get_int(); } std::shared_ptr<CReserveScript> coinbase_script; pwallet->GetScriptForMining(coinbase_script); // If the keypool is exhausted, no script is returned at all. Catch this. if (!coinbase_script) { throw JSONRPCError( RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } // throw an error if no script was provided if (coinbase_script->reserveScript.empty()) { throw JSONRPCError(RPC_INTERNAL_ERROR, "No coinbase script available"); } return generateBlocks(config, coinbase_script, num_generate, max_tries, true); } 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; } if (request.fHelp || request.params.size() > 2) { throw std::runtime_error(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")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error(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 or P2WSH)\n" " \"embedded\" : {...}, (object, optional) " "Information about the address embedded in P2SH or P2WSH, 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\"")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error(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\"")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() > 1) { throw std::runtime_error(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")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() > 2) { throw std::runtime_error(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\"")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 4) { throw std::runtime_error(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\"")}, } .ToString()); } 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; } if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) { throw std::runtime_error(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\\\"}]\"")}, } .ToString()); } 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 // ------------------- ------------------------ ---------------------- ---------- { "generating", "generate", generate, {"nblocks","maxtries"} }, { "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"} }, + { "wallet", "createwallet", createwallet, {"wallet_name", "disable_private_keys", "blank", "passphrase"} }, { "wallet", "encryptwallet", encryptwallet, {"passphrase"} }, { "wallet", "getaddressesbylabel", getaddressesbylabel, {"label"} }, { "wallet", "getaddressinfo", getaddressinfo, {"address"} }, { "wallet", "getbalance", getbalance, {"dummy","minconf","include_watchonly"} }, { "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", "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"} }, { "wallet", "sethdseed", sethdseed, {"newkeypool","seed"} }, { "wallet", "setlabel", setlabel, {"address","label"} }, { "wallet", "settxfee", settxfee, {"amount"} }, { "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])); } } diff --git a/test/functional/wallet_createwallet.py b/test/functional/wallet_createwallet.py index a7b09dae0..810ac2162 100755 --- a/test/functional/wallet_createwallet.py +++ b/test/functional/wallet_createwallet.py @@ -1,128 +1,179 @@ #!/usr/bin/env python3 # Copyright (c) 2018 The Bitcoin Core developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Test createwallet arguments. """ from test_framework.test_framework import BitcoinTestFramework from test_framework.util import ( assert_equal, assert_raises_rpc_error, ) class CreateWalletTest(BitcoinTestFramework): def set_test_params(self): self.setup_clean_chain = False self.num_nodes = 1 self.supports_cli = True def skip_test_if_missing_module(self): self.skip_if_no_wallet() def run_test(self): node = self.nodes[0] # Leave IBD for sethdseed node.generate(1) self.nodes[0].createwallet(wallet_name='w0') w0 = node.get_wallet_rpc('w0') address1 = w0.getnewaddress() self.log.info("Test disableprivatekeys creation.") self.nodes[0].createwallet(wallet_name='w1', disable_private_keys=True) w1 = node.get_wallet_rpc('w1') assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getnewaddress) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w1.getrawchangeaddress) w1.importpubkey(w0.getaddressinfo(address1)['pubkey']) self.log.info('Test that private keys cannot be imported') addr = w0.getnewaddress('', 'legacy') privkey = w0.dumpprivkey(addr) assert_raises_rpc_error( -4, 'Cannot import private keys to a wallet with private keys disabled', w1.importprivkey, privkey) result = w1.importmulti( [{'scriptPubKey': {'address': addr}, 'timestamp': 'now', 'keys': [privkey]}]) assert(not result[0]['success']) assert('warning' not in result[0]) assert_equal(result[0]['error']['code'], -4) assert_equal(result[0]['error']['message'], 'Cannot import private keys to a wallet with private keys disabled') self.log.info("Test blank creation with private keys disabled.") self.nodes[0].createwallet( wallet_name='w2', disable_private_keys=True, blank=True) w2 = node.get_wallet_rpc('w2') assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getnewaddress) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w2.getrawchangeaddress) w2.importpubkey(w0.getaddressinfo(address1)['pubkey']) self.log.info("Test blank creation with private keys enabled.") self.nodes[0].createwallet( wallet_name='w3', disable_private_keys=False, blank=True) w3 = node.get_wallet_rpc('w3') assert_equal(w3.getwalletinfo()['keypoolsize'], 0) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getrawchangeaddress) # Import private key w3.importprivkey(w0.dumpprivkey(address1)) # Imported private keys are currently ignored by the keypool assert_equal(w3.getwalletinfo()['keypoolsize'], 0) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w3.getnewaddress) # Set the seed w3.sethdseed() assert_equal(w3.getwalletinfo()['keypoolsize'], 1) w3.getnewaddress() w3.getrawchangeaddress() self.log.info( "Test blank creation with privkeys enabled and then encryption") self.nodes[0].createwallet( wallet_name='w4', disable_private_keys=False, blank=True) w4 = node.get_wallet_rpc('w4') assert_equal(w4.getwalletinfo()['keypoolsize'], 0) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress) # Encrypt the wallet. Nothing should change about the keypool w4.encryptwallet('pass') assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getnewaddress) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w4.getrawchangeaddress) # Now set a seed and it should work. Wallet should also be encrypted w4.walletpassphrase('pass', 2) w4.sethdseed() w4.getnewaddress() w4.getrawchangeaddress() self.log.info( "Test blank creation with privkeys disabled and then encryption") self.nodes[0].createwallet( wallet_name='w5', disable_private_keys=True, blank=True) w5 = node.get_wallet_rpc('w5') assert_equal(w5.getwalletinfo()['keypoolsize'], 0) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress) # Encrypt the wallet w5.encryptwallet('pass') assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getnewaddress) assert_raises_rpc_error(-4, "Error: This wallet has no available keys", w5.getrawchangeaddress) + self.log.info('New blank and encrypted wallets can be created') + self.nodes[0].createwallet( + wallet_name='wblank', + disable_private_keys=False, + blank=True, + passphrase='thisisapassphrase') + wblank = node.get_wallet_rpc('wblank') + assert_raises_rpc_error( + -13, + "Error: Please enter the wallet passphrase with walletpassphrase first.", + wblank.signmessage, + "needanargument", + "test") + wblank.walletpassphrase('thisisapassphrase', 10) + assert_raises_rpc_error(-4, + "Error: This wallet has no available keys", + wblank.getnewaddress) + assert_raises_rpc_error(-4, + "Error: This wallet has no available keys", + wblank.getrawchangeaddress) + + self.log.info('Test creating a new encrypted wallet.') + # Born encrypted wallet is created (has keys) + self.nodes[0].createwallet( + wallet_name='w6', + disable_private_keys=False, + blank=False, + passphrase='thisisapassphrase') + w6 = node.get_wallet_rpc('w6') + assert_raises_rpc_error( + -13, + "Error: Please enter the wallet passphrase with walletpassphrase first.", + w6.signmessage, + "needanargument", + "test") + w6.walletpassphrase('thisisapassphrase', 10) + w6.signmessage(w6.getnewaddress('', 'legacy'), "test") + w6.keypoolrefill(1) + # There should only be 1 key + walletinfo = w6.getwalletinfo() + assert_equal(walletinfo['keypoolsize'], 1) + assert_equal(walletinfo['keypoolsize_hd_internal'], 1) + # Empty passphrase, error + assert_raises_rpc_error(-16, + 'Cannot encrypt a wallet with a blank password', + self.nodes[0].createwallet, + 'w7', + False, + False, + '') + if __name__ == '__main__': CreateWalletTest().main()