diff options
Diffstat (limited to 'src/wallet/rpcwallet.cpp')
-rw-r--r-- | src/wallet/rpcwallet.cpp | 1693 |
1 files changed, 925 insertions, 768 deletions
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 45b572aa2e..6efbc23281 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -9,52 +9,74 @@ #include "consensus/validation.h" #include "core_io.h" #include "init.h" +#include "httpserver.h" #include "validation.h" #include "net.h" +#include "policy/feerate.h" +#include "policy/fees.h" #include "policy/policy.h" #include "policy/rbf.h" +#include "rpc/mining.h" #include "rpc/server.h" #include "script/sign.h" #include "timedata.h" #include "util.h" #include "utilmoneystr.h" -#include "wallet.h" -#include "walletdb.h" +#include "wallet/coincontrol.h" +#include "wallet/feebumper.h" +#include "wallet/wallet.h" +#include "wallet/walletdb.h" #include <stdint.h> -#include <boost/assign/list_of.hpp> - #include <univalue.h> -using namespace std; +static const std::string WALLET_ENDPOINT_BASE = "/wallet/"; -int64_t nWalletUnlockTime; -static CCriticalSection cs_nWalletUnlockTime; +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() +std::string HelpRequiringPassphrase(CWallet * const pwallet) { - return pwalletMain && pwalletMain->IsCrypted() + return pwallet && pwallet->IsCrypted() ? "\nRequires wallet passphrase to be set with walletpassphrase call." : ""; } -bool EnsureWalletIsAvailable(bool avoidException) +bool EnsureWalletIsAvailable(CWallet * const pwallet, bool avoidException) { - if (!pwalletMain) - { - if (!avoidException) - throw JSONRPCError(RPC_METHOD_NOT_FOUND, "Method not found (disabled)"); - else - return false; - } - return true; + 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/<filename> uri-path)."); } -void EnsureWalletIsUnlocked() +void EnsureWalletIsUnlocked(CWallet * const pwallet) { - if (pwalletMain->IsLocked()) + 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) @@ -74,7 +96,7 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) uint256 hash = wtx.GetHash(); entry.push_back(Pair("txid", hash.GetHex())); UniValue conflicts(UniValue::VARR); - BOOST_FOREACH(const uint256& conflict, wtx.GetConflicts()) + for (const uint256& conflict : wtx.GetConflicts()) conflicts.push_back(conflict.GetHex()); entry.push_back(Pair("walletconflicts", conflicts)); entry.push_back(Pair("time", wtx.GetTxTime())); @@ -92,13 +114,13 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) } entry.push_back(Pair("bip125-replaceable", rbfStatus)); - BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue) + for (const std::pair<std::string, std::string>& item : wtx.mapValue) entry.push_back(Pair(item.first, item.second)); } -string AccountFromValue(const UniValue& value) +std::string AccountFromValue(const UniValue& value) { - string strAccount = value.get_str(); + std::string strAccount = value.get_str(); if (strAccount == "*") throw JSONRPCError(RPC_WALLET_INVALID_ACCOUNT_NAME, "Invalid account name"); return strAccount; @@ -106,11 +128,13 @@ string AccountFromValue(const UniValue& value) UniValue getnewaddress(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() > 1) - throw runtime_error( + 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" @@ -124,32 +148,34 @@ UniValue getnewaddress(const JSONRPCRequest& request) + HelpExampleRpc("getnewaddress", "") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); // Parse the account first so we don't generate a key if there's an error - string strAccount; - if (request.params.size() > 0) + std::string strAccount; + if (!request.params[0].isNull()) strAccount = AccountFromValue(request.params[0]); - if (!pwalletMain->IsLocked()) - pwalletMain->TopUpKeyPool(); + if (!pwallet->IsLocked()) { + pwallet->TopUpKeyPool(); + } // Generate a new key that is added to wallet CPubKey newKey; - if (!pwalletMain->GetKeyFromPool(newKey)) + if (!pwallet->GetKeyFromPool(newKey)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); + } CKeyID keyID = newKey.GetID(); - pwalletMain->SetAddressBook(keyID, strAccount, "receive"); + pwallet->SetAddressBook(keyID, strAccount, "receive"); return CBitcoinAddress(keyID).ToString(); } -CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) +CBitcoinAddress GetAccountAddress(CWallet* const pwallet, std::string strAccount, bool bForceNew=false) { CPubKey pubKey; - if (!pwalletMain->GetAccountPubkey(pubKey, strAccount, bForceNew)) { + if (!pwallet->GetAccountPubkey(pubKey, strAccount, bForceNew)) { throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); } @@ -158,11 +184,13 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) UniValue getaccountaddress(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() != 1) - throw runtime_error( + throw std::runtime_error( "getaccountaddress \"account\"\n" "\nDEPRECATED. Returns the current Bitcoin address for receiving payments to this account.\n" "\nArguments:\n" @@ -176,25 +204,27 @@ UniValue getaccountaddress(const JSONRPCRequest& request) + HelpExampleRpc("getaccountaddress", "\"myaccount\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); // Parse the account first so we don't generate a key if there's an error - string strAccount = AccountFromValue(request.params[0]); + std::string strAccount = AccountFromValue(request.params[0]); UniValue ret(UniValue::VSTR); - ret = GetAccountAddress(strAccount).ToString(); + ret = GetAccountAddress(pwallet, strAccount).ToString(); return ret; } UniValue getrawchangeaddress(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (request.fHelp || request.params.size() > 1) - throw runtime_error( + if (request.fHelp || request.params.size() > 0) + 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" @@ -205,14 +235,15 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) + HelpExampleRpc("getrawchangeaddress", "") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - if (!pwalletMain->IsLocked()) - pwalletMain->TopUpKeyPool(); + if (!pwallet->IsLocked()) { + pwallet->TopUpKeyPool(); + } - CReserveKey reservekey(pwalletMain); + CReserveKey reservekey(pwallet); CPubKey vchPubKey; - if (!reservekey.GetReservedKey(vchPubKey)) + if (!reservekey.GetReservedKey(vchPubKey, true)) throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); reservekey.KeepKey(); @@ -225,42 +256,43 @@ UniValue getrawchangeaddress(const JSONRPCRequest& request) UniValue setaccount(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw runtime_error( + 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", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" \"tabby\"") - + HelpExampleRpc("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\", \"tabby\"") + + HelpExampleCli("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"tabby\"") + + HelpExampleRpc("setaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"tabby\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); CBitcoinAddress address(request.params[0].get_str()); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); - string strAccount; + std::string strAccount; if (request.params.size() > 1) strAccount = AccountFromValue(request.params[1]); // Only add the account if the address is yours. - if (IsMine(*pwalletMain, address.Get())) - { + if (IsMine(*pwallet, address.Get())) { // Detect when changing the account of an address that is the 'unused current key' of another account: - if (pwalletMain->mapAddressBook.count(address.Get())) - { - string strOldAccount = pwalletMain->mapAddressBook[address.Get()].name; - if (address == GetAccountAddress(strOldAccount)) - GetAccountAddress(strOldAccount, true); + if (pwallet->mapAddressBook.count(address.Get())) { + std::string strOldAccount = pwallet->mapAddressBook[address.Get()].name; + if (address == GetAccountAddress(pwallet, strOldAccount)) { + GetAccountAddress(pwallet, strOldAccount, true); + } } - pwalletMain->SetAddressBook(address.Get(), strAccount, "receive"); + pwallet->SetAddressBook(address.Get(), strAccount, "receive"); } else throw JSONRPCError(RPC_MISC_ERROR, "setaccount can only be used with own address"); @@ -271,11 +303,13 @@ UniValue setaccount(const JSONRPCRequest& request) UniValue getaccount(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() != 1) - throw runtime_error( + throw std::runtime_error( "getaccount \"address\"\n" "\nDEPRECATED. Returns the account associated with the given address.\n" "\nArguments:\n" @@ -283,31 +317,34 @@ UniValue getaccount(const JSONRPCRequest& request) "\nResult:\n" "\"accountname\" (string) the account address\n" "\nExamples:\n" - + HelpExampleCli("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\"") - + HelpExampleRpc("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\"") + + HelpExampleCli("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + + HelpExampleRpc("getaccount", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); CBitcoinAddress address(request.params[0].get_str()); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); - string strAccount; - map<CTxDestination, CAddressBookData>::iterator mi = pwalletMain->mapAddressBook.find(address.Get()); - if (mi != pwalletMain->mapAddressBook.end() && !(*mi).second.name.empty()) + std::string strAccount; + std::map<CTxDestination, CAddressBookData>::iterator mi = pwallet->mapAddressBook.find(address.Get()); + if (mi != pwallet->mapAddressBook.end() && !(*mi).second.name.empty()) { strAccount = (*mi).second.name; + } return strAccount; } UniValue getaddressesbyaccount(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() != 1) - throw runtime_error( + throw std::runtime_error( "getaddressesbyaccount \"account\"\n" "\nDEPRECATED. Returns the list of addresses for the given account.\n" "\nArguments:\n" @@ -322,25 +359,24 @@ UniValue getaddressesbyaccount(const JSONRPCRequest& request) + HelpExampleRpc("getaddressesbyaccount", "\"tabby\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - string strAccount = AccountFromValue(request.params[0]); + std::string strAccount = AccountFromValue(request.params[0]); // Find all addresses that have the given account UniValue ret(UniValue::VARR); - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) - { + for (const std::pair<CBitcoinAddress, CAddressBookData>& item : pwallet->mapAddressBook) { const CBitcoinAddress& address = item.first; - const string& strName = item.second.name; + const std::string& strName = item.second.name; if (strName == strAccount) ret.push_back(address.ToString()); } return ret; } -static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew) +static void SendMoney(CWallet * const pwallet, const CTxDestination &address, CAmount nValue, bool fSubtractFeeFromAmount, CWalletTx& wtxNew, const CCoinControl& coin_control) { - CAmount curBalance = pwalletMain->GetBalance(); + CAmount curBalance = pwallet->GetBalance(); // Check amount if (nValue <= 0) @@ -349,27 +385,28 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr if (nValue > curBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); - if (pwalletMain->GetBroadcastTransactions() && !g_connman) + 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(pwalletMain); + CReserveKey reservekey(pwallet); CAmount nFeeRequired; std::string strError; - vector<CRecipient> vecSend; + std::vector<CRecipient> vecSend; int nChangePosRet = -1; CRecipient recipient = {scriptPubKey, nValue, fSubtractFeeFromAmount}; vecSend.push_back(recipient); - if (!pwalletMain->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError)) { + if (!pwallet->CreateTransaction(vecSend, wtxNew, reservekey, nFeeRequired, nChangePosRet, strError, coin_control)) { 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 (!pwalletMain->CommitTransaction(wtxNew, reservekey, g_connman.get(), 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); } @@ -377,14 +414,16 @@ static void SendMoney(const CTxDestination &address, CAmount nValue, bool fSubtr UniValue sendtoaddress(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) - throw runtime_error( - "sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount )\n" + if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) + throw std::runtime_error( + "sendtoaddress \"address\" amount ( \"comment\" \"comment_to\" subtractfeefromamount replaceable conf_target \"estimate_mode\")\n" "\nSend an amount to a given address.\n" - + HelpRequiringPassphrase() + + + 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" @@ -395,6 +434,12 @@ UniValue sendtoaddress(const JSONRPCRequest& request) " 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" + "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n" + "7. conf_target (numeric, optional) Confirmation target (in blocks)\n" + "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" "\nResult:\n" "\"txid\" (string) The transaction id.\n" "\nExamples:\n" @@ -404,7 +449,7 @@ UniValue sendtoaddress(const JSONRPCRequest& request) + HelpExampleRpc("sendtoaddress", "\"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.1, \"donation\", \"seans outpost\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); CBitcoinAddress address(request.params[0].get_str()); if (!address.IsValid()) @@ -423,23 +468,42 @@ UniValue sendtoaddress(const JSONRPCRequest& request) wtx.mapValue["to"] = request.params[3].get_str(); bool fSubtractFeeFromAmount = false; - if (request.params.size() > 4) + if (request.params.size() > 4 && !request.params[4].isNull()) { fSubtractFeeFromAmount = request.params[4].get_bool(); + } + + CCoinControl coin_control; + if (request.params.size() > 5 && !request.params[5].isNull()) { + coin_control.signalRbf = request.params[5].get_bool(); + } + + if (request.params.size() > 6 && !request.params[6].isNull()) { + coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]); + } + + if (request.params.size() > 7 && !request.params[7].isNull()) { + if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } + - EnsureWalletIsUnlocked(); + EnsureWalletIsUnlocked(pwallet); - SendMoney(address.Get(), nAmount, fSubtractFeeFromAmount, wtx); + SendMoney(pwallet, address.Get(), nAmount, fSubtractFeeFromAmount, wtx, coin_control); return wtx.GetHash().GetHex(); } UniValue listaddressgroupings(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (request.fHelp) - throw runtime_error( + if (request.fHelp || request.params.size() != 0) + 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" @@ -461,21 +525,21 @@ UniValue listaddressgroupings(const JSONRPCRequest& request) + HelpExampleRpc("listaddressgroupings", "") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); UniValue jsonGroupings(UniValue::VARR); - map<CTxDestination, CAmount> balances = pwalletMain->GetAddressBalances(); - BOOST_FOREACH(set<CTxDestination> grouping, pwalletMain->GetAddressGroupings()) - { + std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(); + for (std::set<CTxDestination> grouping : pwallet->GetAddressGroupings()) { UniValue jsonGrouping(UniValue::VARR); - BOOST_FOREACH(CTxDestination address, grouping) + for (CTxDestination address : grouping) { UniValue addressInfo(UniValue::VARR); addressInfo.push_back(CBitcoinAddress(address).ToString()); addressInfo.push_back(ValueFromAmount(balances[address])); { - if (pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwalletMain->mapAddressBook.end()) - addressInfo.push_back(pwalletMain->mapAddressBook.find(CBitcoinAddress(address).Get())->second.name); + if (pwallet->mapAddressBook.find(CBitcoinAddress(address).Get()) != pwallet->mapAddressBook.end()) { + addressInfo.push_back(pwallet->mapAddressBook.find(CBitcoinAddress(address).Get())->second.name); + } } jsonGrouping.push_back(addressInfo); } @@ -486,14 +550,16 @@ UniValue listaddressgroupings(const JSONRPCRequest& request) UniValue signmessage(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() != 2) - throw runtime_error( + throw std::runtime_error( "signmessage \"address\" \"message\"\n" "\nSign a message with the private key of an address" - + HelpRequiringPassphrase() + "\n" + + 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" @@ -503,19 +569,19 @@ UniValue signmessage(const JSONRPCRequest& request) "\nUnlock the wallet for 30 seconds\n" + HelpExampleCli("walletpassphrase", "\"mypassphrase\" 30") + "\nCreate the signature\n" - + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" \"my message\"") + + + HelpExampleCli("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"my message\"") + "\nVerify the signature\n" - + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" \"signature\" \"my message\"") + + + HelpExampleCli("verifymessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" \"signature\" \"my message\"") + "\nAs json rpc\n" - + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\", \"my message\"") + + HelpExampleRpc("signmessage", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", \"my message\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - EnsureWalletIsUnlocked(); + EnsureWalletIsUnlocked(pwallet); - string strAddress = request.params[0].get_str(); - string strMessage = request.params[1].get_str(); + std::string strAddress = request.params[0].get_str(); + std::string strMessage = request.params[1].get_str(); CBitcoinAddress addr(strAddress); if (!addr.IsValid()) @@ -526,14 +592,15 @@ UniValue signmessage(const JSONRPCRequest& request) throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key"); CKey key; - if (!pwalletMain->GetKey(keyID, key)) + if (!pwallet->GetKey(keyID, key)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key not available"); + } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; ss << strMessage; - vector<unsigned char> vchSig; + std::vector<unsigned char> vchSig; if (!key.SignCompact(ss.GetHash(), vchSig)) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Sign failed"); @@ -542,11 +609,13 @@ UniValue signmessage(const JSONRPCRequest& request) UniValue getreceivedbyaddress(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw runtime_error( + 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" @@ -556,39 +625,39 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request) "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", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\"") + + + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\"") + "\nThe amount including unconfirmed transactions, zero confirmations\n" - + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" 0") + - "\nThe amount with at least 6 confirmation, very safe\n" - + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\" 6") + + + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 0") + + "\nThe amount with at least 6 confirmations\n" + + HelpExampleCli("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\" 6") + "\nAs a json rpc call\n" - + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\", 6") + + HelpExampleRpc("getreceivedbyaddress", "\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\", 6") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); // Bitcoin address CBitcoinAddress address = CBitcoinAddress(request.params[0].get_str()); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); CScript scriptPubKey = GetScriptForDestination(address.Get()); - if (!IsMine(*pwalletMain, scriptPubKey)) + if (!IsMine(*pwallet, scriptPubKey)) { return ValueFromAmount(0); + } // Minimum confirmations int nMinDepth = 1; - if (request.params.size() > 1) + if (!request.params[1].isNull()) nMinDepth = request.params[1].get_int(); // Tally CAmount nAmount = 0; - for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + const CWalletTx& wtx = pairWtx.second; if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; - BOOST_FOREACH(const CTxOut& txout, wtx.tx->vout) + for (const CTxOut& txout : wtx.tx->vout) if (txout.scriptPubKey == scriptPubKey) if (wtx.GetDepthInMainChain() >= nMinDepth) nAmount += txout.nValue; @@ -600,11 +669,13 @@ UniValue getreceivedbyaddress(const JSONRPCRequest& request) UniValue getreceivedbyaccount(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw runtime_error( + throw std::runtime_error( "getreceivedbyaccount \"account\" ( minconf )\n" "\nDEPRECATED. Returns the total amount received by addresses with <account> in transactions with at least [minconf] confirmations.\n" "\nArguments:\n" @@ -617,37 +688,37 @@ UniValue getreceivedbyaccount(const JSONRPCRequest& request) + 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" + "\nThe amount with at least 6 confirmations\n" + HelpExampleCli("getreceivedbyaccount", "\"tabby\" 6") + "\nAs a json rpc call\n" + HelpExampleRpc("getreceivedbyaccount", "\"tabby\", 6") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); // Minimum confirmations int nMinDepth = 1; - if (request.params.size() > 1) + if (!request.params[1].isNull()) nMinDepth = request.params[1].get_int(); // Get the set of pub keys assigned to account - string strAccount = AccountFromValue(request.params[0]); - set<CTxDestination> setAddress = pwalletMain->GetAccountAddresses(strAccount); + std::string strAccount = AccountFromValue(request.params[0]); + std::set<CTxDestination> setAddress = pwallet->GetAccountAddresses(strAccount); // Tally CAmount nAmount = 0; - for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + const CWalletTx& wtx = pairWtx.second; if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; - BOOST_FOREACH(const CTxOut& txout, wtx.tx->vout) + for (const CTxOut& txout : wtx.tx->vout) { CTxDestination address; - if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwalletMain, address) && setAddress.count(address)) + if (ExtractDestination(txout.scriptPubKey, address) && IsMine(*pwallet, address) && setAddress.count(address)) { if (wtx.GetDepthInMainChain() >= nMinDepth) nAmount += txout.nValue; + } } } @@ -657,11 +728,13 @@ UniValue getreceivedbyaccount(const JSONRPCRequest& request) UniValue getbalance(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() > 3) - throw runtime_error( + 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" @@ -685,88 +758,60 @@ UniValue getbalance(const JSONRPCRequest& request) "\nResult:\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" "\nExamples:\n" - "\nThe total amount in the wallet\n" + "\nThe total amount in the wallet with 1 or more confirmations\n" + HelpExampleCli("getbalance", "") + - "\nThe total amount in the wallet at least 5 blocks confirmed\n" + "\nThe total amount in the wallet at least 6 blocks confirmed\n" + HelpExampleCli("getbalance", "\"*\" 6") + "\nAs a json rpc call\n" + HelpExampleRpc("getbalance", "\"*\", 6") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); if (request.params.size() == 0) - return ValueFromAmount(pwalletMain->GetBalance()); + return ValueFromAmount(pwallet->GetBalance()); + + const std::string& account_param = request.params[0].get_str(); + const std::string* account = account_param != "*" ? &account_param : nullptr; int nMinDepth = 1; - if (request.params.size() > 1) + if (!request.params[1].isNull()) nMinDepth = request.params[1].get_int(); isminefilter filter = ISMINE_SPENDABLE; - if(request.params.size() > 2) + if(!request.params[2].isNull()) if(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. - CAmount nBalance = 0; - for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; - if (!CheckFinalTx(wtx) || wtx.GetBlocksToMaturity() > 0 || wtx.GetDepthInMainChain() < 0) - continue; - - CAmount allFee; - string strSentAccount; - list<COutputEntry> listReceived; - list<COutputEntry> listSent; - wtx.GetAmounts(listReceived, listSent, allFee, strSentAccount, filter); - if (wtx.GetDepthInMainChain() >= nMinDepth) - { - BOOST_FOREACH(const COutputEntry& r, listReceived) - nBalance += r.amount; - } - BOOST_FOREACH(const COutputEntry& s, listSent) - nBalance -= s.amount; - nBalance -= allFee; - } - return ValueFromAmount(nBalance); - } - - string strAccount = AccountFromValue(request.params[0]); - - CAmount nBalance = pwalletMain->GetAccountBalance(strAccount, nMinDepth, filter); - - return ValueFromAmount(nBalance); + return ValueFromAmount(pwallet->GetLegacyBalance(filter, nMinDepth, account)); } UniValue getunconfirmedbalance(const JSONRPCRequest &request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() > 0) - throw runtime_error( + throw std::runtime_error( "getunconfirmedbalance\n" "Returns the server's total unconfirmed balance\n"); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - return ValueFromAmount(pwalletMain->GetUnconfirmedBalance()); + return ValueFromAmount(pwallet->GetUnconfirmedBalance()); } UniValue movecmd(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 3 || request.params.size() > 5) - throw runtime_error( + 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" @@ -786,22 +831,23 @@ UniValue movecmd(const JSONRPCRequest& request) + HelpExampleRpc("move", "\"timotei\", \"akiko\", 0.01, 6, \"happy birthday!\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - string strFrom = AccountFromValue(request.params[0]); - string strTo = AccountFromValue(request.params[1]); + std::string strFrom = AccountFromValue(request.params[0]); + std::string strTo = AccountFromValue(request.params[1]); CAmount nAmount = AmountFromValue(request.params[2]); if (nAmount <= 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(); - string strComment; + std::string strComment; if (request.params.size() > 4) strComment = request.params[4].get_str(); - if (!pwalletMain->AccountMove(strFrom, strTo, nAmount, strComment)) + if (!pwallet->AccountMove(strFrom, strTo, nAmount, strComment)) { throw JSONRPCError(RPC_DATABASE_ERROR, "database error"); + } return true; } @@ -809,16 +855,21 @@ UniValue movecmd(const JSONRPCRequest& request) UniValue sendfrom(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 3 || request.params.size() > 6) - throw runtime_error( + 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() + "\n" + + 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" @@ -838,9 +889,9 @@ UniValue sendfrom(const JSONRPCRequest& request) + HelpExampleRpc("sendfrom", "\"tabby\", \"1M72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd\", 0.01, 6, \"donation\", \"seans outpost\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - string strAccount = AccountFromValue(request.params[0]); + std::string strAccount = AccountFromValue(request.params[0]); CBitcoinAddress address(request.params[1].get_str()); if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); @@ -858,14 +909,15 @@ UniValue sendfrom(const JSONRPCRequest& request) if (request.params.size() > 5 && !request.params[5].isNull() && !request.params[5].get_str().empty()) wtx.mapValue["to"] = request.params[5].get_str(); - EnsureWalletIsUnlocked(); + EnsureWalletIsUnlocked(pwallet); // Check funds - CAmount nBalance = pwalletMain->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); + CAmount nBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount); if (nAmount > nBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); - SendMoney(address.Get(), nAmount, false, wtx); + CCoinControl no_coin_control; // This is a deprecated API + SendMoney(pwallet, address.Get(), nAmount, false, wtx, no_coin_control); return wtx.GetHash().GetHex(); } @@ -873,14 +925,16 @@ UniValue sendfrom(const JSONRPCRequest& request) UniValue sendmany(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (request.fHelp || request.params.size() < 2 || request.params.size() > 5) - throw runtime_error( - "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] )\n" + if (request.fHelp || request.params.size() < 2 || request.params.size() > 8) + throw std::runtime_error( + "sendmany \"fromaccount\" {\"address\":amount,...} ( minconf \"comment\" [\"address\",...] replaceable conf_target \"estimate_mode\")\n" "\nSend multiple times. Amounts are double-precision floating point numbers." - + HelpRequiringPassphrase() + "\n" + + 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" @@ -898,29 +952,36 @@ UniValue sendmany(const JSONRPCRequest& request) " \"address\" (string) Subtract fee from this address\n" " ,...\n" " ]\n" - "\nResult:\n" + "6. replaceable (boolean, optional) Allow this transaction to be replaced by a transaction with higher fees via BIP 125\n" + "7. conf_target (numeric, optional) Confirmation target (in blocks)\n" + "8. \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\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", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + + + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\"") + "\nSend two amounts to two different addresses setting the confirmation and comment:\n" - + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") + + + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 6 \"testing\"") + "\nSend two amounts to two different addresses, subtract fee from amount:\n" - + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + + + HelpExampleCli("sendmany", "\"\" \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\" 1 \"\" \"[\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\",\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\"]\"") + "\nAs a json rpc call\n" - + HelpExampleRpc("sendmany", "\"\", \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XZ\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\", 6, \"testing\"") + + HelpExampleRpc("sendmany", "\"\", \"{\\\"1D1ZrZNe3JUo7ZycKEYQQiQAWd9y54F4XX\\\":0.01,\\\"1353tsE8YMTA4EuV7dgUXGjNFf9KpVvKHz\\\":0.02}\", 6, \"testing\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - if (pwalletMain->GetBroadcastTransactions() && !g_connman) + if (pwallet->GetBroadcastTransactions() && !g_connman) { throw JSONRPCError(RPC_CLIENT_P2P_DISABLED, "Error: Peer-to-peer functionality missing or disabled"); + } - string strAccount = AccountFromValue(request.params[0]); + std::string strAccount = AccountFromValue(request.params[0]); UniValue sendTo = request.params[1].get_obj(); int nMinDepth = 1; - if (request.params.size() > 2) + if (!request.params[2].isNull()) nMinDepth = request.params[2].get_int(); CWalletTx wtx; @@ -929,22 +990,37 @@ UniValue sendmany(const JSONRPCRequest& request) wtx.mapValue["comment"] = request.params[3].get_str(); UniValue subtractFeeFromAmount(UniValue::VARR); - if (request.params.size() > 4) + if (request.params.size() > 4 && !request.params[4].isNull()) subtractFeeFromAmount = request.params[4].get_array(); - set<CBitcoinAddress> setAddress; - vector<CRecipient> vecSend; + CCoinControl coin_control; + if (request.params.size() > 5 && !request.params[5].isNull()) { + coin_control.signalRbf = request.params[5].get_bool(); + } + + if (request.params.size() > 6 && !request.params[6].isNull()) { + coin_control.m_confirm_target = ParseConfirmTarget(request.params[6]); + } + + if (request.params.size() > 7 && !request.params[7].isNull()) { + if (!FeeModeFromString(request.params[7].get_str(), coin_control.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } + + std::set<CBitcoinAddress> setAddress; + std::vector<CRecipient> vecSend; CAmount totalAmount = 0; - vector<string> keys = sendTo.getKeys(); - BOOST_FOREACH(const string& name_, keys) + std::vector<std::string> keys = sendTo.getKeys(); + for (const std::string& name_ : keys) { CBitcoinAddress address(name_); if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+name_); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ")+name_); if (setAddress.count(address)) - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+name_); + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ")+name_); setAddress.insert(address); CScript scriptPubKey = GetScriptForDestination(address.Get()); @@ -964,23 +1040,23 @@ UniValue sendmany(const JSONRPCRequest& request) vecSend.push_back(recipient); } - EnsureWalletIsUnlocked(); + EnsureWalletIsUnlocked(pwallet); // Check funds - CAmount nBalance = pwalletMain->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); + CAmount nBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount); if (totalAmount > nBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); // Send - CReserveKey keyChange(pwalletMain); + CReserveKey keyChange(pwallet); CAmount nFeeRequired = 0; int nChangePosRet = -1; - string strFailReason; - bool fCreated = pwalletMain->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason); + std::string strFailReason; + bool fCreated = pwallet->CreateTransaction(vecSend, wtx, keyChange, nFeeRequired, nChangePosRet, strFailReason, coin_control); if (!fCreated) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, strFailReason); CValidationState state; - if (!pwalletMain->CommitTransaction(wtx, keyChange, g_connman.get(), state)) { + if (!pwallet->CommitTransaction(wtx, keyChange, g_connman.get(), state)) { strFailReason = strprintf("Transaction commit failed:: %s", state.GetRejectReason()); throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); } @@ -989,16 +1065,18 @@ UniValue sendmany(const JSONRPCRequest& request) } // Defined in rpc/misc.cpp -extern CScript _createmultisig_redeemScript(const UniValue& params); +extern CScript _createmultisig_redeemScript(CWallet * const pwallet, const UniValue& params); UniValue addmultisigaddress(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 2 || request.params.size() > 3) { - string msg = "addmultisigaddress nrequired [\"key\",...] ( \"account\" )\n" + 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" @@ -1021,41 +1099,47 @@ UniValue addmultisigaddress(const JSONRPCRequest& request) "\nAs json rpc call\n" + HelpExampleRpc("addmultisigaddress", "2, \"[\\\"16sSauSf5pF2UkUwvKGq4qjNRzBZYqgEL5\\\",\\\"171sgjn4YtPu27adkKGrdDwzRTxnRkBfKV\\\"]\"") ; - throw runtime_error(msg); + throw std::runtime_error(msg); } - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - string strAccount; + std::string strAccount; if (request.params.size() > 2) strAccount = AccountFromValue(request.params[2]); // Construct using pay-to-script-hash: - CScript inner = _createmultisig_redeemScript(request.params); + CScript inner = _createmultisig_redeemScript(pwallet, request.params); CScriptID innerID(inner); - pwalletMain->AddCScript(inner); + pwallet->AddCScript(inner); - pwalletMain->SetAddressBook(innerID, strAccount, "send"); + pwallet->SetAddressBook(innerID, strAccount, "send"); return CBitcoinAddress(innerID).ToString(); } class Witnessifier : public boost::static_visitor<bool> { public: + CWallet * const pwallet; CScriptID result; + Witnessifier(CWallet *_pwallet) : pwallet(_pwallet) {} + bool operator()(const CNoDestination &dest) const { return false; } bool operator()(const CKeyID &keyID) { - CPubKey pubkey; - if (pwalletMain) { + if (pwallet) { CScript basescript = GetScriptForDestination(keyID); - isminetype typ; - typ = IsMine(*pwalletMain, basescript, SIGVERSION_WITNESS_V0); - if (typ != ISMINE_SPENDABLE && typ != ISMINE_WATCH_SOLVABLE) - return false; CScript witscript = GetScriptForWitness(basescript); - pwalletMain->AddCScript(witscript); + SignatureData sigs; + // This check is to make sure that the script we created can actually be solved for and signed by us + // if we were to have the private keys. This is just to make sure that the script is valid and that, + // if found in a transaction, we would still accept and relay that transcation. + if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) || + !VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) { + return false; + } + pwallet->AddCScript(witscript); result = CScriptID(witscript); return true; } @@ -1064,19 +1148,23 @@ public: bool operator()(const CScriptID &scriptID) { CScript subscript; - if (pwalletMain && pwalletMain->GetCScript(scriptID, subscript)) { + if (pwallet && pwallet->GetCScript(scriptID, subscript)) { int witnessversion; std::vector<unsigned char> witprog; if (subscript.IsWitnessProgram(witnessversion, witprog)) { result = scriptID; return true; } - isminetype typ; - typ = IsMine(*pwalletMain, subscript, SIGVERSION_WITNESS_V0); - if (typ != ISMINE_SPENDABLE && typ != ISMINE_WATCH_SOLVABLE) - return false; CScript witscript = GetScriptForWitness(subscript); - pwalletMain->AddCScript(witscript); + SignatureData sigs; + // This check is to make sure that the script we created can actually be solved for and signed by us + // if we were to have the private keys. This is just to make sure that the script is valid and that, + // if found in a transaction, we would still accept and relay that transcation. + if (!ProduceSignature(DummySignatureCreator(pwallet), witscript, sigs) || + !VerifyScript(sigs.scriptSig, witscript, &sigs.scriptWitness, MANDATORY_SCRIPT_VERIFY_FLAGS | SCRIPT_VERIFY_WITNESS_PUBKEYTYPE, DummySignatureCreator(pwallet).Checker())) { + return false; + } + pwallet->AddCScript(witscript); result = CScriptID(witscript); return true; } @@ -1086,12 +1174,14 @@ public: UniValue addwitnessaddress(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) { - string msg = "addwitnessaddress \"address\"\n" + std::string msg = "addwitnessaddress \"address\"\n" "\nAdd a witness address for a script (with pubkey or redeemscript known).\n" "It returns the witness script.\n" @@ -1102,12 +1192,12 @@ UniValue addwitnessaddress(const JSONRPCRequest& request) "\"witnessaddress\", (string) The value of the new address (P2SH of witness script).\n" "}\n" ; - throw runtime_error(msg); + throw std::runtime_error(msg); } { LOCK(cs_main); - if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) && !GetBoolArg("-walletprematurewitness", false)) { + if (!IsWitnessEnabled(chainActive.Tip(), Params().GetConsensus()) && !gArgs.GetBoolArg("-walletprematurewitness", false)) { throw JSONRPCError(RPC_WALLET_ERROR, "Segregated witness not enabled on network"); } } @@ -1116,14 +1206,14 @@ UniValue addwitnessaddress(const JSONRPCRequest& request) if (!address.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address"); - Witnessifier w; + Witnessifier w(pwallet); CTxDestination dest = address.Get(); bool ret = boost::apply_visitor(w, dest); if (!ret) { throw JSONRPCError(RPC_WALLET_ERROR, "Public key or redeemscript not known to wallet, or the key is uncompressed"); } - pwalletMain->SetAddressBook(w.result, "", "receive"); + pwallet->SetAddressBook(w.result, "", "receive"); return CBitcoinAddress(w.result).ToString(); } @@ -1132,7 +1222,7 @@ struct tallyitem { CAmount nAmount; int nConf; - vector<uint256> txids; + std::vector<uint256> txids; bool fIsWatchonly; tallyitem() { @@ -1142,28 +1232,27 @@ struct tallyitem } }; -UniValue ListReceived(const UniValue& params, bool fByAccounts) +UniValue ListReceived(CWallet * const pwallet, const UniValue& params, bool fByAccounts) { // Minimum confirmations int nMinDepth = 1; - if (params.size() > 0) + if (!params[0].isNull()) nMinDepth = params[0].get_int(); // Whether to include empty accounts bool fIncludeEmpty = false; - if (params.size() > 1) + if (!params[1].isNull()) fIncludeEmpty = params[1].get_bool(); isminefilter filter = ISMINE_SPENDABLE; - if(params.size() > 2) + if(!params[2].isNull()) if(params[2].get_bool()) filter = filter | ISMINE_WATCH_ONLY; // Tally - map<CBitcoinAddress, tallyitem> mapTally; - for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; + std::map<CBitcoinAddress, tallyitem> mapTally; + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + const CWalletTx& wtx = pairWtx.second; if (wtx.IsCoinBase() || !CheckFinalTx(*wtx.tx)) continue; @@ -1172,19 +1261,19 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) if (nDepth < nMinDepth) continue; - BOOST_FOREACH(const CTxOut& txout, wtx.tx->vout) + for (const CTxOut& txout : wtx.tx->vout) { CTxDestination address; if (!ExtractDestination(txout.scriptPubKey, address)) continue; - isminefilter mine = IsMine(*pwalletMain, address); + isminefilter mine = IsMine(*pwallet, address); if(!(mine & filter)) continue; tallyitem& item = mapTally[address]; item.nAmount += txout.nValue; - item.nConf = min(item.nConf, nDepth); + item.nConf = std::min(item.nConf, nDepth); item.txids.push_back(wtx.GetHash()); if (mine & ISMINE_WATCH_ONLY) item.fIsWatchonly = true; @@ -1193,12 +1282,11 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) // Reply UniValue ret(UniValue::VARR); - map<string, tallyitem> mapAccountTally; - BOOST_FOREACH(const PAIRTYPE(CBitcoinAddress, CAddressBookData)& item, pwalletMain->mapAddressBook) - { + std::map<std::string, tallyitem> mapAccountTally; + for (const std::pair<CBitcoinAddress, CAddressBookData>& item : pwallet->mapAddressBook) { const CBitcoinAddress& address = item.first; - const string& strAccount = item.second.name; - map<CBitcoinAddress, tallyitem>::iterator it = mapTally.find(address); + const std::string& strAccount = item.second.name; + std::map<CBitcoinAddress, tallyitem>::iterator it = mapTally.find(address); if (it == mapTally.end() && !fIncludeEmpty) continue; @@ -1216,7 +1304,7 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) { tallyitem& _item = mapAccountTally[strAccount]; _item.nAmount += nAmount; - _item.nConf = min(_item.nConf, nConf); + _item.nConf = std::min(_item.nConf, nConf); _item.fIsWatchonly = fIsWatchonly; } else @@ -1233,7 +1321,7 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) UniValue transactions(UniValue::VARR); if (it != mapTally.end()) { - BOOST_FOREACH(const uint256& _item, (*it).second.txids) + for (const uint256& _item : (*it).second.txids) { transactions.push_back(_item.GetHex()); } @@ -1245,7 +1333,7 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) if (fByAccounts) { - for (map<string, tallyitem>::iterator it = mapAccountTally.begin(); it != mapAccountTally.end(); ++it) + for (std::map<std::string, tallyitem>::iterator it = mapAccountTally.begin(); it != mapAccountTally.end(); ++it) { CAmount nAmount = (*it).second.nAmount; int nConf = (*it).second.nConf; @@ -1264,11 +1352,13 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts) UniValue listreceivedbyaddress(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() > 3) - throw runtime_error( + throw std::runtime_error( "listreceivedbyaddress ( minconf include_empty include_watchonly)\n" "\nList balances by receiving address.\n" "\nArguments:\n" @@ -1299,18 +1389,20 @@ UniValue listreceivedbyaddress(const JSONRPCRequest& request) + HelpExampleRpc("listreceivedbyaddress", "6, true, true") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - return ListReceived(request.params, false); + return ListReceived(pwallet, request.params, false); } UniValue listreceivedbyaccount(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() > 3) - throw runtime_error( + throw std::runtime_error( "listreceivedbyaccount ( minconf include_empty include_watchonly)\n" "\nDEPRECATED. List balances by account.\n" "\nArguments:\n" @@ -1336,9 +1428,9 @@ UniValue listreceivedbyaccount(const JSONRPCRequest& request) + HelpExampleRpc("listreceivedbyaccount", "6, true, true") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - return ListReceived(request.params, true); + return ListReceived(pwallet, request.params, true); } static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) @@ -1348,32 +1440,45 @@ static void MaybePushAddress(UniValue & entry, const CTxDestination &dest) entry.push_back(Pair("address", addr.ToString())); } -void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) +/** + * List transactions based on the given criteria. + * + * @param pwallet The wallet. + * @param wtx The wallet transaction. + * @param strAccount The account, if any, or "*" for all. + * @param nMinDepth The minimum confirmation depth. + * @param fLong Whether to include the JSON version of the transaction. + * @param ret The UniValue into which the result is stored. + * @param filter The "is mine" filter bool. + */ +void ListTransactions(CWallet* const pwallet, const CWalletTx& wtx, const std::string& strAccount, int nMinDepth, bool fLong, UniValue& ret, const isminefilter& filter) { CAmount nFee; - string strSentAccount; - list<COutputEntry> listReceived; - list<COutputEntry> listSent; + std::string strSentAccount; + std::list<COutputEntry> listReceived; + std::list<COutputEntry> listSent; wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, filter); - bool fAllAccounts = (strAccount == string("*")); + bool fAllAccounts = (strAccount == std::string("*")); bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); // Sent if ((!listSent.empty() || nFee != 0) && (fAllAccounts || strAccount == strSentAccount)) { - BOOST_FOREACH(const COutputEntry& s, listSent) + for (const COutputEntry& s : listSent) { UniValue entry(UniValue::VOBJ); - if(involvesWatchonly || (::IsMine(*pwalletMain, s.destination) & ISMINE_WATCH_ONLY)) + 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 (pwalletMain->mapAddressBook.count(s.destination)) - entry.push_back(Pair("label", pwalletMain->mapAddressBook[s.destination].name)); + 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(-nFee))); if (fLong) @@ -1386,16 +1491,18 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe // Received if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) { - BOOST_FOREACH(const COutputEntry& r, listReceived) + for (const COutputEntry& r : listReceived) { - string account; - if (pwalletMain->mapAddressBook.count(r.destination)) - account = pwalletMain->mapAddressBook[r.destination].name; + 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(*pwalletMain, r.destination) & ISMINE_WATCH_ONLY)) + 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()) @@ -1412,8 +1519,9 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe entry.push_back(Pair("category", "receive")); } entry.push_back(Pair("amount", ValueFromAmount(r.amount))); - if (pwalletMain->mapAddressBook.count(r.destination)) + if (pwallet->mapAddressBook.count(r.destination)) { entry.push_back(Pair("label", account)); + } entry.push_back(Pair("vout", r.vout)); if (fLong) WalletTxToJSON(wtx, entry); @@ -1423,9 +1531,9 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe } } -void AcentryToJSON(const CAccountingEntry& acentry, const string& strAccount, UniValue& ret) +void AcentryToJSON(const CAccountingEntry& acentry, const std::string& strAccount, UniValue& ret) { - bool fAllAccounts = (strAccount == string("*")); + bool fAllAccounts = (strAccount == std::string("*")); if (fAllAccounts || acentry.strAccount == strAccount) { @@ -1442,11 +1550,13 @@ void AcentryToJSON(const CAccountingEntry& acentry, const string& strAccount, Un UniValue listtransactions(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() > 4) - throw runtime_error( + 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" @@ -1505,19 +1615,19 @@ UniValue listtransactions(const JSONRPCRequest& request) + HelpExampleRpc("listtransactions", "\"*\", 20, 100") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - string strAccount = "*"; - if (request.params.size() > 0) + std::string strAccount = "*"; + if (!request.params[0].isNull()) strAccount = request.params[0].get_str(); int nCount = 10; - if (request.params.size() > 1) + if (!request.params[1].isNull()) nCount = request.params[1].get_int(); int nFrom = 0; - if (request.params.size() > 2) + if (!request.params[2].isNull()) nFrom = request.params[2].get_int(); isminefilter filter = ISMINE_SPENDABLE; - if(request.params.size() > 3) + if(!request.params[3].isNull()) if(request.params[3].get_bool()) filter = filter | ISMINE_WATCH_ONLY; @@ -1528,14 +1638,14 @@ UniValue listtransactions(const JSONRPCRequest& request) UniValue ret(UniValue::VARR); - const CWallet::TxItems & txOrdered = pwalletMain->wtxOrdered; + 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(*pwtx, strAccount, 0, true, ret, filter); + ListTransactions(pwallet, *pwtx, strAccount, 0, true, ret, filter); CAccountingEntry *const pacentry = (*it).second.second; if (pacentry != 0) AcentryToJSON(*pacentry, strAccount, ret); @@ -1549,11 +1659,11 @@ UniValue listtransactions(const JSONRPCRequest& request) if ((nFrom + nCount) > (int)ret.size()) nCount = ret.size() - nFrom; - vector<UniValue> arrTmp = ret.getValues(); + std::vector<UniValue> arrTmp = ret.getValues(); - vector<UniValue>::iterator first = arrTmp.begin(); + std::vector<UniValue>::iterator first = arrTmp.begin(); std::advance(first, nFrom); - vector<UniValue>::iterator last = arrTmp.begin(); + std::vector<UniValue>::iterator last = arrTmp.begin(); std::advance(last, nFrom+nCount); if (last != arrTmp.end()) arrTmp.erase(last, arrTmp.end()); @@ -1570,11 +1680,13 @@ UniValue listtransactions(const JSONRPCRequest& request) UniValue listaccounts(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() > 2) - throw runtime_error( + 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" @@ -1596,7 +1708,7 @@ UniValue listaccounts(const JSONRPCRequest& request) + HelpExampleRpc("listaccounts", "6") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); int nMinDepth = 1; if (request.params.size() > 0) @@ -1606,42 +1718,43 @@ UniValue listaccounts(const JSONRPCRequest& request) if(request.params[1].get_bool()) includeWatchonly = includeWatchonly | ISMINE_WATCH_ONLY; - map<string, CAmount> mapAccountBalances; - BOOST_FOREACH(const PAIRTYPE(CTxDestination, CAddressBookData)& entry, pwalletMain->mapAddressBook) { - if (IsMine(*pwalletMain, entry.first) & includeWatchonly) // This address belongs to me + std::map<std::string, CAmount> mapAccountBalances; + for (const std::pair<CTxDestination, CAddressBookData>& entry : pwallet->mapAddressBook) { + if (IsMine(*pwallet, entry.first) & includeWatchonly) { // This address belongs to me mapAccountBalances[entry.second.name] = 0; + } } - for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); ++it) - { - const CWalletTx& wtx = (*it).second; + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + const CWalletTx& wtx = pairWtx.second; CAmount nFee; - string strSentAccount; - list<COutputEntry> listReceived; - list<COutputEntry> listSent; + std::string strSentAccount; + std::list<COutputEntry> listReceived; + std::list<COutputEntry> listSent; int nDepth = wtx.GetDepthInMainChain(); if (wtx.GetBlocksToMaturity() > 0 || nDepth < 0) continue; wtx.GetAmounts(listReceived, listSent, nFee, strSentAccount, includeWatchonly); mapAccountBalances[strSentAccount] -= nFee; - BOOST_FOREACH(const COutputEntry& s, listSent) + for (const COutputEntry& s : listSent) mapAccountBalances[strSentAccount] -= s.amount; if (nDepth >= nMinDepth) { - BOOST_FOREACH(const COutputEntry& r, listReceived) - if (pwalletMain->mapAddressBook.count(r.destination)) - mapAccountBalances[pwalletMain->mapAddressBook[r.destination].name] += r.amount; + for (const COutputEntry& r : listReceived) + if (pwallet->mapAddressBook.count(r.destination)) { + mapAccountBalances[pwallet->mapAddressBook[r.destination].name] += r.amount; + } else mapAccountBalances[""] += r.amount; } } - const list<CAccountingEntry> & acentries = pwalletMain->laccentries; - BOOST_FOREACH(const CAccountingEntry& entry, acentries) + const std::list<CAccountingEntry>& acentries = pwallet->laccentries; + for (const CAccountingEntry& entry : acentries) mapAccountBalances[entry.strAccount] += entry.nCreditDebit; UniValue ret(UniValue::VOBJ); - BOOST_FOREACH(const PAIRTYPE(string, CAmount)& accountBalance, mapAccountBalances) { + for (const std::pair<std::string, CAmount>& accountBalance : mapAccountBalances) { ret.push_back(Pair(accountBalance.first, ValueFromAmount(accountBalance.second))); } return ret; @@ -1649,17 +1762,23 @@ UniValue listaccounts(const JSONRPCRequest& request) UniValue listsinceblock(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (request.fHelp) - throw runtime_error( - "listsinceblock ( \"blockhash\" target_confirmations include_watchonly)\n" - "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted\n" + if (request.fHelp || request.params.size() > 4) + throw std::runtime_error( + "listsinceblock ( \"blockhash\" target_confirmations include_watchonly include_removed )\n" + "\nGet all transactions in blocks since block [blockhash], or all transactions if omitted.\n" + "If \"blockhash\" is no longer a part of the main chain, transactions from the fork point onward are included.\n" + "Additionally, if include_removed is set, transactions affecting the wallet which were removed are returned in the \"removed\" array.\n" "\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')" + "2. target_confirmations: (numeric, optional, default=1) Return the nth block hash from the main chain. e.g. 1 would mean the best block hash. Note: this is not used as a filter, but only affects [lastblock] in the return value\n" + "3. include_watchonly: (bool, optional, default=false) Include transactions to watch-only addresses (see 'importaddress')\n" + "4. include_removed: (bool, optional, default=true) Show transactions that were removed due to a reorg in the \"removed\" array\n" + " (not guaranteed to work on pruned nodes)\n" "\nResult:\n" "{\n" " \"transactions\": [\n" @@ -1684,8 +1803,12 @@ UniValue listsinceblock(const JSONRPCRequest& request) " \"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" + " \"removed\": [\n" + " <structure is the same as \"transactions\" above, only present if include_removed=true>\n" + " Note: transactions that were readded in the active chain will appear as-is in this array, and may thus have a positive confirmation count.\n" + " ],\n" + " \"lastblock\": \"lastblockhash\" (string) The hash of the block (target_confirmations-1) from the best block on the main chain. This is typically used to feed back into listsinceblock the next time you call it. So you would generally use a target_confirmations of say 6, so you will be continually re-notified of transactions until they've reached 6 confirmations plus any new ones\n" "}\n" "\nExamples:\n" + HelpExampleCli("listsinceblock", "") @@ -1693,54 +1816,72 @@ UniValue listsinceblock(const JSONRPCRequest& request) + HelpExampleRpc("listsinceblock", "\"000000000000000bacf66f7497b7dc45ef753ee9a7d38571037cdb1a57f663ad\", 6") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - const CBlockIndex *pindex = NULL; + const CBlockIndex* pindex = nullptr; // Block index of the specified block or the common ancestor, if the block provided was in a deactivated chain. + const CBlockIndex* paltindex = nullptr; // Block index of the specified block, even if it's in a deactivated chain. int target_confirms = 1; isminefilter filter = ISMINE_SPENDABLE; - if (request.params.size() > 0) - { + if (!request.params[0].isNull() && !request.params[0].get_str().empty()) { 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 (it == mapBlockIndex.end()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + } + paltindex = 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) - { + if (!request.params[1].isNull()) { target_confirms = request.params[1].get_int(); - if (target_confirms < 1) + if (target_confirms < 1) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); + } } - if (request.params.size() > 2 && request.params[2].get_bool()) - { + if (!request.params[2].isNull() && request.params[2].get_bool()) { filter = filter | ISMINE_WATCH_ONLY; } + bool include_removed = (request.params[3].isNull() || request.params[3].get_bool()); + int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1; UniValue transactions(UniValue::VARR); - for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); it != pwalletMain->mapWallet.end(); it++) - { - CWalletTx tx = (*it).second; + for (const std::pair<uint256, CWalletTx>& pairWtx : pwallet->mapWallet) { + CWalletTx tx = pairWtx.second; - if (depth == -1 || tx.GetDepthInMainChain() < depth) - ListTransactions(tx, "*", 0, true, transactions, filter); + if (depth == -1 || tx.GetDepthInMainChain() < depth) { + ListTransactions(pwallet, tx, "*", 0, true, transactions, filter); + } + } + + // when a reorg'd block is requested, we also list any relevant transactions + // in the blocks of the chain that was detached + UniValue removed(UniValue::VARR); + while (include_removed && paltindex && paltindex != pindex) { + CBlock block; + if (!ReadBlockFromDisk(block, paltindex, Params().GetConsensus())) { + throw JSONRPCError(RPC_INTERNAL_ERROR, "Can't read block from disk"); + } + for (const CTransactionRef& tx : block.vtx) { + if (pwallet->mapWallet.count(tx->GetHash()) > 0) { + // We want all transactions regardless of confirmation count to appear here, + // even negative confirmation ones, hence the big negative. + ListTransactions(pwallet, pwallet->mapWallet[tx->GetHash()], "*", -100000000, true, removed, filter); + } + } + paltindex = paltindex->pprev; } CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms]; @@ -1748,6 +1889,7 @@ UniValue listsinceblock(const JSONRPCRequest& request) UniValue ret(UniValue::VOBJ); ret.push_back(Pair("transactions", transactions)); + if (include_removed) ret.push_back(Pair("removed", removed)); ret.push_back(Pair("lastblock", lastblock.GetHex())); return ret; @@ -1755,11 +1897,13 @@ UniValue listsinceblock(const JSONRPCRequest& request) UniValue gettransaction(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw runtime_error( + throw std::runtime_error( "gettransaction \"txid\" ( include_watchonly )\n" "\nGet detailed information about in-wallet transaction <txid>\n" "\nArguments:\n" @@ -1790,7 +1934,7 @@ UniValue gettransaction(const JSONRPCRequest& request) " \"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" + " 'send' category of transactions.\n" " }\n" " ,...\n" " ],\n" @@ -1803,20 +1947,21 @@ UniValue gettransaction(const JSONRPCRequest& request) + HelpExampleRpc("gettransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); uint256 hash; hash.SetHex(request.params[0].get_str()); isminefilter filter = ISMINE_SPENDABLE; - if(request.params.size() > 1) + if(!request.params[1].isNull()) if(request.params[1].get_bool()) filter = filter | ISMINE_WATCH_ONLY; UniValue entry(UniValue::VOBJ); - if (!pwalletMain->mapWallet.count(hash)) + if (!pwallet->mapWallet.count(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); - const CWalletTx& wtx = pwalletMain->mapWallet[hash]; + } + const CWalletTx& wtx = pwallet->mapWallet[hash]; CAmount nCredit = wtx.GetCredit(filter); CAmount nDebit = wtx.GetDebit(filter); @@ -1830,10 +1975,10 @@ UniValue gettransaction(const JSONRPCRequest& request) WalletTxToJSON(wtx, entry); UniValue details(UniValue::VARR); - ListTransactions(wtx, "*", 0, false, details, filter); + ListTransactions(pwallet, wtx, "*", 0, false, details, filter); entry.push_back(Pair("details", details)); - string strHex = EncodeHexTx(static_cast<CTransaction>(wtx), RPCSerializationFlags()); + std::string strHex = EncodeHexTx(static_cast<CTransaction>(wtx), RPCSerializationFlags()); entry.push_back(Pair("hex", strHex)); return entry; @@ -1841,11 +1986,13 @@ UniValue gettransaction(const JSONRPCRequest& request) UniValue abandontransaction(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() != 1) - throw runtime_error( + throw std::runtime_error( "abandontransaction \"txid\"\n" "\nMark in-wallet transaction <txid> as abandoned\n" "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n" @@ -1860,15 +2007,17 @@ UniValue abandontransaction(const JSONRPCRequest& request) + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); uint256 hash; hash.SetHex(request.params[0].get_str()); - if (!pwalletMain->mapWallet.count(hash)) + if (!pwallet->mapWallet.count(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); - if (!pwalletMain->AbandonTransaction(hash)) + } + if (!pwallet->AbandonTransaction(hash)) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment"); + } return NullUniValue; } @@ -1876,11 +2025,13 @@ UniValue abandontransaction(const JSONRPCRequest& request) UniValue backupwallet(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() != 1) - throw runtime_error( + 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" @@ -1890,11 +2041,12 @@ UniValue backupwallet(const JSONRPCRequest& request) + HelpExampleRpc("backupwallet", "\"backup.dat\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - string strDest = request.params[0].get_str(); - if (!pwalletMain->BackupWallet(strDest)) + std::string strDest = request.params[0].get_str(); + if (!pwallet->BackupWallet(strDest)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet backup failed!"); + } return NullUniValue; } @@ -1902,14 +2054,16 @@ UniValue backupwallet(const JSONRPCRequest& request) UniValue keypoolrefill(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() > 1) - throw runtime_error( + throw std::runtime_error( "keypoolrefill ( newsize )\n" "\nFills the keypool." - + HelpRequiringPassphrase() + "\n" + + HelpRequiringPassphrase(pwallet) + "\n" "\nArguments\n" "1. newsize (numeric, optional, default=100) The new keypool size\n" "\nExamples:\n" @@ -1917,21 +2071,22 @@ UniValue keypoolrefill(const JSONRPCRequest& request) + HelpExampleRpc("keypoolrefill", "") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + 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].isNull()) { if (request.params[0].get_int() < 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected valid size."); kpSize = (unsigned int)request.params[0].get_int(); } - EnsureWalletIsUnlocked(); - pwalletMain->TopUpKeyPool(kpSize); + EnsureWalletIsUnlocked(pwallet); + pwallet->TopUpKeyPool(kpSize); - if (pwalletMain->GetKeyPoolSize() < kpSize) + if (pwallet->GetKeyPoolSize() < kpSize) { throw JSONRPCError(RPC_WALLET_ERROR, "Error refreshing keypool."); + } return NullUniValue; } @@ -1939,18 +2094,20 @@ UniValue keypoolrefill(const JSONRPCRequest& request) static void LockWallet(CWallet* pWallet) { - LOCK(cs_nWalletUnlockTime); - nWalletUnlockTime = 0; + LOCK(pWallet->cs_wallet); + pWallet->nRelockTime = 0; pWallet->Lock(); } UniValue walletpassphrase(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (pwalletMain->IsCrypted() && (request.fHelp || request.params.size() != 2)) - throw runtime_error( + 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" @@ -1961,20 +2118,22 @@ UniValue walletpassphrase(const JSONRPCRequest& request) "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" + "\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, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); if (request.fHelp) return true; - if (!pwalletMain->IsCrypted()) + 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; @@ -1985,20 +2144,20 @@ UniValue walletpassphrase(const JSONRPCRequest& request) if (strWalletPass.length() > 0) { - if (!pwalletMain->Unlock(strWalletPass)) + if (!pwallet->Unlock(strWalletPass)) { throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + } } else - throw runtime_error( + throw std::runtime_error( "walletpassphrase <passphrase> <timeout>\n" "Stores the wallet decryption key in memory for <timeout> seconds."); - pwalletMain->TopUpKeyPool(); + pwallet->TopUpKeyPool(); int64_t nSleepTime = request.params[1].get_int64(); - LOCK(cs_nWalletUnlockTime); - nWalletUnlockTime = GetTime() + nSleepTime; - RPCRunLater("lockwallet", boost::bind(LockWallet, pwalletMain), nSleepTime); + pwallet->nRelockTime = GetTime() + nSleepTime; + RPCRunLater(strprintf("lockwallet(%s)", pwallet->GetName()), boost::bind(LockWallet, pwallet), nSleepTime); return NullUniValue; } @@ -2006,11 +2165,13 @@ UniValue walletpassphrase(const JSONRPCRequest& request) UniValue walletpassphrasechange(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (pwalletMain->IsCrypted() && (request.fHelp || request.params.size() != 2)) - throw runtime_error( + 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" @@ -2020,13 +2181,15 @@ UniValue walletpassphrasechange(const JSONRPCRequest& request) + HelpExampleCli("walletpassphrasechange", "\"old one\" \"new one\"") + HelpExampleRpc("walletpassphrasechange", "\"old one\", \"new one\"") ); + } - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); if (request.fHelp) return true; - if (!pwalletMain->IsCrypted()) + 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. @@ -2039,12 +2202,13 @@ UniValue walletpassphrasechange(const JSONRPCRequest& request) strNewWalletPass = request.params[1].get_str().c_str(); if (strOldWalletPass.length() < 1 || strNewWalletPass.length() < 1) - throw runtime_error( + throw std::runtime_error( "walletpassphrasechange <oldpassphrase> <newpassphrase>\n" "Changes the wallet passphrase from <oldpassphrase> to <newpassphrase>."); - if (!pwalletMain->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) + if (!pwallet->ChangeWalletPassphrase(strOldWalletPass, strNewWalletPass)) { throw JSONRPCError(RPC_WALLET_PASSPHRASE_INCORRECT, "Error: The wallet passphrase entered was incorrect."); + } return NullUniValue; } @@ -2052,11 +2216,13 @@ UniValue walletpassphrasechange(const JSONRPCRequest& request) UniValue walletlock(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (pwalletMain->IsCrypted() && (request.fHelp || request.params.size() != 0)) - throw runtime_error( + 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" @@ -2071,31 +2237,32 @@ UniValue walletlock(const JSONRPCRequest& request) "\nAs json rpc call\n" + HelpExampleRpc("walletlock", "") ); + } - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); if (request.fHelp) return true; - if (!pwalletMain->IsCrypted()) + if (!pwallet->IsCrypted()) { throw JSONRPCError(RPC_WALLET_WRONG_ENC_STATE, "Error: running with an unencrypted wallet, but walletlock was called."); - - { - LOCK(cs_nWalletUnlockTime); - pwalletMain->Lock(); - nWalletUnlockTime = 0; } + pwallet->Lock(); + pwallet->nRelockTime = 0; + return NullUniValue; } UniValue encryptwallet(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (!pwalletMain->IsCrypted() && (request.fHelp || request.params.size() != 1)) - throw runtime_error( + 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" @@ -2106,24 +2273,26 @@ UniValue encryptwallet(const JSONRPCRequest& request) "\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" + "\nEncrypt your wallet\n" + HelpExampleCli("encryptwallet", "\"my pass phrase\"") + "\nNow set the passphrase to use the wallet, such as for signing or sending bitcoin\n" + HelpExampleCli("walletpassphrase", "\"my pass phrase\"") + - "\nNow we can so something like sign\n" + "\nNow we can do something like sign\n" + HelpExampleCli("signmessage", "\"address\" \"test message\"") + "\nNow lock the wallet again by removing the passphrase\n" + HelpExampleCli("walletlock", "") + "\nAs a json rpc call\n" + HelpExampleRpc("encryptwallet", "\"my pass phrase\"") ); + } - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); if (request.fHelp) return true; - if (pwalletMain->IsCrypted()) + 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. @@ -2132,12 +2301,13 @@ UniValue encryptwallet(const JSONRPCRequest& request) strWalletPass = request.params[0].get_str().c_str(); if (strWalletPass.length() < 1) - throw runtime_error( + throw std::runtime_error( "encryptwallet <passphrase>\n" "Encrypts the wallet with <passphrase>."); - if (!pwalletMain->EncryptWallet(strWalletPass)) + 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 @@ -2148,11 +2318,13 @@ UniValue encryptwallet(const JSONRPCRequest& request) UniValue lockunspent(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw runtime_error( + 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" @@ -2188,18 +2360,18 @@ UniValue lockunspent(const JSONRPCRequest& request) + HelpExampleRpc("lockunspent", "false, \"[{\\\"txid\\\":\\\"a08e6907dbbd3d809776dbfc5d82e371b764ed838b5655e72f463568df1aadf0\\\",\\\"vout\\\":1}]\"") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); if (request.params.size() == 1) - RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VBOOL)); + RPCTypeCheck(request.params, {UniValue::VBOOL}); else - RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VBOOL)(UniValue::VARR)); + RPCTypeCheck(request.params, {UniValue::VBOOL, UniValue::VARR}); bool fUnlock = request.params[0].get_bool(); if (request.params.size() == 1) { if (fUnlock) - pwalletMain->UnlockAllCoins(); + pwallet->UnlockAllCoins(); return true; } @@ -2216,7 +2388,7 @@ UniValue lockunspent(const JSONRPCRequest& request) {"vout", UniValueType(UniValue::VNUM)}, }); - string txid = find_value(o, "txid").get_str(); + std::string txid = find_value(o, "txid").get_str(); if (!IsHex(txid)) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid"); @@ -2227,9 +2399,9 @@ UniValue lockunspent(const JSONRPCRequest& request) COutPoint outpt(uint256S(txid), nOutput); if (fUnlock) - pwalletMain->UnlockCoin(outpt); + pwallet->UnlockCoin(outpt); else - pwalletMain->LockCoin(outpt); + pwallet->LockCoin(outpt); } return true; @@ -2237,11 +2409,13 @@ UniValue lockunspent(const JSONRPCRequest& request) UniValue listlockunspent(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() > 0) - throw runtime_error( + 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" @@ -2266,14 +2440,14 @@ UniValue listlockunspent(const JSONRPCRequest& request) + HelpExampleRpc("listlockunspent", "") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); - vector<COutPoint> vOutpts; - pwalletMain->ListLockedCoins(vOutpts); + std::vector<COutPoint> vOutpts; + pwallet->ListLockedCoins(vOutpts); UniValue ret(UniValue::VARR); - BOOST_FOREACH(COutPoint &outpt, vOutpts) { + for (COutPoint &outpt : vOutpts) { UniValue o(UniValue::VOBJ); o.push_back(Pair("txid", outpt.hash.GetHex())); @@ -2286,11 +2460,13 @@ UniValue listlockunspent(const JSONRPCRequest& request) UniValue settxfee(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 1 || request.params.size() > 1) - throw runtime_error( + throw std::runtime_error( "settxfee amount\n" "\nSet the transaction fee per kB. Overwrites the paytxfee parameter.\n" "\nArguments:\n" @@ -2302,7 +2478,7 @@ UniValue settxfee(const JSONRPCRequest& request) + HelpExampleRpc("settxfee", "0.00001") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); // Amount CAmount nAmount = AmountFromValue(request.params[0]); @@ -2313,72 +2489,123 @@ UniValue settxfee(const JSONRPCRequest& request) UniValue getwalletinfo(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() != 0) - throw runtime_error( + throw std::runtime_error( "getwalletinfo\n" "Returns an object containing various wallet state info.\n" "\nResult:\n" "{\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\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\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\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\": \"<hash160>\" (string) the Hash160 of the HD master pubkey\n" "}\n" "\nExamples:\n" + HelpExampleCli("getwalletinfo", "") + HelpExampleRpc("getwalletinfo", "") ); - LOCK2(cs_main, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); UniValue obj(UniValue::VOBJ); - obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); - obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); - obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwalletMain->GetUnconfirmedBalance()))); - obj.push_back(Pair("immature_balance", ValueFromAmount(pwalletMain->GetImmatureBalance()))); - obj.push_back(Pair("txcount", (int)pwalletMain->mapWallet.size())); - obj.push_back(Pair("keypoololdest", pwalletMain->GetOldestKeyPoolTime())); - obj.push_back(Pair("keypoolsize", (int)pwalletMain->GetKeyPoolSize())); - if (pwalletMain->IsCrypted()) - obj.push_back(Pair("unlocked_until", nWalletUnlockTime)); + + 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()))); - CKeyID masterKeyID = pwalletMain->GetHDChain().masterKeyID; if (!masterKeyID.IsNull()) obj.push_back(Pair("hdmasterkeyid", masterKeyID.GetHex())); return obj; } +UniValue listwallets(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; +} + UniValue resendwallettransactions(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() != 0) - throw runtime_error( + 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 an RPC error if -walletbroadcast is set to false.\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, pwalletMain->cs_wallet); + LOCK2(cs_main, pwallet->cs_wallet); + + if (!pwallet->GetBroadcastTransactions()) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error: Wallet transaction broadcasting is disabled with -walletbroadcast"); + } - std::vector<uint256> txids = pwalletMain->ResendWalletTransactionsBefore(GetTime(), g_connman.get()); + std::vector<uint256> txids = pwallet->ResendWalletTransactionsBefore(GetTime(), g_connman.get()); UniValue result(UniValue::VARR); - BOOST_FOREACH(const uint256& txid, txids) + for (const uint256& txid : txids) { result.push_back(txid.ToString()); } @@ -2387,27 +2614,34 @@ UniValue resendwallettransactions(const JSONRPCRequest& request) UniValue listunspent(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } - if (request.fHelp || request.params.size() > 4) - throw runtime_error( - "listunspent ( minconf maxconf [\"addresses\",...] [include_unsafe] )\n" + if (request.fHelp || request.params.size() > 5) + throw std::runtime_error( + "listunspent ( minconf maxconf [\"addresses\",...] [include_unsafe] [query_options])\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" + "3. \"addresses\" (string) A json array of bitcoin addresses to filter\n" " [\n" - " \"address\" (string) bitcoin address\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" + " See description of \"safe\" attribute below.\n" + "5. query_options (json, optional) JSON with query options\n" + " {\n" + " \"minimumAmount\" (numeric or string, default=0) Minimum value of each UTXO in " + CURRENCY_UNIT + "\n" + " \"maximumAmount\" (numeric or string, default=unlimited) Maximum value of each UTXO in " + CURRENCY_UNIT + "\n" + " \"maximumCount\" (numeric or string, default=unlimited) Maximum number of UTXOs\n" + " \"minimumSumAmount\" (numeric or string, default=unlimited) Minimum sum value of all UTXOs in " + CURRENCY_UNIT + "\n" + " }\n" "\nResult\n" "[ (array of json object)\n" " {\n" @@ -2420,7 +2654,10 @@ UniValue listunspent(const JSONRPCRequest& request) " \"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" + " \"solvable\" : xxx, (bool) Whether we know how to spend this output, ignoring the lack of keys\n" + " \"safe\" : xxx (bool) Whether this output is considered safe to spend. Unconfirmed transactions\n" + " from outside keys and unconfirmed replacement transactions are considered unsafe\n" + " and are not eligible for spending by fundrawtransaction and sendtoaddress.\n" " }\n" " ,...\n" "]\n" @@ -2429,6 +2666,8 @@ UniValue listunspent(const JSONRPCRequest& request) + HelpExampleCli("listunspent", "") + HelpExampleCli("listunspent", "6 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + HelpExampleRpc("listunspent", "6, 9999999 \"[\\\"1PGFqEzfmQch1gKD3ra4k18PNj3tTUUSqg\\\",\\\"1LtvqCaApEdUGFkpKMM4MstjcaL4dKg8SP\\\"]\"") + + HelpExampleCli("listunspent", "6 9999999 '[]' true '{ \"minimumAmount\": 0.005 }'") + + HelpExampleRpc("listunspent", "6, 9999999, [] , true, { \"minimumAmount\": 0.005 } ") ); int nMinDepth = 1; @@ -2443,7 +2682,7 @@ UniValue listunspent(const JSONRPCRequest& request) nMaxDepth = request.params[1].get_int(); } - set<CBitcoinAddress> setAddress; + std::set<CBitcoinAddress> setAddress; if (request.params.size() > 2 && !request.params[2].isNull()) { RPCTypeCheckArgument(request.params[2], UniValue::VARR); UniValue inputs = request.params[2].get_array(); @@ -2451,9 +2690,9 @@ UniValue listunspent(const JSONRPCRequest& request) const UniValue& input = inputs[idx]; CBitcoinAddress address(input.get_str()); if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, string("Invalid Bitcoin address: ")+input.get_str()); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, std::string("Invalid Bitcoin address: ")+input.get_str()); if (setAddress.count(address)) - throw JSONRPCError(RPC_INVALID_PARAMETER, string("Invalid parameter, duplicated address: ")+input.get_str()); + throw JSONRPCError(RPC_INVALID_PARAMETER, std::string("Invalid parameter, duplicated address: ")+input.get_str()); setAddress.insert(address); } } @@ -2464,15 +2703,34 @@ UniValue listunspent(const JSONRPCRequest& request) include_unsafe = request.params[3].get_bool(); } + CAmount nMinimumAmount = 0; + CAmount nMaximumAmount = MAX_MONEY; + CAmount nMinimumSumAmount = MAX_MONEY; + uint64_t nMaximumCount = 0; + + if (!request.params[4].isNull()) { + const UniValue& options = request.params[4].get_obj(); + + if (options.exists("minimumAmount")) + nMinimumAmount = AmountFromValue(options["minimumAmount"]); + + if (options.exists("maximumAmount")) + nMaximumAmount = AmountFromValue(options["maximumAmount"]); + + if (options.exists("minimumSumAmount")) + nMinimumSumAmount = AmountFromValue(options["minimumSumAmount"]); + + if (options.exists("maximumCount")) + nMaximumCount = options["maximumCount"].get_int64(); + } + UniValue results(UniValue::VARR); - vector<COutput> vecOutputs; - assert(pwalletMain != NULL); - LOCK2(cs_main, pwalletMain->cs_wallet); - pwalletMain->AvailableCoins(vecOutputs, !include_unsafe, NULL, true); - BOOST_FOREACH(const COutput& out, vecOutputs) { - if (out.nDepth < nMinDepth || out.nDepth > nMaxDepth) - continue; + std::vector<COutput> vecOutputs; + assert(pwallet != nullptr); + LOCK2(cs_main, pwallet->cs_wallet); + pwallet->AvailableCoins(vecOutputs, !include_unsafe, nullptr, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount, nMinDepth, nMaxDepth); + for (const COutput& out : vecOutputs) { CTxDestination address; const CScript& scriptPubKey = out.tx->tx->vout[out.i].scriptPubKey; bool fValidAddress = ExtractDestination(scriptPubKey, address); @@ -2487,14 +2745,16 @@ UniValue listunspent(const JSONRPCRequest& request) if (fValidAddress) { entry.push_back(Pair("address", CBitcoinAddress(address).ToString())); - if (pwalletMain->mapAddressBook.count(address)) - entry.push_back(Pair("account", pwalletMain->mapAddressBook[address].name)); + if (pwallet->mapAddressBook.count(address)) { + entry.push_back(Pair("account", pwallet->mapAddressBook[address].name)); + } if (scriptPubKey.IsPayToScriptHash()) { const CScriptID& hash = boost::get<CScriptID>(address); CScript redeemScript; - if (pwalletMain->GetCScript(hash, redeemScript)) + if (pwallet->GetCScript(hash, redeemScript)) { entry.push_back(Pair("redeemScript", HexStr(redeemScript.begin(), redeemScript.end()))); + } } } @@ -2503,6 +2763,7 @@ UniValue listunspent(const JSONRPCRequest& request) entry.push_back(Pair("confirmations", out.nDepth)); entry.push_back(Pair("spendable", out.fSpendable)); entry.push_back(Pair("solvable", out.fSolvable)); + entry.push_back(Pair("safe", out.fSafe)); results.push_back(entry); } @@ -2511,11 +2772,13 @@ UniValue listunspent(const JSONRPCRequest& request) UniValue fundrawtransaction(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { return NullUniValue; + } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) - throw runtime_error( + 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" @@ -2535,7 +2798,6 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) " \"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" @@ -2543,6 +2805,13 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) " 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" + " \"replaceable\" (boolean, optional) Marks this transaction as BIP125 replaceable.\n" + " Allows this transaction to be replaced by a transaction with higher fees\n" + " \"conf_target\" (numeric, optional) Confirmation target (in blocks)\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" " }\n" " for backward compatibility: passing in a true instead of an object will result in {\"includeWatching\":true}\n" "\nResult:\n" @@ -2562,25 +2831,21 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("sendrawtransaction", "\"signedtransactionhex\"") ); - RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)); + RPCTypeCheck(request.params, {UniValue::VSTR}); - CTxDestination changeAddress = CNoDestination(); + CCoinControl coinControl; int changePosition = -1; - bool includeWatching = false; bool lockUnspents = false; - bool reserveChangeKey = true; - CFeeRate feeRate = CFeeRate(0); - bool overrideEstimatedFeerate = false; UniValue subtractFeeFromOutputs; - set<int> setSubtractFeeFromOutputs; + std::set<int> setSubtractFeeFromOutputs; - if (request.params.size() > 1) { + if (!request.params[1].isNull()) { if (request.params[1].type() == UniValue::VBOOL) { // backward compatibility bool only fallback - includeWatching = request.params[1].get_bool(); + coinControl.fAllowWatchOnly = request.params[1].get_bool(); } else { - RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ)); + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); UniValue options = request.params[1]; @@ -2590,9 +2855,12 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) {"changePosition", UniValueType(UniValue::VNUM)}, {"includeWatching", UniValueType(UniValue::VBOOL)}, {"lockUnspents", UniValueType(UniValue::VBOOL)}, - {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, + {"reserveChangeKey", UniValueType(UniValue::VBOOL)}, // DEPRECATED (and ignored), should be removed in 0.16 or so. {"feeRate", UniValueType()}, // will be checked below {"subtractFeeFromOutputs", UniValueType(UniValue::VARR)}, + {"replaceable", UniValueType(UniValue::VBOOL)}, + {"conf_target", UniValueType(UniValue::VNUM)}, + {"estimate_mode", UniValueType(UniValue::VSTR)}, }, true, true); @@ -2600,31 +2868,46 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) CBitcoinAddress address(options["changeAddress"].get_str()); if (!address.IsValid()) - throw JSONRPCError(RPC_INVALID_PARAMETER, "changeAddress must be a valid bitcoin address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "changeAddress must be a valid bitcoin address"); - changeAddress = address.Get(); + coinControl.destChange = address.Get(); } if (options.exists("changePosition")) changePosition = options["changePosition"].get_int(); if (options.exists("includeWatching")) - includeWatching = options["includeWatching"].get_bool(); + coinControl.fAllowWatchOnly = 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; + coinControl.m_feerate = CFeeRate(AmountFromValue(options["feeRate"])); + coinControl.fOverrideFeeRate = true; } if (options.exists("subtractFeeFromOutputs")) subtractFeeFromOutputs = options["subtractFeeFromOutputs"].get_array(); + + if (options.exists("replaceable")) { + coinControl.signalRbf = options["replaceable"].get_bool(); + } + if (options.exists("conf_target")) { + if (options.exists("feeRate")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both conf_target and feeRate"); + } + coinControl.m_confirm_target = ParseConfirmTarget(options["conf_target"]); + } + if (options.exists("estimate_mode")) { + if (options.exists("feeRate")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot specify both estimate_mode and feeRate"); + } + if (!FeeModeFromString(options["estimate_mode"].get_str(), coinControl.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } + } } } @@ -2651,10 +2934,11 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) } CAmount nFeeOut; - string strFailReason; + std::string strFailReason; - if(!pwalletMain->FundTransaction(tx, nFeeOut, overrideEstimatedFeerate, feeRate, changePosition, strFailReason, includeWatching, lockUnspents, setSubtractFeeFromOutputs, reserveChangeKey, changeAddress)) - throw JSONRPCError(RPC_INTERNAL_ERROR, strFailReason); + if (!pwallet->FundTransaction(tx, nFeeOut, changePosition, strFailReason, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { + throw JSONRPCError(RPC_WALLET_ERROR, strFailReason); + } UniValue result(UniValue::VOBJ); result.push_back(Pair("hex", EncodeHexTx(tx))); @@ -2664,41 +2948,15 @@ UniValue fundrawtransaction(const JSONRPCRequest& request) return result; } -// Calculate the size of the transaction assuming all signatures are max size -// Use DummySignatureCreator, which inserts 72 byte signatures everywhere. -// TODO: re-use this in CWallet::CreateTransaction (right now -// CreateTransaction uses the constructed dummy-signed tx to do a priority -// calculation, but we should be able to refactor after priority is removed). -// NOTE: this requires that all inputs must be in mapWallet (eg the tx should -// be IsAllFromMe). -int64_t CalculateMaximumSignedTxSize(const CTransaction &tx) -{ - CMutableTransaction txNew(tx); - std::vector<pair<CWalletTx *, unsigned int>> vCoins; - // Look up the inputs. We should have already checked that this transaction - // IsAllFromMe(ISMINE_SPENDABLE), so every input should already be in our - // wallet, with a valid index into the vout array. - for (auto& input : tx.vin) { - const auto mi = pwalletMain->mapWallet.find(input.prevout.hash); - assert(mi != pwalletMain->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size()); - vCoins.emplace_back(make_pair(&(mi->second), input.prevout.n)); - } - if (!pwalletMain->DummySignTx(txNew, vCoins)) { - // This should never happen, because IsAllFromMe(ISMINE_SPENDABLE) - // implies that we can sign for every input. - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that cannot be signed"); - } - return GetVirtualTransactionSize(txNew); -} - UniValue bumpfee(const JSONRPCRequest& request) { - if (!EnsureWalletIsAvailable(request.fHelp)) { + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) return NullUniValue; - } if (request.fHelp || request.params.size() < 1 || request.params.size() > 2) { - throw runtime_error( + throw std::runtime_error( "bumpfee \"txid\" ( options ) \n" "\nBumps the fee of an opt-in-RBF transaction T, replacing it with a new transaction B.\n" "An opt-in RBF transaction with the given txid must be in the wallet.\n" @@ -2708,7 +2966,7 @@ UniValue bumpfee(const JSONRPCRequest& request) "The command will fail if the wallet or mempool contains a transaction that spends one of T's outputs.\n" "By default, the new fee will be calculated automatically using estimatefee.\n" "The user can specify a confirmation target for estimatefee.\n" - "Alternatively, the user can specify totalFee, or use RPC setpaytxfee to set a higher fee rate.\n" + "Alternatively, the user can specify totalFee, or use RPC settxfee to set a higher fee rate.\n" "At a minimum, the new fee rate must be high enough to pay an additional new relay fee (incrementalfee\n" "returned by getnetworkinfo) to enter the node's mempool.\n" "\nArguments:\n" @@ -2725,8 +2983,12 @@ UniValue bumpfee(const JSONRPCRequest& request) " be left unchanged from the original. If false, any input sequence numbers in the\n" " original transaction that were less than 0xfffffffe will be increased to 0xfffffffe\n" " so the new transaction will not be explicitly bip-125 replaceable (though it may\n" - " still be replacable in practice, for example if it has unconfirmed ancestors which\n" + " still be replaceable in practice, for example if it has unconfirmed ancestors which\n" " are replaceable).\n" + " \"estimate_mode\" (string, optional, default=UNSET) The fee estimate mode, must be one of:\n" + " \"UNSET\"\n" + " \"ECONOMICAL\"\n" + " \"CONSERVATIVE\"\n" " }\n" "\nResult:\n" "{\n" @@ -2740,247 +3002,138 @@ UniValue bumpfee(const JSONRPCRequest& request) HelpExampleCli("bumpfee", "<txid>")); } - RPCTypeCheck(request.params, boost::assign::list_of(UniValue::VSTR)(UniValue::VOBJ)); + RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VOBJ}); uint256 hash; hash.SetHex(request.params[0].get_str()); - // retrieve the original tx from the wallet - LOCK2(cs_main, pwalletMain->cs_wallet); - EnsureWalletIsUnlocked(); - if (!pwalletMain->mapWallet.count(hash)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id"); - } - CWalletTx& wtx = pwalletMain->mapWallet[hash]; - - if (pwalletMain->HasWalletSpend(hash)) { - throw JSONRPCError(RPC_MISC_ERROR, "Transaction has descendants in the wallet"); - } - - { - LOCK(mempool.cs); - auto it = mempool.mapTx.find(hash); - if (it != mempool.mapTx.end() && it->GetCountWithDescendants() > 1) { - throw JSONRPCError(RPC_MISC_ERROR, "Transaction has descendants in the mempool"); - } - } - - if (wtx.GetDepthInMainChain() != 0) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction has been mined, or is conflicted with a mined transaction"); - } - - if (!SignalsOptInRBF(wtx)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction is not BIP 125 replaceable"); - } - - if (wtx.mapValue.count("replaced_by_txid")) { - throw JSONRPCError(RPC_INVALID_REQUEST, strprintf("Cannot bump transaction %s which was already bumped by transaction %s", hash.ToString(), wtx.mapValue.at("replaced_by_txid"))); - } - - // check that original tx consists entirely of our inputs - // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) - if (!pwalletMain->IsAllFromMe(wtx, ISMINE_SPENDABLE)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction contains inputs that don't belong to this wallet"); - } - - // figure out which output was change - // if there was no change output or multiple change outputs, fail - int nOutput = -1; - for (size_t i = 0; i < wtx.tx->vout.size(); ++i) { - if (pwalletMain->IsChange(wtx.tx->vout[i])) { - if (nOutput != -1) { - throw JSONRPCError(RPC_MISC_ERROR, "Transaction has multiple change outputs"); - } - nOutput = i; - } - } - if (nOutput == -1) { - throw JSONRPCError(RPC_MISC_ERROR, "Transaction does not have a change output"); - } - - // Calculate the expected size of the new transaction. - int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); - const int64_t maxNewTxSize = CalculateMaximumSignedTxSize(*wtx.tx); - // optional parameters - bool specifiedConfirmTarget = false; - int newConfirmTarget = nTxConfirmTarget; CAmount totalFee = 0; - bool replaceable = true; - if (request.params.size() > 1) { + CCoinControl coin_control; + coin_control.signalRbf = true; + if (!request.params[1].isNull()) { UniValue options = request.params[1]; RPCTypeCheckObj(options, { {"confTarget", UniValueType(UniValue::VNUM)}, {"totalFee", UniValueType(UniValue::VNUM)}, {"replaceable", UniValueType(UniValue::VBOOL)}, + {"estimate_mode", UniValueType(UniValue::VSTR)}, }, true, true); if (options.exists("confTarget") && options.exists("totalFee")) { throw JSONRPCError(RPC_INVALID_PARAMETER, "confTarget and totalFee options should not both be set. Please provide either a confirmation target for fee estimation or an explicit total fee for the transaction."); - } else if (options.exists("confTarget")) { - specifiedConfirmTarget = true; - newConfirmTarget = options["confTarget"].get_int(); - if (newConfirmTarget <= 0) { // upper-bound will be checked by estimatefee/smartfee - throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid confTarget (cannot be <= 0)"); - } + } else if (options.exists("confTarget")) { // TODO: alias this to conf_target + coin_control.m_confirm_target = ParseConfirmTarget(options["confTarget"]); } else if (options.exists("totalFee")) { totalFee = options["totalFee"].get_int64(); - CAmount requiredFee = CWallet::GetRequiredFee(maxNewTxSize); - if (totalFee < requiredFee ) { - throw JSONRPCError(RPC_INVALID_PARAMETER, - strprintf("Insufficient totalFee (cannot be less than required fee %s)", - FormatMoney(requiredFee))); + if (totalFee <= 0) { + throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Invalid totalFee %s (must be greater than 0)", FormatMoney(totalFee))); } } if (options.exists("replaceable")) { - replaceable = options["replaceable"].get_bool(); + coin_control.signalRbf = options["replaceable"].get_bool(); } - } - - // calculate the old fee and fee-rate - CAmount nOldFee = wtx.GetDebit(ISMINE_SPENDABLE) - wtx.tx->GetValueOut(); - CFeeRate nOldFeeRate(nOldFee, txSize); - CAmount nNewFee; - CFeeRate nNewFeeRate; - // The wallet uses a conservative WALLET_INCREMENTAL_RELAY_FEE value to - // future proof against changes to network wide policy for incremental relay - // fee that our node may not be aware of. - CFeeRate walletIncrementalRelayFee = CFeeRate(WALLET_INCREMENTAL_RELAY_FEE); - if (::incrementalRelayFee > walletIncrementalRelayFee) { - walletIncrementalRelayFee = ::incrementalRelayFee; - } - - if (totalFee > 0) { - CAmount minTotalFee = nOldFeeRate.GetFee(maxNewTxSize) + ::incrementalRelayFee.GetFee(maxNewTxSize); - if (totalFee < minTotalFee) { - throw JSONRPCError(RPC_INVALID_PARAMETER, strprintf("Insufficient totalFee, must be at least %s (oldFee %s + incrementalFee %s)", - FormatMoney(minTotalFee), FormatMoney(nOldFeeRate.GetFee(maxNewTxSize)), FormatMoney(::incrementalRelayFee.GetFee(maxNewTxSize)))); - } - nNewFee = totalFee; - nNewFeeRate = CFeeRate(totalFee, maxNewTxSize); - } else { - // if user specified a confirm target then don't consider any global payTxFee - if (specifiedConfirmTarget) { - nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool, CAmount(0)); - } - // otherwise use the regular wallet logic to select payTxFee or default confirm target - else { - nNewFee = CWallet::GetMinimumFee(maxNewTxSize, newConfirmTarget, mempool); + if (options.exists("estimate_mode")) { + if (!FeeModeFromString(options["estimate_mode"].get_str(), coin_control.m_fee_mode)) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid estimate_mode parameter"); + } } + } - nNewFeeRate = CFeeRate(nNewFee, maxNewTxSize); + LOCK2(cs_main, pwallet->cs_wallet); + EnsureWalletIsUnlocked(pwallet); - // New fee rate must be at least old rate + minimum incremental relay rate - // walletIncrementalRelayFee.GetFeePerK() should be exact, because it's initialized - // in that unit (fee per kb). - // However, nOldFeeRate is a calculated value from the tx fee/size, so - // add 1 satoshi to the result, because it may have been rounded down. - if (nNewFeeRate.GetFeePerK() < nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()) { - nNewFeeRate = CFeeRate(nOldFeeRate.GetFeePerK() + 1 + walletIncrementalRelayFee.GetFeePerK()); - nNewFee = nNewFeeRate.GetFee(maxNewTxSize); + CFeeBumper feeBump(pwallet, hash, coin_control, totalFee); + BumpFeeResult res = feeBump.getResult(); + if (res != BumpFeeResult::OK) + { + switch(res) { + case BumpFeeResult::INVALID_ADDRESS_OR_KEY: + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, feeBump.getErrors()[0]); + break; + case BumpFeeResult::INVALID_REQUEST: + throw JSONRPCError(RPC_INVALID_REQUEST, feeBump.getErrors()[0]); + break; + case BumpFeeResult::INVALID_PARAMETER: + throw JSONRPCError(RPC_INVALID_PARAMETER, feeBump.getErrors()[0]); + break; + case BumpFeeResult::WALLET_ERROR: + throw JSONRPCError(RPC_WALLET_ERROR, feeBump.getErrors()[0]); + break; + default: + throw JSONRPCError(RPC_MISC_ERROR, feeBump.getErrors()[0]); + break; } } - // Check that in all cases the new fee doesn't violate maxTxFee - if (nNewFee > maxTxFee) { - throw JSONRPCError(RPC_MISC_ERROR, - strprintf("Specified or calculated fee %s is too high (cannot be higher than maxTxFee %s)", - FormatMoney(nNewFee), FormatMoney(maxTxFee))); - } - - // check that fee rate is higher than mempool's minimum fee - // (no point in bumping fee if we know that the new tx won't be accepted to the mempool) - // This may occur if the user set TotalFee or paytxfee too low, if fallbackfee is too low, or, perhaps, - // in a rare situation where the mempool minimum fee increased significantly since the fee estimation just a - // moment earlier. In this case, we report an error to the user, who may use totalFee to make an adjustment. - CFeeRate minMempoolFeeRate = mempool.GetMinFee(GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); - if (nNewFeeRate.GetFeePerK() < minMempoolFeeRate.GetFeePerK()) { - throw JSONRPCError(RPC_MISC_ERROR, strprintf("New fee rate (%s) is less than the minimum fee rate (%s) to get into the mempool. totalFee value should to be at least %s or settxfee value should be at least %s to add transaction.", FormatMoney(nNewFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFeePerK()), FormatMoney(minMempoolFeeRate.GetFee(maxNewTxSize)), FormatMoney(minMempoolFeeRate.GetFeePerK()))); - } - - // Now modify the output to increase the fee. - // If the output is not large enough to pay the fee, fail. - CAmount nDelta = nNewFee - nOldFee; - assert(nDelta > 0); - CMutableTransaction tx(*(wtx.tx)); - CTxOut* poutput = &(tx.vout[nOutput]); - if (poutput->nValue < nDelta) { - throw JSONRPCError(RPC_MISC_ERROR, "Change output is too small to bump the fee"); - } - - // If the output would become dust, discard it (converting the dust to fee) - poutput->nValue -= nDelta; - if (poutput->nValue <= poutput->GetDustThreshold(::dustRelayFee)) { - LogPrint("rpc", "Bumping fee and discarding dust output\n"); - nNewFee += poutput->nValue; - tx.vout.erase(tx.vout.begin() + nOutput); - } - - // Mark new tx not replaceable, if requested. - if (!replaceable) { - for (auto& input : tx.vin) { - if (input.nSequence < 0xfffffffe) input.nSequence = 0xfffffffe; - } + // sign bumped transaction + if (!feeBump.signTransaction(pwallet)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction."); + } + // commit the bumped transaction + if(!feeBump.commit(pwallet)) { + throw JSONRPCError(RPC_WALLET_ERROR, feeBump.getErrors()[0]); } + UniValue result(UniValue::VOBJ); + result.push_back(Pair("txid", feeBump.getBumpedTxId().GetHex())); + result.push_back(Pair("origfee", ValueFromAmount(feeBump.getOldFee()))); + result.push_back(Pair("fee", ValueFromAmount(feeBump.getNewFee()))); + UniValue errors(UniValue::VARR); + for (const std::string& err: feeBump.getErrors()) + errors.push_back(err); + result.push_back(Pair("errors", errors)); - // sign the new tx - CTransaction txNewConst(tx); - int nIn = 0; - for (auto& input : tx.vin) { - std::map<uint256, CWalletTx>::const_iterator mi = pwalletMain->mapWallet.find(input.prevout.hash); - assert(mi != pwalletMain->mapWallet.end() && input.prevout.n < mi->second.tx->vout.size()); - const CScript& scriptPubKey = mi->second.tx->vout[input.prevout.n].scriptPubKey; - const CAmount& amount = mi->second.tx->vout[input.prevout.n].nValue; - SignatureData sigdata; - if (!ProduceSignature(TransactionSignatureCreator(pwalletMain, &txNewConst, nIn, amount, SIGHASH_ALL), scriptPubKey, sigdata)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Can't sign transaction."); - } - UpdateTransaction(tx, nIn, sigdata); - nIn++; - } - - // commit/broadcast the tx - CReserveKey reservekey(pwalletMain); - CWalletTx wtxBumped(pwalletMain, MakeTransactionRef(std::move(tx))); - wtxBumped.mapValue = wtx.mapValue; - wtxBumped.mapValue["replaces_txid"] = hash.ToString(); - wtxBumped.vOrderForm = wtx.vOrderForm; - wtxBumped.strFromAccount = wtx.strFromAccount; - wtxBumped.fTimeReceivedIsTxTime = true; - wtxBumped.fFromMe = true; - CValidationState state; - if (!pwalletMain->CommitTransaction(wtxBumped, reservekey, g_connman.get(), state)) { - // NOTE: CommitTransaction never returns false, so this should never happen. - throw JSONRPCError(RPC_WALLET_ERROR, strprintf("Error: The transaction was rejected! Reason given: %s", state.GetRejectReason())); + return result; +} + +UniValue generate(const JSONRPCRequest& request) +{ + CWallet * const pwallet = GetWalletForJSONRPCRequest(request); + + if (!EnsureWalletIsAvailable(pwallet, request.fHelp)) { + return NullUniValue; } - UniValue vErrors(UniValue::VARR); - if (state.IsInvalid()) { - // This can happen if the mempool rejected the transaction. Report - // what happened in the "errors" response. - vErrors.push_back(strprintf("Error: The transaction was rejected: %s", FormatStateMessage(state))); + 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") + ); } - // mark the original tx as bumped - if (!pwalletMain->MarkReplaced(wtx.GetHash(), wtxBumped.GetHash())) { - // TODO: see if JSON-RPC has a standard way of returning a response - // along with an exception. It would be good to return information about - // wtxBumped to the caller even if marking the original transaction - // replaced does not succeed for some reason. - vErrors.push_back("Error: Created new bumpfee transaction but could not mark the original transaction as replaced."); + 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(); } - UniValue result(UniValue::VOBJ); - result.push_back(Pair("txid", wtxBumped.GetHash().GetHex())); - result.push_back(Pair("origfee", ValueFromAmount(nOldFee))); - result.push_back(Pair("fee", ValueFromAmount(nNewFee))); - result.push_back(Pair("errors", vErrors)); + std::shared_ptr<CReserveScript> coinbase_script; + pwallet->GetScriptForMining(coinbase_script); - return result; + // 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(coinbase_script, num_generate, max_tries, true); } +extern UniValue abortrescan(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue dumpprivkey(const JSONRPCRequest& request); // in rpcdump.cpp extern UniValue importprivkey(const JSONRPCRequest& request); extern UniValue importaddress(const JSONRPCRequest& request); @@ -2997,6 +3150,7 @@ static const CRPCCommand commands[] = { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false, {"hexstring","options"} }, { "hidden", "resendwallettransactions", &resendwallettransactions, true, {} }, { "wallet", "abandontransaction", &abandontransaction, false, {"txid"} }, + { "wallet", "abortrescan", &abortrescan, false, {} }, { "wallet", "addmultisigaddress", &addmultisigaddress, true, {"nrequired","keys","account"} }, { "wallet", "addwitnessaddress", &addwitnessaddress, true, {"address"} }, { "wallet", "backupwallet", &backupwallet, true, {"destination"} }, @@ -3027,14 +3181,15 @@ static const CRPCCommand commands[] = { "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", "listsinceblock", &listsinceblock, false, {"blockhash","target_confirmations","include_watchonly","include_removed"} }, { "wallet", "listtransactions", &listtransactions, false, {"account","count","skip","include_watchonly"} }, - { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe"} }, + { "wallet", "listunspent", &listunspent, false, {"minconf","maxconf","addresses","include_unsafe","query_options"} }, + { "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", "sendmany", &sendmany, false, {"fromaccount","amounts","minconf","comment","subtractfeefrom","replaceable","conf_target","estimate_mode"} }, + { "wallet", "sendtoaddress", &sendtoaddress, false, {"address","amount","comment","comment_to","subtractfeefromamount","replaceable","conf_target","estimate_mode"} }, { "wallet", "setaccount", &setaccount, true, {"address","account"} }, { "wallet", "settxfee", &settxfee, true, {"amount"} }, { "wallet", "signmessage", &signmessage, true, {"address","message"} }, @@ -3042,11 +3197,13 @@ static const CRPCCommand commands[] = { "wallet", "walletpassphrasechange", &walletpassphrasechange, true, {"oldpassphrase","newpassphrase"} }, { "wallet", "walletpassphrase", &walletpassphrase, true, {"passphrase","timeout"} }, { "wallet", "removeprunedfunds", &removeprunedfunds, true, {"txid"} }, + + { "generating", "generate", &generate, true, {"nblocks","maxtries"} }, }; void RegisterWalletRPCCommands(CRPCTable &t) { - if (GetBoolArg("-disablewallet", false)) + if (gArgs.GetBoolArg("-disablewallet", false)) return; for (unsigned int vcidx = 0; vcidx < ARRAYLEN(commands); vcidx++) |