diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/interfaces.cpp | 16 | ||||
-rw-r--r-- | src/wallet/load.cpp | 2 | ||||
-rw-r--r-- | src/wallet/receive.cpp | 471 | ||||
-rw-r--r-- | src/wallet/receive.h | 20 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 1 | ||||
-rw-r--r-- | src/wallet/spend.cpp | 978 | ||||
-rw-r--r-- | src/wallet/spend.h | 64 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 8 | ||||
-rw-r--r-- | src/wallet/transaction.cpp | 25 | ||||
-rw-r--r-- | src/wallet/transaction.h | 358 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 1500 | ||||
-rw-r--r-- | src/wallet/wallet.h | 420 | ||||
-rw-r--r-- | src/wallet/walletdb.cpp | 8 |
13 files changed, 1975 insertions, 1896 deletions
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp index 05656aec93..ee92316b89 100644 --- a/src/wallet/interfaces.cpp +++ b/src/wallet/interfaces.cpp @@ -197,22 +197,14 @@ public: } return result; } - bool addDestData(const CTxDestination& dest, const std::string& key, const std::string& value) override - { + std::vector<std::string> getAddressReceiveRequests() override { LOCK(m_wallet->cs_wallet); - WalletBatch batch{m_wallet->GetDatabase()}; - return m_wallet->AddDestData(batch, dest, key, value); + return m_wallet->GetAddressReceiveRequests(); } - bool eraseDestData(const CTxDestination& dest, const std::string& key) override - { + bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) override { LOCK(m_wallet->cs_wallet); WalletBatch batch{m_wallet->GetDatabase()}; - return m_wallet->EraseDestData(batch, dest, key); - } - std::vector<std::string> getDestValues(const std::string& prefix) override - { - LOCK(m_wallet->cs_wallet); - return m_wallet->GetDestValues(prefix); + return m_wallet->SetAddressReceiveRequest(batch, dest, id, value); } bool displayAddress(const CTxDestination& dest) override { diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp index e0df96666f..dbf9fd46b6 100644 --- a/src/wallet/load.cpp +++ b/src/wallet/load.cpp @@ -105,7 +105,7 @@ bool LoadWallets(interfaces::Chain& chain) if (!database && status == DatabaseStatus::FAILED_NOT_FOUND) { continue; } - chain.initMessage(_("Loading wallet...").translated); + chain.initMessage(_("Loading wallet…").translated); std::shared_ptr<CWallet> pwallet = database ? CWallet::Create(&chain, name, std::move(database), options.create_flags, error, warnings) : nullptr; if (!warnings.empty()) chain.initWarning(Join(warnings, Untranslated("\n"))); if (!pwallet) { diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp new file mode 100644 index 0000000000..de81dbf324 --- /dev/null +++ b/src/wallet/receive.cpp @@ -0,0 +1,471 @@ +// 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/consensus.h> +#include <wallet/receive.h> +#include <wallet/transaction.h> +#include <wallet/wallet.h> + +isminetype CWallet::IsMine(const CTxIn &txin) const +{ + AssertLockHeld(cs_wallet); + std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash); + if (mi != mapWallet.end()) + { + const CWalletTx& prev = (*mi).second; + if (txin.prevout.n < prev.tx->vout.size()) + return IsMine(prev.tx->vout[txin.prevout.n]); + } + return ISMINE_NO; +} + +bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const +{ + LOCK(cs_wallet); + + for (const CTxIn& txin : tx.vin) + { + auto mi = mapWallet.find(txin.prevout.hash); + if (mi == 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 (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter)) + return false; + } + return true; +} + +CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const +{ + if (!MoneyRange(txout.nValue)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + LOCK(cs_wallet); + return ((IsMine(txout) & filter) ? txout.nValue : 0); +} + +CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const +{ + CAmount nCredit = 0; + for (const CTxOut& txout : tx.vout) + { + nCredit += GetCredit(txout, filter); + if (!MoneyRange(nCredit)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + } + return nCredit; +} + +bool CWallet::IsChange(const CScript& script) const +{ + // 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(cs_wallet); + if (IsMine(script)) + { + CTxDestination address; + if (!ExtractDestination(script, address)) + return true; + if (!FindAddressBookEntry(address)) { + return true; + } + } + return false; +} + +bool CWallet::IsChange(const CTxOut& txout) const +{ + return IsChange(txout.scriptPubKey); +} + +CAmount CWallet::GetChange(const CTxOut& txout) const +{ + AssertLockHeld(cs_wallet); + if (!MoneyRange(txout.nValue)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + return (IsChange(txout) ? txout.nValue : 0); +} + +CAmount CWallet::GetChange(const CTransaction& tx) const +{ + LOCK(cs_wallet); + CAmount nChange = 0; + for (const CTxOut& txout : tx.vout) + { + nChange += GetChange(txout); + if (!MoneyRange(nChange)) + throw std::runtime_error(std::string(__func__) + ": value out of range"); + } + return nChange; +} + +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(*tx, filter) : pwallet->GetCredit(*tx, filter)); + m_is_cache_empty = false; + } + return amount.m_value[filter]; +} + +CAmount CWalletTx::GetCredit(const isminefilter& filter) const +{ + // Must wait until coinbase is safely deep enough in the chain before valuing it + if (IsImmatureCoinBase()) + return 0; + + CAmount credit = 0; + if (filter & ISMINE_SPENDABLE) { + // GetBalance can assume transactions in mapWallet won't change + credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE); + } + if (filter & ISMINE_WATCH_ONLY) { + credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY); + } + return credit; +} + +CAmount CWalletTx::GetDebit(const isminefilter& filter) const +{ + if (tx->vin.empty()) + return 0; + + CAmount debit = 0; + if (filter & ISMINE_SPENDABLE) { + debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE); + } + if (filter & ISMINE_WATCH_ONLY) { + debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY); + } + return debit; +} + +CAmount CWalletTx::GetChange() const +{ + if (fChangeCached) + return nChangeCached; + nChangeCached = pwallet->GetChange(*tx); + fChangeCached = true; + return nChangeCached; +} + +CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const +{ + if (IsImmatureCoinBase() && IsInMainChain()) { + return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); + } + + return 0; +} + +CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const +{ + if (IsImmatureCoinBase() && IsInMainChain()) { + return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); + } + + return 0; +} + +CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const +{ + if (pwallet == nullptr) + return 0; + + // 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 (IsImmatureCoinBase()) + return 0; + + if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { + return m_amounts[AVAILABLE_CREDIT].m_value[filter]; + } + + bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); + CAmount nCredit = 0; + uint256 hashTx = GetHash(); + for (unsigned int i = 0; i < tx->vout.size(); i++) + { + if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) { + const CTxOut &txout = tx->vout[i]; + nCredit += pwallet->GetCredit(txout, filter); + if (!MoneyRange(nCredit)) + throw std::runtime_error(std::string(__func__) + " : value out of range"); + } + } + + if (allow_cache) { + m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit); + m_is_cache_empty = false; + } + + return nCredit; +} + +void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, + std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const +{ + nFee = 0; + listReceived.clear(); + listSent.clear(); + + // Compute fee: + CAmount nDebit = GetDebit(filter); + if (nDebit > 0) // debit>0 means we signed/sent this transaction + { + CAmount nValueOut = tx->GetValueOut(); + nFee = nDebit - nValueOut; + } + + LOCK(pwallet->cs_wallet); + // Sent/received. + for (unsigned int i = 0; i < tx->vout.size(); ++i) + { + const CTxOut& txout = tx->vout[i]; + isminetype fIsMine = pwallet->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 (pwallet->IsChange(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()) + { + pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", + this->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 CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const +{ + AssertLockHeld(cs_wallet); + // Quick answer in most cases + if (!chain().checkFinalTx(*wtx.tx)) return false; + int nDepth = wtx.GetDepthInMainChain(); + if (nDepth >= 1) return true; + if (nDepth < 0) return false; + // using wtx's cached debit + if (!m_spend_zero_conf_change || !wtx.IsFromMe(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 = 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 (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 (!IsTrusted(*parent, trusted_parents)) return false; + trusted_parents.insert(parent->GetHash()); + } + return true; +} + +bool CWalletTx::IsTrusted() const +{ + std::set<uint256> trusted_parents; + LOCK(pwallet->cs_wallet); + return pwallet->IsTrusted(*this, trusted_parents); +} + +CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const +{ + Balance ret; + isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; + { + LOCK(cs_wallet); + std::set<uint256> trusted_parents; + for (const auto& entry : mapWallet) + { + const CWalletTx& wtx = entry.second; + const bool is_trusted{IsTrusted(wtx, trusted_parents)}; + const int tx_depth{wtx.GetDepthInMainChain()}; + const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; + const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* 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 += wtx.GetImmatureCredit(); + ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(); + } + } + return ret; +} + +std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const +{ + std::map<CTxDestination, CAmount> balances; + + { + LOCK(cs_wallet); + std::set<uint256> trusted_parents; + for (const auto& walletEntry : mapWallet) + { + const CWalletTx& wtx = walletEntry.second; + + if (!IsTrusted(wtx, trusted_parents)) + continue; + + if (wtx.IsImmatureCoinBase()) + continue; + + int nDepth = wtx.GetDepthInMainChain(); + if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) + continue; + + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) + { + CTxDestination addr; + if (!IsMine(wtx.tx->vout[i])) + continue; + if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) + continue; + + CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; + balances[addr] += n; + } + } + } + + return balances; +} + +std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const +{ + AssertLockHeld(cs_wallet); + std::set< std::set<CTxDestination> > groupings; + std::set<CTxDestination> grouping; + + for (const auto& walletEntry : 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(!IsMine(txin)) /* If this input isn't mine, ignore it */ + continue; + if(!ExtractDestination(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 (IsChange(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 (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; +} diff --git a/src/wallet/receive.h b/src/wallet/receive.h new file mode 100644 index 0000000000..8eead32413 --- /dev/null +++ b/src/wallet/receive.h @@ -0,0 +1,20 @@ +// 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. + +#ifndef BITCOIN_WALLET_RECEIVE_H +#define BITCOIN_WALLET_RECEIVE_H + +#include <amount.h> +#include <wallet/ismine.h> +#include <wallet/transaction.h> +#include <wallet/wallet.h> + +struct COutputEntry +{ + CTxDestination destination; + CAmount amount; + int vout; +}; + +#endif // BITCOIN_WALLET_RECEIVE_H diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index f270e1ad05..534c974178 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -3735,6 +3735,7 @@ public: return obj; } + UniValue operator()(const WitnessV1Taproot& id) const { return UniValue(UniValue::VOBJ); } UniValue operator()(const WitnessUnknown& id) const { return UniValue(UniValue::VOBJ); } }; diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp new file mode 100644 index 0000000000..97fc7acca5 --- /dev/null +++ b/src/wallet/spend.cpp @@ -0,0 +1,978 @@ +// 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/validation.h> +#include <interfaces/chain.h> +#include <policy/policy.h> +#include <util/check.h> +#include <util/fees.h> +#include <util/moneystr.h> +#include <util/rbf.h> +#include <util/translation.h> +#include <wallet/coincontrol.h> +#include <wallet/fees.h> +#include <wallet/receive.h> +#include <wallet/spend.h> +#include <wallet/transaction.h> +#include <wallet/wallet.h> + +using interfaces::FoundBlock; + +static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100}; + +std::string COutput::ToString() const +{ + return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); +} + +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) +{ + CMutableTransaction txn; + txn.vin.push_back(CTxIn(COutPoint())); + if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) { + return -1; + } + return GetVirtualTransactionInputSize(txn.vin[0]); +} + +// txouts needs to be in the order of tx.vin +TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig) +{ + CMutableTransaction txNew(tx); + if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { + return TxSize{-1, -1}; + } + CTransaction ctx(txNew); + int64_t vsize = GetVirtualTransactionSize(ctx); + int64_t weight = GetTransactionWeight(ctx); + return TxSize{vsize, weight}; +} + +TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) +{ + std::vector<CTxOut> txouts; + for (const CTxIn& input : tx.vin) { + const auto mi = wallet->mapWallet.find(input.prevout.hash); + // Can not estimate size without knowing the input details + if (mi == wallet->mapWallet.end()) { + return TxSize{-1, -1}; + } + assert(input.prevout.n < mi->second.tx->vout.size()); + txouts.emplace_back(mi->second.tx->vout[input.prevout.n]); + } + return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig); +} + +void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) const +{ + AssertLockHeld(cs_wallet); + + vCoins.clear(); + CAmount nTotal = 0; + // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where + // a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses + bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); + const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH}; + const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH}; + const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true}; + + std::set<uint256> trusted_parents; + for (const auto& entry : mapWallet) + { + const uint256& wtxid = entry.first; + const CWalletTx& wtx = entry.second; + + if (!chain().checkFinalTx(*wtx.tx)) { + continue; + } + + if (wtx.IsImmatureCoinBase()) + continue; + + int nDepth = wtx.GetDepthInMainChain(); + if (nDepth < 0) + continue; + + // We should not consider coins which aren't at least in our mempool + // It's possible for these to be conflicted via ancestors which we may never be able to detect + if (nDepth == 0 && !wtx.InMempool()) + continue; + + bool safeTx = IsTrusted(wtx, trusted_parents); + + // We should not consider coins from transactions that are replacing + // other transactions. + // + // Example: There is a transaction A which is replaced by bumpfee + // transaction B. In this case, we want to prevent creation of + // a transaction B' which spends an output of B. + // + // Reason: If transaction A were initially confirmed, transactions B + // and B' would no longer be valid, so the user would have to create + // a new transaction C to replace B'. However, in the case of a + // one-block reorg, transactions B' and C might BOTH be accepted, + // when the user only wanted one of them. Specifically, there could + // be a 1-block reorg away from the chain where transactions A and C + // were accepted to another chain where B, B', and C were all + // accepted. + if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) { + safeTx = false; + } + + // Similarly, we should not consider coins from transactions that + // have been replaced. In the example above, we would want to prevent + // creation of a transaction A' spending an output of A, because if + // transaction B were initially confirmed, conflicting with A and + // A', we wouldn't want to the user to create a transaction D + // intending to replace A', but potentially resulting in a scenario + // where A, A', and D could all be accepted (instead of just B and + // D, or just A and A' like the user would want). + if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) { + safeTx = false; + } + + if (only_safe && !safeTx) { + continue; + } + + if (nDepth < min_depth || nDepth > max_depth) { + continue; + } + + for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { + // Only consider selected coins if add_inputs is false + if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) { + continue; + } + + if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) + continue; + + if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) + continue; + + if (IsLockedCoin(entry.first, i)) + continue; + + if (IsSpent(wtxid, i)) + continue; + + isminetype mine = IsMine(wtx.tx->vout[i]); + + if (mine == ISMINE_NO) { + continue; + } + + if (!allow_used_addresses && IsSpentKey(wtxid, i)) { + continue; + } + + std::unique_ptr<SigningProvider> provider = GetSolvingProvider(wtx.tx->vout[i].scriptPubKey); + + bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; + bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); + + vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); + + // Checks the sum amount of all UTXO's. + if (nMinimumSumAmount != MAX_MONEY) { + nTotal += wtx.tx->vout[i].nValue; + + if (nTotal >= nMinimumSumAmount) { + return; + } + } + + // Checks the maximum number of UTXO's. + if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { + return; + } + } + } +} + +CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const +{ + LOCK(cs_wallet); + + CAmount balance = 0; + std::vector<COutput> vCoins; + AvailableCoins(vCoins, coinControl); + for (const COutput& out : vCoins) { + if (out.fSpendable) { + balance += out.tx->tx->vout[out.i].nValue; + } + } + return balance; +} + +const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const +{ + AssertLockHeld(cs_wallet); + const CTransaction* ptx = &tx; + int n = output; + while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) { + const COutPoint& prevout = ptx->vin[0].prevout; + auto it = mapWallet.find(prevout.hash); + if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n || + !IsMine(it->second.tx->vout[prevout.n])) { + break; + } + ptx = it->second.tx.get(); + n = prevout.n; + } + return ptx->vout[n]; +} + +std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const +{ + AssertLockHeld(cs_wallet); + + std::map<CTxDestination, std::vector<COutput>> result; + std::vector<COutput> availableCoins; + + AvailableCoins(availableCoins); + + for (const COutput& coin : availableCoins) { + CTxDestination address; + if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && + ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { + result[address].emplace_back(std::move(coin)); + } + } + + std::vector<COutPoint> lockedCoins; + ListLockedCoins(lockedCoins); + // Include watch-only for LegacyScriptPubKeyMan wallets without private keys + const bool include_watch_only = GetLegacyScriptPubKeyMan() && IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); + const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; + for (const COutPoint& output : lockedCoins) { + auto it = mapWallet.find(output.hash); + if (it != mapWallet.end()) { + int depth = it->second.GetDepthInMainChain(); + if (depth >= 0 && output.n < it->second.tx->vout.size() && + IsMine(it->second.tx->vout[output.n]) == is_mine_filter + ) { + CTxDestination address; + if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { + result[address].emplace_back( + &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); + } + } + } + } + + return result; +} + +std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only) const +{ + std::vector<OutputGroup> groups_out; + + if (!coin_sel_params.m_avoid_partial_spends) { + // Allowing partial spends means no grouping. Each COutput gets its own OutputGroup. + for (const COutput& output : outputs) { + // Skip outputs we cannot spend + if (!output.fSpendable) continue; + + size_t ancestors, descendants; + chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); + CInputCoin input_coin = output.GetInputCoin(); + + // Make an OutputGroup containing just this output + OutputGroup group{coin_sel_params}; + group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); + + // Check the OutputGroup's eligibility. Only add the eligible ones. + if (positive_only && group.GetSelectionAmount() <= 0) continue; + if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group); + } + return groups_out; + } + + // We want to combine COutputs that have the same scriptPubKey into single OutputGroups + // except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup. + // To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups. + // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added + // to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has + // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector. + std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map; + for (const auto& output : outputs) { + // Skip outputs we cannot spend + if (!output.fSpendable) continue; + + size_t ancestors, descendants; + chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); + CInputCoin input_coin = output.GetInputCoin(); + CScript spk = input_coin.txout.scriptPubKey; + + std::vector<OutputGroup>& groups = spk_to_groups_map[spk]; + + if (groups.size() == 0) { + // No OutputGroups for this scriptPubKey yet, add one + groups.emplace_back(coin_sel_params); + } + + // Get the last OutputGroup in the vector so that we can add the CInputCoin to it + // A pointer is used here so that group can be reassigned later if it is full. + OutputGroup* group = &groups.back(); + + // Check if this OutputGroup is full. We limit to OUTPUT_GROUP_MAX_ENTRIES when using -avoidpartialspends + // to avoid surprising users with very high fees. + if (group->m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) { + // The last output group is full, add a new group to the vector and use that group for the insertion + groups.emplace_back(coin_sel_params); + group = &groups.back(); + } + + // Add the input_coin to group + group->Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); + } + + // Now we go through the entire map and pull out the OutputGroups + for (const auto& spk_and_groups_pair: spk_to_groups_map) { + const std::vector<OutputGroup>& groups_per_spk= spk_and_groups_pair.second; + + // Go through the vector backwards. This allows for the first item we deal with being the partial group. + for (auto group_it = groups_per_spk.rbegin(); group_it != groups_per_spk.rend(); group_it++) { + const OutputGroup& group = *group_it; + + // Don't include partial groups if there are full groups too and we don't want partial groups + if (group_it == groups_per_spk.rbegin() && groups_per_spk.size() > 1 && !filter.m_include_partial_groups) { + continue; + } + + // Check the OutputGroup's eligibility. Only add the eligible ones. + if (positive_only && group.GetSelectionAmount() <= 0) continue; + if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group); + } + } + + return groups_out; +} + +bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, + std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const +{ + setCoinsRet.clear(); + nValueRet = 0; + + // Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output. + std::vector<OutputGroup> positive_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, true /* positive_only */); + if (SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change, setCoinsRet, nValueRet)) { + return true; + } + // The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here. + std::vector<OutputGroup> all_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, false /* positive_only */); + // While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output. + // So we need to include that for KnapsackSolver as well, as we are expecting to create a change output. + return KnapsackSolver(nTargetValue + coin_selection_params.m_change_fee, all_groups, setCoinsRet, nValueRet); +} + +bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) const +{ + std::vector<COutput> vCoins(vAvailableCoins); + CAmount value_to_select = nTargetValue; + + // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) + if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) + { + for (const COutput& out : vCoins) + { + if (!out.fSpendable) + continue; + nValueRet += out.tx->tx->vout[out.i].nValue; + setCoinsRet.insert(out.GetInputCoin()); + } + return (nValueRet >= nTargetValue); + } + + // calculate value from preset inputs and store them + std::set<CInputCoin> setPresetCoins; + CAmount nValueFromPresetInputs = 0; + + std::vector<COutPoint> vPresetInputs; + coin_control.ListSelected(vPresetInputs); + for (const COutPoint& outpoint : vPresetInputs) + { + std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); + if (it != mapWallet.end()) + { + const CWalletTx& wtx = it->second; + // Clearly invalid input, fail + if (wtx.tx->vout.size() <= outpoint.n) { + return false; + } + // Just to calculate the marginal byte size + CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false)); + nValueFromPresetInputs += coin.txout.nValue; + if (coin.m_input_bytes <= 0) { + return false; // Not solvable, can't estimate size for fee + } + coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes); + if (coin_selection_params.m_subtract_fee_outputs) { + value_to_select -= coin.txout.nValue; + } else { + value_to_select -= coin.effective_value; + } + setPresetCoins.insert(coin); + } else { + return false; // TODO: Allow non-wallet inputs + } + } + + // remove preset inputs from vCoins so that Coin Selection doesn't pick them. + for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) + { + if (setPresetCoins.count(it->GetInputCoin())) + it = vCoins.erase(it); + else + ++it; + } + + unsigned int limit_ancestor_count = 0; + unsigned int limit_descendant_count = 0; + chain().getPackageLimits(limit_ancestor_count, limit_descendant_count); + const size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count); + const size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count); + const bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); + + // form groups from remaining coins; note that preset coins will not + // automatically have their associated (same address) coins included + if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) { + // Cases where we have 101+ outputs all pointing to the same destination may result in + // privacy leaks as they will potentially be deterministically sorted. We solve that by + // explicitly shuffling the outputs before processing + Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext()); + } + + // Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the + // transaction at a target feerate. If an attempt fails, more attempts may be made using a more + // permissive CoinEligibilityFilter. + const bool res = [&] { + // Pre-selected inputs already cover the target amount. + if (value_to_select <= 0) return true; + + // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six + // confirmations on outputs received from other wallets and only spend confirmed change. + if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; + if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; + + // Fall back to using zero confirmation change (but with as few ancestors in the mempool as + // possible) if we cannot fund the transaction otherwise. + if (m_spend_zero_conf_change) { + if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; + if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), + vCoins, setCoinsRet, nValueRet, coin_selection_params)) { + return true; + } + if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), + vCoins, setCoinsRet, nValueRet, coin_selection_params)) { + return true; + } + // If partial groups are allowed, relax the requirement of spending OutputGroups (groups + // of UTXOs sent to the same address, which are obviously controlled by a single wallet) + // in their entirety. + if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), + vCoins, setCoinsRet, nValueRet, coin_selection_params)) { + return true; + } + // Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs + // received from other wallets. + if (coin_control.m_include_unsafe_inputs + && SelectCoinsMinConf(value_to_select, + CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), + vCoins, setCoinsRet, nValueRet, coin_selection_params)) { + return true; + } + // Try with unlimited ancestors/descendants. The transaction will still need to meet + // mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but + // OutputGroups use heuristics that may overestimate ancestor/descendant counts. + if (!fRejectLongChains && SelectCoinsMinConf(value_to_select, + CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */), + vCoins, setCoinsRet, nValueRet, coin_selection_params)) { + return true; + } + } + // Coin Selection failed. + return false; + }(); + + // SelectCoinsMinConf clears setCoinsRet, so add the preset inputs from coin_control to the coinset + util::insert(setCoinsRet, setPresetCoins); + + // add preset inputs to the total value selected + nValueRet += nValueFromPresetInputs; + + return res; +} + +static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& block_hash) +{ + if (chain.isInitialBlockDownload()) { + return false; + } + constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; // in seconds + int64_t block_time; + CHECK_NONFATAL(chain.findBlock(block_hash, FoundBlock().time(block_time))); + if (block_time < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) { + return false; + } + return true; +} + +/** + * Return a height-based locktime for new transactions (uses the height of the + * current chain tip unless we are not synced with the current chain + */ +static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uint256& block_hash, int block_height) +{ + uint32_t locktime; + // Discourage fee sniping. + // + // For a large miner the value of the transactions in the best block and + // the mempool can exceed the cost of deliberately attempting to mine two + // blocks to orphan the current best block. By setting nLockTime such that + // only the next block can include the transaction, we discourage this + // practice as the height restricted and limited blocksize gives miners + // considering fee sniping fewer options for pulling off this attack. + // + // A simple way to think about this is from the wallet's point of view we + // always want the blockchain to move forward. By setting nLockTime this + // way we're basically making the statement that we only want this + // transaction to appear in the next block; we don't want to potentially + // encourage reorgs by allowing transactions to appear at lower heights + // than the next block in forks of the best chain. + // + // Of course, the subsidy is high enough, and transaction volume low + // enough, that fee sniping isn't a problem yet, but by implementing a fix + // now we ensure code won't be written that makes assumptions about + // nLockTime that preclude a fix later. + if (IsCurrentForAntiFeeSniping(chain, block_hash)) { + locktime = block_height; + + // Secondly occasionally randomly pick a nLockTime even further back, so + // that transactions that are delayed after signing for whatever reason, + // e.g. high-latency mix networks and some CoinJoin implementations, have + // better privacy. + if (GetRandInt(10) == 0) + locktime = std::max(0, (int)locktime - GetRandInt(100)); + } else { + // If our chain is lagging behind, we can't discourage fee sniping nor help + // the privacy of high-latency transactions. To avoid leaking a potentially + // unique "nLockTime fingerprint", set nLockTime to a constant. + locktime = 0; + } + assert(locktime < LOCKTIME_THRESHOLD); + return locktime; +} + +bool CWallet::CreateTransactionInternal( + const std::vector<CRecipient>& vecSend, + CTransactionRef& tx, + CAmount& nFeeRet, + int& nChangePosInOut, + bilingual_str& error, + const CCoinControl& coin_control, + FeeCalculation& fee_calc_out, + bool sign) +{ + CAmount nValue = 0; + const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); + ReserveDestination reservedest(this, change_type); + unsigned int nSubtractFeeFromAmount = 0; + for (const auto& recipient : vecSend) + { + if (nValue < 0 || recipient.nAmount < 0) + { + error = _("Transaction amounts must not be negative"); + return false; + } + nValue += recipient.nAmount; + + if (recipient.fSubtractFeeFromAmount) + nSubtractFeeFromAmount++; + } + if (vecSend.empty()) + { + error = _("Transaction must have at least one recipient"); + return false; + } + + CMutableTransaction txNew; + FeeCalculation feeCalc; + TxSize tx_sizes; + int nBytes; + { + std::set<CInputCoin> setCoins; + LOCK(cs_wallet); + txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight()); + { + std::vector<COutput> vAvailableCoins; + AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); + CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy + coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends; + + // Create change script that will be used if we need change + // TODO: pass in scriptChange instead of reservedest so + // change transaction isn't always pay-to-bitcoin-address + CScript scriptChange; + + // coin control: send change to custom address + if (!std::get_if<CNoDestination>(&coin_control.destChange)) { + scriptChange = GetScriptForDestination(coin_control.destChange); + } else { // no coin control: send change to newly generated address + // Note: We use a new key here to keep it from being obvious which side is the change. + // The drawback is that by not reusing a previous key, the change may be lost if a + // backup is restored, if the backup doesn't have the new private key for the change. + // If we reused the old key, it would be possible to add code to look for and + // rediscover unknown transactions that were written with keys of ours to recover + // post-backup change. + + // Reserve a new key pair from key pool. If it fails, provide a dummy + // destination in case we don't need change. + CTxDestination dest; + if (!reservedest.GetReservedDestination(dest, true)) { + error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first."); + } + scriptChange = GetScriptForDestination(dest); + // A valid destination implies a change script (and + // vice-versa). An empty change script will abort later, if the + // change keypool ran out, but change is required. + CHECK_NONFATAL(IsValidDestination(dest) != scriptChange.empty()); + } + CTxOut change_prototype_txout(0, scriptChange); + coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); + + // Get size of spending the change output + int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this); + // If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh + // as lower-bound to allow BnB to do it's thing + if (change_spend_size == -1) { + coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE; + } else { + coin_selection_params.change_spend_size = (size_t)change_spend_size; + } + + // Set discard feerate + coin_selection_params.m_discard_feerate = GetDiscardRate(*this); + + // Get the fee rate to use effective values in coin selection + coin_selection_params.m_effective_feerate = GetMinimumFeeRate(*this, coin_control, &feeCalc); + // Do not, ever, assume that it's fine to change the fee rate if the user has explicitly + // provided one + if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) { + error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB)); + return false; + } + if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) { + // eventually allow a fallback fee + error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); + return false; + } + + // Get long term estimate + CCoinControl cc_temp; + cc_temp.m_confirm_target = chain().estimateMaxBlocks(); + coin_selection_params.m_long_term_feerate = GetMinimumFeeRate(*this, cc_temp, nullptr); + + // Calculate the cost of change + // Cost of change is the cost of creating the change output + cost of spending the change output in the future. + // For creating the change output now, we use the effective feerate. + // For spending the change output in the future, we use the discard feerate for now. + // So cost of change = (change output size * effective feerate) + (size of spending change output * discard feerate) + coin_selection_params.m_change_fee = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size); + coin_selection_params.m_cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_change_fee; + + coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values + + // vouts to the payees + if (!coin_selection_params.m_subtract_fee_outputs) { + coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size) + } + for (const auto& recipient : vecSend) + { + CTxOut txout(recipient.nAmount, recipient.scriptPubKey); + + // Include the fee cost for outputs. + if (!coin_selection_params.m_subtract_fee_outputs) { + coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); + } + + if (IsDust(txout, chain().relayDustFee())) + { + error = _("Transaction amount too small"); + return false; + } + txNew.vout.push_back(txout); + } + + // Include the fees for things that aren't inputs, excluding the change output + const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size); + CAmount nValueToSelect = nValue + not_input_fees; + + // Choose coins to use + CAmount inputs_sum = 0; + setCoins.clear(); + if (!SelectCoins(vAvailableCoins, /* nTargetValue */ nValueToSelect, setCoins, inputs_sum, coin_control, coin_selection_params)) + { + error = _("Insufficient funds"); + return false; + } + + // Always make a change output + // We will reduce the fee from this change output later, and remove the output if it is too small. + const CAmount change_and_fee = inputs_sum - nValue; + assert(change_and_fee >= 0); + CTxOut newTxOut(change_and_fee, scriptChange); + + if (nChangePosInOut == -1) + { + // Insert change txn at random position: + nChangePosInOut = GetRandInt(txNew.vout.size()+1); + } + else if ((unsigned int)nChangePosInOut > txNew.vout.size()) + { + error = _("Change index out of range"); + return false; + } + + assert(nChangePosInOut != -1); + auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut); + + // Dummy fill vin for maximum size estimation + // + for (const auto& coin : setCoins) { + txNew.vin.push_back(CTxIn(coin.outpoint,CScript())); + } + + // Calculate the transaction fee + tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); + nBytes = tx_sizes.vsize; + if (nBytes < 0) { + error = _("Signing transaction failed"); + return false; + } + nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes); + + // Subtract fee from the change output if not subtrating it from recipient outputs + CAmount fee_needed = nFeeRet; + if (nSubtractFeeFromAmount == 0) { + change_position->nValue -= fee_needed; + } + + // We want to drop the change to fees if: + // 1. The change output would be dust + // 2. The change is within the (almost) exact match window, i.e. it is less than or equal to the cost of the change output (cost_of_change) + CAmount change_amount = change_position->nValue; + if (IsDust(*change_position, coin_selection_params.m_discard_feerate) || change_amount <= coin_selection_params.m_cost_of_change) + { + nChangePosInOut = -1; + change_amount = 0; + txNew.vout.erase(change_position); + + // Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those + tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); + nBytes = tx_sizes.vsize; + fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes); + } + + // Update nFeeRet in case fee_needed changed due to dropping the change output + if (fee_needed <= change_and_fee - change_amount) { + nFeeRet = change_and_fee - change_amount; + } + + // Reduce output values for subtractFeeFromAmount + if (nSubtractFeeFromAmount != 0) { + CAmount to_reduce = fee_needed + change_amount - change_and_fee; + int i = 0; + bool fFirst = true; + for (const auto& recipient : vecSend) + { + if (i == nChangePosInOut) { + ++i; + } + CTxOut& txout = txNew.vout[i]; + + if (recipient.fSubtractFeeFromAmount) + { + txout.nValue -= to_reduce / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient + + if (fFirst) // first receiver pays the remainder not divisible by output count + { + fFirst = false; + txout.nValue -= to_reduce % nSubtractFeeFromAmount; + } + + // Error if this output is reduced to be below dust + if (IsDust(txout, chain().relayDustFee())) { + if (txout.nValue < 0) { + error = _("The transaction amount is too small to pay the fee"); + } else { + error = _("The transaction amount is too small to send after the fee has been deducted"); + } + return false; + } + } + ++i; + } + nFeeRet = fee_needed; + } + + // Give up if change keypool ran out and change is required + if (scriptChange.empty() && nChangePosInOut != -1) { + return false; + } + } + + // Shuffle selected coins and fill in final vin + txNew.vin.clear(); + std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end()); + Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext()); + + // Note how the sequence number is set to non-maxint so that + // the nLockTime set above actually works. + // + // BIP125 defines opt-in RBF as any nSequence < maxint-1, so + // we use the highest possible value in that range (maxint-2) + // to avoid conflicting with other possible uses of nSequence, + // and in the spirit of "smallest possible change from prior + // behavior." + const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1); + for (const auto& coin : selected_coins) { + txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence)); + } + + if (sign && !SignTransaction(txNew)) { + error = _("Signing transaction failed"); + return false; + } + + // Return the constructed transaction data. + tx = MakeTransactionRef(std::move(txNew)); + + // Limit size + if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) || + (!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT)) + { + error = _("Transaction too large"); + return false; + } + } + + if (nFeeRet > m_default_max_tx_fee) { + error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); + return false; + } + + if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { + // Lastly, ensure this tx will pass the mempool's chain limits + if (!chain().checkChainLimits(tx)) { + error = _("Transaction has too long of a mempool chain"); + return false; + } + } + + // Before we return success, we assume any change key will be used to prevent + // accidental re-use. + reservedest.KeepDestination(); + fee_calc_out = feeCalc; + + WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", + nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay, + feeCalc.est.pass.start, feeCalc.est.pass.end, + (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) > 0.0 ? 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) : 0.0, + feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool, + feeCalc.est.fail.start, feeCalc.est.fail.end, + (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0, + feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool); + return true; +} + +bool CWallet::CreateTransaction( + const std::vector<CRecipient>& vecSend, + CTransactionRef& tx, + CAmount& nFeeRet, + int& nChangePosInOut, + bilingual_str& error, + const CCoinControl& coin_control, + FeeCalculation& fee_calc_out, + bool sign) +{ + int nChangePosIn = nChangePosInOut; + Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr) + bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign); + // try with avoidpartialspends unless it's enabled already + if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { + CCoinControl tmp_cc = coin_control; + tmp_cc.m_avoid_partial_spends = true; + CAmount nFeeRet2; + CTransactionRef tx2; + int nChangePosInOut2 = nChangePosIn; + bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results + if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) { + // if fee of this alternative one is within the range of the max fee, we use this one + const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee; + WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); + if (use_aps) { + tx = tx2; + nFeeRet = nFeeRet2; + nChangePosInOut = nChangePosInOut2; + } + } + } + return res; +} + +bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) +{ + std::vector<CRecipient> vecSend; + + // Turn the txout set into a CRecipient vector. + for (size_t idx = 0; idx < tx.vout.size(); idx++) { + const CTxOut& txOut = tx.vout[idx]; + CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1}; + vecSend.push_back(recipient); + } + + coinControl.fAllowOtherInputs = true; + + for (const CTxIn& txin : tx.vin) { + coinControl.Select(txin.prevout); + } + + // Acquire the locks to prevent races to the new locked unspents between the + // CreateTransaction call and LockCoin calls (when lockUnspents is true). + LOCK(cs_wallet); + + CTransactionRef tx_new; + FeeCalculation fee_calc_out; + if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) { + return false; + } + + if (nChangePosInOut != -1) { + tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); + } + + // Copy output sizes from new transaction; they may have had the fee + // subtracted from them. + for (unsigned int idx = 0; idx < tx.vout.size(); idx++) { + tx.vout[idx].nValue = tx_new->vout[idx].nValue; + } + + // Add new txins while keeping original txin scriptSig/order. + for (const CTxIn& txin : tx_new->vin) { + if (!coinControl.IsSelected(txin.prevout)) { + tx.vin.push_back(txin); + + } + if (lockUnspents) { + LockCoin(txin.prevout); + } + + } + + return true; +} diff --git a/src/wallet/spend.h b/src/wallet/spend.h new file mode 100644 index 0000000000..03f9a7c2b5 --- /dev/null +++ b/src/wallet/spend.h @@ -0,0 +1,64 @@ +// 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. + +#ifndef BITCOIN_WALLET_SPEND_H +#define BITCOIN_WALLET_SPEND_H + +#include <wallet/coinselection.h> +#include <wallet/transaction.h> +#include <wallet/wallet.h> + +class COutput +{ +public: + const CWalletTx *tx; + + /** Index in tx->vout. */ + int i; + + /** + * Depth in block chain. + * If > 0: the tx is on chain and has this many confirmations. + * If = 0: the tx is waiting confirmation. + * If < 0: a conflicting tx is on chain and has this many confirmations. */ + int nDepth; + + /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */ + int nInputBytes; + + /** Whether we have the private keys to spend this output */ + bool fSpendable; + + /** Whether we know how to spend this output, ignoring the lack of keys */ + bool fSolvable; + + /** Whether to use the maximum sized, 72 byte signature when calculating the size of the input spend. This should only be set when watch-only outputs are allowed */ + bool use_max_sig; + + /** + * Whether this output is considered safe to spend. Unconfirmed transactions + * from outside keys and unconfirmed replacement transactions are considered + * unsafe and will not be used to fund new spending transactions. + */ + bool fSafe; + + COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) + { + tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in; + // If known and signable by the given wallet, compute nInputBytes + // Failure will keep this value -1 + if (fSpendable && tx) { + nInputBytes = tx->GetSpendSize(i, use_max_sig); + } + } + + std::string ToString() const; + + inline CInputCoin GetInputCoin() const + { + return CInputCoin(tx->tx, i, nInputBytes); + } +}; + +#endif // BITCOIN_WALLET_SPEND_H diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 34bb29f79f..6a791748b4 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -385,11 +385,11 @@ BOOST_AUTO_TEST_CASE(LoadReceiveRequests) CTxDestination dest = PKHash(); LOCK(m_wallet.cs_wallet); WalletBatch batch{m_wallet.GetDatabase()}; - m_wallet.AddDestData(batch, dest, "misc", "val_misc"); - m_wallet.AddDestData(batch, dest, "rr0", "val_rr0"); - m_wallet.AddDestData(batch, dest, "rr1", "val_rr1"); + m_wallet.SetAddressUsed(batch, dest, true); + m_wallet.SetAddressReceiveRequest(batch, dest, "0", "val_rr0"); + m_wallet.SetAddressReceiveRequest(batch, dest, "1", "val_rr1"); - auto values = m_wallet.GetDestValues("rr"); + auto values = m_wallet.GetAddressReceiveRequests(); BOOST_CHECK_EQUAL(values.size(), 2U); BOOST_CHECK_EQUAL(values[0], "val_rr0"); BOOST_CHECK_EQUAL(values[1], "val_rr1"); diff --git a/src/wallet/transaction.cpp b/src/wallet/transaction.cpp new file mode 100644 index 0000000000..cf98b516f1 --- /dev/null +++ b/src/wallet/transaction.cpp @@ -0,0 +1,25 @@ +// 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 <wallet/transaction.h> + +bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const +{ + CMutableTransaction tx1 {*this->tx}; + CMutableTransaction tx2 {*_tx.tx}; + for (auto& txin : tx1.vin) txin.scriptSig = CScript(); + for (auto& txin : tx2.vin) txin.scriptSig = CScript(); + return CTransaction(tx1) == CTransaction(tx2); +} + +bool CWalletTx::InMempool() const +{ + return fInMempool; +} + +int64_t CWalletTx::GetTxTime() const +{ + int64_t n = nTimeSmart; + return n ? n : nTimeReceived; +} diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h new file mode 100644 index 0000000000..131faefe0b --- /dev/null +++ b/src/wallet/transaction.h @@ -0,0 +1,358 @@ +// 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. + +#ifndef BITCOIN_WALLET_TRANSACTION_H +#define BITCOIN_WALLET_TRANSACTION_H + +#include <amount.h> +#include <primitives/transaction.h> +#include <serialize.h> +#include <wallet/ismine.h> +#include <threadsafety.h> +#include <tinyformat.h> +#include <util/strencodings.h> +#include <util/string.h> + +#include <list> +#include <vector> + +struct COutputEntry; + +typedef std::map<std::string, std::string> mapValue_t; + +//Get the marginal bytes of spending the specified output +int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false); + +static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue) +{ + if (!mapValue.count("n")) + { + nOrderPos = -1; // TODO: calculate elsewhere + return; + } + nOrderPos = atoi64(mapValue["n"]); +} + +static inline void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue) +{ + if (nOrderPos == -1) + return; + mapValue["n"] = ToString(nOrderPos); +} + +/** Legacy class used for deserializing vtxPrev for backwards compatibility. + * vtxPrev was removed in commit 93a18a3650292afbb441a47d1fa1b94aeb0164e3, + * but old wallet.dat files may still contain vtxPrev vectors of CMerkleTxs. + * These need to get deserialized for field alignment when deserializing + * a CWalletTx, but the deserialized values are discarded.**/ +class CMerkleTx +{ +public: + template<typename Stream> + void Unserialize(Stream& s) + { + CTransactionRef tx; + uint256 hashBlock; + std::vector<uint256> vMerkleBranch; + int nIndex; + + s >> tx >> hashBlock >> vMerkleBranch >> nIndex; + } +}; + +/** + * A transaction with a bunch of additional info that only the owner cares about. + * It includes any unrecorded transactions needed to link it back to the block chain. + */ +class CWalletTx +{ +private: + const CWallet* const pwallet; + + /** Constant used in hashBlock to indicate tx has been abandoned, only used at + * serialization/deserialization to avoid ambiguity with conflicted. + */ + static constexpr const uint256& ABANDON_HASH = uint256::ONE; + +public: + /** + * Key/value map with information about the transaction. + * + * The following keys can be read and written through the map and are + * serialized in the wallet database: + * + * "comment", "to" - comment strings provided to sendtoaddress, + * and sendmany wallet RPCs + * "replaces_txid" - txid (as HexStr) of transaction replaced by + * bumpfee on transaction created by bumpfee + * "replaced_by_txid" - txid (as HexStr) of transaction created by + * bumpfee on transaction replaced by bumpfee + * "from", "message" - obsolete fields that could be set in UI prior to + * 2011 (removed in commit 4d9b223) + * + * The following keys are serialized in the wallet database, but shouldn't + * be read or written through the map (they will be temporarily added and + * removed from the map during serialization): + * + * "fromaccount" - serialized strFromAccount value + * "n" - serialized nOrderPos value + * "timesmart" - serialized nTimeSmart value + * "spent" - serialized vfSpent value that existed prior to + * 2014 (removed in commit 93a18a3) + */ + mapValue_t mapValue; + std::vector<std::pair<std::string, std::string> > vOrderForm; + unsigned int fTimeReceivedIsTxTime; + unsigned int nTimeReceived; //!< time received by this node + /** + * Stable timestamp that never changes, and reflects the order a transaction + * was added to the wallet. Timestamp is based on the block time for a + * transaction added as part of a block, or else the time when the + * transaction was received if it wasn't part of a block, with the timestamp + * adjusted in both cases so timestamp order matches the order transactions + * were added to the wallet. More details can be found in + * CWallet::ComputeTimeSmart(). + */ + unsigned int nTimeSmart; + /** + * From me flag is set to 1 for transactions that were created by the wallet + * on this bitcoin node, and set to 0 for transactions that were created + * externally and came in through the network or sendrawtransaction RPC. + */ + bool fFromMe; + int64_t nOrderPos; //!< position in ordered transaction list + std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered; + + // memory only + enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; + CAmount GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate = false) const; + mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS]; + /** + * This flag is true if all m_amounts caches are empty. This is particularly + * useful in places where MarkDirty is conditionally called and the + * condition can be expensive and thus can be skipped if the flag is true. + * See MarkDestinationsDirty. + */ + mutable bool m_is_cache_empty{true}; + mutable bool fChangeCached; + mutable bool fInMempool; + mutable CAmount nChangeCached; + + CWalletTx(const CWallet* wallet, CTransactionRef arg) + : pwallet(wallet), + tx(std::move(arg)) + { + Init(); + } + + void Init() + { + mapValue.clear(); + vOrderForm.clear(); + fTimeReceivedIsTxTime = false; + nTimeReceived = 0; + nTimeSmart = 0; + fFromMe = false; + fChangeCached = false; + fInMempool = false; + nChangeCached = 0; + nOrderPos = -1; + m_confirm = Confirmation{}; + } + + CTransactionRef tx; + + /** New transactions start as UNCONFIRMED. At BlockConnected, + * they will transition to CONFIRMED. In case of reorg, at BlockDisconnected, + * they roll back to UNCONFIRMED. If we detect a conflicting transaction at + * block connection, we update conflicted tx and its dependencies as CONFLICTED. + * If tx isn't confirmed and outside of mempool, the user may switch it to ABANDONED + * by using the abandontransaction call. This last status may be override by a CONFLICTED + * or CONFIRMED transition. + */ + enum Status { + UNCONFIRMED, + CONFIRMED, + CONFLICTED, + ABANDONED + }; + + /** Confirmation includes tx status and a triplet of {block height/block hash/tx index in block} + * at which tx has been confirmed. All three are set to 0 if tx is unconfirmed or abandoned. + * Meaning of these fields changes with CONFLICTED state where they instead point to block hash + * and block height of the deepest conflicting tx. + */ + struct Confirmation { + Status status; + int block_height; + uint256 hashBlock; + int nIndex; + Confirmation(Status s = UNCONFIRMED, int b = 0, uint256 h = uint256(), int i = 0) : status(s), block_height(b), hashBlock(h), nIndex(i) {} + }; + + Confirmation m_confirm; + + template<typename Stream> + void Serialize(Stream& s) const + { + mapValue_t mapValueCopy = mapValue; + + mapValueCopy["fromaccount"] = ""; + WriteOrderPos(nOrderPos, mapValueCopy); + if (nTimeSmart) { + mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart); + } + + std::vector<uint8_t> dummy_vector1; //!< Used to be vMerkleBranch + std::vector<uint8_t> dummy_vector2; //!< Used to be vtxPrev + bool dummy_bool = false; //!< Used to be fSpent + uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock; + int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex; + s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool; + } + + template<typename Stream> + void Unserialize(Stream& s) + { + Init(); + + std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch + std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev + bool dummy_bool; //! Used to be fSpent + int serializedIndex; + s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool; + + /* At serialization/deserialization, an nIndex == -1 means that hashBlock refers to + * the earliest block in the chain we know this or any in-wallet ancestor conflicts + * with. If nIndex == -1 and hashBlock is ABANDON_HASH, it means transaction is abandoned. + * In same context, an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) or + * unconfirmed one. Older clients interpret nIndex == -1 as unconfirmed for backward + * compatibility (pre-commit 9ac63d6). + */ + if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) { + setAbandoned(); + } else if (serializedIndex == -1) { + setConflicted(); + } else if (!m_confirm.hashBlock.IsNull()) { + m_confirm.nIndex = serializedIndex; + setConfirmed(); + } + + ReadOrderPos(nOrderPos, mapValue); + nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; + + mapValue.erase("fromaccount"); + mapValue.erase("spent"); + mapValue.erase("n"); + mapValue.erase("timesmart"); + } + + void SetTx(CTransactionRef arg) + { + tx = std::move(arg); + } + + //! make sure balances are recalculated + void MarkDirty() + { + m_amounts[DEBIT].Reset(); + m_amounts[CREDIT].Reset(); + m_amounts[IMMATURE_CREDIT].Reset(); + m_amounts[AVAILABLE_CREDIT].Reset(); + fChangeCached = false; + m_is_cache_empty = true; + } + + //! filter decides which addresses will count towards the debit + CAmount GetDebit(const isminefilter& filter) const; + CAmount GetCredit(const isminefilter& filter) const; + CAmount GetImmatureCredit(bool fUseCache = true) const; + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The + // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid + // having to resolve the issue of member access into incomplete type CWallet. + CAmount GetAvailableCredit(bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; + CAmount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const; + CAmount GetChange() const; + + /** Get the marginal bytes if spending the specified output from this transaction */ + int GetSpendSize(unsigned int out, bool use_max_sig = false) const + { + return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig); + } + + void GetAmounts(std::list<COutputEntry>& listReceived, + std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const; + + bool IsFromMe(const isminefilter& filter) const + { + return (GetDebit(filter) > 0); + } + + /** True if only scriptSigs are different */ + bool IsEquivalentTo(const CWalletTx& tx) const; + + bool InMempool() const; + bool IsTrusted() const; + + int64_t GetTxTime() const; + + /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ + bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay); + + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; + + /** + * Return depth of transaction in blockchain: + * <0 : conflicts with a transaction this deep in the blockchain + * 0 : in memory pool, waiting to be included in a block + * >=1 : this many blocks deep in the main chain + */ + // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct + // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation + // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to + // resolve the issue of member access into incomplete type CWallet. Note + // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" + // in place. + int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS; + bool IsInMainChain() const { return GetDepthInMainChain() > 0; } + + /** + * @return number of blocks to maturity for this transaction: + * 0 : is not a coinbase transaction, or is a mature coinbase transaction + * >0 : is a coinbase transaction which matures in this many blocks + */ + int GetBlocksToMaturity() const; + bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } + void setAbandoned() + { + m_confirm.status = CWalletTx::ABANDONED; + m_confirm.hashBlock = uint256(); + m_confirm.block_height = 0; + m_confirm.nIndex = 0; + } + bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; } + void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; } + bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; } + void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; } + bool isConfirmed() const { return m_confirm.status == CWalletTx::CONFIRMED; } + void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } + const uint256& GetHash() const { return tx->GetHash(); } + bool IsCoinBase() const { return tx->IsCoinBase(); } + bool IsImmatureCoinBase() const; + + // Disable copying of CWalletTx objects to prevent bugs where instances get + // copied in and out of the mapWallet map, and fields are updated in the + // wrong copy. + CWalletTx(CWalletTx const &) = delete; + void operator=(CWalletTx const &x) = delete; +}; + +#endif // BITCOIN_WALLET_TRANSACTION_H diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 1bb2438d88..4b6630de3c 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -53,8 +53,6 @@ const std::map<uint64_t,std::string> WALLET_FLAG_CAVEATS{ }, }; -static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100}; - RecursiveMutex cs_wallets; static std::vector<std::shared_ptr<CWallet>> vpwallets GUARDED_BY(cs_wallets); static std::list<LoadWalletFn> g_load_wallet_fns GUARDED_BY(cs_wallets); @@ -213,7 +211,7 @@ std::shared_ptr<CWallet> LoadWalletInternal(interfaces::Chain& chain, const std: return nullptr; } - chain.initMessage(_("Loading wallet...").translated); + chain.initMessage(_("Loading wallet…").translated); std::shared_ptr<CWallet> wallet = CWallet::Create(&chain, name, std::move(database), options.create_flags, error, warnings); if (!wallet) { error = Untranslated("Wallet loading failed.") + Untranslated(" ") + error; @@ -293,7 +291,7 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin } // Make the wallet - chain.initMessage(_("Loading wallet...").translated); + chain.initMessage(_("Loading wallet…").translated); std::shared_ptr<CWallet> wallet = CWallet::Create(&chain, name, std::move(database), wallet_creation_flags, error, warnings); if (!wallet) { error = Untranslated("Wallet creation failed.") + Untranslated(" ") + error; @@ -351,11 +349,6 @@ std::shared_ptr<CWallet> CreateWallet(interfaces::Chain& chain, const std::strin * @{ */ -std::string COutput::ToString() const -{ - return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); -} - const CWalletTx* CWallet::GetWalletTx(const uint256& hash) const { AssertLockHeld(cs_wallet); @@ -821,12 +814,11 @@ void CWallet::SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned CTxDestination dst; if (ExtractDestination(srctx->tx->vout[n].scriptPubKey, dst)) { if (IsMine(dst)) { - if (used && !GetDestData(dst, "used", nullptr)) { - if (AddDestData(batch, dst, "used", "p")) { // p for "present", opposite of absent (null) + if (used != IsAddressUsed(dst)) { + if (used) { tx_destinations.insert(dst); } - } else if (!used && GetDestData(dst, "used", nullptr)) { - EraseDestData(batch, dst, "used"); + SetAddressUsed(batch, dst, used); } } } @@ -842,7 +834,7 @@ bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const if (!ExtractDestination(srctx->tx->vout[n].scriptPubKey, dest)) { return false; } - if (GetDestData(dest, "used", nullptr)) { + if (IsAddressUsed(dest)) { return true; } if (IsLegacy()) { @@ -850,15 +842,15 @@ bool CWallet::IsSpentKey(const uint256& hash, unsigned int n) const assert(spk_man != nullptr); for (const auto& keyid : GetAffectedKeys(srctx->tx->vout[n].scriptPubKey, *spk_man)) { WitnessV0KeyHash wpkh_dest(keyid); - if (GetDestData(wpkh_dest, "used", nullptr)) { + if (IsAddressUsed(wpkh_dest)) { return true; } ScriptHash sh_wpkh_dest(GetScriptForDestination(wpkh_dest)); - if (GetDestData(sh_wpkh_dest, "used", nullptr)) { + if (IsAddressUsed(sh_wpkh_dest)) { return true; } PKHash pkh_dest(keyid); - if (GetDestData(pkh_dest, "used", nullptr)) { + if (IsAddressUsed(pkh_dest)) { return true; } } @@ -1283,20 +1275,6 @@ void CWallet::BlockUntilSyncedToCurrentChain() const { chain().waitForNotificationsIfTipChanged(last_block_hash); } - -isminetype CWallet::IsMine(const CTxIn &txin) const -{ - AssertLockHeld(cs_wallet); - std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash); - if (mi != mapWallet.end()) - { - const CWalletTx& prev = (*mi).second; - if (txin.prevout.n < prev.tx->vout.size()) - return IsMine(prev.tx->vout[txin.prevout.n]); - } - return ISMINE_NO; -} - // Note that this function doesn't distinguish between a 0-valued input, // and a not-"is mine" (according to the filter) input. CAmount CWallet::GetDebit(const CTxIn &txin, const isminefilter& filter) const @@ -1337,49 +1315,6 @@ isminetype CWallet::IsMine(const CScript& script) const return result; } -CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const -{ - if (!MoneyRange(txout.nValue)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - LOCK(cs_wallet); - return ((IsMine(txout) & filter) ? txout.nValue : 0); -} - -bool CWallet::IsChange(const CTxOut& txout) const -{ - return IsChange(txout.scriptPubKey); -} - -bool CWallet::IsChange(const CScript& script) const -{ - // 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(cs_wallet); - if (IsMine(script)) - { - CTxDestination address; - if (!ExtractDestination(script, address)) - return true; - if (!FindAddressBookEntry(address)) { - return true; - } - } - return false; -} - -CAmount CWallet::GetChange(const CTxOut& txout) const -{ - AssertLockHeld(cs_wallet); - if (!MoneyRange(txout.nValue)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - return (IsChange(txout) ? txout.nValue : 0); -} - bool CWallet::IsMine(const CTransaction& tx) const { AssertLockHeld(cs_wallet); @@ -1406,52 +1341,6 @@ CAmount CWallet::GetDebit(const CTransaction& tx, const isminefilter& filter) co return nDebit; } -bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const -{ - LOCK(cs_wallet); - - for (const CTxIn& txin : tx.vin) - { - auto mi = mapWallet.find(txin.prevout.hash); - if (mi == 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 (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter)) - return false; - } - return true; -} - -CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const -{ - CAmount nCredit = 0; - for (const CTxOut& txout : tx.vout) - { - nCredit += GetCredit(txout, filter); - if (!MoneyRange(nCredit)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - } - return nCredit; -} - -CAmount CWallet::GetChange(const CTransaction& tx) const -{ - LOCK(cs_wallet); - CAmount nChange = 0; - for (const CTxOut& txout : tx.vout) - { - nChange += GetChange(txout); - if (!MoneyRange(nChange)) - throw std::runtime_error(std::string(__func__) + ": value out of range"); - } - return nChange; -} - bool CWallet::IsHDEnabled() const { // All Active ScriptPubKeyMans must be HD for this to be true @@ -1531,12 +1420,6 @@ bool CWallet::AddWalletFlags(uint64_t flags) return LoadWalletFlags(flags); } -int64_t CWalletTx::GetTxTime() const -{ - int64_t n = nTimeSmart; - return n ? n : nTimeReceived; -} - // Helper for producing a max-sized low-S low-R signature (eg 71 bytes) // or a max-sized low-S signature (e.g. 72 bytes) if use_max_sig is true bool CWallet::DummySignInput(CTxIn &tx_in, const CTxOut &txout, bool use_max_sig) const @@ -1627,100 +1510,6 @@ bool CWallet::ImportScriptPubKeys(const std::string& label, const std::set<CScri return true; } -TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, bool use_max_sig) -{ - std::vector<CTxOut> txouts; - for (const CTxIn& input : tx.vin) { - const auto mi = wallet->mapWallet.find(input.prevout.hash); - // Can not estimate size without knowing the input details - if (mi == wallet->mapWallet.end()) { - return TxSize{-1, -1}; - } - assert(input.prevout.n < mi->second.tx->vout.size()); - txouts.emplace_back(mi->second.tx->vout[input.prevout.n]); - } - return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig); -} - -// txouts needs to be in the order of tx.vin -TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wallet, const std::vector<CTxOut>& txouts, bool use_max_sig) -{ - CMutableTransaction txNew(tx); - if (!wallet->DummySignTx(txNew, txouts, use_max_sig)) { - return TxSize{-1, -1}; - } - CTransaction ctx(txNew); - int64_t vsize = GetVirtualTransactionSize(ctx); - int64_t weight = GetTransactionWeight(ctx); - return TxSize{vsize, weight}; -} - -int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* wallet, bool use_max_sig) -{ - CMutableTransaction txn; - txn.vin.push_back(CTxIn(COutPoint())); - if (!wallet->DummySignInput(txn.vin[0], txout, use_max_sig)) { - return -1; - } - return GetVirtualTransactionInputSize(txn.vin[0]); -} - -void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, - std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const -{ - nFee = 0; - listReceived.clear(); - listSent.clear(); - - // Compute fee: - CAmount nDebit = GetDebit(filter); - if (nDebit > 0) // debit>0 means we signed/sent this transaction - { - CAmount nValueOut = tx->GetValueOut(); - nFee = nDebit - nValueOut; - } - - LOCK(pwallet->cs_wallet); - // Sent/received. - for (unsigned int i = 0; i < tx->vout.size(); ++i) - { - const CTxOut& txout = tx->vout[i]; - isminetype fIsMine = pwallet->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 (pwallet->IsChange(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()) - { - pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", - this->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); - } - -} - /** * Scan active chain for relevant transactions after importing keys. This should * be called whenever new keys are added to the wallet, with the oldest key @@ -1943,165 +1732,6 @@ std::set<uint256> CWalletTx::GetConflicts() const return result; } -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(*tx, filter) : pwallet->GetCredit(*tx, filter)); - m_is_cache_empty = false; - } - return amount.m_value[filter]; -} - -CAmount CWalletTx::GetDebit(const isminefilter& filter) const -{ - if (tx->vin.empty()) - return 0; - - CAmount debit = 0; - if (filter & ISMINE_SPENDABLE) { - debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE); - } - if (filter & ISMINE_WATCH_ONLY) { - debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY); - } - return debit; -} - -CAmount CWalletTx::GetCredit(const isminefilter& filter) const -{ - // Must wait until coinbase is safely deep enough in the chain before valuing it - if (IsImmatureCoinBase()) - return 0; - - CAmount credit = 0; - if (filter & ISMINE_SPENDABLE) { - // GetBalance can assume transactions in mapWallet won't change - credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE); - } - if (filter & ISMINE_WATCH_ONLY) { - credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY); - } - return credit; -} - -CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const -{ - if (IsImmatureCoinBase() && IsInMainChain()) { - return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); - } - - return 0; -} - -CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const -{ - if (pwallet == nullptr) - return 0; - - // 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 (IsImmatureCoinBase()) - return 0; - - if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { - return m_amounts[AVAILABLE_CREDIT].m_value[filter]; - } - - bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); - CAmount nCredit = 0; - uint256 hashTx = GetHash(); - for (unsigned int i = 0; i < tx->vout.size(); i++) - { - if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) { - const CTxOut &txout = tx->vout[i]; - nCredit += pwallet->GetCredit(txout, filter); - if (!MoneyRange(nCredit)) - throw std::runtime_error(std::string(__func__) + " : value out of range"); - } - } - - if (allow_cache) { - m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit); - m_is_cache_empty = false; - } - - return nCredit; -} - -CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const -{ - if (IsImmatureCoinBase() && IsInMainChain()) { - return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); - } - - return 0; -} - -CAmount CWalletTx::GetChange() const -{ - if (fChangeCached) - return nChangeCached; - nChangeCached = pwallet->GetChange(*tx); - fChangeCached = true; - return nChangeCached; -} - -bool CWalletTx::InMempool() const -{ - return fInMempool; -} - -bool CWalletTx::IsTrusted() const -{ - std::set<uint256> trusted_parents; - LOCK(pwallet->cs_wallet); - return pwallet->IsTrusted(*this, trusted_parents); -} - -bool CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const -{ - AssertLockHeld(cs_wallet); - // Quick answer in most cases - if (!chain().checkFinalTx(*wtx.tx)) return false; - int nDepth = wtx.GetDepthInMainChain(); - if (nDepth >= 1) return true; - if (nDepth < 0) return false; - // using wtx's cached debit - if (!m_spend_zero_conf_change || !wtx.IsFromMe(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 = 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 (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 (!IsTrusted(*parent, trusted_parents)) return false; - trusted_parents.insert(parent->GetHash()); - } - return true; -} - -bool CWalletTx::IsEquivalentTo(const CWalletTx& _tx) const -{ - CMutableTransaction tx1 {*this->tx}; - CMutableTransaction tx2 {*_tx.tx}; - for (auto& txin : tx1.vin) txin.scriptSig = CScript(); - for (auto& txin : tx2.vin) txin.scriptSig = CScript(); - return CTransaction(tx1) == CTransaction(tx2); -} - // Rebroadcast transactions from the wallet. We do this on a random timer // to slightly obfuscate which transactions come from our wallet. // @@ -2162,394 +1792,6 @@ void MaybeResendWalletTxs() * @{ */ - -CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const -{ - Balance ret; - isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; - { - LOCK(cs_wallet); - std::set<uint256> trusted_parents; - for (const auto& entry : mapWallet) - { - const CWalletTx& wtx = entry.second; - const bool is_trusted{IsTrusted(wtx, trusted_parents)}; - const int tx_depth{wtx.GetDepthInMainChain()}; - const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; - const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* 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 += wtx.GetImmatureCredit(); - ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(); - } - } - return ret; -} - -CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const -{ - LOCK(cs_wallet); - - CAmount balance = 0; - std::vector<COutput> vCoins; - AvailableCoins(vCoins, coinControl); - for (const COutput& out : vCoins) { - if (out.fSpendable) { - balance += out.tx->tx->vout[out.i].nValue; - } - } - return balance; -} - -void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount) const -{ - AssertLockHeld(cs_wallet); - - vCoins.clear(); - CAmount nTotal = 0; - // Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where - // a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses - bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse); - const int min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH}; - const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH}; - const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true}; - - std::set<uint256> trusted_parents; - for (const auto& entry : mapWallet) - { - const uint256& wtxid = entry.first; - const CWalletTx& wtx = entry.second; - - if (!chain().checkFinalTx(*wtx.tx)) { - continue; - } - - if (wtx.IsImmatureCoinBase()) - continue; - - int nDepth = wtx.GetDepthInMainChain(); - if (nDepth < 0) - continue; - - // We should not consider coins which aren't at least in our mempool - // It's possible for these to be conflicted via ancestors which we may never be able to detect - if (nDepth == 0 && !wtx.InMempool()) - continue; - - bool safeTx = IsTrusted(wtx, trusted_parents); - - // We should not consider coins from transactions that are replacing - // other transactions. - // - // Example: There is a transaction A which is replaced by bumpfee - // transaction B. In this case, we want to prevent creation of - // a transaction B' which spends an output of B. - // - // Reason: If transaction A were initially confirmed, transactions B - // and B' would no longer be valid, so the user would have to create - // a new transaction C to replace B'. However, in the case of a - // one-block reorg, transactions B' and C might BOTH be accepted, - // when the user only wanted one of them. Specifically, there could - // be a 1-block reorg away from the chain where transactions A and C - // were accepted to another chain where B, B', and C were all - // accepted. - if (nDepth == 0 && wtx.mapValue.count("replaces_txid")) { - safeTx = false; - } - - // Similarly, we should not consider coins from transactions that - // have been replaced. In the example above, we would want to prevent - // creation of a transaction A' spending an output of A, because if - // transaction B were initially confirmed, conflicting with A and - // A', we wouldn't want to the user to create a transaction D - // intending to replace A', but potentially resulting in a scenario - // where A, A', and D could all be accepted (instead of just B and - // D, or just A and A' like the user would want). - if (nDepth == 0 && wtx.mapValue.count("replaced_by_txid")) { - safeTx = false; - } - - if (only_safe && !safeTx) { - continue; - } - - if (nDepth < min_depth || nDepth > max_depth) { - continue; - } - - for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) { - // Only consider selected coins if add_inputs is false - if (coinControl && !coinControl->m_add_inputs && !coinControl->IsSelected(COutPoint(entry.first, i))) { - continue; - } - - if (wtx.tx->vout[i].nValue < nMinimumAmount || wtx.tx->vout[i].nValue > nMaximumAmount) - continue; - - if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) - continue; - - if (IsLockedCoin(entry.first, i)) - continue; - - if (IsSpent(wtxid, i)) - continue; - - isminetype mine = IsMine(wtx.tx->vout[i]); - - if (mine == ISMINE_NO) { - continue; - } - - if (!allow_used_addresses && IsSpentKey(wtxid, i)) { - continue; - } - - std::unique_ptr<SigningProvider> provider = GetSolvingProvider(wtx.tx->vout[i].scriptPubKey); - - bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; - bool spendable = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) || (((mine & ISMINE_WATCH_ONLY) != ISMINE_NO) && (coinControl && coinControl->fAllowWatchOnly && solvable)); - - vCoins.push_back(COutput(&wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly))); - - // Checks the sum amount of all UTXO's. - if (nMinimumSumAmount != MAX_MONEY) { - nTotal += wtx.tx->vout[i].nValue; - - if (nTotal >= nMinimumSumAmount) { - return; - } - } - - // Checks the maximum number of UTXO's. - if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) { - return; - } - } - } -} - -std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const -{ - AssertLockHeld(cs_wallet); - - std::map<CTxDestination, std::vector<COutput>> result; - std::vector<COutput> availableCoins; - - AvailableCoins(availableCoins); - - for (const COutput& coin : availableCoins) { - CTxDestination address; - if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && - ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { - result[address].emplace_back(std::move(coin)); - } - } - - std::vector<COutPoint> lockedCoins; - ListLockedCoins(lockedCoins); - // Include watch-only for LegacyScriptPubKeyMan wallets without private keys - const bool include_watch_only = GetLegacyScriptPubKeyMan() && IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); - const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; - for (const COutPoint& output : lockedCoins) { - auto it = mapWallet.find(output.hash); - if (it != mapWallet.end()) { - int depth = it->second.GetDepthInMainChain(); - if (depth >= 0 && output.n < it->second.tx->vout.size() && - IsMine(it->second.tx->vout[output.n]) == is_mine_filter - ) { - CTxDestination address; - if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { - result[address].emplace_back( - &it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); - } - } - } - } - - return result; -} - -const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const -{ - AssertLockHeld(cs_wallet); - const CTransaction* ptx = &tx; - int n = output; - while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) { - const COutPoint& prevout = ptx->vin[0].prevout; - auto it = mapWallet.find(prevout.hash); - if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n || - !IsMine(it->second.tx->vout[prevout.n])) { - break; - } - ptx = it->second.tx.get(); - n = prevout.n; - } - return ptx->vout[n]; -} - -bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, - std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const -{ - setCoinsRet.clear(); - nValueRet = 0; - - // Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output. - std::vector<OutputGroup> positive_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, true /* positive_only */); - if (SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change, setCoinsRet, nValueRet)) { - return true; - } - // The knapsack solver has some legacy behavior where it will spend dust outputs. We retain this behavior, so don't filter for positive only here. - std::vector<OutputGroup> all_groups = GroupOutputs(coins, coin_selection_params, eligibility_filter, false /* positive_only */); - // While nTargetValue includes the transaction fees for non-input things, it does not include the fee for creating a change output. - // So we need to include that for KnapsackSolver as well, as we are expecting to create a change output. - return KnapsackSolver(nTargetValue + coin_selection_params.m_change_fee, all_groups, setCoinsRet, nValueRet); -} - -bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) const -{ - std::vector<COutput> vCoins(vAvailableCoins); - CAmount value_to_select = nTargetValue; - - // coin control -> return all selected outputs (we want all selected to go into the transaction for sure) - if (coin_control.HasSelected() && !coin_control.fAllowOtherInputs) - { - for (const COutput& out : vCoins) - { - if (!out.fSpendable) - continue; - nValueRet += out.tx->tx->vout[out.i].nValue; - setCoinsRet.insert(out.GetInputCoin()); - } - return (nValueRet >= nTargetValue); - } - - // calculate value from preset inputs and store them - std::set<CInputCoin> setPresetCoins; - CAmount nValueFromPresetInputs = 0; - - std::vector<COutPoint> vPresetInputs; - coin_control.ListSelected(vPresetInputs); - for (const COutPoint& outpoint : vPresetInputs) - { - std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); - if (it != mapWallet.end()) - { - const CWalletTx& wtx = it->second; - // Clearly invalid input, fail - if (wtx.tx->vout.size() <= outpoint.n) { - return false; - } - // Just to calculate the marginal byte size - CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false)); - nValueFromPresetInputs += coin.txout.nValue; - if (coin.m_input_bytes <= 0) { - return false; // Not solvable, can't estimate size for fee - } - coin.effective_value = coin.txout.nValue - coin_selection_params.m_effective_feerate.GetFee(coin.m_input_bytes); - if (coin_selection_params.m_subtract_fee_outputs) { - value_to_select -= coin.txout.nValue; - } else { - value_to_select -= coin.effective_value; - } - setPresetCoins.insert(coin); - } else { - return false; // TODO: Allow non-wallet inputs - } - } - - // remove preset inputs from vCoins so that Coin Selection doesn't pick them. - for (std::vector<COutput>::iterator it = vCoins.begin(); it != vCoins.end() && coin_control.HasSelected();) - { - if (setPresetCoins.count(it->GetInputCoin())) - it = vCoins.erase(it); - else - ++it; - } - - unsigned int limit_ancestor_count = 0; - unsigned int limit_descendant_count = 0; - chain().getPackageLimits(limit_ancestor_count, limit_descendant_count); - const size_t max_ancestors = (size_t)std::max<int64_t>(1, limit_ancestor_count); - const size_t max_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count); - const bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS); - - // form groups from remaining coins; note that preset coins will not - // automatically have their associated (same address) coins included - if (coin_control.m_avoid_partial_spends && vCoins.size() > OUTPUT_GROUP_MAX_ENTRIES) { - // Cases where we have 101+ outputs all pointing to the same destination may result in - // privacy leaks as they will potentially be deterministically sorted. We solve that by - // explicitly shuffling the outputs before processing - Shuffle(vCoins.begin(), vCoins.end(), FastRandomContext()); - } - - // Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the - // transaction at a target feerate. If an attempt fails, more attempts may be made using a more - // permissive CoinEligibilityFilter. - const bool res = [&] { - // Pre-selected inputs already cover the target amount. - if (value_to_select <= 0) return true; - - // If possible, fund the transaction with confirmed UTXOs only. Prefer at least six - // confirmations on outputs received from other wallets and only spend confirmed change. - if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; - if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; - - // Fall back to using zero confirmation change (but with as few ancestors in the mempool as - // possible) if we cannot fund the transaction otherwise. - if (m_spend_zero_conf_change) { - if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; - if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), - vCoins, setCoinsRet, nValueRet, coin_selection_params)) { - return true; - } - if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), - vCoins, setCoinsRet, nValueRet, coin_selection_params)) { - return true; - } - // If partial groups are allowed, relax the requirement of spending OutputGroups (groups - // of UTXOs sent to the same address, which are obviously controlled by a single wallet) - // in their entirety. - if (SelectCoinsMinConf(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), - vCoins, setCoinsRet, nValueRet, coin_selection_params)) { - return true; - } - // Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs - // received from other wallets. - if (coin_control.m_include_unsafe_inputs - && SelectCoinsMinConf(value_to_select, - CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), - vCoins, setCoinsRet, nValueRet, coin_selection_params)) { - return true; - } - // Try with unlimited ancestors/descendants. The transaction will still need to meet - // mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but - // OutputGroups use heuristics that may overestimate ancestor/descendant counts. - if (!fRejectLongChains && SelectCoinsMinConf(value_to_select, - CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */), - vCoins, setCoinsRet, nValueRet, coin_selection_params)) { - return true; - } - } - // Coin Selection failed. - return false; - }(); - - // SelectCoinsMinConf clears setCoinsRet, so add the preset inputs from coin_control to the coinset - util::insert(setCoinsRet, setPresetCoins); - - // add preset inputs to the total value selected - nValueRet += nValueFromPresetInputs; - - return res; -} - bool CWallet::SignTransaction(CMutableTransaction& tx) const { AssertLockHeld(cs_wallet); @@ -2645,118 +1887,6 @@ SigningResult CWallet::SignMessage(const std::string& message, const PKHash& pkh return SigningResult::PRIVATE_KEY_NOT_AVAILABLE; } -bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) -{ - std::vector<CRecipient> vecSend; - - // Turn the txout set into a CRecipient vector. - for (size_t idx = 0; idx < tx.vout.size(); idx++) { - const CTxOut& txOut = tx.vout[idx]; - CRecipient recipient = {txOut.scriptPubKey, txOut.nValue, setSubtractFeeFromOutputs.count(idx) == 1}; - vecSend.push_back(recipient); - } - - coinControl.fAllowOtherInputs = true; - - for (const CTxIn& txin : tx.vin) { - coinControl.Select(txin.prevout); - } - - // Acquire the locks to prevent races to the new locked unspents between the - // CreateTransaction call and LockCoin calls (when lockUnspents is true). - LOCK(cs_wallet); - - CTransactionRef tx_new; - FeeCalculation fee_calc_out; - if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) { - return false; - } - - if (nChangePosInOut != -1) { - tx.vout.insert(tx.vout.begin() + nChangePosInOut, tx_new->vout[nChangePosInOut]); - } - - // Copy output sizes from new transaction; they may have had the fee - // subtracted from them. - for (unsigned int idx = 0; idx < tx.vout.size(); idx++) { - tx.vout[idx].nValue = tx_new->vout[idx].nValue; - } - - // Add new txins while keeping original txin scriptSig/order. - for (const CTxIn& txin : tx_new->vin) { - if (!coinControl.IsSelected(txin.prevout)) { - tx.vin.push_back(txin); - - } - if (lockUnspents) { - LockCoin(txin.prevout); - } - - } - - return true; -} - -static bool IsCurrentForAntiFeeSniping(interfaces::Chain& chain, const uint256& block_hash) -{ - if (chain.isInitialBlockDownload()) { - return false; - } - constexpr int64_t MAX_ANTI_FEE_SNIPING_TIP_AGE = 8 * 60 * 60; // in seconds - int64_t block_time; - CHECK_NONFATAL(chain.findBlock(block_hash, FoundBlock().time(block_time))); - if (block_time < (GetTime() - MAX_ANTI_FEE_SNIPING_TIP_AGE)) { - return false; - } - return true; -} - -/** - * Return a height-based locktime for new transactions (uses the height of the - * current chain tip unless we are not synced with the current chain - */ -static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uint256& block_hash, int block_height) -{ - uint32_t locktime; - // Discourage fee sniping. - // - // For a large miner the value of the transactions in the best block and - // the mempool can exceed the cost of deliberately attempting to mine two - // blocks to orphan the current best block. By setting nLockTime such that - // only the next block can include the transaction, we discourage this - // practice as the height restricted and limited blocksize gives miners - // considering fee sniping fewer options for pulling off this attack. - // - // A simple way to think about this is from the wallet's point of view we - // always want the blockchain to move forward. By setting nLockTime this - // way we're basically making the statement that we only want this - // transaction to appear in the next block; we don't want to potentially - // encourage reorgs by allowing transactions to appear at lower heights - // than the next block in forks of the best chain. - // - // Of course, the subsidy is high enough, and transaction volume low - // enough, that fee sniping isn't a problem yet, but by implementing a fix - // now we ensure code won't be written that makes assumptions about - // nLockTime that preclude a fix later. - if (IsCurrentForAntiFeeSniping(chain, block_hash)) { - locktime = block_height; - - // Secondly occasionally randomly pick a nLockTime even further back, so - // that transactions that are delayed after signing for whatever reason, - // e.g. high-latency mix networks and some CoinJoin implementations, have - // better privacy. - if (GetRandInt(10) == 0) - locktime = std::max(0, (int)locktime - GetRandInt(100)); - } else { - // If our chain is lagging behind, we can't discourage fee sniping nor help - // the privacy of high-latency transactions. To avoid leaking a potentially - // unique "nLockTime fingerprint", set nLockTime to a constant. - locktime = 0; - } - assert(locktime < LOCKTIME_THRESHOLD); - return locktime; -} - OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const { // If -changetype is specified, always use that change type. @@ -2785,363 +1915,6 @@ OutputType CWallet::TransactionChangeType(const std::optional<OutputType>& chang return m_default_address_type; } -bool CWallet::CreateTransactionInternal( - const std::vector<CRecipient>& vecSend, - CTransactionRef& tx, - CAmount& nFeeRet, - int& nChangePosInOut, - bilingual_str& error, - const CCoinControl& coin_control, - FeeCalculation& fee_calc_out, - bool sign) -{ - CAmount nValue = 0; - const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); - ReserveDestination reservedest(this, change_type); - unsigned int nSubtractFeeFromAmount = 0; - for (const auto& recipient : vecSend) - { - if (nValue < 0 || recipient.nAmount < 0) - { - error = _("Transaction amounts must not be negative"); - return false; - } - nValue += recipient.nAmount; - - if (recipient.fSubtractFeeFromAmount) - nSubtractFeeFromAmount++; - } - if (vecSend.empty()) - { - error = _("Transaction must have at least one recipient"); - return false; - } - - CMutableTransaction txNew; - FeeCalculation feeCalc; - TxSize tx_sizes; - int nBytes; - { - std::set<CInputCoin> setCoins; - LOCK(cs_wallet); - txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight()); - { - std::vector<COutput> vAvailableCoins; - AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); - CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy - coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends; - - // Create change script that will be used if we need change - // TODO: pass in scriptChange instead of reservedest so - // change transaction isn't always pay-to-bitcoin-address - CScript scriptChange; - - // coin control: send change to custom address - if (!std::get_if<CNoDestination>(&coin_control.destChange)) { - scriptChange = GetScriptForDestination(coin_control.destChange); - } else { // no coin control: send change to newly generated address - // Note: We use a new key here to keep it from being obvious which side is the change. - // The drawback is that by not reusing a previous key, the change may be lost if a - // backup is restored, if the backup doesn't have the new private key for the change. - // If we reused the old key, it would be possible to add code to look for and - // rediscover unknown transactions that were written with keys of ours to recover - // post-backup change. - - // Reserve a new key pair from key pool. If it fails, provide a dummy - // destination in case we don't need change. - CTxDestination dest; - if (!reservedest.GetReservedDestination(dest, true)) { - error = _("Transaction needs a change address, but we can't generate it. Please call keypoolrefill first."); - } - scriptChange = GetScriptForDestination(dest); - // A valid destination implies a change script (and - // vice-versa). An empty change script will abort later, if the - // change keypool ran out, but change is required. - CHECK_NONFATAL(IsValidDestination(dest) != scriptChange.empty()); - } - CTxOut change_prototype_txout(0, scriptChange); - coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); - - // Get size of spending the change output - int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this); - // If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh - // as lower-bound to allow BnB to do it's thing - if (change_spend_size == -1) { - coin_selection_params.change_spend_size = DUMMY_NESTED_P2WPKH_INPUT_SIZE; - } else { - coin_selection_params.change_spend_size = (size_t)change_spend_size; - } - - // Set discard feerate - coin_selection_params.m_discard_feerate = GetDiscardRate(*this); - - // Get the fee rate to use effective values in coin selection - coin_selection_params.m_effective_feerate = GetMinimumFeeRate(*this, coin_control, &feeCalc); - // Do not, ever, assume that it's fine to change the fee rate if the user has explicitly - // provided one - if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) { - error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB)); - return false; - } - if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) { - // eventually allow a fallback fee - error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); - return false; - } - - // Get long term estimate - CCoinControl cc_temp; - cc_temp.m_confirm_target = chain().estimateMaxBlocks(); - coin_selection_params.m_long_term_feerate = GetMinimumFeeRate(*this, cc_temp, nullptr); - - // Calculate the cost of change - // Cost of change is the cost of creating the change output + cost of spending the change output in the future. - // For creating the change output now, we use the effective feerate. - // For spending the change output in the future, we use the discard feerate for now. - // So cost of change = (change output size * effective feerate) + (size of spending change output * discard feerate) - coin_selection_params.m_change_fee = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.change_output_size); - coin_selection_params.m_cost_of_change = coin_selection_params.m_discard_feerate.GetFee(coin_selection_params.change_spend_size) + coin_selection_params.m_change_fee; - - coin_selection_params.m_subtract_fee_outputs = nSubtractFeeFromAmount != 0; // If we are doing subtract fee from recipient, don't use effective values - - // vouts to the payees - if (!coin_selection_params.m_subtract_fee_outputs) { - coin_selection_params.tx_noinputs_size = 11; // Static vsize overhead + outputs vsize. 4 nVersion, 4 nLocktime, 1 input count, 1 output count, 1 witness overhead (dummy, flag, stack size) - } - for (const auto& recipient : vecSend) - { - CTxOut txout(recipient.nAmount, recipient.scriptPubKey); - - // Include the fee cost for outputs. - if (!coin_selection_params.m_subtract_fee_outputs) { - coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); - } - - if (IsDust(txout, chain().relayDustFee())) - { - error = _("Transaction amount too small"); - return false; - } - txNew.vout.push_back(txout); - } - - // Include the fees for things that aren't inputs, excluding the change output - const CAmount not_input_fees = coin_selection_params.m_effective_feerate.GetFee(coin_selection_params.tx_noinputs_size); - CAmount nValueToSelect = nValue + not_input_fees; - - // Choose coins to use - CAmount inputs_sum = 0; - setCoins.clear(); - if (!SelectCoins(vAvailableCoins, /* nTargetValue */ nValueToSelect, setCoins, inputs_sum, coin_control, coin_selection_params)) - { - error = _("Insufficient funds"); - return false; - } - - // Always make a change output - // We will reduce the fee from this change output later, and remove the output if it is too small. - const CAmount change_and_fee = inputs_sum - nValue; - assert(change_and_fee >= 0); - CTxOut newTxOut(change_and_fee, scriptChange); - - if (nChangePosInOut == -1) - { - // Insert change txn at random position: - nChangePosInOut = GetRandInt(txNew.vout.size()+1); - } - else if ((unsigned int)nChangePosInOut > txNew.vout.size()) - { - error = _("Change index out of range"); - return false; - } - - assert(nChangePosInOut != -1); - auto change_position = txNew.vout.insert(txNew.vout.begin() + nChangePosInOut, newTxOut); - - // Dummy fill vin for maximum size estimation - // - for (const auto& coin : setCoins) { - txNew.vin.push_back(CTxIn(coin.outpoint,CScript())); - } - - // Calculate the transaction fee - tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); - nBytes = tx_sizes.vsize; - if (nBytes < 0) { - error = _("Signing transaction failed"); - return false; - } - nFeeRet = coin_selection_params.m_effective_feerate.GetFee(nBytes); - - // Subtract fee from the change output if not subtrating it from recipient outputs - CAmount fee_needed = nFeeRet; - if (nSubtractFeeFromAmount == 0) { - change_position->nValue -= fee_needed; - } - - // We want to drop the change to fees if: - // 1. The change output would be dust - // 2. The change is within the (almost) exact match window, i.e. it is less than or equal to the cost of the change output (cost_of_change) - CAmount change_amount = change_position->nValue; - if (IsDust(*change_position, coin_selection_params.m_discard_feerate) || change_amount <= coin_selection_params.m_cost_of_change) - { - nChangePosInOut = -1; - change_amount = 0; - txNew.vout.erase(change_position); - - // Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those - tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); - nBytes = tx_sizes.vsize; - fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes); - } - - // Update nFeeRet in case fee_needed changed due to dropping the change output - if (fee_needed <= change_and_fee - change_amount) { - nFeeRet = change_and_fee - change_amount; - } - - // Reduce output values for subtractFeeFromAmount - if (nSubtractFeeFromAmount != 0) { - CAmount to_reduce = fee_needed + change_amount - change_and_fee; - int i = 0; - bool fFirst = true; - for (const auto& recipient : vecSend) - { - if (i == nChangePosInOut) { - ++i; - } - CTxOut& txout = txNew.vout[i]; - - if (recipient.fSubtractFeeFromAmount) - { - txout.nValue -= to_reduce / nSubtractFeeFromAmount; // Subtract fee equally from each selected recipient - - if (fFirst) // first receiver pays the remainder not divisible by output count - { - fFirst = false; - txout.nValue -= to_reduce % nSubtractFeeFromAmount; - } - - // Error if this output is reduced to be below dust - if (IsDust(txout, chain().relayDustFee())) { - if (txout.nValue < 0) { - error = _("The transaction amount is too small to pay the fee"); - } else { - error = _("The transaction amount is too small to send after the fee has been deducted"); - } - return false; - } - } - ++i; - } - nFeeRet = fee_needed; - } - - // Give up if change keypool ran out and change is required - if (scriptChange.empty() && nChangePosInOut != -1) { - return false; - } - } - - // Shuffle selected coins and fill in final vin - txNew.vin.clear(); - std::vector<CInputCoin> selected_coins(setCoins.begin(), setCoins.end()); - Shuffle(selected_coins.begin(), selected_coins.end(), FastRandomContext()); - - // Note how the sequence number is set to non-maxint so that - // the nLockTime set above actually works. - // - // BIP125 defines opt-in RBF as any nSequence < maxint-1, so - // we use the highest possible value in that range (maxint-2) - // to avoid conflicting with other possible uses of nSequence, - // and in the spirit of "smallest possible change from prior - // behavior." - const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1); - for (const auto& coin : selected_coins) { - txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence)); - } - - if (sign && !SignTransaction(txNew)) { - error = _("Signing transaction failed"); - return false; - } - - // Return the constructed transaction data. - tx = MakeTransactionRef(std::move(txNew)); - - // Limit size - if ((sign && GetTransactionWeight(*tx) > MAX_STANDARD_TX_WEIGHT) || - (!sign && tx_sizes.weight > MAX_STANDARD_TX_WEIGHT)) - { - error = _("Transaction too large"); - return false; - } - } - - if (nFeeRet > m_default_max_tx_fee) { - error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); - return false; - } - - if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { - // Lastly, ensure this tx will pass the mempool's chain limits - if (!chain().checkChainLimits(tx)) { - error = _("Transaction has too long of a mempool chain"); - return false; - } - } - - // Before we return success, we assume any change key will be used to prevent - // accidental re-use. - reservedest.KeepDestination(); - fee_calc_out = feeCalc; - - WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n", - nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay, - feeCalc.est.pass.start, feeCalc.est.pass.end, - (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) > 0.0 ? 100 * feeCalc.est.pass.withinTarget / (feeCalc.est.pass.totalConfirmed + feeCalc.est.pass.inMempool + feeCalc.est.pass.leftMempool) : 0.0, - feeCalc.est.pass.withinTarget, feeCalc.est.pass.totalConfirmed, feeCalc.est.pass.inMempool, feeCalc.est.pass.leftMempool, - feeCalc.est.fail.start, feeCalc.est.fail.end, - (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) > 0.0 ? 100 * feeCalc.est.fail.withinTarget / (feeCalc.est.fail.totalConfirmed + feeCalc.est.fail.inMempool + feeCalc.est.fail.leftMempool) : 0.0, - feeCalc.est.fail.withinTarget, feeCalc.est.fail.totalConfirmed, feeCalc.est.fail.inMempool, feeCalc.est.fail.leftMempool); - return true; -} - -bool CWallet::CreateTransaction( - const std::vector<CRecipient>& vecSend, - CTransactionRef& tx, - CAmount& nFeeRet, - int& nChangePosInOut, - bilingual_str& error, - const CCoinControl& coin_control, - FeeCalculation& fee_calc_out, - bool sign) -{ - int nChangePosIn = nChangePosInOut; - Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr) - bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign); - // try with avoidpartialspends unless it's enabled already - if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) { - CCoinControl tmp_cc = coin_control; - tmp_cc.m_avoid_partial_spends = true; - CAmount nFeeRet2; - CTransactionRef tx2; - int nChangePosInOut2 = nChangePosIn; - bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results - if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) { - // if fee of this alternative one is within the range of the max fee, we use this one - const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee; - WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); - if (use_aps) { - tx = tx2; - nFeeRet = nFeeRet2; - nChangePosInOut = nChangePosInOut2; - } - } - } - return res; -} - void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm) { LOCK(cs_wallet); @@ -3385,137 +2158,6 @@ void CWallet::MarkDestinationsDirty(const std::set<CTxDestination>& destinations } } -std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const -{ - std::map<CTxDestination, CAmount> balances; - - { - LOCK(cs_wallet); - std::set<uint256> trusted_parents; - for (const auto& walletEntry : mapWallet) - { - const CWalletTx& wtx = walletEntry.second; - - if (!IsTrusted(wtx, trusted_parents)) - continue; - - if (wtx.IsImmatureCoinBase()) - continue; - - int nDepth = wtx.GetDepthInMainChain(); - if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) - continue; - - for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) - { - CTxDestination addr; - if (!IsMine(wtx.tx->vout[i])) - continue; - if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) - continue; - - CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; - balances[addr] += n; - } - } - } - - return balances; -} - -std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const -{ - AssertLockHeld(cs_wallet); - std::set< std::set<CTxDestination> > groupings; - std::set<CTxDestination> grouping; - - for (const auto& walletEntry : 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(!IsMine(txin)) /* If this input isn't mine, ignore it */ - continue; - if(!ExtractDestination(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 (IsChange(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 (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; -} - std::set<CTxDestination> CWallet::GetLabelAddresses(const std::string& label) const { LOCK(cs_wallet); @@ -3744,45 +2386,45 @@ unsigned int CWallet::ComputeTimeSmart(const CWalletTx& wtx) const return nTimeSmart; } -bool CWallet::AddDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key, const std::string &value) +bool CWallet::SetAddressUsed(WalletBatch& batch, const CTxDestination& dest, bool used) { + const std::string key{"used"}; if (std::get_if<CNoDestination>(&dest)) return false; + if (!used) { + if (auto* data = util::FindKey(m_address_book, dest)) data->destdata.erase(key); + return batch.EraseDestData(EncodeDestination(dest), key); + } + + const std::string value{"1"}; m_address_book[dest].destdata.insert(std::make_pair(key, value)); return batch.WriteDestData(EncodeDestination(dest), key, value); } -bool CWallet::EraseDestData(WalletBatch& batch, const CTxDestination &dest, const std::string &key) -{ - if (!m_address_book[dest].destdata.erase(key)) - return false; - return batch.EraseDestData(EncodeDestination(dest), key); -} - void CWallet::LoadDestData(const CTxDestination &dest, const std::string &key, const std::string &value) { m_address_book[dest].destdata.insert(std::make_pair(key, value)); } -bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key, std::string *value) const +bool CWallet::IsAddressUsed(const CTxDestination& dest) const { + const std::string key{"used"}; std::map<CTxDestination, CAddressBookData>::const_iterator i = m_address_book.find(dest); if(i != m_address_book.end()) { CAddressBookData::StringMap::const_iterator j = i->second.destdata.find(key); if(j != i->second.destdata.end()) { - if(value) - *value = j->second; return true; } } return false; } -std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const +std::vector<std::string> CWallet::GetAddressReceiveRequests() const { + const std::string prefix{"rr"}; std::vector<std::string> values; for (const auto& address : m_address_book) { for (const auto& data : address.second.destdata) { @@ -3794,6 +2436,20 @@ std::vector<std::string> CWallet::GetDestValues(const std::string& prefix) const return values; } +bool CWallet::SetAddressReceiveRequest(WalletBatch& batch, const CTxDestination& dest, const std::string& id, const std::string& value) +{ + const std::string key{"rr" + id}; // "rr" prefix = "receive request" in destdata + CAddressBookData& data = m_address_book.at(dest); + if (value.empty()) { + if (!batch.EraseDestData(EncodeDestination(dest), key)) return false; + data.destdata.erase(key); + } else { + if (!batch.WriteDestData(EncodeDestination(dest), key, value)) return false; + data.destdata[key] = value; + } + return true; +} + std::unique_ptr<WalletDatabase> MakeWalletDatabase(const std::string& name, const DatabaseOptions& options, DatabaseStatus& status, bilingual_str& error_string) { // Do some checking on wallet path. It should be either a: @@ -4231,92 +2887,6 @@ bool CWalletTx::IsImmatureCoinBase() const return GetBlocksToMaturity() > 0; } -std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only) const -{ - std::vector<OutputGroup> groups_out; - - if (!coin_sel_params.m_avoid_partial_spends) { - // Allowing partial spends means no grouping. Each COutput gets its own OutputGroup. - for (const COutput& output : outputs) { - // Skip outputs we cannot spend - if (!output.fSpendable) continue; - - size_t ancestors, descendants; - chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); - CInputCoin input_coin = output.GetInputCoin(); - - // Make an OutputGroup containing just this output - OutputGroup group{coin_sel_params}; - group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); - - // Check the OutputGroup's eligibility. Only add the eligible ones. - if (positive_only && group.GetSelectionAmount() <= 0) continue; - if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group); - } - return groups_out; - } - - // We want to combine COutputs that have the same scriptPubKey into single OutputGroups - // except when there are more than OUTPUT_GROUP_MAX_ENTRIES COutputs grouped in an OutputGroup. - // To do this, we maintain a map where the key is the scriptPubKey and the value is a vector of OutputGroups. - // For each COutput, we check if the scriptPubKey is in the map, and if it is, the COutput's CInputCoin is added - // to the last OutputGroup in the vector for the scriptPubKey. When the last OutputGroup has - // OUTPUT_GROUP_MAX_ENTRIES CInputCoins, a new OutputGroup is added to the end of the vector. - std::map<CScript, std::vector<OutputGroup>> spk_to_groups_map; - for (const auto& output : outputs) { - // Skip outputs we cannot spend - if (!output.fSpendable) continue; - - size_t ancestors, descendants; - chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); - CInputCoin input_coin = output.GetInputCoin(); - CScript spk = input_coin.txout.scriptPubKey; - - std::vector<OutputGroup>& groups = spk_to_groups_map[spk]; - - if (groups.size() == 0) { - // No OutputGroups for this scriptPubKey yet, add one - groups.emplace_back(coin_sel_params); - } - - // Get the last OutputGroup in the vector so that we can add the CInputCoin to it - // A pointer is used here so that group can be reassigned later if it is full. - OutputGroup* group = &groups.back(); - - // Check if this OutputGroup is full. We limit to OUTPUT_GROUP_MAX_ENTRIES when using -avoidpartialspends - // to avoid surprising users with very high fees. - if (group->m_outputs.size() >= OUTPUT_GROUP_MAX_ENTRIES) { - // The last output group is full, add a new group to the vector and use that group for the insertion - groups.emplace_back(coin_sel_params); - group = &groups.back(); - } - - // Add the input_coin to group - group->Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); - } - - // Now we go through the entire map and pull out the OutputGroups - for (const auto& spk_and_groups_pair: spk_to_groups_map) { - const std::vector<OutputGroup>& groups_per_spk= spk_and_groups_pair.second; - - // Go through the vector backwards. This allows for the first item we deal with being the partial group. - for (auto group_it = groups_per_spk.rbegin(); group_it != groups_per_spk.rend(); group_it++) { - const OutputGroup& group = *group_it; - - // Don't include partial groups if there are full groups too and we don't want partial groups - if (group_it == groups_per_spk.rbegin() && groups_per_spk.size() > 1 && !filter.m_include_partial_groups) { - continue; - } - - // Check the OutputGroup's eligibility. Only add the eligible ones. - if (positive_only && group.GetSelectionAmount() <= 0) continue; - if (group.m_outputs.size() > 0 && group.EligibleForSpending(filter)) groups_out.push_back(group); - } - } - - return groups_out; -} - bool CWallet::IsCrypted() const { return HasEncryptionKeys(); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 9a572bc610..788a901f95 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -21,8 +21,11 @@ #include <validationinterface.h> #include <wallet/coinselection.h> #include <wallet/crypter.h> -#include <wallet/scriptpubkeyman.h> #include <external_signer.h> +#include <wallet/receive.h> +#include <wallet/scriptpubkeyman.h> +#include <wallet/spend.h> +#include <wallet/transaction.h> #include <wallet/walletdb.h> #include <wallet/walletutil.h> @@ -215,403 +218,6 @@ struct CRecipient bool fSubtractFeeFromAmount; }; -typedef std::map<std::string, std::string> mapValue_t; - - -static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue) -{ - if (!mapValue.count("n")) - { - nOrderPos = -1; // TODO: calculate elsewhere - return; - } - nOrderPos = atoi64(mapValue["n"]); -} - - -static inline void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue) -{ - if (nOrderPos == -1) - return; - mapValue["n"] = ToString(nOrderPos); -} - -struct COutputEntry -{ - CTxDestination destination; - CAmount amount; - int vout; -}; - -/** Legacy class used for deserializing vtxPrev for backwards compatibility. - * vtxPrev was removed in commit 93a18a3650292afbb441a47d1fa1b94aeb0164e3, - * but old wallet.dat files may still contain vtxPrev vectors of CMerkleTxs. - * These need to get deserialized for field alignment when deserializing - * a CWalletTx, but the deserialized values are discarded.**/ -class CMerkleTx -{ -public: - template<typename Stream> - void Unserialize(Stream& s) - { - CTransactionRef tx; - uint256 hashBlock; - std::vector<uint256> vMerkleBranch; - int nIndex; - - s >> tx >> hashBlock >> vMerkleBranch >> nIndex; - } -}; - -//Get the marginal bytes of spending the specified output -int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false); - -/** - * A transaction with a bunch of additional info that only the owner cares about. - * It includes any unrecorded transactions needed to link it back to the block chain. - */ -class CWalletTx -{ -private: - const CWallet* const pwallet; - - /** Constant used in hashBlock to indicate tx has been abandoned, only used at - * serialization/deserialization to avoid ambiguity with conflicted. - */ - static constexpr const uint256& ABANDON_HASH = uint256::ONE; - -public: - /** - * Key/value map with information about the transaction. - * - * The following keys can be read and written through the map and are - * serialized in the wallet database: - * - * "comment", "to" - comment strings provided to sendtoaddress, - * and sendmany wallet RPCs - * "replaces_txid" - txid (as HexStr) of transaction replaced by - * bumpfee on transaction created by bumpfee - * "replaced_by_txid" - txid (as HexStr) of transaction created by - * bumpfee on transaction replaced by bumpfee - * "from", "message" - obsolete fields that could be set in UI prior to - * 2011 (removed in commit 4d9b223) - * - * The following keys are serialized in the wallet database, but shouldn't - * be read or written through the map (they will be temporarily added and - * removed from the map during serialization): - * - * "fromaccount" - serialized strFromAccount value - * "n" - serialized nOrderPos value - * "timesmart" - serialized nTimeSmart value - * "spent" - serialized vfSpent value that existed prior to - * 2014 (removed in commit 93a18a3) - */ - mapValue_t mapValue; - std::vector<std::pair<std::string, std::string> > vOrderForm; - unsigned int fTimeReceivedIsTxTime; - unsigned int nTimeReceived; //!< time received by this node - /** - * Stable timestamp that never changes, and reflects the order a transaction - * was added to the wallet. Timestamp is based on the block time for a - * transaction added as part of a block, or else the time when the - * transaction was received if it wasn't part of a block, with the timestamp - * adjusted in both cases so timestamp order matches the order transactions - * were added to the wallet. More details can be found in - * CWallet::ComputeTimeSmart(). - */ - unsigned int nTimeSmart; - /** - * From me flag is set to 1 for transactions that were created by the wallet - * on this bitcoin node, and set to 0 for transactions that were created - * externally and came in through the network or sendrawtransaction RPC. - */ - bool fFromMe; - int64_t nOrderPos; //!< position in ordered transaction list - std::multimap<int64_t, CWalletTx*>::const_iterator m_it_wtxOrdered; - - // memory only - enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; - CAmount GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate = false) const; - mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS]; - /** - * This flag is true if all m_amounts caches are empty. This is particularly - * useful in places where MarkDirty is conditionally called and the - * condition can be expensive and thus can be skipped if the flag is true. - * See MarkDestinationsDirty. - */ - mutable bool m_is_cache_empty{true}; - mutable bool fChangeCached; - mutable bool fInMempool; - mutable CAmount nChangeCached; - - CWalletTx(const CWallet* wallet, CTransactionRef arg) - : pwallet(wallet), - tx(std::move(arg)) - { - Init(); - } - - void Init() - { - mapValue.clear(); - vOrderForm.clear(); - fTimeReceivedIsTxTime = false; - nTimeReceived = 0; - nTimeSmart = 0; - fFromMe = false; - fChangeCached = false; - fInMempool = false; - nChangeCached = 0; - nOrderPos = -1; - m_confirm = Confirmation{}; - } - - CTransactionRef tx; - - /** New transactions start as UNCONFIRMED. At BlockConnected, - * they will transition to CONFIRMED. In case of reorg, at BlockDisconnected, - * they roll back to UNCONFIRMED. If we detect a conflicting transaction at - * block connection, we update conflicted tx and its dependencies as CONFLICTED. - * If tx isn't confirmed and outside of mempool, the user may switch it to ABANDONED - * by using the abandontransaction call. This last status may be override by a CONFLICTED - * or CONFIRMED transition. - */ - enum Status { - UNCONFIRMED, - CONFIRMED, - CONFLICTED, - ABANDONED - }; - - /** Confirmation includes tx status and a triplet of {block height/block hash/tx index in block} - * at which tx has been confirmed. All three are set to 0 if tx is unconfirmed or abandoned. - * Meaning of these fields changes with CONFLICTED state where they instead point to block hash - * and block height of the deepest conflicting tx. - */ - struct Confirmation { - Status status; - int block_height; - uint256 hashBlock; - int nIndex; - Confirmation(Status s = UNCONFIRMED, int b = 0, uint256 h = uint256(), int i = 0) : status(s), block_height(b), hashBlock(h), nIndex(i) {} - }; - - Confirmation m_confirm; - - template<typename Stream> - void Serialize(Stream& s) const - { - mapValue_t mapValueCopy = mapValue; - - mapValueCopy["fromaccount"] = ""; - WriteOrderPos(nOrderPos, mapValueCopy); - if (nTimeSmart) { - mapValueCopy["timesmart"] = strprintf("%u", nTimeSmart); - } - - std::vector<char> dummy_vector1; //!< Used to be vMerkleBranch - std::vector<char> dummy_vector2; //!< Used to be vtxPrev - bool dummy_bool = false; //!< Used to be fSpent - uint256 serializedHash = isAbandoned() ? ABANDON_HASH : m_confirm.hashBlock; - int serializedIndex = isAbandoned() || isConflicted() ? -1 : m_confirm.nIndex; - s << tx << serializedHash << dummy_vector1 << serializedIndex << dummy_vector2 << mapValueCopy << vOrderForm << fTimeReceivedIsTxTime << nTimeReceived << fFromMe << dummy_bool; - } - - template<typename Stream> - void Unserialize(Stream& s) - { - Init(); - - std::vector<uint256> dummy_vector1; //!< Used to be vMerkleBranch - std::vector<CMerkleTx> dummy_vector2; //!< Used to be vtxPrev - bool dummy_bool; //! Used to be fSpent - int serializedIndex; - s >> tx >> m_confirm.hashBlock >> dummy_vector1 >> serializedIndex >> dummy_vector2 >> mapValue >> vOrderForm >> fTimeReceivedIsTxTime >> nTimeReceived >> fFromMe >> dummy_bool; - - /* At serialization/deserialization, an nIndex == -1 means that hashBlock refers to - * the earliest block in the chain we know this or any in-wallet ancestor conflicts - * with. If nIndex == -1 and hashBlock is ABANDON_HASH, it means transaction is abandoned. - * In same context, an nIndex >= 0 refers to a confirmed transaction (if hashBlock set) or - * unconfirmed one. Older clients interpret nIndex == -1 as unconfirmed for backward - * compatibility (pre-commit 9ac63d6). - */ - if (serializedIndex == -1 && m_confirm.hashBlock == ABANDON_HASH) { - setAbandoned(); - } else if (serializedIndex == -1) { - setConflicted(); - } else if (!m_confirm.hashBlock.IsNull()) { - m_confirm.nIndex = serializedIndex; - setConfirmed(); - } - - ReadOrderPos(nOrderPos, mapValue); - nTimeSmart = mapValue.count("timesmart") ? (unsigned int)atoi64(mapValue["timesmart"]) : 0; - - mapValue.erase("fromaccount"); - mapValue.erase("spent"); - mapValue.erase("n"); - mapValue.erase("timesmart"); - } - - void SetTx(CTransactionRef arg) - { - tx = std::move(arg); - } - - //! make sure balances are recalculated - void MarkDirty() - { - m_amounts[DEBIT].Reset(); - m_amounts[CREDIT].Reset(); - m_amounts[IMMATURE_CREDIT].Reset(); - m_amounts[AVAILABLE_CREDIT].Reset(); - fChangeCached = false; - m_is_cache_empty = true; - } - - //! filter decides which addresses will count towards the debit - CAmount GetDebit(const isminefilter& filter) const; - CAmount GetCredit(const isminefilter& filter) const; - CAmount GetImmatureCredit(bool fUseCache = true) const; - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The - // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid - // having to resolve the issue of member access into incomplete type CWallet. - CAmount GetAvailableCredit(bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS; - CAmount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const; - CAmount GetChange() const; - - /** Get the marginal bytes if spending the specified output from this transaction */ - int GetSpendSize(unsigned int out, bool use_max_sig = false) const - { - return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig); - } - - void GetAmounts(std::list<COutputEntry>& listReceived, - std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const; - - bool IsFromMe(const isminefilter& filter) const - { - return (GetDebit(filter) > 0); - } - - /** True if only scriptSigs are different */ - bool IsEquivalentTo(const CWalletTx& tx) const; - - bool InMempool() const; - bool IsTrusted() const; - - int64_t GetTxTime() const; - - /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */ - bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay); - - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS; - - /** - * Return depth of transaction in blockchain: - * <0 : conflicts with a transaction this deep in the blockchain - * 0 : in memory pool, waiting to be included in a block - * >=1 : this many blocks deep in the main chain - */ - // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct - // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation - // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to - // resolve the issue of member access into incomplete type CWallet. Note - // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)" - // in place. - int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS; - bool IsInMainChain() const { return GetDepthInMainChain() > 0; } - - /** - * @return number of blocks to maturity for this transaction: - * 0 : is not a coinbase transaction, or is a mature coinbase transaction - * >0 : is a coinbase transaction which matures in this many blocks - */ - int GetBlocksToMaturity() const; - bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; } - void setAbandoned() - { - m_confirm.status = CWalletTx::ABANDONED; - m_confirm.hashBlock = uint256(); - m_confirm.block_height = 0; - m_confirm.nIndex = 0; - } - bool isConflicted() const { return m_confirm.status == CWalletTx::CONFLICTED; } - void setConflicted() { m_confirm.status = CWalletTx::CONFLICTED; } - bool isUnconfirmed() const { return m_confirm.status == CWalletTx::UNCONFIRMED; } - void setUnconfirmed() { m_confirm.status = CWalletTx::UNCONFIRMED; } - bool isConfirmed() const { return m_confirm.status == CWalletTx::CONFIRMED; } - void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } - const uint256& GetHash() const { return tx->GetHash(); } - bool IsCoinBase() const { return tx->IsCoinBase(); } - bool IsImmatureCoinBase() const; - - // Disable copying of CWalletTx objects to prevent bugs where instances get - // copied in and out of the mapWallet map, and fields are updated in the - // wrong copy. - CWalletTx(CWalletTx const &) = delete; - void operator=(CWalletTx const &x) = delete; -}; - -class COutput -{ -public: - const CWalletTx *tx; - - /** Index in tx->vout. */ - int i; - - /** - * Depth in block chain. - * If > 0: the tx is on chain and has this many confirmations. - * If = 0: the tx is waiting confirmation. - * If < 0: a conflicting tx is on chain and has this many confirmations. */ - int nDepth; - - /** Pre-computed estimated size of this output as a fully-signed input in a transaction. Can be -1 if it could not be calculated */ - int nInputBytes; - - /** Whether we have the private keys to spend this output */ - bool fSpendable; - - /** Whether we know how to spend this output, ignoring the lack of keys */ - bool fSolvable; - - /** Whether to use the maximum sized, 72 byte signature when calculating the size of the input spend. This should only be set when watch-only outputs are allowed */ - bool use_max_sig; - - /** - * Whether this output is considered safe to spend. Unconfirmed transactions - * from outside keys and unconfirmed replacement transactions are considered - * unsafe and will not be used to fund new spending transactions. - */ - bool fSafe; - - COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) - { - tx = txIn; i = iIn; nDepth = nDepthIn; fSpendable = fSpendableIn; fSolvable = fSolvableIn; fSafe = fSafeIn; nInputBytes = -1; use_max_sig = use_max_sig_in; - // If known and signable by the given wallet, compute nInputBytes - // Failure will keep this value -1 - if (fSpendable && tx) { - nInputBytes = tx->GetSpendSize(i, use_max_sig); - } - } - - std::string ToString() const; - - inline CInputCoin GetInputCoin() const - { - return CInputCoin(tx->tx, i, nInputBytes); - } -}; - class WalletRescanReserver; //forward declarations for ScanForWalletTransactions/RescanFromTime /** * A CWallet maintains a set of transactions and balances, and provides the ability to create new transactions. @@ -621,7 +227,6 @@ class CWallet final : public WalletStorage, public interfaces::Chain::Notificati private: CKeyingMaterial vMasterKey GUARDED_BY(cs_wallet); - bool Unlock(const CKeyingMaterial& vMasterKeyIn, bool accept_no_keys = false); std::atomic<bool> fAbortRescan{false}; @@ -874,19 +479,8 @@ public: bool LoadMinVersion(int nVersion) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); nWalletVersion = nVersion; return true; } - /** - * Adds a destination data tuple to the store, and saves it to disk - * When adding new fields, take care to consider how DelAddressBook should handle it! - */ - bool AddDestData(WalletBatch& batch, const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - //! Erases a destination data tuple in the store and on disk - bool EraseDestData(WalletBatch& batch, const CTxDestination& dest, const std::string& key) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Adds a destination data tuple to the store, without saving it to disk void LoadDestData(const CTxDestination& dest, const std::string& key, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - //! Look up a destination data tuple in the store, return true if found false otherwise - bool GetDestData(const CTxDestination& dest, const std::string& key, std::string* value) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - //! Get all destination values matching a prefix. - std::vector<std::string> GetDestValues(const std::string& prefix) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! Holds a timestamp at which point the wallet is scheduled (externally) to be relocked. Caller must arrange for actual relocking to occur via Lock(). int64_t nRelockTime GUARDED_BY(cs_wallet){0}; @@ -1100,6 +694,12 @@ public: bool DelAddressBook(const CTxDestination& address); + bool IsAddressUsed(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool SetAddressUsed(WalletBatch& batch, const CTxDestination& dest, bool used) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + + std::vector<std::string> GetAddressReceiveRequests() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + bool SetAddressReceiveRequest(WalletBatch& batch, const CTxDestination& dest, const std::string& id, const std::string& value) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); + unsigned int GetKeyPoolSize() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); //! signify that a particular wallet feature is now used. diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 3d9248009f..c06b319b0b 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -155,7 +155,7 @@ bool WalletBatch::WriteWatchOnly(const CScript &dest, const CKeyMetadata& keyMet if (!WriteIC(std::make_pair(DBKeys::WATCHMETA, dest), keyMeta)) { return false; } - return WriteIC(std::make_pair(DBKeys::WATCHS, dest), '1'); + return WriteIC(std::make_pair(DBKeys::WATCHS, dest), uint8_t{'1'}); } bool WalletBatch::EraseWatchOnly(const CScript &dest) @@ -308,8 +308,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, { if (!ssValue.empty()) { - char fTmp; - char fUnused; + uint8_t fTmp; + uint8_t fUnused; std::string unused_string; ssValue >> fTmp >> fUnused >> unused_string; strErr = strprintf("LoadWallet() upgrading tx ver=%d %d %s", @@ -336,7 +336,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, wss.nWatchKeys++; CScript script; ssKey >> script; - char fYes; + uint8_t fYes; ssValue >> fYes; if (fYes == '1') { pwallet->GetOrCreateLegacyScriptPubKeyMan()->LoadWatchOnly(script); |