diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f8006a625..98efb8bf6 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -729,6 +729,8 @@ UniValue getbalance(const JSONRPCRequest& request) if (request.params.size() == 0) return ValueFromAmount(pwallet->GetBalance()); + const std::string* account = request.params[0].get_str() != "*" ? &request.params[0].get_str() : nullptr; + int nMinDepth = 1; if (request.params.size() > 1) nMinDepth = request.params[1].get_int(); @@ -737,6 +739,8 @@ UniValue getbalance(const JSONRPCRequest& request) if(request.params[2].get_bool()) filter = filter | ISMINE_WATCH_ONLY; + CAmount legacyBalance = pwallet->GetLegacyBalance(filter, nMinDepth, account); + 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 @@ -764,6 +768,7 @@ UniValue getbalance(const JSONRPCRequest& request) nBalance -= s.amount; nBalance -= allFee; } + assert(nBalance == legacyBalance); return ValueFromAmount(nBalance); } @@ -771,6 +776,7 @@ UniValue getbalance(const JSONRPCRequest& request) CAmount nBalance = pwallet->GetAccountBalance(strAccount, nMinDepth, filter); + assert(nBalance == legacyBalance); return ValueFromAmount(nBalance); } @@ -902,6 +908,8 @@ UniValue sendfrom(const JSONRPCRequest& request) // Check funds CAmount nBalance = pwallet->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); + CAmount legacyBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount); + assert(nBalance == legacyBalance); if (nAmount > nBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); @@ -1011,6 +1019,8 @@ UniValue sendmany(const JSONRPCRequest& request) // Check funds CAmount nBalance = pwallet->GetAccountBalance(strAccount, nMinDepth, ISMINE_SPENDABLE); + CAmount legacyBalance = pwallet->GetLegacyBalance(ISMINE_SPENDABLE, nMinDepth, &strAccount); + assert(nBalance == legacyBalance); if (totalAmount > nBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Account has insufficient funds"); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b63171c4b..cd94eefb2 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1975,6 +1975,49 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const return nTotal; } +// Calculate total balance in a 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 CWallet::GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const +{ + LOCK2(cs_main, cs_wallet); + + CAmount balance = 0; + for (const auto& entry : mapWallet) { + const CWalletTx& wtx = entry.second; + const int depth = wtx.GetDepthInMainChain(); + if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.GetBlocksToMaturity() > 0) { + continue; + } + + // Loop through tx outputs and add incoming payments. For outgoing txs, + // treat change outputs specially, as part of the amount debited. + CAmount debit = wtx.GetDebit(filter); + const bool outgoing = debit > 0; + for (const CTxOut& out : wtx.tx->vout) { + if (outgoing && IsChange(out)) { + debit -= out.nValue; + } else if (IsMine(out) & filter && depth >= minDepth && (!account || *account == GetAccountName(out.scriptPubKey))) { + balance += out.nValue; + } + } + + // For outgoing txs, subtract amount debited. + if (outgoing && (!account || *account == wtx.strFromAccount)) { + balance -= debit; + } + } + + if (account) { + balance += CWalletDB(*dbw).GetAccountCreditDebit(*account); + } + + return balance; +} + void CWallet::AvailableCoins(std::vector& vCoins, bool fOnlySafe, const CCoinControl *coinControl, bool fIncludeZeroValue) const { vCoins.clear(); @@ -2911,6 +2954,21 @@ bool CWallet::DelAddressBook(const CTxDestination& address) return CWalletDB(*dbw).EraseName(CBitcoinAddress(address).ToString()); } +const std::string& CWallet::GetAccountName(const CScript& scriptPubKey) const +{ + CTxDestination address; + if (ExtractDestination(scriptPubKey, address) && !scriptPubKey.IsUnspendable()) { + auto mi = mapAddressBook.find(address); + if (mi != mapAddressBook.end()) { + return mi->second.name; + } + } + // A scriptPubKey that doesn't have an entry in the address book is + // associated with the default account (""). + const static std::string DEFAULT_ACCOUNT_NAME; + return DEFAULT_ACCOUNT_NAME; +} + bool CWallet::SetDefaultKey(const CPubKey &vchPubKey) { if (!CWalletDB(*dbw).WriteDefaultKey(vchPubKey)) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index a52ae99ed..342715b47 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -918,6 +918,7 @@ public: CAmount GetWatchOnlyBalance() const; CAmount GetUnconfirmedWatchOnlyBalance() const; CAmount GetImmatureWatchOnlyBalance() const; + CAmount GetLegacyBalance(const isminefilter& filter, int minDepth, const std::string* account) const; /** * Insert additional inputs into the transaction by @@ -1004,6 +1005,8 @@ public: bool DelAddressBook(const CTxDestination& address); + const std::string& GetAccountName(const CScript& scriptPubKey) const; + void Inventory(const uint256 &hash) override { {