Files
bitcoin/src/wallet/receive.cpp
Lőrinc 039307554e refactor: unify container presence checks - trivial counts
The changes made here were:

| From              | To               |
|-------------------|------------------|
| `m.count(k)`      | `m.contains(k)`  |
| `!m.count(k)`     | `!m.contains(k)` |
| `m.count(k) == 0` | `!m.contains(k)` |
| `m.count(k) != 0` | `m.contains(k)`  |
| `m.count(k) > 0`  | `m.contains(k)`  |

The commit contains the trivial, mechanical refactors where it doesn't matter if the container can have multiple elements or not

Co-authored-by: Jan B <608446+janb84@users.noreply.github.com>
2025-12-03 13:36:58 +01:00

397 lines
14 KiB
C++

// Copyright (c) 2021-2022 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <consensus/amount.h>
#include <consensus/consensus.h>
#include <util/check.h>
#include <wallet/receive.h>
#include <wallet/transaction.h>
#include <wallet/wallet.h>
namespace wallet {
bool InputIsMine(const CWallet& wallet, const CTxIn& txin)
{
AssertLockHeld(wallet.cs_wallet);
const CWalletTx* prev = wallet.GetWalletTx(txin.prevout.hash);
if (prev && txin.prevout.n < prev->tx->vout.size()) {
return wallet.IsMine(prev->tx->vout[txin.prevout.n]);
}
return false;
}
bool AllInputsMine(const CWallet& wallet, const CTransaction& tx)
{
LOCK(wallet.cs_wallet);
for (const CTxIn& txin : tx.vin) {
if (!InputIsMine(wallet, txin)) return false;
}
return true;
}
CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout)
{
if (!MoneyRange(txout.nValue))
throw std::runtime_error(std::string(__func__) + ": value out of range");
LOCK(wallet.cs_wallet);
return (wallet.IsMine(txout) ? txout.nValue : 0);
}
CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx)
{
CAmount nCredit = 0;
for (const CTxOut& txout : tx.vout)
{
nCredit += OutputGetCredit(wallet, txout);
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
return nCredit;
}
bool ScriptIsChange(const CWallet& wallet, const CScript& script)
{
// TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a script that is ours, but is not in the address book
// is change. That assumption is likely to break when we implement multisignature
// wallets that return change back into a multi-signature-protected address;
// a better way of identifying which outputs are 'the send' and which are
// 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change).
AssertLockHeld(wallet.cs_wallet);
if (wallet.IsMine(script))
{
CTxDestination address;
if (!ExtractDestination(script, address))
return true;
if (!wallet.FindAddressBookEntry(address)) {
return true;
}
}
return false;
}
bool OutputIsChange(const CWallet& wallet, const CTxOut& txout)
{
return ScriptIsChange(wallet, txout.scriptPubKey);
}
CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout)
{
AssertLockHeld(wallet.cs_wallet);
if (!MoneyRange(txout.nValue))
throw std::runtime_error(std::string(__func__) + ": value out of range");
return (OutputIsChange(wallet, txout) ? txout.nValue : 0);
}
CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx)
{
LOCK(wallet.cs_wallet);
CAmount nChange = 0;
for (const CTxOut& txout : tx.vout)
{
nChange += OutputGetChange(wallet, txout);
if (!MoneyRange(nChange))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
return nChange;
}
static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CWalletTx::AmountType type, bool avoid_reuse)
{
auto& amount = wtx.m_amounts[type];
if (!amount.IsCached(avoid_reuse)) {
amount.Set(avoid_reuse, type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx) : TxGetCredit(wallet, *wtx.tx));
wtx.m_is_cache_empty = false;
}
return amount.Get(avoid_reuse);
}
CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, bool avoid_reuse)
{
AssertLockHeld(wallet.cs_wallet);
// Must wait until coinbase is safely deep enough in the chain before valuing it
if (wallet.IsTxImmatureCoinBase(wtx))
return 0;
// GetBalance can assume transactions in mapWallet won't change
return GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, avoid_reuse);
}
CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, bool avoid_reuse)
{
if (wtx.tx->vin.empty())
return 0;
return GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, avoid_reuse);
}
CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx)
{
if (wtx.fChangeCached)
return wtx.nChangeCached;
wtx.nChangeCached = TxGetChange(wallet, *wtx.tx);
wtx.fChangeCached = true;
return wtx.nChangeCached;
}
void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
std::list<COutputEntry>& listReceived,
std::list<COutputEntry>& listSent, CAmount& nFee,
bool include_change)
{
nFee = 0;
listReceived.clear();
listSent.clear();
// Compute fee:
CAmount nDebit = CachedTxGetDebit(wallet, wtx, /*avoid_reuse=*/false);
if (nDebit > 0) // debit>0 means we signed/sent this transaction
{
CAmount nValueOut = wtx.tx->GetValueOut();
nFee = nDebit - nValueOut;
}
LOCK(wallet.cs_wallet);
// Sent/received.
for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i)
{
const CTxOut& txout = wtx.tx->vout[i];
bool ismine = wallet.IsMine(txout);
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
// 2) the output is to us (received)
if (nDebit > 0)
{
if (!include_change && OutputIsChange(wallet, txout))
continue;
}
else if (!ismine)
continue;
// In either case, we need to get the destination address
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable())
{
wallet.WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
wtx.GetHash().ToString());
address = CNoDestination();
}
COutputEntry output = {address, txout.nValue, (int)i};
// If we are debited by the transaction, add the output as a "sent" entry
if (nDebit > 0)
listSent.push_back(output);
// If we are receiving the output, add it as a "received" entry
if (ismine)
listReceived.push_back(output);
}
}
bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx)
{
if (!wtx.m_cached_from_me.has_value()) {
wtx.m_cached_from_me = wallet.IsFromMe(*wtx.tx);
}
return wtx.m_cached_from_me.value();
}
// NOLINTNEXTLINE(misc-no-recursion)
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<Txid>& trusted_parents)
{
AssertLockHeld(wallet.cs_wallet);
// This wtx is already trusted
if (trusted_parents.contains(wtx.GetHash())) return true;
if (wtx.isConfirmed()) return true;
if (wtx.isBlockConflicted()) return false;
// using wtx's cached debit
if (!wallet.m_spend_zero_conf_change || !CachedTxIsFromMe(wallet, wtx)) return false;
// Don't trust unconfirmed transactions from us unless they are in the mempool.
if (!wtx.InMempool()) return false;
// Trusted if all inputs are from us and are in the mempool:
for (const CTxIn& txin : wtx.tx->vin)
{
// Transactions not sent by us: not trusted
const CWalletTx* parent = wallet.GetWalletTx(txin.prevout.hash);
if (parent == nullptr) return false;
const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
// Check that this specific input being spent is trusted
if (!wallet.IsMine(parentOut)) return false;
// If we've already trusted this parent, continue
if (trusted_parents.contains(parent->GetHash())) continue;
// Recurse to check that the parent is also trusted
if (!CachedTxIsTrusted(wallet, *parent, trusted_parents)) return false;
trusted_parents.insert(parent->GetHash());
}
return true;
}
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx)
{
std::set<Txid> trusted_parents;
LOCK(wallet.cs_wallet);
return CachedTxIsTrusted(wallet, wtx, trusted_parents);
}
Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse)
{
Balance ret;
bool allow_used_addresses = !avoid_reuse || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
{
LOCK(wallet.cs_wallet);
std::set<Txid> trusted_parents;
for (const auto& [outpoint, txo] : wallet.GetTXOs()) {
const CWalletTx& wtx = txo.GetWalletTx();
const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)};
const int tx_depth{wallet.GetTxDepthInMainChain(wtx)};
if (!wallet.IsSpent(outpoint) && (allow_used_addresses || !wallet.IsSpentKey(txo.GetTxOut().scriptPubKey))) {
// Get the amounts for mine
CAmount credit_mine = txo.GetTxOut().nValue;
// Set the amounts in the return object
if (wallet.IsTxImmatureCoinBase(wtx) && wtx.isConfirmed()) {
ret.m_mine_immature += credit_mine;
} else if (is_trusted && tx_depth >= min_depth) {
ret.m_mine_trusted += credit_mine;
} else if (!is_trusted && wtx.InMempool()) {
ret.m_mine_untrusted_pending += credit_mine;
}
}
}
}
return ret;
}
std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet)
{
std::map<CTxDestination, CAmount> balances;
{
LOCK(wallet.cs_wallet);
std::set<Txid> trusted_parents;
for (const auto& [outpoint, txo] : wallet.GetTXOs()) {
const CWalletTx& wtx = txo.GetWalletTx();
if (!CachedTxIsTrusted(wallet, wtx, trusted_parents)) continue;
if (wallet.IsTxImmatureCoinBase(wtx)) continue;
int nDepth = wallet.GetTxDepthInMainChain(wtx);
if (nDepth < (CachedTxIsFromMe(wallet, wtx) ? 0 : 1)) continue;
CTxDestination addr;
Assume(wallet.IsMine(txo.GetTxOut()));
if(!ExtractDestination(txo.GetTxOut().scriptPubKey, addr)) continue;
CAmount n = wallet.IsSpent(outpoint) ? 0 : txo.GetTxOut().nValue;
balances[addr] += n;
}
}
return balances;
}
std::set< std::set<CTxDestination> > GetAddressGroupings(const CWallet& wallet)
{
AssertLockHeld(wallet.cs_wallet);
std::set< std::set<CTxDestination> > groupings;
std::set<CTxDestination> grouping;
for (const auto& walletEntry : wallet.mapWallet)
{
const CWalletTx& wtx = walletEntry.second;
if (wtx.tx->vin.size() > 0)
{
bool any_mine = false;
// group all input addresses with each other
for (const CTxIn& txin : wtx.tx->vin)
{
CTxDestination address;
if(!InputIsMine(wallet, txin)) /* If this input isn't mine, ignore it */
continue;
if(!ExtractDestination(wallet.mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address))
continue;
grouping.insert(address);
any_mine = true;
}
// group change with input addresses
if (any_mine)
{
for (const CTxOut& txout : wtx.tx->vout)
if (OutputIsChange(wallet, txout))
{
CTxDestination txoutAddr;
if(!ExtractDestination(txout.scriptPubKey, txoutAddr))
continue;
grouping.insert(txoutAddr);
}
}
if (grouping.size() > 0)
{
groupings.insert(grouping);
grouping.clear();
}
}
// group lone addrs by themselves
for (const auto& txout : wtx.tx->vout)
if (wallet.IsMine(txout))
{
CTxDestination address;
if(!ExtractDestination(txout.scriptPubKey, address))
continue;
grouping.insert(address);
groupings.insert(grouping);
grouping.clear();
}
}
std::set< std::set<CTxDestination>* > uniqueGroupings; // a set of pointers to groups of addresses
std::map< CTxDestination, std::set<CTxDestination>* > setmap; // map addresses to the unique group containing it
for (const std::set<CTxDestination>& _grouping : groupings)
{
// make a set of all the groups hit by this new group
std::set< std::set<CTxDestination>* > hits;
std::map< CTxDestination, std::set<CTxDestination>* >::iterator it;
for (const CTxDestination& address : _grouping)
if ((it = setmap.find(address)) != setmap.end())
hits.insert((*it).second);
// merge all hit groups into a new single group and delete old groups
std::set<CTxDestination>* merged = new std::set<CTxDestination>(_grouping);
for (std::set<CTxDestination>* hit : hits)
{
merged->insert(hit->begin(), hit->end());
uniqueGroupings.erase(hit);
delete hit;
}
uniqueGroupings.insert(merged);
// update setmap
for (const CTxDestination& element : *merged)
setmap[element] = merged;
}
std::set< std::set<CTxDestination> > ret;
for (const std::set<CTxDestination>* uniqueGrouping : uniqueGroupings)
{
ret.insert(*uniqueGrouping);
delete uniqueGrouping;
}
return ret;
}
} // namespace wallet