Merge pull request #11213

ba3884e wallet2: exclude outputs from an unconfirmed send in reserve proofs (Cole Munz)

ACKs: jpk68, selsta
This commit is contained in:
tobtoht
2026-09-09 20:10:50 +00:00
+14 -4
View File
@@ -12558,22 +12558,32 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote
std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t, uint64_t>> &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<size_t> 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(); });