diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index e18c589ba..593f17b92 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1,3512 +1,3546 @@ // Copyright (c) 2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "amount.h" #include "chain.h" #include "chainparams.h" // for GetConsensus. #include "config.h" #include "consensus/validation.h" #include "core_io.h" #include "dstencode.h" #include "init.h" #include "net.h" #include "policy/fees.h" #include "policy/policy.h" #include "rpc/mining.h" #include "rpc/misc.h" #include "rpc/server.h" #include "timedata.h" #include "util.h" #include "utilmoneystr.h" #include "validation.h" #include "wallet.h" #include "walletdb.h" #include #include static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; static std::string urlDecode(const std::string &urlEncoded) { std::string res; if (!urlEncoded.empty()) { char *decoded = evhttp_uridecode(urlEncoded.c_str(), false, nullptr); if (decoded) { res = std::string(decoded); free(decoded); } } return res; } CWallet *GetWalletForJSONRPCRequest(const JSONRPCRequest &request) { if (request.URI.substr(0, WALLET_ENDPOINT_BASE.size()) == WALLET_ENDPOINT_BASE) { // wallet endpoint was used std::string requestedWallet = urlDecode(request.URI.substr(WALLET_ENDPOINT_BASE.size())); for (CWalletRef pwallet : ::vpwallets) { if (pwallet->GetName() == requestedWallet) { return pwallet; } } throw JSONRPCError(RPC_WALLET_NOT_FOUND, "Requested wallet does not exist or is not loaded"); } return ::vpwallets.size() == 1 || (request.fHelp && ::vpwallets.size() > 0) ? ::vpwallets[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 (::vpwallets.empty()) { // Note: It isn't currently possible to trigger this error because // wallet RPC methods aren't registered unless a wallet is loaded. But // this error is being kept as a precaution, because it's possible in // the future that wallet RPC methods might get or remain registered // when no wallets are loaded. 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/ 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."); } } void WalletTxToJSON(const CWalletTx &wtx, UniValue &entry) { int confirms = wtx.GetDepthInMainChain(); entry.push_back(Pair("confirmations", confirms)); if (wtx.IsCoinBase()) { entry.push_back(Pair("generated", true)); } if (confirms > 0) { entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex())); entry.push_back(Pair("blockindex", wtx.nIndex)); entry.push_back( Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime())); } else { entry.push_back(Pair("trusted", wtx.IsTrusted())); } uint256 hash = wtx.GetId(); entry.push_back(Pair("txid", hash.GetHex())); UniValue conflicts(UniValue::VARR); for (const uint256 &conflict : wtx.GetConflicts()) { conflicts.push_back(conflict.GetHex()); } entry.push_back(Pair("walletconflicts", conflicts)); entry.push_back(Pair("time", wtx.GetTxTime())); entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived)); for (const std::pair &item : wtx.mapValue) { entry.push_back(Pair(item.first, item.second)); } } std::string AccountFromValue(const UniValue &value) { std::string strAccount = value.get_str(); if (strAccount == "*") { throw JSONRPCError(RPC_WALLET_INVALID_ACCOUNT_NAME, "Invalid account name"); } return strAccount; } static UniValue getnewaddress(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( "getnewaddress ( \"account\" )\n" "\nReturns a new Bitcoin address for receiving payments.\n" "If 'account' is specified (DEPRECATED), it is added to the " "address book \n" "so payments received with the address will be credited to " "'account'.\n" "\nArguments:\n" "1. \"account\" (string, optional) DEPRECATED. The account " "name for the address to be linked to. If not provided, the " "default account \"\" is used. It can also be set to the empty " "string \"\" to represent the default account. The account does " "not need to exist, it will be created if there is no account by " "the given name.\n" "\nResult:\n" "\"address\" (string) The new bitcoin address\n" "\nExamples:\n" + HelpExampleCli("getnewaddress", "") + HelpExampleRpc("getnewaddress", "")); } LOCK2(cs_main, pwallet->cs_wallet); // Parse the account first so we don't generate a key if there's an error std::string strAccount; if (request.params.size() > 0) { strAccount = AccountFromValue(request.params[0]); } 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"); } CKeyID keyID = newKey.GetID(); pwallet->SetAddressBook(keyID, strAccount, "receive"); return EncodeDestination(keyID); } CTxDestination GetAccountAddress(CWallet *const pwallet, std::string strAccount, bool bForceNew = false) { CPubKey pubKey; if (!pwallet->GetAccountPubkey(pubKey, strAccount, bForceNew)) { throw JSONRPCError( RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } return pubKey.GetID(); } static UniValue getaccountaddress(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "getaccountaddress \"account\"\n" "\nDEPRECATED. Returns the current Bitcoin address for receiving " "payments to this account.\n" "\nArguments:\n" "1. \"account\" (string, required) The account name for the " "address. It can also be set to the empty string \"\" to represent " "the default account. The account does not need to exist, it will " "be created and a new address created if there is no account by " "the given name.\n" "\nResult:\n" "\"address\" (string) The account bitcoin address\n" "\nExamples:\n" + HelpExampleCli("getaccountaddress", "") + HelpExampleCli("getaccountaddress", "\"\"") + HelpExampleCli("getaccountaddress", "\"myaccount\"") + HelpExampleRpc("getaccountaddress", "\"myaccount\"")); } LOCK2(cs_main, pwallet->cs_wallet); // Parse the account first so we don't generate a key if there's an error std::string strAccount = AccountFromValue(request.params[0]); UniValue ret(UniValue::VSTR); ret = EncodeDestination(GetAccountAddress(pwallet, strAccount)); return ret; } static UniValue getrawchangeaddress(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( "getrawchangeaddress\n" "\nReturns a new Bitcoin address, for receiving change.\n" "This is for use with raw transactions, NOT normal use.\n" "\nResult:\n" "\"address\" (string) The address\n" "\nExamples:\n" + HelpExampleCli("getrawchangeaddress", "") + HelpExampleRpc("getrawchangeaddress", "")); } LOCK2(cs_main, pwallet->cs_wallet); if (!pwallet->IsLocked()) { pwallet->TopUpKeyPool(); } 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(); CKeyID keyID = vchPubKey.GetID(); return EncodeDestination(keyID); } static UniValue setaccount(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "setaccount \"address\" \"account\"\n" "\nDEPRECATED. Sets the account associated with the given " "address.\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address to " "be associated with an account.\n" "2. \"account\" (string, required) The account to assign " "the address to.\n" "\nExamples:\n" + HelpExampleCli("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + HelpExampleRpc( "setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"")); } LOCK2(cs_main, pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } std::string strAccount; if (request.params.size() > 1) { strAccount = AccountFromValue(request.params[1]); } // Only add the account if the address is yours. if (IsMine(*pwallet, dest)) { // Detect when changing the account of an address that is the 'unused // current key' of another account: if (pwallet->mapAddressBook.count(dest)) { std::string strOldAccount = pwallet->mapAddressBook[dest].name; if (dest == GetAccountAddress(pwallet, strOldAccount)) { GetAccountAddress(pwallet, strOldAccount, true); } } pwallet->SetAddressBook(dest, strAccount, "receive"); } else { throw JSONRPCError(RPC_MISC_ERROR, "setaccount can only be used with own address"); } return NullUniValue; } static UniValue getaccount(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "getaccount \"address\"\n" "\nDEPRECATED. Returns the account associated with the given " "address.\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address for " "account lookup.\n" "\nResult:\n" "\"accountname\" (string) the account address\n" "\nExamples:\n" + HelpExampleCli("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + HelpExampleRpc("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"")); } LOCK2(cs_main, pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } std::string strAccount; std::map::iterator mi = pwallet->mapAddressBook.find(dest); if (mi != pwallet->mapAddressBook.end() && !(*mi).second.name.empty()) { strAccount = (*mi).second.name; } return strAccount; } static UniValue getaddressesbyaccount(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "getaddressesbyaccount \"account\"\n" "\nDEPRECATED. Returns the list of addresses for the given " "account.\n" "\nArguments:\n" "1. \"account\" (string, required) The account name.\n" "\nResult:\n" "[ (json array of string)\n" " \"address\" (string) a bitcoin address associated with " "the given account\n" " ,...\n" "]\n" "\nExamples:\n" + HelpExampleCli("getaddressesbyaccount", "\"tabby\"") + HelpExampleRpc("getaddressesbyaccount", "\"tabby\"")); } LOCK2(cs_main, pwallet->cs_wallet); std::string strAccount = AccountFromValue(request.params[0]); // Find all addresses that have the given account UniValue ret(UniValue::VARR); for (const std::pair &item : pwallet->mapAddressBook) { const CTxDestination &dest = item.first; const std::string &strName = item.second.name; if (strName == strAccount) { ret.push_back(EncodeDestination(dest)); } } return ret; } static void SendMoney(CWallet *const pwallet, const CTxDestination &address, Amount nValue, bool fSubtractFeeFromAmount, CWalletTx &wtxNew) { Amount curBalance = pwallet->GetBalance(); // Check amount if (nValue <= Amount(0)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); } if (nValue > curBalance) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); } if (pwallet->GetBroadcastTransactions() && !g_connman) { 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 vecSend; int nChangePosRet = -1; CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) { 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(wtxNew, reservekey, g_connman.get(), state)) { strError = strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason()); throw JSONRPCError(RPC_WALLET_ERROR, strError); } } static UniValue sendtoaddress(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) { throw std::runtime_error( "sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" " "subtractfeefromamount )\n" "\nSend an amount to a given address.\n" + HelpRequiringPassphrase(pwallet) + "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address " "to send to.\n" "2. \"amount\" (numeric or string, required) The " "amount in " + CURRENCY_UNIT + " to send. eg 0.1\n" "3. \"comment\" (string, optional) A comment used to " "store what the transaction is for. \n" " This is not part of the transaction, " "just kept in your wallet.\n" "4. \"comment_to\" (string, optional) 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.\n" "5. subtractfeefromamount (boolean, optional, 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.\n" "\nResult:\n" "\"txid\" (string) The transaction id.\n" "\nExamples:\n" + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1") + HelpExampleCli("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay" "dd\" 0.1 \"donation\" \"seans " "outpost\"") + HelpExampleCli( "sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.1 \"\" \"\" true") + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvay" "dd\", 0.1, \"donation\", \"seans " "outpost\"")); } LOCK2(cs_main, pwallet->cs_wallet); CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); } // Amount Amount nAmount = AmountFromValue(request.params[1]); if (nAmount <= Amount(0)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); } // Wallet comments CWalletTx wtx; if (request.params.size() > 2 && !request.params[2].isNull() && !request.params[2].get_str().empty()) { wtx.mapValue["comment"] = request.params[2].get_str(); } if (request.params.size() > 3 && !request.params[3].isNull() && !request.params[3].get_str().empty()) { wtx.mapValue["to"] = request.params[3].get_str(); } bool fSubtractFeeFromAmount = false; if (request.params.size() > 4) { fSubtractFeeFromAmount = request.params[4].get_bool(); } EnsureWalletIsUnlocked(pwallet); SendMoney(pwallet, dest, nAmount, fSubtractFeeFromAmount, wtx); return wtx.GetId().GetHex(); } static UniValue listaddressgroupings(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp) { throw std::runtime_error( "listaddressgroupings\n" "\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" "\nResult:\n" "[\n" " [\n" " [\n" " \"address\", (string) The bitcoin address\n" " amount, (numeric) The amount in " + CURRENCY_UNIT + "\n" " \"account\" (string, optional) " "DEPRECATED. The account\n" " ]\n" " ,...\n" " ]\n" " ,...\n" "]\n" "\nExamples:\n" + HelpExampleCli("listaddressgroupings", "") + HelpExampleRpc("listaddressgroupings", "")); } LOCK2(cs_main, pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); std::map balances = pwallet->GetAddressBalances(); for (const std::set &grouping : pwallet->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); for (const CTxDestination &address : grouping) { UniValue addressInfo(UniValue::VARR); addressInfo.push_back(EncodeDestination(address)); 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) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 2) { throw std::runtime_error( "signmessage \"address\" \"message\"\n" "\nSign a message with the private key of an address" + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address to " "use for the private key.\n" "2. \"message\" (string, required) The message to create a " "signature of.\n" "\nResult:\n" "\"signature\" (string) The signature of the message " "encoded in base 64\n" "\nExamples:\n" "\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 json rpc\n" + HelpExampleRpc( "signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"")); } LOCK2(cs_main, 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); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address"); } const CKeyID *keyID = boost::get(&dest); if (!keyID) { throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); } CKey key; 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 vchSig; if (!key.SignCompact(ss.GetHash(), vchSig)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); } return EncodeBase64(&vchSig[0], vchSig.size()); } static UniValue getreceivedbyaddress(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "getreceivedbyaddress \"address\" ( minconf )\n" "\nReturns the total amount received by the given address in " "transactions with at least minconf confirmations.\n" "\nArguments:\n" "1. \"address\" (string, required) The bitcoin address for " "transactions.\n" "2. minconf (numeric, optional, default=1) Only " "include transactions confirmed at least this many times.\n" "\nResult:\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received at this address.\n" "\nExamples:\n" "\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 confirmation, very safe\n" + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") + "\nAs a json rpc call\n" + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6")); } LOCK2(cs_main, pwallet->cs_wallet); // Bitcoin address CTxDestination dest = DecodeDestination(request.params[0].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } CScript scriptPubKey = GetScriptForDestination(dest); if (!IsMine(*pwallet, scriptPubKey)) { return ValueFromAmount(Amount(0)); } // Minimum confirmations int nMinDepth = 1; if (request.params.size() > 1) { nMinDepth = request.params[1].get_int(); } // Tally Amount nAmount(0); for (const std::pair &pairWtx : pwallet->mapWallet) { const CWalletTx &wtx = pairWtx.second; CValidationState state; if (wtx.IsCoinBase() || !ContextualCheckTransactionForCurrentBlock(config, *wtx.tx, state)) { continue; } for (const CTxOut &txout : wtx.tx->vout) { if (txout.scriptPubKey == scriptPubKey) { if (wtx.GetDepthInMainChain() >= nMinDepth) { nAmount += txout.nValue; } } } } return ValueFromAmount(nAmount); } static UniValue getreceivedbyaccount(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "getreceivedbyaccount \"account\" ( minconf )\n" "\nDEPRECATED. Returns the total amount received by addresses with " " in transactions with at least [minconf] confirmations.\n" "\nArguments:\n" "1. \"account\" (string, required) The selected account, may " "be the default account using \"\".\n" "2. minconf (numeric, optional, default=1) Only include " "transactions confirmed at least this many times.\n" "\nResult:\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" "\nExamples:\n" "\nAmount received by the default account with at " "least 1 confirmation\n" + HelpExampleCli("getreceivedbyaccount", "\"\"") + "\nAmount received at the tabby account including unconfirmed " "amounts with zero confirmations\n" + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 0") + "\nThe amount with at least 6 confirmation, very safe\n" + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 6") + "\nAs a json rpc call\n" + HelpExampleRpc("getreceivedbyaccount", "\"tabby\", 6")); } LOCK2(cs_main, pwallet->cs_wallet); // Minimum confirmations int nMinDepth = 1; if (request.params.size() > 1) { nMinDepth = request.params[1].get_int(); } // Get the set of pub keys assigned to account std::string strAccount = AccountFromValue(request.params[0]); std::set setAddress = pwallet->GetAccountAddresses(strAccount); // Tally Amount nAmount(0); for (const std::pair &pairWtx : pwallet->mapWallet) { const CWalletTx &wtx = pairWtx.second; CValidationState state; if (wtx.IsCoinBase() || !ContextualCheckTransactionForCurrentBlock(config, *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() >= nMinDepth) { nAmount += txout.nValue; } } } } return ValueFromAmount(nAmount); } static UniValue getbalance(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 3) { throw std::runtime_error( "getbalance ( \"account\" minconf include_watchonly )\n" "\nIf account is not specified, returns the server's total " "available balance.\n" "If account is specified (DEPRECATED), returns the balance in the " "account.\n" "Note that the account \"\" is not the same as leaving the " "parameter out.\n" "The server total may be different to the balance in the default " "\"\" account.\n" "\nArguments:\n" "1. \"account\" (string, optional) DEPRECATED. The account " "string may be given as a\n" " specific account name to find the balance " "associated with wallet keys in\n" " a named account, or as the empty string " "(\"\") to find the balance\n" " associated with wallet keys not in any named " "account, or as \"*\" to find\n" " the balance associated with all wallet keys " "regardless of account.\n" " When this option is specified, it calculates " "the balance in a different\n" " way than when it is not specified, and which " "can count spends twice when\n" " there are conflicting pending transactions " "temporarily resulting in low\n" " or even negative balances.\n" " In general, account balance calculation is " "not considered reliable and\n" " has resulted in confusing outcomes, so it is " "recommended to avoid passing\n" " this argument.\n" "2. minconf (numeric, optional, default=1) Only include " "transactions confirmed at least this many times.\n" "3. include_watchonly (bool, optional, default=false) Also include " "balance in watch-only addresses (see 'importaddress')\n" "\nResult:\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" "\nExamples:\n" "\nThe total amount in the wallet\n" + HelpExampleCli("getbalance", "") + "\nThe total amount in the wallet at least 5 blocks confirmed\n" + HelpExampleCli("getbalance", "\"*\" 6") + "\nAs a json rpc call\n" + HelpExampleRpc("getbalance", "\"*\", 6")); } LOCK2(cs_main, pwallet->cs_wallet); if (request.params.size() == 0) { return ValueFromAmount(pwallet->GetBalance()); } int nMinDepth = 1; if (request.params.size() > 1) { nMinDepth = request.params[1].get_int(); } isminefilter filter = ISMINE_SPENDABLE; if (request.params.size() > 2 && request.params[2].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } if (request.params[0].get_str() == "*") { // Calculate total balance in a very different way from GetBalance(). // The biggest difference is that GetBalance() sums up all unspent // TxOuts paying to the wallet, while this sums up both spent and // unspent TxOuts paying to the wallet, and then subtracts the values of // TxIns spending from the wallet. This also has fewer restrictions on // which unconfirmed transactions are considered trusted. Amount nBalance(0); for (const std::pair &pairWtx : pwallet->mapWallet) { const CWalletTx &wtx = pairWtx.second; CValidationState state; if (!ContextualCheckTransactionForCurrentBlock(config, wtx, state) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0) { continue; } Amount allFee; std::string strSentAccount; std::list listReceived; std::list listSent; wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, filter); if (wtx.GetDepthInMainChain() >= nMinDepth) { for (const COutputEntry &r : listReceived) { nBalance += r.amount; } } for (const COutputEntry &s : listSent) { nBalance -= s.amount; } nBalance -= allFee; } return ValueFromAmount(nBalance); } std::string strAccount = AccountFromValue(request.params[0]); Amount nBalance = pwallet->GetAccountBalance(strAccount, nMinDepth, filter); return ValueFromAmount(nBalance); } static UniValue getunconfirmedbalance(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( "getunconfirmedbalance\n" "Returns the server's total unconfirmed balance\n"); } LOCK2(cs_main, pwallet->cs_wallet); return ValueFromAmount(pwallet->GetUnconfirmedBalance()); } static UniValue movecmd(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 3 || request.params.size() > 5) { throw std::runtime_error( "move \"fromaccount\" \"toaccount\" amount ( minconf \"comment\" " ")\n" "\nDEPRECATED. Move a specified amount from one account in your " "wallet to another.\n" "\nArguments:\n" "1. \"fromaccount\" (string, required) The name of the account " "to move funds from. May be the default account using \"\".\n" "2. \"toaccount\" (string, required) The name of the account " "to move funds to. May be the default account using \"\".\n" "3. amount (numeric) Quantity of " + CURRENCY_UNIT + " to move between accounts.\n" "4. (dummy) (numeric, optional) Ignored. Remains for " "backward compatibility.\n" "5. \"comment\" (string, optional) An optional comment, " "stored in the wallet only.\n" "\nResult:\n" "true|false (boolean) true if successful.\n" "\nExamples:\n" "\nMove 0.01 " + CURRENCY_UNIT + " from the default account to the account named tabby\n" + HelpExampleCli("move", "\"\" \"tabby\" 0.01") + "\nMove 0.01 " + CURRENCY_UNIT + " timotei to akiko with a comment and funds have 6 " "confirmations\n" + HelpExampleCli("move", "\"timotei\" \"akiko\" 0.01 6 \"happy birthday!\"") + "\nAs a json rpc call\n" + HelpExampleRpc( "move", "\"timotei\", \"akiko\", 0.01, 6, \"happy birthday!\"")); } LOCK2(cs_main, pwallet->cs_wallet); std::string strFrom = AccountFromValue(request.params[0]); std::string strTo = AccountFromValue(request.params[1]); Amount nAmount = AmountFromValue(request.params[2]); if (nAmount <= Amount(0)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); } if (request.params.size() > 3) { // Unused parameter, used to be nMinDepth, keep type-checking it though. (void)request.params[3].get_int(); } std::string strComment; if (request.params.size() > 4) { strComment = request.params[4].get_str(); } if (!pwallet->AccountMove(strFrom, strTo, nAmount, strComment)) { throw JSONRPCError(RPC_DATABASE_ERROR, "database error"); } return true; } static UniValue sendfrom(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 3 || request.params.size() > 6) { throw std::runtime_error( "sendfrom \"fromaccount\" \"toaddress\" amount ( minconf " "\"comment\" \"comment_to\" )\n" "\nDEPRECATED (use sendtoaddress). Sent an amount from an account " "to a bitcoin address." + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments:\n" "1. \"fromaccount\" (string, required) The name of the " "account to send funds from. May be the default account using " "\"\".\n" " Specifying an account does not influence " "coin selection, but it does associate the newly created\n" " transaction with the account, so the " "account's balance computation and transaction history can " "reflect\n" " the spend.\n" "2. \"toaddress\" (string, required) The bitcoin address " "to send funds to.\n" "3. amount (numeric or string, required) The amount " "in " + CURRENCY_UNIT + " (transaction fee is added on top).\n" "4. minconf (numeric, optional, default=1) Only use " "funds with at least this many confirmations.\n" "5. \"comment\" (string, optional) A comment used to " "store what the transaction is for. \n" " This is not part of the " "transaction, just kept in your wallet.\n" "6. \"comment_to\" (string, optional) An optional comment " "to store the name of the person or organization \n" " to which you're sending the " "transaction. This is not part of the transaction, \n" " it is just kept in your " "wallet.\n" "\nResult:\n" "\"txid\" (string) The transaction id.\n" "\nExamples:\n" "\nSend 0.01 " + CURRENCY_UNIT + " from the default account to the address, must " "have at least 1 confirmation\n" + HelpExampleCli("sendfrom", "\"\" \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" 0.01") + "\nSend 0.01 from the tabby account to the given address, funds " "must have at least 6 confirmations\n" + HelpExampleCli("sendfrom", "\"tabby\" \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\" " "0.01 6 \"donation\" \"seans outpost\"") + "\nAs a json rpc call\n" + HelpExampleRpc("sendfrom", "\"tabby\", \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", " "0.01, 6, \"donation\", \"seans outpost\"")); } LOCK2(cs_main, pwallet->cs_wallet); std::string strAccount = AccountFromValue(request.params[0]); CTxDestination dest = DecodeDestination(request.params[1].get_str()); if (!IsValidDestination(dest)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); } Amount nAmount = AmountFromValue(request.params[2]); if (nAmount <= Amount(0)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); } int nMinDepth = 1; if (request.params.size() > 3) { nMinDepth = request.params[3].get_int(); } CWalletTx wtx; wtx.strFromAccount = strAccount; if (request.params.size() > 4 && !request.params[4].isNull() && !request.params[4].get_str().empty()) { wtx.mapValue["comment"] = request.params[4].get_str(); } if (request.params.size() > 5 && !request.params[5].isNull() && !request.params[5].get_str().empty()) { wtx.mapValue["to"] = request.params[5].get_str(); } EnsureWalletIsUnlocked(pwallet); // Check funds Amount nBalance = pwallet->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); if (nAmount > nBalance) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); } SendMoney(pwallet, dest, nAmount, false, wtx); return wtx.GetId().GetHex(); } static UniValue sendmany(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) { throw std::runtime_error( "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf " "\"comment\" [\"address\",...] )\n" "\nSend multiple times. Amounts are double-precision floating " "point numbers." + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments:\n" "1. \"fromaccount\" (string, required) DEPRECATED. The " "account to send the funds from. Should be \"\" for the default " "account\n" "2. \"amounts\" (string, required) A json object with " "addresses and amounts\n" " {\n" " \"address\":amount (numeric or string) The bitcoin " "address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n" " ,...\n" " }\n" "3. minconf (numeric, optional, default=1) Only " "use the balance confirmed at least this many times.\n" "4. \"comment\" (string, optional) A comment\n" "5. subtractfeefrom (array, optional) 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.\n" " [\n" " \"address\" (string) Subtract fee from this " "address\n" " ,...\n" " ]\n" "\nResult:\n" "\"txid\" (string) The transaction id for the " "send. Only 1 transaction is created regardless of \n" " the number of addresses.\n" "\nExamples:\n" "\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\"")); } LOCK2(cs_main, pwallet->cs_wallet); if (pwallet->GetBroadcastTransactions() && !g_connman) { throw JSONRPCError( RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } std::string strAccount = AccountFromValue(request.params[0]); UniValue sendTo = request.params[1].get_obj(); int nMinDepth = 1; if (request.params.size() > 2) { nMinDepth = request.params[2].get_int(); } CWalletTx wtx; wtx.strFromAccount = strAccount; if (request.params.size() > 3 && !request.params[3].isNull() && !request.params[3].get_str().empty()) { wtx.mapValue["comment"] = request.params[3].get_str(); } UniValue subtractFeeFromAmount(UniValue::VARR); if (request.params.size() > 4) { subtractFeeFromAmount = request.params[4].get_array(); } std::set destinations; std::vector vecSend; Amount totalAmount(0); std::vector keys = sendTo.getKeys(); for (const std::string &name_ : keys) { CTxDestination dest = DecodeDestination(name_); 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(0)) { throw JSONRPCError(RPC_TYPE_ERROR, "Invalid amount for send"); } totalAmount += nAmount; 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); // Check funds Amount nBalance = pwallet->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); if (totalAmount > nBalance) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); } // Send CReserveKey keyChange(pwallet); Amount nFeeRequired(0); int nChangePosRet = -1; std::string strFailReason; bool fCreated = pwallet->CreateTransaction( vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason); if (!fCreated) { throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); } CValidationState state; if (!pwallet->CommitTransaction(wtx, keyChange, g_connman.get(), state)) { strFailReason = strprintf("Transaction commit failed:: %s", state.GetRejectReason()); throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } return wtx.GetId().GetHex(); } static UniValue addmultisigaddress(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { std::string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" )\n" "\nAdd a nrequired-to-sign multisignature address to the wallet.\n" "Each key is a Bitcoin address or hex-encoded public key.\n" "If 'account' is specified (DEPRECATED), assign address to that " "account.\n" "\nArguments:\n" "1. nrequired (numeric, required) The number of required " "signatures out of the n keys or addresses.\n" "2. \"keys\" (string, required) A json array of bitcoin " "addresses or hex-encoded public keys\n" " [\n" " \"address\" (string) bitcoin address or hex-encoded " "public key\n" " ...,\n" " ]\n" "3. \"account\" (string, optional) DEPRECATED. An account to " "assign the addresses to.\n" "\nResult:\n" "\"address\" (string) A bitcoin address associated with " "the keys.\n" "\nExamples:\n" "\nAdd a multisig address from 2 addresses\n" + HelpExampleCli("addmultisigaddress", "2 " "\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\"," "\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") + "\nAs json rpc call\n" + HelpExampleRpc("addmultisigaddress", "2, " "\"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\"," "\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\""); throw std::runtime_error(msg); } LOCK2(cs_main, pwallet->cs_wallet); std::string strAccount; if (request.params.size() > 2) { strAccount = AccountFromValue(request.params[2]); } // Construct using pay-to-script-hash: CScript inner = createmultisig_redeemScript(pwallet, request.params); CScriptID innerID(inner); pwallet->AddCScript(inner); pwallet->SetAddressBook(innerID, strAccount, "send"); return EncodeDestination(innerID); } struct tallyitem { Amount nAmount; int nConf; std::vector txids; bool fIsWatchonly; tallyitem() { nAmount = Amount(0); nConf = std::numeric_limits::max(); fIsWatchonly = false; } }; static UniValue ListReceived(const Config &config, CWallet *const pwallet, const UniValue ¶ms, bool fByAccounts) { // Minimum confirmations int nMinDepth = 1; if (params.size() > 0) { nMinDepth = params[0].get_int(); } // Whether to include empty accounts bool fIncludeEmpty = false; if (params.size() > 1) { fIncludeEmpty = params[1].get_bool(); } isminefilter filter = ISMINE_SPENDABLE; if (params.size() > 2 && params[2].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } // Tally std::map mapTally; for (const std::pair &pairWtx : pwallet->mapWallet) { const CWalletTx &wtx = pairWtx.second; CValidationState state; if (wtx.IsCoinBase() || !ContextualCheckTransactionForCurrentBlock(config, *wtx.tx, state)) { continue; } int nDepth = wtx.GetDepthInMainChain(); if (nDepth < nMinDepth) { continue; } for (const CTxOut &txout : wtx.tx->vout) { CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, 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 mapAccountTally; for (const std::pair &item : pwallet->mapAddressBook) { const CTxDestination &dest = item.first; const std::string &strAccount = item.second.name; std::map::iterator it = mapTally.find(dest); if (it == mapTally.end() && !fIncludeEmpty) { continue; } Amount nAmount(0); int nConf = std::numeric_limits::max(); bool fIsWatchonly = false; if (it != mapTally.end()) { nAmount = (*it).second.nAmount; nConf = (*it).second.nConf; fIsWatchonly = (*it).second.fIsWatchonly; } if (fByAccounts) { tallyitem &_item = mapAccountTally[strAccount]; _item.nAmount += nAmount; _item.nConf = std::min(_item.nConf, nConf); _item.fIsWatchonly = fIsWatchonly; } else { UniValue obj(UniValue::VOBJ); if (fIsWatchonly) { obj.push_back(Pair("involvesWatchonly", true)); } obj.push_back(Pair("address", EncodeDestination(dest))); obj.push_back(Pair("account", strAccount)); obj.push_back(Pair("amount", ValueFromAmount(nAmount))); obj.push_back( Pair("confirmations", (nConf == std::numeric_limits::max() ? 0 : nConf))); if (!fByAccounts) { obj.push_back(Pair("label", strAccount)); } UniValue transactions(UniValue::VARR); if (it != mapTally.end()) { for (const uint256 &_item : (*it).second.txids) { transactions.push_back(_item.GetHex()); } } obj.push_back(Pair("txids", transactions)); ret.push_back(obj); } } if (fByAccounts) { for (std::map::iterator it = mapAccountTally.begin(); it != mapAccountTally.end(); ++it) { Amount nAmount = (*it).second.nAmount; int nConf = (*it).second.nConf; UniValue obj(UniValue::VOBJ); if ((*it).second.fIsWatchonly) { obj.push_back(Pair("involvesWatchonly", true)); } obj.push_back(Pair("account", (*it).first)); obj.push_back(Pair("amount", ValueFromAmount(nAmount))); obj.push_back( Pair("confirmations", (nConf == std::numeric_limits::max() ? 0 : nConf))); ret.push_back(obj); } } return ret; } static UniValue listreceivedbyaddress(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 3) { throw std::runtime_error( "listreceivedbyaddress ( minconf include_empty include_watchonly)\n" "\nList balances by receiving address.\n" "\nArguments:\n" "1. minconf (numeric, optional, default=1) The minimum " "number of confirmations before payments are included.\n" "2. include_empty (bool, optional, default=false) Whether to " "include addresses that haven't received any payments.\n" "3. include_watchonly (bool, optional, default=false) Whether to " "include watch-only addresses (see 'importaddress').\n" "\nResult:\n" "[\n" " {\n" " \"involvesWatchonly\" : true, (bool) Only returned if " "imported addresses were involved in transaction\n" " \"address\" : \"receivingaddress\", (string) The receiving " "address\n" " \"account\" : \"accountname\", (string) DEPRECATED. The " "account of the receiving address. The default account is \"\".\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) A comment for " "the address/transaction, if any\n" " \"txids\": [\n" " n, (numeric) The ids of " "transactions received with the address \n" " ...\n" " ]\n" " }\n" " ,...\n" "]\n" "\nExamples:\n" + HelpExampleCli("listreceivedbyaddress", "") + HelpExampleCli("listreceivedbyaddress", "6 true") + HelpExampleRpc("listreceivedbyaddress", "6, true, true")); } LOCK2(cs_main, pwallet->cs_wallet); return ListReceived(config, pwallet, request.params, false); } static UniValue listreceivedbyaccount(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 3) { throw std::runtime_error( "listreceivedbyaccount ( minconf include_empty include_watchonly)\n" "\nDEPRECATED. List balances by account.\n" "\nArguments:\n" "1. minconf (numeric, optional, default=1) The minimum " "number of confirmations before payments are included.\n" "2. include_empty (bool, optional, default=false) Whether to " "include accounts that haven't received any payments.\n" "3. include_watchonly (bool, optional, default=false) Whether to " "include watch-only addresses (see 'importaddress').\n" "\nResult:\n" "[\n" " {\n" " \"involvesWatchonly\" : true, (bool) Only returned if " "imported addresses were involved in transaction\n" " \"account\" : \"accountname\", (string) The account name of " "the receiving account\n" " \"amount\" : x.xxx, (numeric) The total amount " "received by addresses with this account\n" " \"confirmations\" : n, (numeric) The number of " "confirmations of the most recent transaction included\n" " \"label\" : \"label\" (string) A comment for the " "address/transaction, if any\n" " }\n" " ,...\n" "]\n" "\nExamples:\n" + HelpExampleCli("listreceivedbyaccount", "") + HelpExampleCli("listreceivedbyaccount", "6 true") + HelpExampleRpc("listreceivedbyaccount", "6, true, true")); } LOCK2(cs_main, pwallet->cs_wallet); return ListReceived(config, pwallet, request.params, true); } static void MaybePushAddress(UniValue &entry, const CTxDestination &dest) { if (IsValidDestination(dest)) { entry.push_back(Pair("address", EncodeDestination(dest))); } } void ListTransactions(CWallet *const pwallet, const CWalletTx &wtx, const std::string &strAccount, int nMinDepth, bool fLong, UniValue &ret, const isminefilter &filter) { Amount nFee; std::string strSentAccount; std::list listReceived; std::list listSent; wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter); bool fAllAccounts = (strAccount == std::string("*")); bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); // Sent if ((!listSent.empty() || nFee != Amount(0)) && (fAllAccounts || strAccount == strSentAccount)) { for (const COutputEntry &s : listSent) { UniValue entry(UniValue::VOBJ); if (involvesWatchonly || (::IsMine(*pwallet, s.destination) & ISMINE_WATCH_ONLY)) { entry.push_back(Pair("involvesWatchonly", true)); } entry.push_back(Pair("account", strSentAccount)); MaybePushAddress(entry, s.destination); entry.push_back(Pair("category", "send")); entry.push_back(Pair("amount", ValueFromAmount(-s.amount))); if (pwallet->mapAddressBook.count(s.destination)) { entry.push_back( Pair("label", pwallet->mapAddressBook[s.destination].name)); } entry.push_back(Pair("vout", s.vout)); entry.push_back(Pair("fee", ValueFromAmount(-1 * nFee))); if (fLong) { WalletTxToJSON(wtx, entry); } entry.push_back(Pair("abandoned", wtx.isAbandoned())); ret.push_back(entry); } } // Received if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) { for (const COutputEntry &r : listReceived) { std::string account; if (pwallet->mapAddressBook.count(r.destination)) { account = pwallet->mapAddressBook[r.destination].name; } if (fAllAccounts || (account == strAccount)) { UniValue entry(UniValue::VOBJ); if (involvesWatchonly || (::IsMine(*pwallet, r.destination) & ISMINE_WATCH_ONLY)) { entry.push_back(Pair("involvesWatchonly", true)); } entry.push_back(Pair("account", account)); MaybePushAddress(entry, r.destination); if (wtx.IsCoinBase()) { if (wtx.GetDepthInMainChain() < 1) { entry.push_back(Pair("category", "orphan")); } else if (wtx.GetBlocksToMaturity() > 0) { entry.push_back(Pair("category", "immature")); } else { entry.push_back(Pair("category", "generate")); } } else { entry.push_back(Pair("category", "receive")); } entry.push_back(Pair("amount", ValueFromAmount(r.amount))); if (pwallet->mapAddressBook.count(r.destination)) { entry.push_back(Pair("label", account)); } entry.push_back(Pair("vout", r.vout)); if (fLong) { WalletTxToJSON(wtx, entry); } ret.push_back(entry); } } } } void AcentryToJSON(const CAccountingEntry &acentry, const std::string &strAccount, UniValue &ret) { bool fAllAccounts = (strAccount == std::string("*")); if (fAllAccounts || acentry.strAccount == strAccount) { UniValue entry(UniValue::VOBJ); entry.push_back(Pair("account", acentry.strAccount)); entry.push_back(Pair("category", "move")); entry.push_back(Pair("time", acentry.nTime)); entry.push_back(Pair("amount", ValueFromAmount(acentry.nCreditDebit))); entry.push_back(Pair("otheraccount", acentry.strOtherAccount)); entry.push_back(Pair("comment", acentry.strComment)); ret.push_back(entry); } } static UniValue listtransactions(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 4) { throw std::runtime_error( "listtransactions ( \"account\" count skip include_watchonly)\n" "\nReturns up to 'count' most recent transactions skipping the " "first 'from' transactions for account 'account'.\n" "\nArguments:\n" "1. \"account\" (string, optional) DEPRECATED. The account " "name. Should be \"*\".\n" "2. count (numeric, optional, default=10) The number of " "transactions to return\n" "3. skip (numeric, optional, default=0) The number of " "transactions to skip\n" "4. include_watchonly (bool, optional, default=false) Include " "transactions to watch-only addresses (see 'importaddress')\n" "\nResult:\n" "[\n" " {\n" " \"account\":\"accountname\", (string) DEPRECATED. The " "account name associated with the transaction. \n" " It will be \"\" " "for the default account.\n" " \"address\":\"address\", (string) The bitcoin address of " "the transaction. Not present for \n" " move transactions " "(category = move).\n" " \"category\":\"send|receive|move\", (string) The transaction " "category. 'move' is a local (off blockchain)\n" " transaction " "between accounts, and not associated with an address,\n" " transaction id or " "block. 'send' and 'receive' transactions are \n" " associated with " "an address, transaction id and block details\n" " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the\n" " 'move' category for " "moves outbound. It is positive for the 'receive' category,\n" " and for the 'move' " "category for inbound funds.\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. Available for 'send' and \n" " 'receive' category of " "transactions. 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. Available for 'send' and 'receive'\n" " category of " "transactions.\n" " \"blockindex\": n, (numeric) The index of the " "transaction in the block that includes it. Available for 'send' " "and 'receive'\n" " 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 (midnight Jan 1 1970 GMT).\n" " \"timereceived\": xxx, (numeric) The time received in " "seconds since epoch (midnight Jan 1 1970 GMT). Available \n" " for 'send' and " "'receive' category of transactions.\n" " \"comment\": \"...\", (string) If a comment is " "associated with the transaction.\n" " \"otheraccount\": \"accountname\", (string) DEPRECATED. For " "the 'move' category of transactions, the account the funds came \n" " from (for receiving " "funds, positive amounts), or went to (for sending funds,\n" " negative amounts).\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" "\nExamples:\n" "\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")); } LOCK2(cs_main, pwallet->cs_wallet); std::string strAccount = "*"; if (request.params.size() > 0) { strAccount = request.params[0].get_str(); } int nCount = 10; if (request.params.size() > 1) { nCount = request.params[1].get_int(); } int nFrom = 0; if (request.params.size() > 2) { nFrom = request.params[2].get_int(); } isminefilter filter = ISMINE_SPENDABLE; if (request.params.size() > 3 && 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); 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.first; if (pwtx != 0) { ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter); } CAccountingEntry *const pacentry = (*it).second.second; if (pacentry != 0) { AcentryToJSON(*pacentry, strAccount, ret); } 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 arrTmp = ret.getValues(); std::vector::iterator first = arrTmp.begin(); std::advance(first, nFrom); std::vector::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 listaccounts(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 2) { throw std::runtime_error( "listaccounts ( minconf include_watchonly)\n" "\nDEPRECATED. Returns Object that has account names as keys, " "account balances as values.\n" "\nArguments:\n" "1. minconf (numeric, optional, default=1) Only " "include transactions with at least this many confirmations\n" "2. include_watchonly (bool, optional, default=false) Include " "balances in watch-only addresses (see 'importaddress')\n" "\nResult:\n" "{ (json object where keys are account names, " "and values are numeric balances\n" " \"account\": x.xxx, (numeric) The property name is the account " "name, and the value is the total balance for the account.\n" " ...\n" "}\n" "\nExamples:\n" "\nList account balances where there at least 1 confirmation\n" + HelpExampleCli("listaccounts", "") + "\nList account balances " "including zero confirmation " "transactions\n" + HelpExampleCli("listaccounts", "0") + "\nList account balances for 6 or more confirmations\n" + HelpExampleCli("listaccounts", "6") + "\nAs json rpc call\n" + HelpExampleRpc("listaccounts", "6")); } LOCK2(cs_main, pwallet->cs_wallet); int nMinDepth = 1; if (request.params.size() > 0) { nMinDepth = request.params[0].get_int(); } isminefilter includeWatchonly = ISMINE_SPENDABLE; if (request.params.size() > 1 && request.params[1].get_bool()) { includeWatchonly = includeWatchonly | ISMINE_WATCH_ONLY; } std::map mapAccountBalances; for (const std::pair &entry : pwallet->mapAddressBook) { // This address belongs to me if (IsMine(*pwallet, entry.first) & includeWatchonly) { mapAccountBalances[entry.second.name] = Amount(0); } } for (const std::pair &pairWtx : pwallet->mapWallet) { const CWalletTx &wtx = pairWtx.second; Amount nFee; std::string strSentAccount; std::list listReceived; std::list listSent; int nDepth = wtx.GetDepthInMainChain(); if (wtx.GetBlocksToMaturity() > 0 || nDepth < 0) { continue; } wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, includeWatchonly); mapAccountBalances[strSentAccount] -= nFee; for (const COutputEntry &s : listSent) { mapAccountBalances[strSentAccount] -= s.amount; } if (nDepth >= nMinDepth) { for (const COutputEntry &r : listReceived) { if (pwallet->mapAddressBook.count(r.destination)) { mapAccountBalances[pwallet->mapAddressBook[r.destination] .name] += r.amount; } else { mapAccountBalances[""] += r.amount; } } } } const std::list &acentries = pwallet->laccentries; for (const CAccountingEntry &entry : acentries) { mapAccountBalances[entry.strAccount] += entry.nCreditDebit; } UniValue ret(UniValue::VOBJ); for (const std::pair &accountBalance : mapAccountBalances) { ret.push_back( Pair(accountBalance.first, ValueFromAmount(accountBalance.second))); } return ret; } static UniValue listsinceblock(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp) { throw std::runtime_error( "listsinceblock ( \"blockhash\" target_confirmations " "include_watchonly)\n" "\nGet all transactions in blocks since block [blockhash], or all " "transactions if omitted\n" "\nArguments:\n" "1. \"blockhash\" (string, optional) The block hash to " "list transactions since\n" "2. target_confirmations: (numeric, optional) The confirmations " "required, must be 1 or more\n" "3. include_watchonly: (bool, optional, default=false) " "Include transactions to watch-only addresses (see 'importaddress')" "\nResult:\n" "{\n" " \"transactions\": [\n" " \"account\":\"accountname\", (string) DEPRECATED. The " "account name associated with the transaction. Will be \"\" for " "the default account.\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" " \"lastblock\": \"lastblockhash\" (string) The hash of the " "last block\n" "}\n" "\nExamples:\n" + HelpExampleCli("listsinceblock", "") + HelpExampleCli("listsinceblock", "\"000000000000000bacf66f7497b7dc4" "5ef753ee9a7d38571037cdb1a57f663ad" "\" 6") + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc4" "5ef753ee9a7d38571037cdb1a57f663ad" "\", 6")); } LOCK2(cs_main, pwallet->cs_wallet); const CBlockIndex *pindex = nullptr; int target_confirms = 1; isminefilter filter = ISMINE_SPENDABLE; if (request.params.size() > 0) { uint256 blockId; blockId.SetHex(request.params[0].get_str()); BlockMap::iterator it = mapBlockIndex.find(blockId); if (it != mapBlockIndex.end()) { pindex = it->second; if (chainActive[pindex->nHeight] != pindex) { // the block being asked for is a part of a deactivated chain; // we don't want to depend on its perceived height in the block // chain, we want to instead use the last common ancestor pindex = chainActive.FindFork(pindex); } } } if (request.params.size() > 1) { target_confirms = request.params[1].get_int(); if (target_confirms < 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); } } if (request.params.size() > 2 && request.params[2].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1; UniValue transactions(UniValue::VARR); for (const std::pair &pairWtx : pwallet->mapWallet) { CWalletTx tx = pairWtx.second; if (depth == -1 || tx.GetDepthInMainChain() < depth) { ListTransactions(pwallet, tx, "*", 0, true, transactions, filter); } } CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms]; uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : uint256(); UniValue ret(UniValue::VOBJ); ret.push_back(Pair("transactions", transactions)); ret.push_back(Pair("lastblock", lastblock.GetHex())); return ret; } static UniValue gettransaction(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "gettransaction \"txid\" ( include_watchonly )\n" "\nGet detailed information about in-wallet transaction \n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction " "id\n" "2. \"include_watchonly\" (bool, optional, default=false) " "Whether to include watch-only addresses in balance calculation " "and details[]\n" "\nResult:\n" "{\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" " \"account\" : \"accountname\", (string) DEPRECATED. " "The account name involved in the transaction, can be \"\" for the " "default account.\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" "\nExamples:\n" + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e211" "5b9345e16c5cf302fc80e9d5fbf5d48d" "\"") + HelpExampleCli("gettransaction", "\"1075db55d416d3ca199f55b6084e211" "5b9345e16c5cf302fc80e9d5fbf5d48d" "\" true") + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e211" "5b9345e16c5cf302fc80e9d5fbf5d48d" "\"")); } LOCK2(cs_main, pwallet->cs_wallet); uint256 hash; hash.SetHex(request.params[0].get_str()); isminefilter filter = ISMINE_SPENDABLE; if (request.params.size() > 1 && request.params[1].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } UniValue entry(UniValue::VOBJ); if (!pwallet->mapWallet.count(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); } const CWalletTx &wtx = pwallet->mapWallet[hash]; Amount nCredit = wtx.GetCredit(filter); Amount nDebit = wtx.GetDebit(filter); Amount nNet = (nCredit - nDebit); Amount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : Amount(0)); entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); if (wtx.IsFromMe(filter)) { entry.push_back(Pair("fee", ValueFromAmount(nFee))); } WalletTxToJSON(wtx, entry); UniValue details(UniValue::VARR); ListTransactions(pwallet, wtx, "*", 0, false, details, filter); entry.push_back(Pair("details", details)); std::string strHex = EncodeHexTx(static_cast(wtx), RPCSerializationFlags()); entry.push_back(Pair("hex", strHex)); return entry; } static UniValue abandontransaction(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "abandontransaction \"txid\"\n" "\nMark in-wallet transaction 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 conflicted or " "abandoned.\n" "\nArguments:\n" "1. \"txid\" (string, required) The transaction id\n" "\nResult:\n" "\nExamples:\n" + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084" "e2115b9345e16c5cf302fc80e9d5f" "bf5d48d\"") + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084" "e2115b9345e16c5cf302fc80e9d5f" "bf5d48d\"")); } LOCK2(cs_main, pwallet->cs_wallet); uint256 hash; hash.SetHex(request.params[0].get_str()); if (!pwallet->mapWallet.count(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); } if (!pwallet->AbandonTransaction(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); } return NullUniValue; } static UniValue backupwallet(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 1) { throw std::runtime_error( "backupwallet \"destination\"\n" "\nSafely copies current wallet file to destination, which can be " "a directory or a path with filename.\n" "\nArguments:\n" "1. \"destination\" (string) The destination directory or file\n" "\nExamples:\n" + HelpExampleCli("backupwallet", "\"backup.dat\"") + HelpExampleRpc("backupwallet", "\"backup.dat\"")); } LOCK2(cs_main, 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) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 1) { throw std::runtime_error( "keypoolrefill ( newsize )\n" "\nFills the keypool." + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments\n" "1. newsize (numeric, optional, default=100) " "The new keypool size\n" "\nExamples:\n" + HelpExampleCli("keypoolrefill", "") + HelpExampleRpc("keypoolrefill", "")); } LOCK2(cs_main, pwallet->cs_wallet); // 0 is interpreted by TopUpKeyPool() as the default keypool size given by // -keypool unsigned int kpSize = 0; if (request.params.size() > 0) { 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 void LockWallet(CWallet *pWallet) { LOCK(pWallet->cs_wallet); pWallet->nRelockTime = 0; pWallet->Lock(); } static UniValue walletpassphrase(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 2)) { throw std::runtime_error( "walletpassphrase \"passphrase\" timeout\n" "\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" "\nArguments:\n" "1. \"passphrase\" (string, required) The wallet passphrase\n" "2. timeout (numeric, required) The time to keep the " "decryption key in seconds.\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" "\nExamples:\n" "\nunlock the wallet for 60 seconds\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\" 60") + "\nLock the wallet again (before 60 seconds)\n" + HelpExampleCli("walletlock", "") + "\nAs json rpc call\n" + HelpExampleRpc("walletpassphrase", "\"my pass phrase\", 60")); } LOCK2(cs_main, 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(); if (strWalletPass.length() > 0) { if (!pwallet->Unlock(strWalletPass)) { throw JSONRPCError( RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); } } else { throw std::runtime_error( "walletpassphrase \n" "Stores the wallet decryption key in memory for " " seconds."); } pwallet->TopUpKeyPool(); int64_t nSleepTime = request.params[1].get_int64(); pwallet->nRelockTime = GetTime() + nSleepTime; RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), boost::bind(LockWallet, pwallet), nSleepTime); return NullUniValue; } static UniValue walletpassphrasechange(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 2)) { throw std::runtime_error( "walletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\n" "\nChanges the wallet passphrase from 'oldpassphrase' to " "'newpassphrase'.\n" "\nArguments:\n" "1. \"oldpassphrase\" (string) The current passphrase\n" "2. \"newpassphrase\" (string) The new passphrase\n" "\nExamples:\n" + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"")); } LOCK2(cs_main, 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.length() < 1 || strNewWalletPass.length() < 1) { throw std::runtime_error( "walletpassphrasechange \n" "Changes the wallet passphrase from to " "."); } 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) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (pwallet->IsCrypted() && (request.fHelp || request.params.size() != 0)) { throw std::runtime_error( "walletlock\n" "\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" "\nExamples:\n" "\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 json rpc call\n" + HelpExampleRpc("walletlock", "")); } LOCK2(cs_main, 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) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (!pwallet->IsCrypted() && (request.fHelp || request.params.size() != 1)) { throw std::runtime_error( "encryptwallet \"passphrase\"\n" "\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" "Note that this will shutdown the server.\n" "\nArguments:\n" "1. \"passphrase\" (string) The pass phrase to encrypt the " "wallet with. It must be at least 1 character, but should be " "long.\n" "\nExamples:\n" "\nEncrypt you 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 so 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\"")); } LOCK2(cs_main, 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.length() < 1) { throw std::runtime_error("encryptwallet \n" "Encrypts the wallet with ."); } if (!pwallet->EncryptWallet(strWalletPass)) { throw JSONRPCError(RPC_WALLET_ENCRYPTION_FAILED, "Error: Failed to encrypt the wallet."); } // BDB seems to have a bad habit of writing old data into // slack space in .dat files; that is bad if the old data is // unencrypted private keys. So: StartShutdown(); return "wallet encrypted; Bitcoin server stopping, restart to run with " "encrypted wallet. 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) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "lockunspent unlock ([{\"txid\":\"txid\",\"vout\":n},...])\n" "\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" "\nArguments:\n" "1. unlock (boolean, required) Whether to unlock (true) " "or lock (false) the specified transactions\n" "2. \"transactions\" (string, optional) A json array of objects. " "Each object the txid (string) vout (numeric)\n" " [ (json array of json objects)\n" " {\n" " \"txid\":\"id\", (string) The transaction id\n" " \"vout\": n (numeric) The output number\n" " }\n" " ,...\n" " ]\n" "\nResult:\n" "true|false (boolean) Whether the command was successful or " "not\n" "\nExamples:\n" "\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}]\"")); } LOCK2(cs_main, pwallet->cs_wallet); if (request.params.size() == 1) { RPCTypeCheck(request.params, {UniValue::VBOOL}); } else { RPCTypeCheck(request.params, {UniValue::VBOOL, UniValue::VARR}); } bool fUnlock = request.params[0].get_bool(); if (request.params.size() == 1) { if (fUnlock) { pwallet->UnlockAllCoins(); } return true; } UniValue outputs = request.params[1].get_array(); for (size_t idx = 0; idx < outputs.size(); idx++) { const UniValue &output = outputs[idx]; if (!output.isObject()) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected object"); } const UniValue &o = output.get_obj(); RPCTypeCheckObj(o, { {"txid", UniValueType(UniValue::VSTR)}, {"vout", UniValueType(UniValue::VNUM)}, }); std::string txid = find_value(o, "txid").get_str(); if (!IsHex(txid)) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid"); } int nOutput = find_value(o, "vout").get_int(); if (nOutput < 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive"); } COutPoint outpt(uint256S(txid), nOutput); if (fUnlock) { pwallet->UnlockCoin(outpt); } else { pwallet->LockCoin(outpt); } } return true; } static UniValue listlockunspent(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 0) { throw std::runtime_error( "listlockunspent\n" "\nReturns list of temporarily unspendable outputs.\n" "See the lockunspent call to lock and unlock transactions for " "spending.\n" "\nResult:\n" "[\n" " {\n" " \"txid\" : \"transactionid\", (string) The transaction id " "locked\n" " \"vout\" : n (numeric) The vout value\n" " }\n" " ,...\n" "]\n" "\nExamples:\n" "\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", "")); } LOCK2(cs_main, pwallet->cs_wallet); std::vector vOutpts; pwallet->ListLockedCoins(vOutpts); UniValue ret(UniValue::VARR); for (COutPoint &outpt : vOutpts) { UniValue o(UniValue::VOBJ); o.push_back(Pair("txid", outpt.hash.GetHex())); o.push_back(Pair("vout", (int)outpt.n)); ret.push_back(o); } return ret; } static UniValue settxfee(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) { throw std::runtime_error( "settxfee amount\n" "\nSet the transaction fee per kB. Overwrites the paytxfee " "parameter.\n" "\nArguments:\n" "1. amount (numeric or string, required) The transaction " "fee in " + CURRENCY_UNIT + "/kB\n" "\nResult\n" "true|false (boolean) Returns true if successful\n" "\nExamples:\n" + HelpExampleCli("settxfee", "0.00001") + HelpExampleRpc("settxfee", "0.00001")); } LOCK2(cs_main, pwallet->cs_wallet); // Amount Amount nAmount = AmountFromValue(request.params[0]); payTxFee = CFeeRate(nAmount, 1000); return true; } static UniValue getwalletinfo(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( "getwalletinfo\n" "Returns an object containing various wallet state info.\n" "\nResult:\n" "{\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" " \"hdmasterkeyid\": \"\" (string) " "the Hash160 of the HD master pubkey\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "")); } LOCK2(cs_main, pwallet->cs_wallet); UniValue obj(UniValue::VOBJ); size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); + obj.push_back(Pair("walletname", pwallet->GetName())); obj.push_back(Pair("walletversion", pwallet->GetVersion())); obj.push_back(Pair("balance", ValueFromAmount(pwallet->GetBalance()))); obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwallet->GetUnconfirmedBalance()))); obj.push_back(Pair("immature_balance", ValueFromAmount(pwallet->GetImmatureBalance()))); obj.push_back(Pair("txcount", (int)pwallet->mapWallet.size())); obj.push_back(Pair("keypoololdest", pwallet->GetOldestKeyPoolTime())); obj.push_back(Pair("keypoolsize", (int64_t)kpExternalSize)); CKeyID masterKeyID = pwallet->GetHDChain().masterKeyID; if (!masterKeyID.IsNull() && pwallet->CanSupportFeature(FEATURE_HD_SPLIT)) { obj.push_back( Pair("keypoolsize_hd_internal", int64_t(pwallet->GetKeyPoolSize() - kpExternalSize))); } if (pwallet->IsCrypted()) { obj.push_back(Pair("unlocked_until", pwallet->nRelockTime)); } obj.push_back(Pair("paytxfee", ValueFromAmount(payTxFee.GetFeePerK()))); if (!masterKeyID.IsNull()) { obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex())); } return obj; } +static UniValue listwallets(const Config &config, + const JSONRPCRequest &request) { + if (request.fHelp || request.params.size() != 0) { + throw std::runtime_error( + "listwallets\n" + "Returns a list of currently loaded wallets.\n" + "For full information on the wallet, use \"getwalletinfo\"\n" + "\nResult:\n" + "[ (json array of strings)\n" + " \"walletname\" (string) the wallet name\n" + " ...\n" + "]\n" + "\nExamples:\n" + + HelpExampleCli("listwallets", "") + + HelpExampleRpc("listwallets", "")); + } + + UniValue obj(UniValue::VARR); + + for (CWalletRef pwallet : vpwallets) { + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; + } + + LOCK(pwallet->cs_wallet); + obj.push_back(pwallet->GetName()); + } + + return obj; +} + static UniValue resendwallettransactions(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() != 0) { throw std::runtime_error( "resendwallettransactions\n" "Immediately re-broadcast unconfirmed wallet transactions to all " "peers.\n" "Intended only for testing; the wallet code periodically " "re-broadcasts\n" "automatically.\n" "Returns array of transaction ids that were re-broadcast.\n"); } if (!g_connman) { throw JSONRPCError( RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); } LOCK2(cs_main, pwallet->cs_wallet); std::vector txids = pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get()); UniValue result(UniValue::VARR); for (const uint256 &txid : txids) { result.push_back(txid.ToString()); } return result; } static UniValue listunspent(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() > 4) { throw std::runtime_error( "listunspent ( minconf maxconf [\"addresses\",...] " "[include_unsafe] )\n" "\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" "\nArguments:\n" "1. minconf (numeric, optional, default=1) The minimum " "confirmations to filter\n" "2. maxconf (numeric, optional, default=9999999) The " "maximum confirmations to filter\n" "3. \"addresses\" (string) A json array of bitcoin addresses to " "filter\n" " [\n" " \"address\" (string) bitcoin address\n" " ,...\n" " ]\n" "4. include_unsafe (bool, optional, default=true) Include outputs " "that are not safe to spend\n" " because they come from unconfirmed untrusted " "transactions or unconfirmed\n" " replacement transactions (cases where we are " "less sure that a conflicting\n" " transaction won't be mined).\n" "\nResult\n" "[ (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" " \"account\" : \"account\", (string) DEPRECATED. The " "associated account, or \"\" for the default account\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" " }\n" " ,...\n" "]\n" "\nExamples\n" + HelpExampleCli("listunspent", "") + HelpExampleCli("listunspent", "6 9999999 " "\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\"," "\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleRpc("listunspent", "6, 9999999 " "\"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\"," "\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"")); } int nMinDepth = 1; if (request.params.size() > 0 && !request.params[0].isNull()) { RPCTypeCheckArgument(request.params[0], UniValue::VNUM); nMinDepth = request.params[0].get_int(); } int nMaxDepth = 9999999; if (request.params.size() > 1 && !request.params[1].isNull()) { RPCTypeCheckArgument(request.params[1], UniValue::VNUM); nMaxDepth = request.params[1].get_int(); } std::set destinations; if (request.params.size() > 2 && !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()); 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.size() > 3 && !request.params[3].isNull()) { RPCTypeCheckArgument(request.params[3], UniValue::VBOOL); include_unsafe = request.params[3].get_bool(); } UniValue results(UniValue::VARR); std::vector vecOutputs; assert(pwallet != nullptr); LOCK2(cs_main, pwallet->cs_wallet); pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, true); for (const COutput &out : vecOutputs) { if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) { continue; } 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.push_back(Pair("txid", out.tx->GetId().GetHex())); entry.push_back(Pair("vout", out.i)); if (fValidAddress) { entry.push_back(Pair("address", EncodeDestination(address))); if (pwallet->mapAddressBook.count(address)) { entry.push_back( Pair("account", pwallet->mapAddressBook[address].name)); } if (scriptPubKey.IsPayToScriptHash()) { const CScriptID &hash = boost::get(address); CScript redeemScript; if (pwallet->GetCScript(hash, redeemScript)) { entry.push_back( Pair("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()))); } } } entry.push_back(Pair("scriptPubKey", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); entry.push_back( Pair("amount", ValueFromAmount(out.tx->tx->vout[out.i].nValue))); entry.push_back(Pair("confirmations", out.nDepth)); entry.push_back(Pair("spendable", out.fSpendable)); entry.push_back(Pair("solvable", out.fSolvable)); results.push_back(entry); } return results; } static UniValue fundrawtransaction(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "fundrawtransaction \"hexstring\" ( options )\n" "\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 signrawtransaction 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" "\nArguments:\n" "1. \"hexstring\" (string, required) The hex string of " "the raw transaction\n" "2. options (object, optional)\n" " {\n" " \"changeAddress\" (string, optional, default pool " "address) The bitcoin address to receive the change\n" " \"changePosition\" (numeric, optional, default " "random) The index of the change output\n" " \"includeWatching\" (boolean, optional, default " "false) Also select inputs which are watch only\n" " \"lockUnspents\" (boolean, optional, default " "false) Lock selected unspent outputs\n" " \"reserveChangeKey\" (boolean, optional, default true) " "Reserves the change output key from the keypool\n" " \"feeRate\" (numeric, optional, default not " "set: makes wallet determine the fee) Set a specific feerate (" + CURRENCY_UNIT + " per KB)\n" " \"subtractFeeFromOutputs\" (array, optional) A json array of " "integers.\n" " The fee will be equally deducted " "from the amount of each specified output.\n" " The outputs are specified by their " "zero-based index, before any change output is added.\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.\n" " [vout_index,...]\n" " }\n" " for backward compatibility: passing in a " "true instead of an object will result in " "{\"includeWatching\":true}\n" "\nResult:\n" "{\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" "\nExamples:\n" "\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("signrawtransaction", "\"fundedtransactionhex\"") + "\nSend the transaction\n" + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"")); } RPCTypeCheck(request.params, {UniValue::VSTR}); CTxDestination changeAddress = CNoDestination(); int changePosition = -1; bool includeWatching = false; bool lockUnspents = false; bool reserveChangeKey = true; CFeeRate feeRate = CFeeRate(Amount(0)); bool overrideEstimatedFeerate = false; UniValue subtractFeeFromOutputs; std::set setSubtractFeeFromOutputs; if (request.params.size() > 1) { if (request.params[1].type() == UniValue::VBOOL) { // backward compatibility bool only fallback includeWatching = request.params[1].get_bool(); } else { RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); UniValue options = request.params[1]; RPCTypeCheckObj( options, { {"changeAddress", UniValueType(UniValue::VSTR)}, {"changePosition", UniValueType(UniValue::VNUM)}, {"includeWatching", UniValueType(UniValue::VBOOL)}, {"lockUnspents", UniValueType(UniValue::VBOOL)}, {"reserveChangeKey", 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()); if (!IsValidDestination(dest)) { throw JSONRPCError( RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address"); } changeAddress = dest; } if (options.exists("changePosition")) { changePosition = options["changePosition"].get_int(); } if (options.exists("includeWatching")) { includeWatching = options["includeWatching"].get_bool(); } if (options.exists("lockUnspents")) { lockUnspents = options["lockUnspents"].get_bool(); } if (options.exists("reserveChangeKey")) { reserveChangeKey = options["reserveChangeKey"].get_bool(); } if (options.exists("feeRate")) { feeRate = CFeeRate(AmountFromValue(options["feeRate"])); overrideEstimatedFeerate = true; } if (options.exists("subtractFeeFromOutputs")) { subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array(); } } } // parse hex string from parameter CMutableTransaction tx; if (!DecodeHexTx(tx, request.params[0].get_str())) { throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed"); } if (tx.vout.size() == 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output"); } if (changePosition != -1 && (changePosition < 0 || (unsigned int)changePosition > 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); } Amount nFeeOut; std::string strFailReason; if (!pwallet->FundTransaction( tx, nFeeOut, overrideEstimatedFeerate, feeRate, changePosition, strFailReason, includeWatching, lockUnspents, setSubtractFeeFromOutputs, reserveChangeKey, changeAddress)) { throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } UniValue result(UniValue::VOBJ); result.push_back(Pair("hex", EncodeHexTx(tx))); result.push_back(Pair("changepos", changePosition)); result.push_back(Pair("fee", ValueFromAmount(nFeeOut))); return result; } static UniValue generate(const Config &config, const JSONRPCRequest &request) { CWallet *const pwallet = GetWalletForJSONRPCRequest(request); if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { throw std::runtime_error( "generate nblocks ( maxtries )\n" "\nMine up to nblocks blocks immediately (before the RPC call " "returns) to an address in the wallet.\n" "\nArguments:\n" "1. nblocks (numeric, required) How many blocks are generated " "immediately.\n" "2. maxtries (numeric, optional) How many iterations to try " "(default = 1000000).\n" "\nResult:\n" "[ blockhashes ] (array) hashes of blocks generated\n" "\nExamples:\n" "\nGenerate 11 blocks\n" + HelpExampleCli("generate", "11")); } int num_generate = request.params[0].get_int(); uint64_t max_tries = 1000000; if (request.params.size() > 1 && !request.params[1].isNull()) { max_tries = request.params[1].get_int(); } std::shared_ptr 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); } // clang-format off static const CRPCCommand commands[] = { // category name actor (function) okSafeMode // ------------------- ------------------------ ---------------------- ---------- { "rawtransactions", "fundrawtransaction", fundrawtransaction, false, {"hexstring","options"} }, { "hidden", "resendwallettransactions", resendwallettransactions, true, {} }, { "wallet", "abandontransaction", abandontransaction, false, {"txid"} }, { "wallet", "addmultisigaddress", addmultisigaddress, true, {"nrequired","keys","account"} }, { "wallet", "backupwallet", backupwallet, true, {"destination"} }, { "wallet", "encryptwallet", encryptwallet, true, {"passphrase"} }, { "wallet", "getaccountaddress", getaccountaddress, true, {"account"} }, { "wallet", "getaccount", getaccount, true, {"address"} }, { "wallet", "getaddressesbyaccount", getaddressesbyaccount, true, {"account"} }, { "wallet", "getbalance", getbalance, false, {"account","minconf","include_watchonly"} }, { "wallet", "getnewaddress", getnewaddress, true, {"account"} }, { "wallet", "getrawchangeaddress", getrawchangeaddress, true, {} }, { "wallet", "getreceivedbyaccount", getreceivedbyaccount, false, {"account","minconf"} }, { "wallet", "getreceivedbyaddress", getreceivedbyaddress, false, {"address","minconf"} }, { "wallet", "gettransaction", gettransaction, false, {"txid","include_watchonly"} }, { "wallet", "getunconfirmedbalance", getunconfirmedbalance, false, {} }, { "wallet", "getwalletinfo", getwalletinfo, false, {} }, { "wallet", "keypoolrefill", keypoolrefill, true, {"newsize"} }, { "wallet", "listaccounts", listaccounts, false, {"minconf","include_watchonly"} }, { "wallet", "listaddressgroupings", listaddressgroupings, false, {} }, { "wallet", "listlockunspent", listlockunspent, false, {} }, { "wallet", "listreceivedbyaccount", listreceivedbyaccount, false, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listreceivedbyaddress", listreceivedbyaddress, false, {"minconf","include_empty","include_watchonly"} }, { "wallet", "listsinceblock", listsinceblock, false, {"blockhash","target_confirmations","include_watchonly"} }, { "wallet", "listtransactions", listtransactions, false, {"account","count","skip","include_watchonly"} }, { "wallet", "listunspent", listunspent, false, {"minconf","maxconf","addresses","include_unsafe"} }, + { "wallet", "listwallets", listwallets, true, {} }, { "wallet", "lockunspent", lockunspent, true, {"unlock","transactions"} }, { "wallet", "move", movecmd, false, {"fromaccount","toaccount","amount","minconf","comment"} }, { "wallet", "sendfrom", sendfrom, false, {"fromaccount","toaddress","amount","minconf","comment","comment_to"} }, { "wallet", "sendmany", sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom"} }, { "wallet", "sendtoaddress", sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount"} }, { "wallet", "setaccount", setaccount, true, {"address","account"} }, { "wallet", "settxfee", settxfee, true, {"amount"} }, { "wallet", "signmessage", signmessage, true, {"address","message"} }, { "wallet", "walletlock", walletlock, true, {} }, { "wallet", "walletpassphrasechange", walletpassphrasechange, true, {"oldpassphrase","newpassphrase"} }, { "wallet", "walletpassphrase", walletpassphrase, true, {"passphrase","timeout"} }, - { "generating", "generate", &generate, true, {"nblocks","maxtries"} }, + { "generating", "generate", generate, true, {"nblocks","maxtries"} }, }; // clang-format on void RegisterWalletRPCCommands(CRPCTable &t) { if (GetBoolArg("-disablewallet", false)) { return; } for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) { t.appendCommand(commands[vcidx].name, &commands[vcidx]); } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 70e7f3103..9da1bcc08 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1,1201 +1,1205 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto // Copyright (c) 2009-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_WALLET_WALLET_H #define BITCOIN_WALLET_WALLET_H #include "amount.h" #include "script/ismine.h" #include "script/sign.h" #include "streams.h" #include "tinyformat.h" #include "ui_interface.h" #include "utilstrencodings.h" #include "validationinterface.h" #include "wallet/crypter.h" #include "wallet/rpcwallet.h" #include "wallet/walletdb.h" #include #include #include #include #include #include #include #include #include #include typedef CWallet *CWalletRef; extern std::vector vpwallets; /** * Settings */ extern CFeeRate payTxFee; extern unsigned int nTxConfirmTarget; extern bool bSpendZeroConfChange; extern bool fSendFreeTransactions; static const unsigned int DEFAULT_KEYPOOL_SIZE = 100; //! -paytxfee default static const Amount DEFAULT_TRANSACTION_FEE(0); //! -fallbackfee default static const Amount DEFAULT_FALLBACK_FEE(20000); //! -mintxfee default static const Amount DEFAULT_TRANSACTION_MINFEE(1000); //! minimum recommended increment for BIP 125 replacement txs static const Amount WALLET_INCREMENTAL_RELAY_FEE(5000); //! target minimum change amount static const Amount MIN_CHANGE = CENT; //! final minimum change amount after paying for fees static const Amount MIN_FINAL_CHANGE = MIN_CHANGE / 2; //! Default for -spendzeroconfchange static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true; //! Default for -sendfreetransactions static const bool DEFAULT_SEND_FREE_TRANSACTIONS = false; //! Default for -walletrejectlongchains static const bool DEFAULT_WALLET_REJECT_LONG_CHAINS = false; //! -txconfirmtarget default static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 6; //! Largest (in bytes) free transaction we're willing to create static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000; static const bool DEFAULT_WALLETBROADCAST = true; static const bool DEFAULT_DISABLE_WALLET = false; //! if set, all keys will be derived by using BIP32 static const bool DEFAULT_USE_HD_WALLET = true; extern const char *DEFAULT_WALLET_DAT; class CBlockIndex; class CCoinControl; class COutput; class CReserveKey; class CScript; class CScheduler; class CTxMemPool; class CWalletTx; /** (client) version numbers for particular wallet features */ enum WalletFeature { // the earliest version new wallets supports (only useful for getinfo's // clientversion output) FEATURE_BASE = 10500, // wallet encryption FEATURE_WALLETCRYPT = 40000, // compressed public keys FEATURE_COMPRPUBKEY = 60000, // Hierarchical key derivation after BIP32 (HD Wallet) FEATURE_HD = 130000, // Wallet with HD chain split (change outputs will use m/0'/1'/k) FEATURE_HD_SPLIT = 160300, // HD is optional, use FEATURE_COMPRPUBKEY as latest version FEATURE_LATEST = FEATURE_COMPRPUBKEY, }; /** A key pool entry */ class CKeyPool { public: int64_t nTime; CPubKey vchPubKey; bool fInternal; // for change outputs CKeyPool(); CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn); ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { int nVersion = s.GetVersion(); if (!(s.GetType() & SER_GETHASH)) { READWRITE(nVersion); } READWRITE(nTime); READWRITE(vchPubKey); if (ser_action.ForRead()) { try { READWRITE(fInternal); } catch (std::ios_base::failure &) { /** * flag as external address if we can't read the internal * boolean * (this will be the case for any wallet before the HD chain * split version) */ fInternal = false; } } else { READWRITE(fInternal); } } }; /** Address book data */ class CAddressBookData { public: std::string name; std::string purpose; CAddressBookData() { purpose = "unknown"; } typedef std::map StringMap; StringMap destdata; }; struct CRecipient { CScript scriptPubKey; Amount nAmount; bool fSubtractFeeFromAmount; }; typedef std::map mapValue_t; static inline void ReadOrderPos(int64_t &nOrderPos, mapValue_t &mapValue) { if (!mapValue.count("n")) { // TODO: calculate elsewhere nOrderPos = -1; return; } nOrderPos = atoi64(mapValue["n"].c_str()); } static inline void WriteOrderPos(const int64_t &nOrderPos, mapValue_t &mapValue) { if (nOrderPos == -1) return; mapValue["n"] = i64tostr(nOrderPos); } struct COutputEntry { CTxDestination destination; Amount amount; int vout; }; /** A transaction with a merkle branch linking it to the block chain. */ class CMerkleTx { private: /** Constant used in hashBlock to indicate tx has been abandoned */ static const uint256 ABANDON_HASH; public: CTransactionRef tx; uint256 hashBlock; /** * An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest * block in the chain we know this or any in-wallet dependency conflicts * with. Older clients interpret nIndex == -1 as unconfirmed for backward * compatibility. */ int nIndex; CMerkleTx() { SetTx(MakeTransactionRef()); Init(); } CMerkleTx(CTransactionRef arg) { SetTx(std::move(arg)); Init(); } /** * Helper conversion operator to allow passing CMerkleTx where CTransaction * is expected. * TODO: adapt callers and remove this operator. */ operator const CTransaction &() const { return *tx; } void Init() { hashBlock = uint256(); nIndex = -1; } void SetTx(CTransactionRef arg) { tx = std::move(arg); } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { // For compatibility with older versions. std::vector vMerkleBranch; READWRITE(tx); READWRITE(hashBlock); READWRITE(vMerkleBranch); READWRITE(nIndex); } void SetMerkleBranch(const CBlockIndex *pIndex, int posInBlock); /** * Return depth of transaction in blockchain: * <0 : conflicts with a transaction this deep in the blockchain * 0 : in memory pool, waiting to be included in a block * >=1 : this many blocks deep in the main chain */ int GetDepthInMainChain(const CBlockIndex *&pindexRet) const; int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); } bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; } int GetBlocksToMaturity() const; /** * Pass this transaction to the mempool. Fails if absolute fee exceeds * absurd fee. */ bool AcceptToMemoryPool(const Amount nAbsurdFee, CValidationState &state); bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); } bool isAbandoned() const { return (hashBlock == ABANDON_HASH); } void setAbandoned() { hashBlock = ABANDON_HASH; } const TxId GetId() const { return tx->GetId(); } bool IsCoinBase() const { return tx->IsCoinBase(); } }; /** * A transaction with a bunch of additional info that only the owner cares * about. It includes any unrecorded transactions needed to link it back to the * block chain. */ class CWalletTx : public CMerkleTx { private: const CWallet *pwallet; public: mapValue_t mapValue; std::vector> vOrderForm; unsigned int fTimeReceivedIsTxTime; //!< time received by this node unsigned int nTimeReceived; /** * Stable timestamp that never changes, and reflects the order a transaction * was added to the wallet. Timestamp is based on the block time for a * transaction added as part of a block, or else the time when the * transaction was received if it wasn't part of a block, with the timestamp * adjusted in both cases so timestamp order matches the order transactions * were added to the wallet. More details can be found in * CWallet::ComputeTimeSmart(). */ unsigned int nTimeSmart; /** * From me flag is set to 1 for transactions that were created by the wallet * on this bitcoin node, and set to 0 for transactions that were created * externally and came in through the network or sendrawtransaction RPC. */ char fFromMe; std::string strFromAccount; //!< position in ordered transaction list int64_t nOrderPos; // memory only mutable bool fDebitCached; mutable bool fCreditCached; mutable bool fImmatureCreditCached; mutable bool fAvailableCreditCached; mutable bool fWatchDebitCached; mutable bool fWatchCreditCached; mutable bool fImmatureWatchCreditCached; mutable bool fAvailableWatchCreditCached; mutable bool fChangeCached; mutable Amount nDebitCached; mutable Amount nCreditCached; mutable Amount nImmatureCreditCached; mutable Amount nAvailableCreditCached; mutable Amount nWatchDebitCached; mutable Amount nWatchCreditCached; mutable Amount nImmatureWatchCreditCached; mutable Amount nAvailableWatchCreditCached; mutable Amount nChangeCached; CWalletTx() { Init(nullptr); } CWalletTx(const CWallet *pwalletIn, CTransactionRef arg) : CMerkleTx(std::move(arg)) { Init(pwalletIn); } void Init(const CWallet *pwalletIn) { pwallet = pwalletIn; mapValue.clear(); vOrderForm.clear(); fTimeReceivedIsTxTime = false; nTimeReceived = 0; nTimeSmart = 0; fFromMe = false; strFromAccount.clear(); fDebitCached = false; fCreditCached = false; fImmatureCreditCached = false; fAvailableCreditCached = false; fWatchDebitCached = false; fWatchCreditCached = false; fImmatureWatchCreditCached = false; fAvailableWatchCreditCached = false; fChangeCached = false; nDebitCached = Amount(0); nCreditCached = Amount(0); nImmatureCreditCached = Amount(0); nAvailableCreditCached = Amount(0); nWatchDebitCached = Amount(0); nWatchCreditCached = Amount(0); nAvailableWatchCreditCached = Amount(0); nImmatureWatchCreditCached = Amount(0); nChangeCached = Amount(0); nOrderPos = -1; } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { if (ser_action.ForRead()) Init(nullptr); char fSpent = false; if (!ser_action.ForRead()) { mapValue["fromaccount"] = strFromAccount; WriteOrderPos(nOrderPos, mapValue); if (nTimeSmart) mapValue["timesmart"] = strprintf("%u", nTimeSmart); } READWRITE(*(CMerkleTx *)this); //!< Used to be vtxPrev std::vector vUnused; READWRITE(vUnused); READWRITE(mapValue); READWRITE(vOrderForm); READWRITE(fTimeReceivedIsTxTime); READWRITE(nTimeReceived); READWRITE(fFromMe); READWRITE(fSpent); if (ser_action.ForRead()) { strFromAccount = mapValue["fromaccount"]; ReadOrderPos(nOrderPos, mapValue); nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; } mapValue.erase("fromaccount"); mapValue.erase("version"); mapValue.erase("spent"); mapValue.erase("n"); mapValue.erase("timesmart"); } //! make sure balances are recalculated void MarkDirty() { fCreditCached = false; fAvailableCreditCached = false; fImmatureCreditCached = false; fWatchDebitCached = false; fWatchCreditCached = false; fAvailableWatchCreditCached = false; fImmatureWatchCreditCached = false; fDebitCached = false; fChangeCached = false; } void BindWallet(CWallet *pwalletIn) { pwallet = pwalletIn; MarkDirty(); } //! filter decides which addresses will count towards the debit Amount GetDebit(const isminefilter &filter) const; Amount GetCredit(const isminefilter &filter) const; Amount GetImmatureCredit(bool fUseCache = true) const; Amount GetAvailableCredit(bool fUseCache = true) const; Amount GetImmatureWatchOnlyCredit(const bool &fUseCache = true) const; Amount GetAvailableWatchOnlyCredit(const bool &fUseCache = true) const; Amount GetChange() const; void GetAmounts(std::list &listReceived, std::list &listSent, Amount &nFee, std::string &strSentAccount, const isminefilter &filter) const; void GetAccountAmounts(const std::string &strAccount, Amount &nReceived, Amount &nSent, Amount &nFee, const isminefilter &filter) const; bool IsFromMe(const isminefilter &filter) const { return (GetDebit(filter) > Amount(0)); } // True if only scriptSigs are different bool IsEquivalentTo(const CWalletTx &tx) const; bool InMempool() const; bool IsTrusted() const; int64_t GetTxTime() const; int GetRequestCount() const; bool RelayWalletTransaction(CConnman *connman); std::set GetConflicts() const; }; class COutput { public: const CWalletTx *tx; int i; int nDepth; bool fSpendable; bool fSolvable; COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn) { tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; } std::string ToString() const; }; /** Private key that includes an expiration date in case it never gets used. */ class CWalletKey { public: CPrivKey vchPrivKey; int64_t nTimeCreated; int64_t nTimeExpires; std::string strComment; //! todo: add something to note what created it (user, getnewaddress, //! change) maybe should have a map property map CWalletKey(int64_t nExpires = 0); ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { int nVersion = s.GetVersion(); if (!(s.GetType() & SER_GETHASH)) READWRITE(nVersion); READWRITE(vchPrivKey); READWRITE(nTimeCreated); READWRITE(nTimeExpires); READWRITE(LIMITED_STRING(strComment, 65536)); } }; /** * Internal transfers. * Database key is acentry. */ class CAccountingEntry { public: std::string strAccount; Amount nCreditDebit; int64_t nTime; std::string strOtherAccount; std::string strComment; mapValue_t mapValue; //!< position in ordered transaction list int64_t nOrderPos; uint64_t nEntryNo; CAccountingEntry() { SetNull(); } void SetNull() { nCreditDebit = Amount(0); nTime = 0; strAccount.clear(); strOtherAccount.clear(); strComment.clear(); nOrderPos = -1; nEntryNo = 0; } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { int nVersion = s.GetVersion(); if (!(s.GetType() & SER_GETHASH)) READWRITE(nVersion); //! Note: strAccount is serialized as part of the key, not here. READWRITE(nCreditDebit); READWRITE(nTime); READWRITE(LIMITED_STRING(strOtherAccount, 65536)); if (!ser_action.ForRead()) { WriteOrderPos(nOrderPos, mapValue); if (!(mapValue.empty() && _ssExtra.empty())) { CDataStream ss(s.GetType(), s.GetVersion()); ss.insert(ss.begin(), '\0'); ss << mapValue; ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end()); strComment.append(ss.str()); } } READWRITE(LIMITED_STRING(strComment, 65536)); size_t nSepPos = strComment.find("\0", 0, 1); if (ser_action.ForRead()) { mapValue.clear(); if (std::string::npos != nSepPos) { CDataStream ss( std::vector(strComment.begin() + nSepPos + 1, strComment.end()), s.GetType(), s.GetVersion()); ss >> mapValue; _ssExtra = std::vector(ss.begin(), ss.end()); } ReadOrderPos(nOrderPos, mapValue); } if (std::string::npos != nSepPos) strComment.erase(nSepPos); mapValue.erase("n"); } private: std::vector _ssExtra; }; /** * A CWallet is an extension of a keystore, which also maintains a set of * transactions and balances, and provides the ability to create new * transactions. */ class CWallet final : public CCryptoKeyStore, public CValidationInterface { private: static std::atomic fFlushScheduled; /** * Select a set of coins such that nValueRet >= nTargetValue and at least * all coins from coinControl are selected; Never select unconfirmed coins * if they are not ours. */ bool SelectCoins( const std::vector &vAvailableCoins, const Amount nTargetValue, std::set> &setCoinsRet, Amount &nValueRet, const CCoinControl *coinControl = nullptr) const; CWalletDB *pwalletdbEncryption; //! the current wallet version: clients below this version are not able to //! load the wallet int nWalletVersion; //! the maximum wallet format version: memory-only variable that specifies //! to what version this wallet may be upgraded int nWalletMaxVersion; int64_t nNextResend; int64_t nLastResend; bool fBroadcastTransactions; /** * Used to keep track of spent outpoints, and detect and report conflicts * (double-spends or mutated transactions where the mutant gets mined). */ typedef std::multimap TxSpends; TxSpends mapTxSpends; void AddToSpends(const COutPoint &outpoint, const uint256 &wtxid); void AddToSpends(const uint256 &wtxid); /* Mark a transaction (and its in-wallet descendants) as conflicting with a * particular block. */ void MarkConflicted(const uint256 &hashBlock, const uint256 &hashTx); void SyncMetaData(std::pair); /* Used by TransactionAddedToMemorypool/BlockConnected/Disconnected */ void SyncTransaction(const CTransactionRef &tx, const CBlockIndex *pindexBlockConnected, int posInBlock); /* the HD chain data model (external chain counters) */ CHDChain hdChain; /* HD derive new child key (on internal or external chain) */ void DeriveNewChildKey(CKeyMetadata &metadata, CKey &secret, bool internal = false); std::set setKeyPool; int64_t nTimeFirstKey; /** * Private version of AddWatchOnly method which does not accept a timestamp, * and which will reset the wallet's nTimeFirstKey value to 1 if the watch * key did not previously have a timestamp associated with it. Because this * is an inherited virtual method, it is accessible despite being marked * private, but it is marked private anyway to encourage use of the other * AddWatchOnly which accepts a timestamp and sets nTimeFirstKey more * intelligently for more efficient rescans. */ bool AddWatchOnly(const CScript &dest) override; std::unique_ptr dbw; // Used to NotifyTransactionChanged of the previous block's coinbase when // the next block comes in uint256 hashPrevBestCoinbase; public: /* * Main wallet lock. * This lock protects all the fields added by CWallet. */ mutable CCriticalSection cs_wallet; /** * Get database handle used by this wallet. Ideally this function would not * be necessary. */ CWalletDBWrapper &GetDBHandle() { return *dbw; } /** * Get a name for this wallet for logging/debugging purposes. */ std::string GetName() const { if (dbw) { return dbw->GetName(); } else { return "dummy"; } } void LoadKeyPool(int nIndex, const CKeyPool &keypool) { setKeyPool.insert(nIndex); // If no metadata exists yet, create a default with the pool key's // creation time. Note that this may be overwritten by actually stored // metadata for that key later, which is fine. CKeyID keyid = keypool.vchPubKey.GetID(); if (mapKeyMetadata.count(keyid) == 0) mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime); } // Map from Key ID (for regular keys) or Script ID (for watch-only keys) to // key metadata. std::map mapKeyMetadata; typedef std::map MasterKeyMap; MasterKeyMap mapMasterKeys; unsigned int nMasterKeyMaxID; // Create wallet with dummy database handle CWallet() : dbw(new CWalletDBWrapper()) { SetNull(); } // Create wallet with passed-in database handle CWallet(std::unique_ptr dbw_in) : dbw(std::move(dbw_in)) { SetNull(); } ~CWallet() { delete pwalletdbEncryption; pwalletdbEncryption = nullptr; } void SetNull() { nWalletVersion = FEATURE_BASE; nWalletMaxVersion = FEATURE_BASE; nMasterKeyMaxID = 0; pwalletdbEncryption = nullptr; nOrderPosNext = 0; nAccountingEntryNumber = 0; nNextResend = 0; nLastResend = 0; nTimeFirstKey = 0; fBroadcastTransactions = false; } std::map mapWallet; std::list laccentries; typedef std::pair TxPair; typedef std::multimap TxItems; TxItems wtxOrdered; int64_t nOrderPosNext; uint64_t nAccountingEntryNumber; std::map mapRequestCount; std::map mapAddressBook; CPubKey vchDefaultKey; std::set setLockedCoins; const CWalletTx *GetWalletTx(const uint256 &hash) const; //! check whether we are allowed to upgrade (or already support) to the //! named feature bool CanSupportFeature(enum WalletFeature wf) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; } /** * populate vCoins with vector of available COutputs. */ void AvailableCoins(std::vector &vCoins, bool fOnlyConfirmed = true, const CCoinControl *coinControl = nullptr, bool fIncludeZeroValue = false) const; /** * Shuffle and select coins until nTargetValue is reached while avoiding * small change; This method is stochastic for some inputs and upon * completion the coin set and corresponding actual target value is * assembled. */ bool SelectCoinsMinConf( const Amount nTargetValue, int nConfMine, int nConfTheirs, uint64_t nMaxAncestors, std::vector vCoins, std::set> &setCoinsRet, Amount &nValueRet) const; bool IsSpent(const uint256 &hash, unsigned int n) const; bool IsLockedCoin(uint256 hash, unsigned int n) const; void LockCoin(const COutPoint &output); void UnlockCoin(const COutPoint &output); void UnlockAllCoins(); void ListLockedCoins(std::vector &vOutpts); /** * keystore implementation * Generate a new key */ CPubKey GenerateNewKey(bool internal = false); //! Adds a key to the store, and saves it to disk. bool AddKeyPubKey(const CKey &key, const CPubKey &pubkey) override; //! Adds a key to the store, without saving it to disk (used by LoadWallet) bool LoadKey(const CKey &key, const CPubKey &pubkey) { return CCryptoKeyStore::AddKeyPubKey(key, pubkey); } //! Load metadata (used by LoadWallet) bool LoadKeyMetadata(const CTxDestination &pubKey, const CKeyMetadata &metadata); bool LoadMinVersion(int nVersion) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; nWalletMaxVersion = std::max(nWalletMaxVersion, nVersion); return true; } void UpdateTimeFirstKey(int64_t nCreateTime); //! Adds an encrypted key to the store, and saves it to disk. bool AddCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret) override; //! Adds an encrypted key to the store, without saving it to disk (used by //! LoadWallet) bool LoadCryptedKey(const CPubKey &vchPubKey, const std::vector &vchCryptedSecret); bool AddCScript(const CScript &redeemScript) override; bool LoadCScript(const CScript &redeemScript); //! Adds a destination data tuple to the store, and saves it to disk bool AddDestData(const CTxDestination &dest, const std::string &key, const std::string &value); //! Erases a destination data tuple in the store and on disk bool EraseDestData(const CTxDestination &dest, const std::string &key); //! Adds a destination data tuple to the store, without saving it to disk bool LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value); //! Look up a destination data tuple in the store, return true if found //! false otherwise bool GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const; //! Adds a watch-only address to the store, and saves it to disk. bool AddWatchOnly(const CScript &dest, int64_t nCreateTime); bool RemoveWatchOnly(const CScript &dest) override; //! Adds a watch-only address to the store, without saving it to disk (used //! by LoadWallet) bool LoadWatchOnly(const CScript &dest); //! Holds a timestamp at which point the wallet is scheduled (externally) to //! be relocked. Caller must arrange for actual relocking to occur via //! Lock(). int64_t nRelockTime; bool Unlock(const SecureString &strWalletPassphrase); bool ChangeWalletPassphrase(const SecureString &strOldWalletPassphrase, const SecureString &strNewWalletPassphrase); bool EncryptWallet(const SecureString &strWalletPassphrase); void GetKeyBirthTimes(std::map &mapKeyBirth) const; unsigned int ComputeTimeSmart(const CWalletTx &wtx) const; /** * Increment the next transaction order id * @return next transaction order id */ int64_t IncOrderPosNext(CWalletDB *pwalletdb = nullptr); DBErrors ReorderTransactions(); bool AccountMove(std::string strFrom, std::string strTo, const Amount nAmount, std::string strComment = ""); bool GetAccountPubkey(CPubKey &pubKey, std::string strAccount, bool bForceNew = false); void MarkDirty(); bool AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose = true); bool LoadToWallet(const CWalletTx &wtxIn); void TransactionAddedToMempool(const CTransactionRef &tx) override; void BlockConnected(const std::shared_ptr &pblock, const CBlockIndex *pindex, const std::vector &vtxConflicted) override; void BlockDisconnected(const std::shared_ptr &pblock) override; bool AddToWalletIfInvolvingMe(const CTransactionRef &tx, const CBlockIndex *pIndex, int posInBlock, bool fUpdate); CBlockIndex *ScanForWalletTransactions(CBlockIndex *pindexStart, bool fUpdate = false); void ReacceptWalletTransactions(); void ResendWalletTransactions(int64_t nBestBlockTime, CConnman *connman) override; std::vector ResendWalletTransactionsBefore(int64_t nTime, CConnman *connman); Amount GetBalance() const; Amount GetUnconfirmedBalance() const; Amount GetImmatureBalance() const; Amount GetWatchOnlyBalance() const; Amount GetUnconfirmedWatchOnlyBalance() const; Amount GetImmatureWatchOnlyBalance() const; /** * Insert additional inputs into the transaction by calling * CreateTransaction(); */ bool FundTransaction(CMutableTransaction &tx, Amount &nFeeRet, bool overrideEstimatedFeeRate, const CFeeRate &specificFeeRate, int &nChangePosInOut, std::string &strFailReason, bool includeWatching, bool lockUnspents, const std::set &setSubtractFeeFromOutputs, bool keepReserveKey = true, const CTxDestination &destChange = CNoDestination()); /** * Create a new transaction paying the recipients with a set of coins * selected by SelectCoins(); Also create the change output, when needed * @note passing nChangePosInOut as -1 will result in setting a random * position */ bool CreateTransaction(const std::vector &vecSend, CWalletTx &wtxNew, CReserveKey &reservekey, Amount &nFeeRet, int &nChangePosInOut, std::string &strFailReason, const CCoinControl *coinControl = nullptr, bool sign = true); bool CommitTransaction(CWalletTx &wtxNew, CReserveKey &reservekey, CConnman *connman, CValidationState &state); void ListAccountCreditDebit(const std::string &strAccount, std::list &entries); bool AddAccountingEntry(const CAccountingEntry &); bool AddAccountingEntry(const CAccountingEntry &, CWalletDB *pwalletdb); template bool DummySignTx(CMutableTransaction &txNew, const ContainerType &coins); static CFeeRate minTxFee; static CFeeRate fallbackFee; /** * Estimate the minimum fee considering user set parameters and the required * fee */ static Amount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool &pool); /** * Estimate the minimum fee considering required fee and targetFee or if 0 * then fee estimation for nConfirmTarget */ static Amount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool &pool, Amount targetFee); /** * Return the minimum required fee taking into account the floating relay * fee and user set minimum transaction fee */ static Amount GetRequiredFee(unsigned int nTxBytes); bool NewKeyPool(); size_t KeypoolCountExternalKeys(); bool TopUpKeyPool(unsigned int kpSize = 0); void ReserveKeyFromKeyPool(int64_t &nIndex, CKeyPool &keypool, bool internal); void KeepKey(int64_t nIndex); void ReturnKey(int64_t nIndex); bool GetKeyFromPool(CPubKey &key, bool internal = false); int64_t GetOldestKeyPoolTime(); void GetAllReserveKeys(std::set &setAddress) const; std::set> GetAddressGroupings(); std::map GetAddressBalances(); Amount GetAccountBalance(const std::string &strAccount, int nMinDepth, const isminefilter &filter); Amount GetAccountBalance(CWalletDB &walletdb, const std::string &strAccount, int nMinDepth, const isminefilter &filter); std::set GetAccountAddresses(const std::string &strAccount) const; isminetype IsMine(const CTxIn &txin) const; /** * Returns amount of debit if the input matches the filter, otherwise * returns 0 */ Amount GetDebit(const CTxIn &txin, const isminefilter &filter) const; isminetype IsMine(const CTxOut &txout) const; Amount GetCredit(const CTxOut &txout, const isminefilter &filter) const; bool IsChange(const CTxOut &txout) const; Amount GetChange(const CTxOut &txout) const; bool IsMine(const CTransaction &tx) const; /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction &tx) const; Amount GetDebit(const CTransaction &tx, const isminefilter &filter) const; /** Returns whether all of the inputs match the filter */ bool IsAllFromMe(const CTransaction &tx, const isminefilter &filter) const; Amount GetCredit(const CTransaction &tx, const isminefilter &filter) const; Amount GetChange(const CTransaction &tx) const; void SetBestChain(const CBlockLocator &loc) override; DBErrors LoadWallet(bool &fFirstRunRet); DBErrors ZapWalletTx(std::vector &vWtx); DBErrors ZapSelectTx(std::vector &vHashIn, std::vector &vHashOut); bool SetAddressBook(const CTxDestination &address, const std::string &strName, const std::string &purpose); bool DelAddressBook(const CTxDestination &address); void Inventory(const uint256 &hash) override { LOCK(cs_wallet); std::map::iterator mi = mapRequestCount.find(hash); if (mi != mapRequestCount.end()) { (*mi).second++; } } void GetScriptForMining(std::shared_ptr &script); unsigned int GetKeyPoolSize() { // setKeyPool AssertLockHeld(cs_wallet); return setKeyPool.size(); } bool SetDefaultKey(const CPubKey &vchPubKey); //! signify that a particular wallet feature is now used. this may change //! nWalletVersion and nWalletMaxVersion if those are lower bool SetMinVersion(enum WalletFeature, CWalletDB *pwalletdbIn = nullptr, bool fExplicit = false); //! change which version we're allowed to upgrade to (note that this does //! not immediately imply upgrading to that format) bool SetMaxVersion(int nVersion); //! get the current wallet format (the oldest client version guaranteed to //! understand this wallet) int GetVersion() { LOCK(cs_wallet); return nWalletVersion; } //! Get wallet transactions that conflict with given transaction (spend same //! outputs) std::set GetConflicts(const uint256 &txid) const; //! Check if a given transaction has any of its outputs spent by another //! transaction in the wallet bool HasWalletSpend(const uint256 &txid) const; //! Flush wallet (bitdb flush) void Flush(bool shutdown = false); - //! Verify the wallet database and perform salvage if required + //! Responsible for reading and validating the -wallet arguments and + //! verifying the wallet database. + // This function will perform salvage on the wallet if requested, as long as + // only one wallet is being loaded (CWallet::ParameterInteraction forbids + // -salvagewallet, -zapwallettxes or -upgradewallet with multiwallet). static bool Verify(); /** * Address book entry changed. * @note called with lock cs_wallet held. */ boost::signals2::signal NotifyAddressBookChanged; /** * Wallet transaction added, removed or updated. * @note called with lock cs_wallet held. */ boost::signals2::signal NotifyTransactionChanged; /** Show progress e.g. for rescan */ boost::signals2::signal ShowProgress; /** Watch-only address added */ boost::signals2::signal NotifyWatchonlyChanged; /** Inquire whether this wallet broadcasts transactions. */ bool GetBroadcastTransactions() const { return fBroadcastTransactions; } /** Set whether this wallet broadcasts transactions. */ void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; } /** * Mark a transaction (and it in-wallet descendants) as abandoned so its * inputs may be respent. */ bool AbandonTransaction(const uint256 &hashTx); /** * Mark a transaction as replaced by another transaction (e.g., BIP 125). */ bool MarkReplaced(const uint256 &originalHash, const uint256 &newHash); /* Returns the wallets help message */ static std::string GetWalletHelpString(bool showDebug); /** * Initializes the wallet, returns a new CWallet instance or a null pointer * in case of an error. */ static CWallet *CreateWalletFromFile(const std::string walletFile); static bool InitLoadWallet(); /** * Wallet post-init setup * Gives the wallet a chance to register repetitive tasks and complete * post-init tasks */ void postInitProcess(CScheduler &scheduler); /* Wallets parameter interaction */ static bool ParameterInteraction(); bool BackupWallet(const std::string &strDest); /* Set the HD chain model (chain child index counters) */ bool SetHDChain(const CHDChain &chain, bool memonly); const CHDChain &GetHDChain() { return hdChain; } /* Returns true if HD is enabled */ bool IsHDEnabled(); /* Generates a new HD master key (will not be activated) */ CPubKey GenerateNewHDMasterKey(); /** * Set the current HD master key (will reset the chain child index counters) * If possibleOldChain is provided, the parameters from the old chain * (version) will be preserved. */ bool SetHDMasterKey(const CPubKey &key, CHDChain *possibleOldChain = nullptr); }; /** A key allocated from the key pool. */ class CReserveKey final : public CReserveScript { protected: CWallet *pwallet; int64_t nIndex; CPubKey vchPubKey; public: CReserveKey(CWallet *pwalletIn) { nIndex = -1; pwallet = pwalletIn; } ~CReserveKey() { ReturnKey(); } void ReturnKey(); bool GetReservedKey(CPubKey &pubkey, bool internal = false); void KeepKey(); void KeepScript() override { KeepKey(); } }; /** * Account information. * Stored in wallet with key "acc"+string account name. */ class CAccount { public: CPubKey vchPubKey; CAccount() { SetNull(); } void SetNull() { vchPubKey = CPubKey(); } ADD_SERIALIZE_METHODS; template inline void SerializationOp(Stream &s, Operation ser_action) { int nVersion = s.GetVersion(); if (!(s.GetType() & SER_GETHASH)) { READWRITE(nVersion); } READWRITE(vchPubKey); } }; // Helper for producing a bunch of max-sized low-S signatures (eg 72 bytes) // ContainerType is meant to hold pair, and be iterable so // that each entry corresponds to each vIn, in order. template bool CWallet::DummySignTx(CMutableTransaction &txNew, const ContainerType &coins) { // Fill in dummy signatures for fee calculation. int nIn = 0; for (const auto &coin : coins) { const CScript &scriptPubKey = coin.first->tx->vout[coin.second].scriptPubKey; SignatureData sigdata; if (!ProduceSignature(DummySignatureCreator(this), scriptPubKey, sigdata)) { return false; } else { UpdateTransaction(txNew, nIn, sigdata); } nIn++; } return true; } #endif // BITCOIN_WALLET_WALLET_H diff --git a/test/functional/multiwallet.py b/test/functional/multiwallet.py index 1444cb53e..a85cb27d2 100755 --- a/test/functional/multiwallet.py +++ b/test/functional/multiwallet.py @@ -1,79 +1,87 @@ #!/usr/bin/env python3 # Copyright (c) 2017 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 multiwallet. Verify that a bitcoind node can load multiple wallet files """ import os from test_framework.test_framework import BitcoinTestFramework -from test_framework.util import * +from test_framework.util import assert_equal, assert_raises_jsonrpc class MultiWalletTest(BitcoinTestFramework): def __init__(self): super().__init__() self.setup_clean_chain = True self.num_nodes = 1 self.extra_args = [['-wallet=w1', '-wallet=w2', '-wallet=w3']] def run_test(self): self.stop_node(0) # should not initialize if there are duplicate wallets self.assert_start_raises_init_error(0, self.options.tmpdir, [ - '-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.') + '-wallet=w1', '-wallet=w1'], 'Error loading wallet w1. Duplicate -wallet filename specified.') # should not initialize if wallet file is a directory os.mkdir(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w11')) self.assert_start_raises_init_error(0, self.options.tmpdir, [ - '-wallet=w11'], 'Error loading wallet w11. -wallet filename must be a regular file.') + '-wallet=w11'], 'Error loading wallet w11. -wallet filename must be a regular file.') # should not initialize if wallet file is a symlink os.symlink(os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w1'), os.path.join(self.options.tmpdir, 'node0', 'regtest', 'w12')) self.assert_start_raises_init_error(0, self.options.tmpdir, [ - '-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.') + '-wallet=w12'], 'Error loading wallet w12. -wallet filename must be a regular file.') self.nodes[0] = self.start_node( 0, self.options.tmpdir, self.extra_args[0]) w1 = self.nodes[0] / "wallet/w1" w1.generate(1) # accessing invalid wallet fails assert_raises_jsonrpc(-18, "Requested wallet does not exist or is not loaded", (self.nodes[0] / "wallet/bad").getwalletinfo) # accessing wallet RPC without using wallet endpoint fails - assert_raises_jsonrpc(-19, "Method not found", + assert_raises_jsonrpc(-19, "Wallet file not specified", self.nodes[0].getwalletinfo) # check w1 wallet balance - walletinfo = w1.getwalletinfo() - assert_equal(walletinfo['immature_balance'], 50) + w1_info = w1.getwalletinfo() + assert_equal(w1_info['immature_balance'], 50) + w1_name = w1_info['walletname'] + assert_equal(w1_name, "w1") # check w1 wallet balance w2 = self.nodes[0] / "wallet/w2" - walletinfo = w2.getwalletinfo() - assert_equal(walletinfo['immature_balance'], 0) + w2_info = w2.getwalletinfo() + assert_equal(w2_info['immature_balance'], 0) + w2_name = w2_info['walletname'] + assert_equal(w2_name, "w2") w3 = self.nodes[0] / "wallet/w3" + w3_name = w3.getwalletinfo()['walletname'] + assert_equal(w3_name, "w3") + + assert_equal({"w1", "w2", "w3"}, {w1_name, w2_name, w3_name}) w1.generate(101) assert_equal(w1.getbalance(), 100) assert_equal(w2.getbalance(), 0) assert_equal(w3.getbalance(), 0) w1.sendtoaddress(w2.getnewaddress(), 1) w1.sendtoaddress(w3.getnewaddress(), 2) w1.generate(1) assert_equal(w2.getbalance(), 1) assert_equal(w3.getbalance(), 2) if __name__ == '__main__': MultiWalletTest().main() diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index 9f02fc466..803a3ae79 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -1,553 +1,553 @@ #!/usr/bin/env python3 # Copyright (c) 2014-2016 The Bitcoin Core developers # Copyright (c) 2017 The Bitcoin developers # Distributed under the MIT software license, see the accompanying # file COPYING or http://www.opensource.org/licenses/mit-license.php. """Run regression test suite. This module calls down into individual test cases via subprocess. It will forward all unrecognized arguments onto the individual test scripts. Functional tests are disabled on Windows by default. Use --force to run them anyway. For a description of arguments recognized by test scripts, see `test/functional/test_framework/test_framework.py:BitcoinTestFramework.main`. """ import argparse import configparser import datetime import os import time import shutil import signal import sys import subprocess import tempfile import re import logging # Formatting. Default colors to empty strings. BOLD, BLUE, RED, GREY = ("", ""), ("", ""), ("", ""), ("", "") try: # Make sure python thinks it can write unicode to its stdout "\u2713".encode("utf_8").decode(sys.stdout.encoding) TICK = "✓ " CROSS = "✖ " CIRCLE = "○ " except UnicodeDecodeError: TICK = "P " CROSS = "x " CIRCLE = "o " if os.name == 'posix': # primitive formatting on supported # terminal via ANSI escape sequences: BOLD = ('\033[0m', '\033[1m') BLUE = ('\033[0m', '\033[0;34m') RED = ('\033[0m', '\033[0;31m') GREY = ('\033[0m', '\033[1;30m') TEST_EXIT_PASSED = 0 TEST_EXIT_SKIPPED = 77 BASE_SCRIPTS = [ # Longest test should go first, to favor running tests in parallel 'wallet-hd.py', 'walletbackup.py', # vv Tests less than 5m vv 'p2p-fullblocktest.py', 'fundrawtransaction.py', 'p2p-compactblocks.py', # vv Tests less than 2m vv 'wallet.py', - 'multiwallet.py', 'wallet-accounts.py', 'wallet-dump.py', 'listtransactions.py', # vv Tests less than 60s vv 'sendheaders.py', 'zapwallettxes.py', 'importmulti.py', 'mempool_limit.py', 'merkle_blocks.py', 'receivedby.py', 'abandonconflict.py', 'bip68-112-113-p2p.py', 'rawtransactions.py', 'reindex.py', # vv Tests less than 30s vv 'zmq_test.py', 'mempool_resurrect_test.py', 'txn_doublespend.py --mineblock', 'txn_clone.py', 'getchaintips.py', 'rest.py', 'mempool_spendcoinbase.py', 'mempool_reorg.py', 'mempool_persist.py', + 'multiwallet.py', 'httpbasics.py', 'multi_rpc.py', 'proxy_test.py', 'signrawtransactions.py', 'disconnect_ban.py', 'decodescript.py', 'blockchain.py', 'disablewallet.py', 'net.py', 'keypool.py', 'p2p-mempool.py', 'prioritise_transaction.py', 'high_priority_transaction.py', 'invalidblockrequest.py', 'invalidtxrequest.py', 'p2p-versionbits-warning.py', 'preciousblock.py', 'importprunedfunds.py', 'signmessages.py', 'nulldummy.py', 'import-rescan.py', 'rpcnamedargs.py', 'listsinceblock.py', 'p2p-leaktests.py', 'abc-cmdline.py', 'abc-p2p-fullblocktest.py', 'abc-rpc.py', 'mempool-accept-txn.py', 'wallet-encryption.py', ] EXTENDED_SCRIPTS = [ # Longest test should go first, to favor running tests in parallel 'pruning.py', # vv Tests less than 20m vv 'smartfees.py', # vv Tests less than 5m vv 'maxuploadtarget.py', 'mempool_packages.py', 'dbcrash.py', # vv Tests less than 2m vv 'bip68-sequence.py', 'getblocktemplate_longpoll.py', 'p2p-timeouts.py', # vv Tests less than 60s vv 'bip9-softforks.py', 'p2p-feefilter.py', 'rpcbind_test.py', # vv Tests less than 30s vv 'assumevalid.py', 'bip65-cltv.py', 'bip65-cltv-p2p.py', 'bipdersig-p2p.py', 'bipdersig.py', 'getblocktemplate_proposals.py', 'txn_doublespend.py', 'txn_clone.py --mineblock', 'forknotify.py', 'invalidateblock.py', 'p2p-acceptblock.py', ] # Place EXTENDED_SCRIPTS first since it has the 3 longest running tests ALL_SCRIPTS = EXTENDED_SCRIPTS + BASE_SCRIPTS NON_SCRIPTS = [ # These are python files that live in the functional tests directory, but are not test scripts. "combine_logs.py", "create_cache.py", "test_runner.py", ] def main(): # Parse arguments and pass through unrecognised args parser = argparse.ArgumentParser(add_help=False, usage='%(prog)s [test_runner.py options] [script options] [scripts]', description=__doc__, epilog=''' Help text and arguments for individual test script:''', formatter_class=argparse.RawTextHelpFormatter) parser.add_argument('--coverage', action='store_true', help='generate a basic coverage report for the RPC interface') parser.add_argument( '--exclude', '-x', help='specify a comma-seperated-list of scripts to exclude. Do not include the .py extension in the name.') parser.add_argument('--extended', action='store_true', help='run the extended test suite in addition to the basic tests') parser.add_argument('--force', '-f', action='store_true', help='run tests even on platforms where they are disabled by default (e.g. windows).') parser.add_argument('--help', '-h', '-?', action='store_true', help='print help text and exit') parser.add_argument('--jobs', '-j', type=int, default=4, help='how many test scripts to run in parallel. Default=4.') parser.add_argument('--keepcache', '-k', action='store_true', help='the default behavior is to flush the cache directory on startup. --keepcache retains the cache from the previous testrun.') parser.add_argument('--quiet', '-q', action='store_true', help='only print results summary and failure logs') parser.add_argument('--tmpdirprefix', '-t', default=tempfile.gettempdir(), help="Root directory for datadirs") args, unknown_args = parser.parse_known_args() # Create a set to store arguments and create the passon string tests = set(arg for arg in unknown_args if arg[:2] != "--") passon_args = [arg for arg in unknown_args if arg[:2] == "--"] # Read config generated by configure. config = configparser.ConfigParser() configfile = os.path.abspath(os.path.dirname(__file__)) + "/../config.ini" config.read_file(open(configfile)) passon_args.append("--configfile=%s" % configfile) # Set up logging logging_level = logging.INFO if args.quiet else logging.DEBUG logging.basicConfig(format='%(message)s', level=logging_level) # Create base test directory tmpdir = "%s/bitcoin_test_runner_%s" % ( args.tmpdirprefix, datetime.datetime.now().strftime("%Y%m%d_%H%M%S")) os.makedirs(tmpdir) logging.debug("Temporary test directory at %s" % tmpdir) enable_wallet = config["components"].getboolean("ENABLE_WALLET") enable_utils = config["components"].getboolean("ENABLE_UTILS") enable_bitcoind = config["components"].getboolean("ENABLE_BITCOIND") if config["environment"]["EXEEXT"] == ".exe" and not args.force: # https://github.com/bitcoin/bitcoin/commit/d52802551752140cf41f0d9a225a43e84404d3e9 # https://github.com/bitcoin/bitcoin/pull/5677#issuecomment-136646964 print( "Tests currently disabled on Windows by default. Use --force option to enable") sys.exit(0) if not (enable_wallet and enable_utils and enable_bitcoind): print( "No functional tests to run. Wallet, utils, and bitcoind must all be enabled") print( "Rerun `configure` with -enable-wallet, -with-utils and -with-daemon and rerun make") sys.exit(0) # Build list of tests if tests: # Individual tests have been specified. Run specified tests that exist # in the ALL_SCRIPTS list. Accept the name with or without .py # extension. test_list = [t for t in ALL_SCRIPTS if (t in tests or re.sub(".py$", "", t) in tests)] else: # No individual tests have been specified. # Run all base tests, and optionally run extended tests. test_list = BASE_SCRIPTS if args.extended: test_list += EXTENDED_SCRIPTS # TODO: BASE_SCRIPTS and EXTENDED_SCRIPTS are sorted by runtime # (for parallel running efficiency). This combined list will is no # longer sorted. # Remove the test cases that the user has explicitly asked to exclude. if args.exclude: for exclude_test in args.exclude.split(','): if exclude_test + ".py" in test_list: test_list.remove(exclude_test + ".py") if not test_list: print("No valid test scripts specified. Check that your test is in one " "of the test lists in test_runner.py, or run test_runner.py with no arguments to run all tests") sys.exit(0) if args.help: # Print help for test_runner.py, then print help of the first script # and exit. parser.print_help() subprocess.check_call( (config["environment"]["SRCDIR"] + '/test/functional/' + test_list[0]).split() + ['-h']) sys.exit(0) check_script_list(config["environment"]["SRCDIR"]) if not args.keepcache: shutil.rmtree("%s/test/cache" % config["environment"]["BUILDDIR"], ignore_errors=True) run_tests(test_list, config["environment"]["SRCDIR"], config["environment"]["BUILDDIR"], config["environment"]["EXEEXT"], tmpdir, args.jobs, args.coverage, passon_args) def run_tests(test_list, src_dir, build_dir, exeext, tmpdir, jobs=1, enable_coverage=False, args=[]): # Warn if bitcoind is already running (unix only) try: pidofOutput = subprocess.check_output(["pidof", "bitcoind"]) if pidofOutput is not None and pidofOutput != b'': print("%sWARNING!%s There is already a bitcoind process running on this system. Tests may fail unexpectedly due to resource contention!" % ( BOLD[1], BOLD[0])) except (OSError, subprocess.SubprocessError): pass # Warn if there is a cache directory cache_dir = "%s/test/cache" % build_dir if os.path.isdir(cache_dir): print("%sWARNING!%s There is a cache directory here: %s. If tests fail unexpectedly, try deleting the cache directory." % ( BOLD[1], BOLD[0], cache_dir)) # Set env vars if "BITCOIND" not in os.environ: os.environ["BITCOIND"] = build_dir + '/src/bitcoind' + exeext tests_dir = src_dir + '/test/functional/' flags = ["--srcdir={}/src".format(build_dir)] + args flags.append("--cachedir=%s" % cache_dir) if enable_coverage: coverage = RPCCoverage() flags.append(coverage.flag) logging.debug("Initializing coverage directory at %s" % coverage.dir) else: coverage = None if len(test_list) > 1 and jobs > 1: # Populate cache subprocess.check_output( [tests_dir + 'create_cache.py'] + flags + ["--tmpdir=%s/cache" % tmpdir]) # Run Tests job_queue = TestHandler(jobs, tests_dir, tmpdir, test_list, flags) time0 = time.time() test_results = [] max_len_name = len(max(test_list, key=len)) for _ in range(len(test_list)): test_result, stdout, stderr = job_queue.get_next() test_results.append(test_result) if test_result.status == "Passed": logging.debug("\n%s%s%s passed, Duration: %s s" % ( BOLD[1], test_result.name, BOLD[0], test_result.time)) elif test_result.status == "Skipped": logging.debug("\n%s%s%s skipped" % (BOLD[1], test_result.name, BOLD[0])) else: print("\n%s%s%s failed, Duration: %s s\n" % (BOLD[1], test_result.name, BOLD[0], test_result.time)) print(BOLD[1] + 'stdout:\n' + BOLD[0] + stdout + '\n') print(BOLD[1] + 'stderr:\n' + BOLD[0] + stderr + '\n') print_results(test_results, max_len_name, (int(time.time() - time0))) if coverage: coverage.report_rpc_coverage() logging.debug("Cleaning up coverage data") coverage.cleanup() # Clear up the temp directory if all subdirectories are gone if not os.listdir(tmpdir): os.rmdir(tmpdir) all_passed = all( map(lambda test_result: test_result.status == "Passed", test_results)) sys.exit(not all_passed) def print_results(test_results, max_len_name, runtime): results = "\n" + BOLD[1] + "%s | %s | %s\n\n" % ( "TEST".ljust(max_len_name), "STATUS ", "DURATION") + BOLD[0] test_results.sort(key=lambda result: result.name.lower()) all_passed = True time_sum = 0 for test_result in test_results: all_passed = all_passed and test_result.status != "Failed" time_sum += test_result.time test_result.padding = max_len_name results += str(test_result) status = TICK + "Passed" if all_passed else CROSS + "Failed" results += BOLD[1] + "\n%s | %s | %s s (accumulated) \n" % ( "ALL".ljust(max_len_name), status.ljust(9), time_sum) + BOLD[0] results += "Runtime: %s s\n" % (runtime) print(results) class TestHandler: """ Trigger the testscrips passed in via the list. """ def __init__(self, num_tests_parallel, tests_dir, tmpdir, test_list=None, flags=None): assert(num_tests_parallel >= 1) self.num_jobs = num_tests_parallel self.tests_dir = tests_dir self.tmpdir = tmpdir self.test_list = test_list self.flags = flags self.num_running = 0 # In case there is a graveyard of zombie bitcoinds, we can apply a # pseudorandom offset to hopefully jump over them. # (625 is PORT_RANGE/MAX_NODES) self.portseed_offset = int(time.time() * 1000) % 625 self.jobs = [] def get_next(self): while self.num_running < self.num_jobs and self.test_list: # Add tests self.num_running += 1 t = self.test_list.pop(0) portseed = len(self.test_list) + self.portseed_offset portseed_arg = ["--portseed={}".format(portseed)] log_stdout = tempfile.SpooledTemporaryFile(max_size=2**16) log_stderr = tempfile.SpooledTemporaryFile(max_size=2**16) test_argv = t.split() tmpdir = ["--tmpdir=%s/%s_%s" % (self.tmpdir, re.sub(".py$", "", test_argv[0]), portseed)] self.jobs.append((t, time.time(), subprocess.Popen([self.tests_dir + test_argv[0]] + test_argv[1:] + self.flags + portseed_arg + tmpdir, universal_newlines=True, stdout=log_stdout, stderr=log_stderr), log_stdout, log_stderr)) if not self.jobs: raise IndexError('pop from empty list') while True: # Return first proc that finishes time.sleep(.5) for j in self.jobs: (name, time0, proc, log_out, log_err) = j if os.getenv('TRAVIS') == 'true' and int(time.time() - time0) > 20 * 60: # In travis, timeout individual tests after 20 minutes (to stop tests hanging and not # providing useful output. proc.send_signal(signal.SIGINT) if proc.poll() is not None: log_out.seek(0), log_err.seek(0) [stdout, stderr] = [l.read().decode('utf-8') for l in (log_out, log_err)] log_out.close(), log_err.close() if proc.returncode == TEST_EXIT_PASSED and stderr == "": status = "Passed" elif proc.returncode == TEST_EXIT_SKIPPED: status = "Skipped" else: status = "Failed" self.num_running -= 1 self.jobs.remove(j) return TestResult(name, status, int(time.time() - time0)), stdout, stderr print('.', end='', flush=True) class TestResult(): def __init__(self, name, status, time): self.name = name self.status = status self.time = time self.padding = 0 def __repr__(self): if self.status == "Passed": color = BLUE glyph = TICK elif self.status == "Failed": color = RED glyph = CROSS elif self.status == "Skipped": color = GREY glyph = CIRCLE return color[1] + "%s | %s%s | %s s\n" % (self.name.ljust(self.padding), glyph, self.status.ljust(7), self.time) + color[0] def check_script_list(src_dir): """Check scripts directory. Check that there are no scripts in the functional tests directory which are not being run by pull-tester.py.""" script_dir = src_dir + '/test/functional/' python_files = set([t for t in os.listdir(script_dir) if t[-3:] == ".py"]) missed_tests = list( python_files - set(map(lambda x: x.split()[0], ALL_SCRIPTS + NON_SCRIPTS))) if len(missed_tests) != 0: print("%sWARNING!%s The following scripts are not being run: %s. Check the test lists in test_runner.py." % ( BOLD[1], BOLD[0], str(missed_tests))) if os.getenv('TRAVIS') == 'true': # On travis this warning is an error to prevent merging incomplete commits into master sys.exit(1) class RPCCoverage(): """ Coverage reporting utilities for test_runner. Coverage calculation works by having each test script subprocess write coverage files into a particular directory. These files contain the RPC commands invoked during testing, as well as a complete listing of RPC commands per `bitcoin-cli help` (`rpc_interface.txt`). After all tests complete, the commands run are combined and diff'd against the complete list to calculate uncovered RPC commands. See also: test/functional/test_framework/coverage.py """ def __init__(self): self.dir = tempfile.mkdtemp(prefix="coverage") self.flag = '--coveragedir={}'.format(self.dir) def report_rpc_coverage(self): """ Print out RPC commands that were unexercised by tests. """ uncovered = self._get_uncovered_rpc_commands() if uncovered: print("Uncovered RPC commands:") print("".join((" - {}\n".format(i)) for i in sorted(uncovered))) else: print("All RPC commands covered.") def cleanup(self): return shutil.rmtree(self.dir) def _get_uncovered_rpc_commands(self): """ Return a set of currently untested RPC commands. """ # This is shared from `test/functional/test-framework/coverage.py` reference_filename = 'rpc_interface.txt' coverage_file_prefix = 'coverage.' coverage_ref_filename = os.path.join(self.dir, reference_filename) coverage_filenames = set() all_cmds = set() covered_cmds = set() if not os.path.isfile(coverage_ref_filename): raise RuntimeError("No coverage reference found") with open(coverage_ref_filename, 'r') as f: all_cmds.update([i.strip() for i in f.readlines()]) for root, dirs, files in os.walk(self.dir): for filename in files: if filename.startswith(coverage_file_prefix): coverage_filenames.add(os.path.join(root, filename)) for filename in coverage_filenames: with open(filename, 'r') as f: covered_cmds.update([i.strip() for i in f.readlines()]) return all_cmds - covered_cmds if __name__ == '__main__': main()