diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index 27f004985e65..6c9c1f0ca772 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -55,7 +55,7 @@ void CActiveMasternode::ManageStatus() return; } - if (pwalletMain->GetBalance() == 0) { + if (pwalletMain->GetAvailableBalance() == 0) { notCapableReason = "Hot node, waiting for remote activation."; LogPrintf("CActiveMasternode::ManageStatus() - not capable: %s\n", notCapableReason); return; diff --git a/src/interface/wallet.cpp b/src/interface/wallet.cpp index a3321ba7d891..d122069d93f9 100644 --- a/src/interface/wallet.cpp +++ b/src/interface/wallet.cpp @@ -10,7 +10,7 @@ namespace interfaces { WalletBalances Wallet::getBalances() { WalletBalances result; - result.balance = m_wallet.GetBalance(); + result.balance = m_wallet.GetAvailableBalance(); result.unconfirmed_balance = m_wallet.GetUnconfirmedBalance(); result.immature_balance = m_wallet.GetImmatureBalance(); result.have_watch_only = m_wallet.HaveWatchOnly(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 4a5b272bc960..c79127c5cf99 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -109,7 +109,7 @@ CAmount WalletModel::getBalance(const CCoinControl* coinControl, bool fIncludeDe return nBalance; } - return wallet->GetBalance(fIncludeDelegated) - (fUnlockedOnly ? wallet->GetLockedCoins() : CAmount(0)); + return wallet->GetAvailableBalance(fIncludeDelegated) - (fUnlockedOnly ? wallet->GetLockedCoins() : CAmount(0)); } CAmount WalletModel::getUnlockedBalance(const CCoinControl* coinControl, bool fIncludeDelegated) const diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index 61a8959c0ce7..04933d57505b 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -125,7 +125,7 @@ UniValue getinfo(const JSONRPCRequest& request) #ifdef ENABLE_WALLET if (pwalletMain) { obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); - obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); + obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetAvailableBalance()))); obj.push_back(Pair("zerocoinbalance", ValueFromAmount(pwalletMain->GetZerocoinBalance(true)))); obj.push_back(Pair("staking status", (pwalletMain->pStakerStatus->IsActive() ? "Staking Active" : diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 7b35308332f1..8bdd2e65bee4 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -937,7 +937,7 @@ void SendMoney(const CTxDestination& address, CAmount nValue, CWalletTx& wtxNew, if (nValue <= 0) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid amount"); - if (nValue > pwalletMain->GetBalance()) + if (nValue > pwalletMain->GetAvailableBalance()) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); std::string strError; @@ -954,7 +954,7 @@ void SendMoney(const CTxDestination& address, CAmount nValue, CWalletTx& wtxNew, CReserveKey reservekey(pwalletMain); CAmount nFeeRequired; if (!pwalletMain->CreateTransaction(scriptPubKey, nValue, wtxNew, reservekey, nFeeRequired, strError, nullptr, ALL_COINS, fUseIX, (CAmount)0)) { - if (nValue + nFeeRequired > pwalletMain->GetBalance()) + if (nValue + nFeeRequired > pwalletMain->GetAvailableBalance()) strError = strprintf("Error: This transaction requires a transaction fee of at least %s because of its amount, complexity, or use of recently received funds!", FormatMoney(nFeeRequired)); LogPrintf("SendMoney() : %s\n", strError); throw JSONRPCError(RPC_WALLET_ERROR, strError); @@ -1051,7 +1051,7 @@ UniValue CreateColdStakeDelegation(const UniValue& params, CWalletTx& wtxNew, CR fUseDelegated = params[4].get_bool(); // Check amount - CAmount currBalance = pwalletMain->GetBalance() + (fUseDelegated ? pwalletMain->GetDelegatedBalance() : 0); + CAmount currBalance = pwalletMain->GetAvailableBalance() + (fUseDelegated ? pwalletMain->GetDelegatedBalance() : 0); if (nValue > currBalance) throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, "Insufficient funds"); @@ -1525,7 +1525,7 @@ UniValue getbalance(const JSONRPCRequest& request) if (IsDeprecatedRPCEnabled("accounts")) { if (request.params.size() == 0) - return ValueFromAmount(pwalletMain->GetBalance()); + return ValueFromAmount(pwalletMain->GetAvailableBalance()); const std::string* account = request.params[0].get_str() != "*" ? &request.params[0].get_str() : nullptr; @@ -1543,7 +1543,7 @@ UniValue getbalance(const JSONRPCRequest& request) // TODO: re-incorporate the includeDelegated argument // after 4.2 branch off for 5.0 - return ValueFromAmount(pwalletMain->GetBalance()); + return ValueFromAmount(pwalletMain->GetAvailableBalance()); } UniValue getcoldstakingbalance(const JSONRPCRequest& request) @@ -3467,7 +3467,7 @@ UniValue getwalletinfo(const JSONRPCRequest& request) UniValue obj(UniValue::VOBJ); obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); - obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); + obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetAvailableBalance()))); obj.push_back(Pair("delegated_balance", ValueFromAmount(pwalletMain->GetDelegatedBalance()))); obj.push_back(Pair("cold_staking_balance", ValueFromAmount(pwalletMain->GetColdStakingBalance()))); obj.push_back(Pair("unconfirmed_balance", ValueFromAmount(pwalletMain->GetUnconfirmedBalance()))); diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 912478035951..956e22fcfc6f 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -4,6 +4,7 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include "wallet/wallet.h" +#include "consensus/merkle.h" #include #include @@ -302,4 +303,171 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) empty_wallet(); } +void removeTxFromMempool(CWalletTx& wtx) +{ + LOCK(mempool.cs); + if (mempool.exists(wtx.GetHash())) { + auto it = mempool.mapTx.find(wtx.GetHash()); + if (it != mempool.mapTx.end()) { + mempool.mapTx.erase(it); + } + } +} + +/** + * Mimic block creation. + */ +CBlockIndex* SimpleFakeMine(CWalletTx& wtx, CBlockIndex* pprev = nullptr) +{ + CBlock block; + block.vtx.push_back(wtx); + block.hashMerkleRoot = BlockMerkleRoot(block); + if (pprev) block.hashPrevBlock = pprev->GetBlockHash(); + CBlockIndex* fakeIndex = new CBlockIndex(block); + fakeIndex->pprev = pprev; + mapBlockIndex.insert(std::make_pair(block.GetHash(), fakeIndex)); + fakeIndex->phashBlock = &mapBlockIndex.find(block.GetHash())->first; + chainActive.SetTip(fakeIndex); + BOOST_CHECK(chainActive.Contains(fakeIndex)); + wtx.SetMerkleBranch(fakeIndex, 0); + removeTxFromMempool(wtx); + return fakeIndex; +} + +void fakeMempoolInsertion(const CWalletTx& wtxCredit) +{ + CTxMemPoolEntry entry(wtxCredit, 0, 0, 0, 0, false, 0, false, 0); + LOCK(mempool.cs); + mempool.mapTx.insert(entry); +} + +CWalletTx& BuildAndLoadTxToWallet(const std::vector& vin, + const std::vector& vout, + CWallet& wallet) +{ + CMutableTransaction mTx; + mTx.vin = vin; + mTx.vout = vout; + CTransaction tx(mTx); + wallet.LoadToWallet({&wallet, tx}); + return wallet.mapWallet[tx.GetHash()]; +} + +CWalletTx& ReceiveBalanceWith(const std::vector& vout, + CWallet& wallet) +{ + std::vector vin; + vin.emplace_back(CTxIn(COutPoint(uint256(), 999))); + return BuildAndLoadTxToWallet(vin, vout, wallet); +} + +void CheckBalances(const CWalletTx& tx, + const CAmount& nCreditAll, + const CAmount& nCreditSpendable, + const CAmount& nAvailableCredit, + const CAmount& nDebitAll, + const CAmount& nDebitSpendable) +{ + BOOST_CHECK_EQUAL(tx.GetCredit(ISMINE_ALL), nCreditAll); + BOOST_CHECK_EQUAL(tx.GetCredit(ISMINE_SPENDABLE), nCreditSpendable); + BOOST_CHECK(tx.IsAmountCached(CWalletTx::CREDIT, ISMINE_SPENDABLE)); + BOOST_CHECK_EQUAL(tx.GetAvailableCredit(), nAvailableCredit); + BOOST_CHECK(tx.IsAmountCached(CWalletTx::AVAILABLE_CREDIT, ISMINE_SPENDABLE)); + BOOST_CHECK_EQUAL(tx.GetDebit(ISMINE_ALL), nDebitAll); + BOOST_CHECK_EQUAL(tx.GetDebit(ISMINE_SPENDABLE), nDebitSpendable); + BOOST_CHECK(tx.IsAmountCached(CWalletTx::DEBIT, ISMINE_SPENDABLE)); +} + +/** + * Validates the correct behaviour of the CWalletTx "standard" balance methods. + * (where "standard" is defined by direct P2PKH scripts, no P2CS contracts nor other types) + * + * 1) CWalletTx::GetCredit. + * 2) CWalletTx::GetDebit. + * 4) CWalletTx::GetAvailableCredit + * 3) CWallet::GetUnconfirmedBalance. + */ +BOOST_AUTO_TEST_CASE(cached_balances_tests) +{ + // 1) Receive balance from an external source and verify: + // * GetCredit(ISMINE_ALL) correctness (must be equal to 'nCredit' amount) + // * GetCredit(ISMINE_SPENDABLE) correctness (must be equal to ISMINE_ALL) + must be cached. + // * GetAvailableCredit() correctness (must be equal to ISMINE_ALL) + // * GetDebit(ISMINE_ALL) correctness (must be 0) + // * wallet.GetUnconfirmedBalance() correctness (must be equal 'nCredit') + + // 2) Confirm the tx and verify: + // * wallet.GetUnconfirmedBalance() correctness (must be 0) + // * GetAvailableCredit() correctness (must be equal to (1) ISMINE_ALL) + + // 3) Spend one of the two outputs of the receiving tx to an external source + // and verify: + // * creditTx.GetAvailableCredit() correctness (must be equal to 'nCredit' / 2) + must be cached. + // * debitTx.GetDebit(ISMINE_ALL) correctness (must be equal to 'nCredit' / 2) + // * debitTx.GetDebit(ISMINE_SPENDABLE) correctness (must be equal to 'nCredit' / 2) + must be cached. + // * debitTx.GetAvailableCredit() correctness (must be 0). + + CAmount nCredit = 20 * COIN; + + // Setup wallet + CWallet &wallet = *pwalletMain; + LOCK2(cs_main, wallet.cs_wallet); + wallet.SetMinVersion(FEATURE_PRE_SPLIT_KEYPOOL); + wallet.SetupSPKM(false); + + // Receive balance from an external source + CTxDestination receivingAddr; + BOOST_ASSERT(wallet.getNewAddress(receivingAddr, "receiving_address").result); + CTxOut creditOut(nCredit/2, GetScriptForDestination(receivingAddr)); + CWalletTx& wtxCredit = ReceiveBalanceWith({creditOut, creditOut},wallet); + + // Validates (1) + CheckBalances( + wtxCredit, + nCredit, // CREDIT-ISMINE_ALL + nCredit, // CREDIT-ISMINE_SPENDABLE + nCredit, // AVAILABLE_CREDIT + 0, // DEBIT-ISMINE_ALL + 0 // DEBIT-ISMINE_SPENDABLE + ); + + // GetUnconfirmedBalance requires tx in mempool. + fakeMempoolInsertion(wtxCredit); + BOOST_CHECK_EQUAL(wallet.GetUnconfirmedBalance(), nCredit); + + // 2) Confirm tx and verify + SimpleFakeMine(wtxCredit); + BOOST_CHECK_EQUAL(wallet.GetUnconfirmedBalance(), 0); + BOOST_CHECK_EQUAL(wtxCredit.GetAvailableCredit(), nCredit); + + // 3) Spend one of the two outputs of the receiving tx to an external source and verify. + // Create debit transaction. + CAmount nDebit = nCredit / 2; + std::vector vinDebit = {CTxIn(COutPoint(wtxCredit.GetHash(), 0))}; + CKey key; + key.MakeNewKey(true); + std::vector voutDebit = {CTxOut(nDebit, GetScriptForDestination(key.GetPubKey().GetID()))}; + CWalletTx& wtxDebit = BuildAndLoadTxToWallet(vinDebit, voutDebit, wallet); + + // Validates (3) + + // First the debit tx + CheckBalances( + wtxDebit, + 0, // CREDIT-ISMINE_ALL + 0, // CREDIT-ISMINE_SPENDABLE + 0, // AVAILABLE_CREDIT + nDebit, // DEBIT-ISMINE_ALL + nDebit // DEBIT-ISMINE_SPENDABLE + ); + + // Secondly the prev credit tx update + + // One output spent, the other one not. Force available credit recalculation. + // If we don't request it, it will not happen. + BOOST_CHECK_EQUAL(wtxCredit.GetAvailableCredit(false), nCredit - nDebit); + BOOST_CHECK(wtxCredit.IsAmountCached(CWalletTx::AVAILABLE_CREDIT, ISMINE_SPENDABLE)); + +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 09ea088401ac..acc610e62306 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -911,7 +911,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFlushOnClose) bool CWallet::LoadToWallet(const CWalletTx& wtxIn) { - uint256 hash = wtxIn.GetHash(); + const uint256& hash = wtxIn.GetHash(); mapWallet[hash] = wtxIn; CWalletTx& wtx = mapWallet[hash]; wtx.BindWallet(this); @@ -1250,15 +1250,19 @@ int CWalletTx::GetRequestCount() const return nRequests; } -CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate, bool fUnspent) const +CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate) const { auto& amount = m_amounts[type]; if (recalculate || !amount.m_cached[filter]) { - amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*this, filter) : pwallet->GetCredit(*this, filter, fUnspent)); + amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*this, filter) : pwallet->GetCredit(*this, filter)); } return amount.m_value[filter]; } +bool CWalletTx::IsAmountCached(AmountType type, const isminefilter& filter) const { + return m_amounts[type].m_cached[filter]; +} + //! filter decides which addresses will count towards the debit CAmount CWalletTx::GetDebit(const isminefilter& filter) const { @@ -1291,30 +1295,21 @@ CAmount CWalletTx::GetStakeDelegationDebit(bool fUseCache) const return GetCachableAmount(DEBIT, ISMINE_SPENDABLE_DELEGATED, !fUseCache); } -CAmount CWalletTx::GetUnspentCredit(const isminefilter& filter) const -{ - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (GetBlocksToMaturity() > 0) - return 0; - - return GetCredit(filter, false, true); -} - -CAmount CWalletTx::GetCredit(const isminefilter& filter, bool recalculate, bool fUnspent) const +CAmount CWalletTx::GetCredit(const isminefilter& filter, bool recalculate) const { CAmount credit = 0; if (filter & ISMINE_SPENDABLE) { // GetBalance can assume transactions in mapWallet won't change - credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE, recalculate, fUnspent); + credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE, recalculate); } if (filter & ISMINE_WATCH_ONLY) { - credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY, recalculate, fUnspent); + credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY, recalculate); } if (filter & ISMINE_COLD) { - credit += GetCachableAmount(CREDIT, ISMINE_COLD, recalculate, fUnspent); + credit += GetCachableAmount(CREDIT, ISMINE_COLD, recalculate); } if (filter & ISMINE_SPENDABLE_DELEGATED) { - credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE_DELEGATED, recalculate, fUnspent); + credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE_DELEGATED, recalculate); } return credit; } @@ -1329,19 +1324,48 @@ CAmount CWalletTx::GetImmatureCredit(bool fUseCache, const isminefilter& filter) return 0; } -CAmount CWalletTx::GetAvailableCredit(bool fUseCache) const +CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const { - return GetUnspentCredit(ISMINE_SPENDABLE_ALL); + if (!pwallet) + return 0; + + // Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future). + bool allow_cache = filter == ISMINE_SPENDABLE || filter == ISMINE_WATCH_ONLY; + + // Must wait until coinbase/coinstake is safely deep enough in the chain before valuing it + if (GetBlocksToMaturity() > 0) + return 0; + + if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { + return m_amounts[AVAILABLE_CREDIT].m_value[filter]; + } + + CAmount nCredit = 0; + const uint256& hashTx = GetHash(); + for (unsigned int i = 0; i < vout.size(); i++) { + if (!pwallet->IsSpent(hashTx, i)) { + const CTxOut &txout = vout[i]; + nCredit += pwallet->GetCredit(txout, filter); + if (!Params().GetConsensus().MoneyRange(nCredit)) + throw std::runtime_error(std::string(__func__) + " : value out of range"); + } + } + + if (allow_cache) { + m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit); + } + + return nCredit; } CAmount CWalletTx::GetColdStakingCredit(bool fUseCache) const { - return GetUnspentCredit(ISMINE_COLD); + return GetAvailableCredit(fUseCache, ISMINE_COLD); } CAmount CWalletTx::GetStakeDelegationCredit(bool fUseCache) const { - return GetUnspentCredit(ISMINE_SPENDABLE_DELEGATED); + return GetAvailableCredit(fUseCache, ISMINE_SPENDABLE_DELEGATED); } // Return sum of unlocked coins @@ -1417,14 +1441,7 @@ CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool& fUseCache) const CAmount CWalletTx::GetAvailableWatchOnlyCredit(const bool& fUseCache) const { - if (pwallet == 0) - return 0; - - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsCoinBase() && GetBlocksToMaturity() > 0) - return 0; - - return GetCachableAmount(AVAILABLE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); + return GetAvailableCredit(fUseCache, ISMINE_WATCH_ONLY); } CAmount CWalletTx::GetLockedWatchOnlyCredit() const @@ -1733,12 +1750,12 @@ CAmount CWallet::loopTxsBalance(std::functionnHeight + 1); } @@ -4035,11 +4050,10 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co return nDebit; } -CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter, const bool fUnspent) const +CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const { CAmount nCredit = 0; for (unsigned int i = 0; i < tx.vout.size(); i++) { - if (fUnspent && IsSpent(tx.GetHash(), i)) continue; nCredit += GetCredit(tx.vout[i], filter); } if (!Params().GetConsensus().MoneyRange(nCredit)) diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 274397ddc481..eeff4a009827 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -493,7 +493,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface void ResendWalletTransactions(); CAmount loopTxsBalance(std::functionmethod) const; - CAmount GetBalance(bool fIncludeDelegated = true) const; + CAmount GetAvailableBalance(bool fIncludeDelegated = true) const; CAmount GetColdStakingBalance() const; // delegated coins for which we have the staking key CAmount GetImmatureColdStakingBalance() const; CAmount GetStakingBalance(const bool fIncludeColdStaking = true) const; @@ -590,7 +590,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface /** should probably be renamed to IsRelevantToMe */ bool IsFromMe(const CTransaction& tx) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; - CAmount GetCredit(const CTransaction& tx, const isminefilter& filter, const bool fUnspent = false) const; + CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const; CAmount GetChange(const CTransaction& tx) const; void SetBestChain(const CBlockLocator& loc); @@ -850,7 +850,8 @@ class CWalletTx : public CMerkleTx // memory only enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; - CAmount GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate = false, bool fUnspent = false) const; + CAmount GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate = false) const; + bool IsAmountCached(AmountType type, const isminefilter& filter) const; // Only used in unit tests mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS]; mutable bool fChangeCached; mutable bool fStakeDelegationVoided; @@ -916,10 +917,9 @@ class CWalletTx : public CMerkleTx //! filter decides which addresses will count towards the debit CAmount GetDebit(const isminefilter& filter) const; - CAmount GetCredit(const isminefilter& filter, bool recalculate = false, bool fUnspent = false) const; - CAmount GetUnspentCredit(const isminefilter& filter) const; + CAmount GetCredit(const isminefilter& filter, bool recalculate = false) const; CAmount GetImmatureCredit(bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE_ALL) const; - CAmount GetAvailableCredit(bool fUseCache = true) const; + CAmount GetAvailableCredit(bool fUseCache = true, const isminefilter& filter=ISMINE_SPENDABLE) const; // Return sum of unlocked coins CAmount GetUnlockedCredit() const; // Return sum of unlocked coins diff --git a/src/wallet/wallet_zerocoin.cpp b/src/wallet/wallet_zerocoin.cpp index 3c0ac0bd0e96..8d72ac733b0d 100644 --- a/src/wallet/wallet_zerocoin.cpp +++ b/src/wallet/wallet_zerocoin.cpp @@ -164,7 +164,7 @@ std::string CWallet::MintZerocoin(CAmount nValue, CWalletTx& wtxNew, std::vector if (nValue <= 0) return _("Invalid amount"); - CAmount nBalance = GetBalance(); + CAmount nBalance = GetAvailableBalance(); const CAmount& nFee = Params().GetConsensus().ZC_MinMintFee; if (nValue + nFee > nBalance) { LogPrintf("%s: balance=%s fee=%s nValue=%s\n", __func__, FormatMoney(nBalance), FormatMoney(nFee), FormatMoney(nValue));