Merge #13566: Fix get balance

702ae1e21a [RPC] [wallet] allow getbalance to use min_conf and watch_only without accounts. (John Newbery)
cf15761f6d [wallet] GetBalance can take a min_depth argument. (John Newbery)
0f3d6e9ab7 [wallet] factor out GetAvailableWatchOnlyBalance() (John Newbery)
7110c830f8 [wallet] deduplicate GetAvailableCredit logic (John Newbery)
ef7bc8893c [wallet] Factor out GetWatchOnlyBalance() (John Newbery)
4279da4785 [wallet] GetBalance can take an isminefilter filter. (John Newbery)

Pull request description:

  #12953 inadvertently removed the functionality to call `getbalance "*" <int> <bool>` to get the wallet's balance with either minconfs or include_watchonly.

  This restores that functionality (when `-deprecatedrpc=accounts`), and also makes it possible to call ``getbalance minconf=<int> include_watchonly=<bool>` when accounts are not being used.

Tree-SHA512: 67e84de9291ed6d34b23c626f4dc5988ba0ae6c99708d02b87dd3aaad3f4b6baa6202a66cc2dadd30dd993a39de8036ee920fcaa8cbb1c5dfe606e6fac183344
This commit is contained in:
Pieter Wuille 2018-07-13 19:40:31 -07:00
commit ad552a54c5
No known key found for this signature in database
GPG Key ID: A636E97631F767E0
5 changed files with 73 additions and 89 deletions

View File

@ -338,7 +338,7 @@ public:
result.immature_balance = m_wallet.GetImmatureBalance(); result.immature_balance = m_wallet.GetImmatureBalance();
result.have_watch_only = m_wallet.HaveWatchOnly(); result.have_watch_only = m_wallet.HaveWatchOnly();
if (result.have_watch_only) { if (result.have_watch_only) {
result.watch_only_balance = m_wallet.GetWatchOnlyBalance(); result.watch_only_balance = m_wallet.GetBalance(ISMINE_WATCH_ONLY);
result.unconfirmed_watch_only_balance = m_wallet.GetUnconfirmedWatchOnlyBalance(); result.unconfirmed_watch_only_balance = m_wallet.GetUnconfirmedWatchOnlyBalance();
result.immature_watch_only_balance = m_wallet.GetImmatureWatchOnlyBalance(); result.immature_watch_only_balance = m_wallet.GetImmatureWatchOnlyBalance();
} }

View File

@ -852,8 +852,9 @@ static UniValue getbalance(const JSONRPCRequest& request)
return NullUniValue; return NullUniValue;
} }
if (request.fHelp || (request.params.size() > 3 && IsDeprecatedRPCEnabled("accounts")) || (request.params.size() != 0 && !IsDeprecatedRPCEnabled("accounts"))) if (request.fHelp || (request.params.size() > 3 ))
throw std::runtime_error( throw std::runtime_error(
(IsDeprecatedRPCEnabled("accounts") ? std::string(
"getbalance ( \"account\" minconf include_watchonly )\n" "getbalance ( \"account\" minconf include_watchonly )\n"
"\nIf account is not specified, returns the server's total available balance.\n" "\nIf account is not specified, returns the server's total available balance.\n"
"The available balance is what the wallet considers currently spendable, and is\n" "The available balance is what the wallet considers currently spendable, and is\n"
@ -875,8 +876,17 @@ static UniValue getbalance(const JSONRPCRequest& request)
" balances. In general, account balance calculation is not considered\n" " balances. In general, account balance calculation is not considered\n"
" reliable and has resulted in confusing outcomes, so it is recommended to\n" " reliable and has resulted in confusing outcomes, so it is recommended to\n"
" avoid passing this argument.\n" " avoid passing this argument.\n"
"2. minconf (numeric, optional, default=1) DEPRECATED. Only valid when an account is specified. This argument will be removed in V0.18. To use this deprecated argument, start bitcoind with -deprecatedrpc=accounts. Only include transactions confirmed at least this many times.\n" "2. minconf (numeric, optional) Only include transactions confirmed at least this many times. \n"
"3. include_watchonly (bool, optional, default=false) DEPRECATED. Only valid when an account is specified. This argument will be removed in V0.18. To use this deprecated argument, start bitcoind with -deprecatedrpc=accounts. Also include balance in watch-only addresses (see 'importaddress')\n" " The default is 1 if an account is provided or 0 if no account is provided\n")
: std::string(
"getbalance ( \"(dummy)\" minconf include_watchonly )\n"
"\nReturns the total available balance.\n"
"The available balance is what the wallet considers currently spendable, and is\n"
"thus affected by options which limit spendability such as -spendzeroconfchange.\n"
"\nArguments:\n"
"1. (dummy) (string, optional) Remains for backward compatibility. Must be excluded or set to \"*\".\n"
"2. minconf (numeric, optional, default=0) Only include transactions confirmed at least this many times.\n")) +
"3. include_watchonly (bool, optional, default=false) Also include balance in watch-only addresses (see 'importaddress')\n"
"\nResult:\n" "\nResult:\n"
"amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n" "amount (numeric) The total amount in " + CURRENCY_UNIT + " received for this account.\n"
"\nExamples:\n" "\nExamples:\n"
@ -894,38 +904,35 @@ static UniValue getbalance(const JSONRPCRequest& request)
LOCK2(cs_main, pwallet->cs_wallet); LOCK2(cs_main, pwallet->cs_wallet);
if (IsDeprecatedRPCEnabled("accounts")) { const UniValue& account_value = request.params[0];
const UniValue& account_value = request.params[0];
const UniValue& minconf = request.params[1];
const UniValue& include_watchonly = request.params[2];
if (account_value.isNull()) { int min_depth = 0;
if (!minconf.isNull()) { if (IsDeprecatedRPCEnabled("accounts") && !account_value.isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, // Default min_depth to 1 when an account is provided.
"getbalance minconf option is only currently supported if an account is specified"); min_depth = 1;
} }
if (!include_watchonly.isNull()) { if (!request.params[1].isNull()) {
throw JSONRPCError(RPC_INVALID_PARAMETER, min_depth = request.params[1].get_int();
"getbalance include_watchonly option is only currently supported if an account is specified"); }
}
return ValueFromAmount(pwallet->GetBalance()); isminefilter filter = ISMINE_SPENDABLE;
} if (!request.params[2].isNull() && request.params[2].get_bool()) {
filter = filter | ISMINE_WATCH_ONLY;
}
if (!account_value.isNull()) {
const std::string& account_param = account_value.get_str(); const std::string& account_param = account_value.get_str();
const std::string* account = account_param != "*" ? &account_param : nullptr; const std::string* account = account_param != "*" ? &account_param : nullptr;
int nMinDepth = 1; if (!IsDeprecatedRPCEnabled("accounts") && account_param != "*") {
if (!minconf.isNull()) throw JSONRPCError(RPC_METHOD_DEPRECATED, "dummy first argument must be excluded or set to \"*\".");
nMinDepth = minconf.get_int(); } else if (IsDeprecatedRPCEnabled("accounts")) {
isminefilter filter = ISMINE_SPENDABLE; return ValueFromAmount(pwallet->GetLegacyBalance(filter, min_depth, account));
if(!include_watchonly.isNull()) }
if(include_watchonly.get_bool())
filter = filter | ISMINE_WATCH_ONLY;
return ValueFromAmount(pwallet->GetLegacyBalance(filter, nMinDepth, account));
} }
return ValueFromAmount(pwallet->GetBalance()); return ValueFromAmount(pwallet->GetBalance(filter, min_depth));
} }
static UniValue getunconfirmedbalance(const JSONRPCRequest &request) static UniValue getunconfirmedbalance(const JSONRPCRequest &request)
@ -4421,7 +4428,7 @@ static const CRPCCommand commands[] =
{ "wallet", "dumpwallet", &dumpwallet, {"filename"} }, { "wallet", "dumpwallet", &dumpwallet, {"filename"} },
{ "wallet", "encryptwallet", &encryptwallet, {"passphrase"} }, { "wallet", "encryptwallet", &encryptwallet, {"passphrase"} },
{ "wallet", "getaddressinfo", &getaddressinfo, {"address"} }, { "wallet", "getaddressinfo", &getaddressinfo, {"address"} },
{ "wallet", "getbalance", &getbalance, {"account","minconf","include_watchonly"} }, { "wallet", "getbalance", &getbalance, {"account|dummy","minconf","include_watchonly"} },
{ "wallet", "getnewaddress", &getnewaddress, {"label|account","address_type"} }, { "wallet", "getnewaddress", &getnewaddress, {"label|account","address_type"} },
{ "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} }, { "wallet", "getrawchangeaddress", &getrawchangeaddress, {"address_type"} },
{ "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} }, { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, {"address","minconf"} },

View File

@ -1929,7 +1929,7 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
return 0; return 0;
} }
CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const
{ {
if (pwallet == nullptr) if (pwallet == nullptr)
return 0; return 0;
@ -1938,8 +1938,20 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
if (IsCoinBase() && GetBlocksToMaturity() > 0) if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0; return 0;
if (fUseCache && fAvailableCreditCached) CAmount* cache = nullptr;
return nAvailableCreditCached; bool* cache_used = nullptr;
if (filter == ISMINE_SPENDABLE) {
cache = &nAvailableCreditCached;
cache_used = &fAvailableCreditCached;
} else if (filter == ISMINE_WATCH_ONLY) {
cache = &nAvailableWatchCreditCached;
cache_used = &fAvailableWatchCreditCached;
}
if (fUseCache && cache_used && *cache_used) {
return *cache;
}
CAmount nCredit = 0; CAmount nCredit = 0;
uint256 hashTx = GetHash(); uint256 hashTx = GetHash();
@ -1948,14 +1960,16 @@ CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const
if (!pwallet->IsSpent(hashTx, i)) if (!pwallet->IsSpent(hashTx, i))
{ {
const CTxOut &txout = tx->vout[i]; const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE); nCredit += pwallet->GetCredit(txout, filter);
if (!MoneyRange(nCredit)) if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + " : value out of range"); throw std::runtime_error(std::string(__func__) + " : value out of range");
} }
} }
nAvailableCreditCached = nCredit; if (cache) {
fAvailableCreditCached = true; *cache = nCredit;
*cache_used = true;
}
return nCredit; return nCredit;
} }
@ -1973,35 +1987,6 @@ CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const
return 0; return 0;
} }
CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool fUseCache) const
{
if (pwallet == nullptr)
return 0;
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsCoinBase() && GetBlocksToMaturity() > 0)
return 0;
if (fUseCache && fAvailableWatchCreditCached)
return nAvailableWatchCreditCached;
CAmount nCredit = 0;
for (unsigned int i = 0; i < tx->vout.size(); i++)
{
if (!pwallet->IsSpent(GetHash(), i))
{
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY);
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
}
nAvailableWatchCreditCached = nCredit;
fAvailableWatchCreditCached = true;
return nCredit;
}
CAmount CWalletTx::GetChange() const CAmount CWalletTx::GetChange() const
{ {
if (fChangeCached) if (fChangeCached)
@ -2115,7 +2100,7 @@ void CWallet::ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman
*/ */
CAmount CWallet::GetBalance() const CAmount CWallet::GetBalance(const isminefilter& filter, const int min_depth) const
{ {
CAmount nTotal = 0; CAmount nTotal = 0;
{ {
@ -2123,8 +2108,9 @@ CAmount CWallet::GetBalance() const
for (const auto& entry : mapWallet) for (const auto& entry : mapWallet)
{ {
const CWalletTx* pcoin = &entry.second; const CWalletTx* pcoin = &entry.second;
if (pcoin->IsTrusted()) if (pcoin->IsTrusted() && pcoin->GetDepthInMainChain() >= min_depth) {
nTotal += pcoin->GetAvailableCredit(); nTotal += pcoin->GetAvailableCredit(true, filter);
}
} }
} }
@ -2160,22 +2146,6 @@ CAmount CWallet::GetImmatureBalance() const
return nTotal; return nTotal;
} }
CAmount CWallet::GetWatchOnlyBalance() const
{
CAmount nTotal = 0;
{
LOCK2(cs_main, cs_wallet);
for (const auto& entry : mapWallet)
{
const CWalletTx* pcoin = &entry.second;
if (pcoin->IsTrusted())
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
}
return nTotal;
}
CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
{ {
CAmount nTotal = 0; CAmount nTotal = 0;
@ -2185,7 +2155,7 @@ CAmount CWallet::GetUnconfirmedWatchOnlyBalance() const
{ {
const CWalletTx* pcoin = &entry.second; const CWalletTx* pcoin = &entry.second;
if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool()) if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 && pcoin->InMempool())
nTotal += pcoin->GetAvailableWatchOnlyCredit(); nTotal += pcoin->GetAvailableCredit(true, ISMINE_WATCH_ONLY);
} }
} }
return nTotal; return nTotal;

View File

@ -459,9 +459,8 @@ public:
CAmount GetDebit(const isminefilter& filter) const; CAmount GetDebit(const isminefilter& filter) const;
CAmount GetCredit(const isminefilter& filter) const; CAmount GetCredit(const isminefilter& filter) const;
CAmount GetImmatureCredit(bool fUseCache=true) const; CAmount GetImmatureCredit(bool fUseCache=true) const;
CAmount GetAvailableCredit(bool fUseCache=true) const; CAmount GetAvailableCredit(bool fUseCache=true, const isminefilter& filter=ISMINE_SPENDABLE) const;
CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const; CAmount GetImmatureWatchOnlyCredit(const bool fUseCache=true) const;
CAmount GetAvailableWatchOnlyCredit(const bool fUseCache=true) const;
CAmount GetChange() const; CAmount GetChange() const;
// Get the marginal bytes if spending the specified output from this transaction // Get the marginal bytes if spending the specified output from this transaction
@ -941,10 +940,9 @@ public:
void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override; void ResendWalletTransactions(int64_t nBestBlockTime, CConnman* connman) override;
// ResendWalletTransactionsBefore may only be called if fBroadcastTransactions! // ResendWalletTransactionsBefore may only be called if fBroadcastTransactions!
std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman); std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime, CConnman* connman);
CAmount GetBalance() const; CAmount GetBalance(const isminefilter& filter=ISMINE_SPENDABLE, const int min_depth=0) const;
CAmount GetUnconfirmedBalance() const; CAmount GetUnconfirmedBalance() const;
CAmount GetImmatureBalance() const; CAmount GetImmatureBalance() const;
CAmount GetWatchOnlyBalance() const;
CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const;
CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const; CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const;

View File

@ -64,6 +64,15 @@ class WalletTest(BitcoinTestFramework):
assert_equal(self.nodes[1].getbalance(), 50) assert_equal(self.nodes[1].getbalance(), 50)
assert_equal(self.nodes[2].getbalance(), 0) assert_equal(self.nodes[2].getbalance(), 0)
# Check getbalance with different arguments
assert_equal(self.nodes[0].getbalance("*"), 50)
assert_equal(self.nodes[0].getbalance("*", 1), 50)
assert_equal(self.nodes[0].getbalance("*", 1, True), 50)
assert_equal(self.nodes[0].getbalance(minconf=1), 50)
# first argument of getbalance must be excluded or set to "*"
assert_raises_rpc_error(-32, "dummy first argument must be excluded or set to \"*\"", self.nodes[0].getbalance, "")
# Check that only first and second nodes have UTXOs # Check that only first and second nodes have UTXOs
utxos = self.nodes[0].listunspent() utxos = self.nodes[0].listunspent()
assert_equal(len(utxos), 1) assert_equal(len(utxos), 1)