aboutsummaryrefslogtreecommitdiff
path: root/src/wallet/receive.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet/receive.cpp')
-rw-r--r--src/wallet/receive.cpp475
1 files changed, 475 insertions, 0 deletions
diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp
new file mode 100644
index 0000000000..1a6f06213c
--- /dev/null
+++ b/src/wallet/receive.cpp
@@ -0,0 +1,475 @@
+// Copyright (c) 2021 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 <wallet/receive.h>
+#include <wallet/transaction.h>
+#include <wallet/wallet.h>
+
+namespace wallet {
+isminetype InputIsMine(const CWallet& wallet, const CTxIn &txin)
+{
+ AssertLockHeld(wallet.cs_wallet);
+ std::map<uint256, CWalletTx>::const_iterator mi = wallet.mapWallet.find(txin.prevout.hash);
+ if (mi != wallet.mapWallet.end())
+ {
+ const CWalletTx& prev = (*mi).second;
+ if (txin.prevout.n < prev.tx->vout.size())
+ return wallet.IsMine(prev.tx->vout[txin.prevout.n]);
+ }
+ return ISMINE_NO;
+}
+
+bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter)
+{
+ LOCK(wallet.cs_wallet);
+
+ for (const CTxIn& txin : tx.vin)
+ {
+ auto mi = wallet.mapWallet.find(txin.prevout.hash);
+ if (mi == wallet.mapWallet.end())
+ return false; // any unknown inputs can't be from us
+
+ const CWalletTx& prev = (*mi).second;
+
+ if (txin.prevout.n >= prev.tx->vout.size())
+ return false; // invalid input!
+
+ if (!(wallet.IsMine(prev.tx->vout[txin.prevout.n]) & filter))
+ return false;
+ }
+ return true;
+}
+
+CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter)
+{
+ if (!MoneyRange(txout.nValue))
+ throw std::runtime_error(std::string(__func__) + ": value out of range");
+ LOCK(wallet.cs_wallet);
+ return ((wallet.IsMine(txout) & filter) ? txout.nValue : 0);
+}
+
+CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter)
+{
+ CAmount nCredit = 0;
+ for (const CTxOut& txout : tx.vout)
+ {
+ nCredit += OutputGetCredit(wallet, txout, filter);
+ 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, const isminefilter& filter, bool recalculate = false)
+{
+ auto& amount = wtx.m_amounts[type];
+ if (recalculate || !amount.m_cached[filter]) {
+ amount.Set(filter, type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx, filter) : TxGetCredit(wallet, *wtx.tx, filter));
+ wtx.m_is_cache_empty = false;
+ }
+ return amount.m_value[filter];
+}
+
+CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
+{
+ // Must wait until coinbase is safely deep enough in the chain before valuing it
+ if (wallet.IsTxImmatureCoinBase(wtx))
+ return 0;
+
+ CAmount credit = 0;
+ if (filter & ISMINE_SPENDABLE) {
+ // GetBalance can assume transactions in mapWallet won't change
+ credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_SPENDABLE);
+ }
+ if (filter & ISMINE_WATCH_ONLY) {
+ credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_WATCH_ONLY);
+ }
+ return credit;
+}
+
+CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
+{
+ if (wtx.tx->vin.empty())
+ return 0;
+
+ CAmount debit = 0;
+ if (filter & ISMINE_SPENDABLE) {
+ debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_SPENDABLE);
+ }
+ if (filter & ISMINE_WATCH_ONLY) {
+ debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_WATCH_ONLY);
+ }
+ return debit;
+}
+
+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;
+}
+
+CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache)
+{
+ if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) {
+ return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
+ }
+
+ return 0;
+}
+
+CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache)
+{
+ if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) {
+ return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache);
+ }
+
+ return 0;
+}
+
+CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache, const isminefilter& filter)
+{
+ // Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future).
+ bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
+
+ // Must wait until coinbase is safely deep enough in the chain before valuing it
+ if (wallet.IsTxImmatureCoinBase(wtx))
+ return 0;
+
+ if (fUseCache && allow_cache && wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_cached[filter]) {
+ return wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_value[filter];
+ }
+
+ bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
+ CAmount nCredit = 0;
+ uint256 hashTx = wtx.GetHash();
+ for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
+ {
+ if (!wallet.IsSpent(hashTx, i) && (allow_used_addresses || !wallet.IsSpentKey(hashTx, i))) {
+ const CTxOut &txout = wtx.tx->vout[i];
+ nCredit += OutputGetCredit(wallet, txout, filter);
+ if (!MoneyRange(nCredit))
+ throw std::runtime_error(std::string(__func__) + " : value out of range");
+ }
+ }
+
+ if (allow_cache) {
+ wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].Set(filter, nCredit);
+ wtx.m_is_cache_empty = false;
+ }
+
+ return nCredit;
+}
+
+void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
+ std::list<COutputEntry>& listReceived,
+ std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter)
+{
+ nFee = 0;
+ listReceived.clear();
+ listSent.clear();
+
+ // Compute fee:
+ CAmount nDebit = CachedTxGetDebit(wallet, wtx, filter);
+ 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];
+ isminetype fIsMine = 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)
+ {
+ // Don't report 'change' txouts
+ if (OutputIsChange(wallet, txout))
+ continue;
+ }
+ else if (!(fIsMine & filter))
+ 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 (fIsMine & filter)
+ listReceived.push_back(output);
+ }
+
+}
+
+bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
+{
+ return (CachedTxGetDebit(wallet, wtx, filter) > 0);
+}
+
+bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents)
+{
+ AssertLockHeld(wallet.cs_wallet);
+ int nDepth = wallet.GetTxDepthInMainChain(wtx);
+ if (nDepth >= 1) return true;
+ if (nDepth < 0) return false;
+ // using wtx's cached debit
+ if (!wallet.m_spend_zero_conf_change || !CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)) 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) != ISMINE_SPENDABLE) return false;
+ // If we've already trusted this parent, continue
+ if (trusted_parents.count(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<uint256> 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;
+ isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED;
+ {
+ LOCK(wallet.cs_wallet);
+ std::set<uint256> trusted_parents;
+ for (const auto& entry : wallet.mapWallet)
+ {
+ const CWalletTx& wtx = entry.second;
+ const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)};
+ const int tx_depth{wallet.GetTxDepthInMainChain(wtx)};
+ const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
+ const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
+ if (is_trusted && tx_depth >= min_depth) {
+ ret.m_mine_trusted += tx_credit_mine;
+ ret.m_watchonly_trusted += tx_credit_watchonly;
+ }
+ if (!is_trusted && tx_depth == 0 && wtx.InMempool()) {
+ ret.m_mine_untrusted_pending += tx_credit_mine;
+ ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
+ }
+ ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx);
+ ret.m_watchonly_immature += CachedTxGetImmatureWatchOnlyCredit(wallet, wtx);
+ }
+ }
+ return ret;
+}
+
+std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet)
+{
+ std::map<CTxDestination, CAmount> balances;
+
+ {
+ LOCK(wallet.cs_wallet);
+ std::set<uint256> trusted_parents;
+ for (const auto& walletEntry : wallet.mapWallet)
+ {
+ const CWalletTx& wtx = walletEntry.second;
+
+ if (!CachedTxIsTrusted(wallet, wtx, trusted_parents))
+ continue;
+
+ if (wallet.IsTxImmatureCoinBase(wtx))
+ continue;
+
+ int nDepth = wallet.GetTxDepthInMainChain(wtx);
+ if (nDepth < (CachedTxIsFromMe(wallet, wtx, ISMINE_ALL) ? 0 : 1))
+ continue;
+
+ for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
+ {
+ CTxDestination addr;
+ if (!wallet.IsMine(wtx.tx->vout[i]))
+ continue;
+ if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr))
+ continue;
+
+ CAmount n = wallet.IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].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 (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