diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 86eba9354..cb2a5e605 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -12424,22 +12424,32 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote std::string wallet2::get_reserve_proof(const boost::optional> &account_minreserve, const std::string &message) { THROW_WALLET_EXCEPTION_IF(m_watch_only || m_multisig, error::wallet_internal_error, "Reserve proof can only be generated by a full wallet"); - THROW_WALLET_EXCEPTION_IF(balance_all(true) == 0, error::wallet_internal_error, "Zero balance"); - THROW_WALLET_EXCEPTION_IF(account_minreserve && balance(account_minreserve->first, true) < account_minreserve->second, error::wallet_internal_error, - "Not enough balance in this account for the requested minimum reserve amount"); // determine which outputs to include in the proof + // is_spent(td, false) excludes an output already used as input to a pending, unconfirmed + // send too, not just a confirmed one, so we don't claim reserve we no longer have std::vector selected_transfers; for (size_t i = 0; i < m_transfers.size(); ++i) { const transfer_details &td = m_transfers[i]; - if (!is_spent(td, true) && !td.m_frozen && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major)) + if (!is_spent(td, false) && !td.m_frozen && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major)) selected_transfers.push_back(i); } + // sum what's actually selected instead of calling balance_all()/balance(): those also fold + // in pending change and self-transfer amounts from our own unconfirmed txs, which have no + // matching entry in m_transfers yet and can leave a balance-based guard non-zero even once + // every output backing it has been excluded above + uint64_t available = 0; + for (size_t idx : selected_transfers) + available += m_transfers[idx].amount(); + THROW_WALLET_EXCEPTION_IF(available == 0, error::wallet_internal_error, "Zero balance"); + if (account_minreserve) { THROW_WALLET_EXCEPTION_IF(account_minreserve->second == 0, error::wallet_internal_error, "Proved amount must be greater than 0"); + THROW_WALLET_EXCEPTION_IF(available < account_minreserve->second, error::wallet_internal_error, + "Not enough balance in this account for the requested minimum reserve amount"); // minimize the number of outputs included in the proof, by only picking the N largest outputs that can cover the requested min reserve amount std::sort(selected_transfers.begin(), selected_transfers.end(), [&](const size_t a, const size_t b) { return m_transfers[a].amount() > m_transfers[b].amount(); });