refactor: Detach wallet transaction methods (followup for move-only)

Followup to commit "MOVEONLY: CWallet transaction code out of
wallet.cpp/.h" that detaches and renames some CWalletTx methods, making
into them into standalone functions or CWallet methods instead.

There are no changes in behavior and no code changes that aren't purely
mechanical. It just gives spend and receive functions more consistent
names and removes the circular dependencies added by the earlier
MOVEONLY commit.

There are also no comment or documentation changes. Removed comments
from transaction.h are just migrated to spend.h, receive.h, and
wallet.h.
This commit is contained in:
Russell Yanofsky 2021-02-12 18:01:22 -05:00
parent b3a2b8c29f
commit b11a195ef4
20 changed files with 504 additions and 492 deletions

View File

@ -6,6 +6,7 @@
#include <interfaces/chain.h> #include <interfaces/chain.h>
#include <node/context.h> #include <node/context.h>
#include <wallet/coinselection.h> #include <wallet/coinselection.h>
#include <wallet/spend.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
#include <set> #include <set>
@ -17,7 +18,7 @@ static void addCoin(const CAmount& nValue, const CWallet& wallet, std::vector<st
tx.nLockTime = nextLockTime++; // so all transactions get different hashes tx.nLockTime = nextLockTime++; // so all transactions get different hashes
tx.vout.resize(1); tx.vout.resize(1);
tx.vout[0].nValue = nValue; tx.vout[0].nValue = nValue;
wtxs.push_back(std::make_unique<CWalletTx>(&wallet, MakeTransactionRef(std::move(tx)))); wtxs.push_back(std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx))));
} }
// Simple benchmark for wallet coin selection. Note that it maybe be necessary // Simple benchmark for wallet coin selection. Note that it maybe be necessary
@ -45,7 +46,7 @@ static void CoinSelection(benchmark::Bench& bench)
// Create coins // Create coins
std::vector<COutput> coins; std::vector<COutput> coins;
for (const auto& wtx : wtxs) { for (const auto& wtx : wtxs) {
coins.emplace_back(wtx.get(), 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */); coins.emplace_back(wallet, *wtx, 0 /* iIn */, 6 * 24 /* nDepthIn */, true /* spendable */, true /* solvable */, true /* safe */);
} }
const CoinEligibilityFilter filter_standard(1, 6, 0); const CoinEligibilityFilter filter_standard(1, 6, 0);
@ -56,7 +57,7 @@ static void CoinSelection(benchmark::Bench& bench)
bench.run([&] { bench.run([&] {
std::set<CInputCoin> setCoinsRet; std::set<CInputCoin> setCoinsRet;
CAmount nValueRet; CAmount nValueRet;
bool success = wallet.AttemptSelection(1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params); bool success = AttemptSelection(wallet, 1003 * COIN, filter_standard, coins, setCoinsRet, nValueRet, coin_selection_params);
assert(success); assert(success);
assert(nValueRet == 1003 * COIN); assert(nValueRet == 1003 * COIN);
assert(setCoinsRet.size() == 2); assert(setCoinsRet.size() == 2);
@ -75,9 +76,9 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>
CMutableTransaction tx; CMutableTransaction tx;
tx.vout.resize(nInput + 1); tx.vout.resize(nInput + 1);
tx.vout[nInput].nValue = nValue; tx.vout[nInput].nValue = nValue;
std::unique_ptr<CWalletTx> wtx = std::make_unique<CWalletTx>(&testWallet, MakeTransactionRef(std::move(tx))); std::unique_ptr<CWalletTx> wtx = std::make_unique<CWalletTx>(MakeTransactionRef(std::move(tx)));
set.emplace_back(); set.emplace_back();
set.back().Insert(COutput(wtx.get(), nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0, false); set.back().Insert(COutput(testWallet, *wtx, nInput, 0, true, true, true).GetInputCoin(), 0, true, 0, 0, false);
wtxn.emplace_back(std::move(wtx)); wtxn.emplace_back(std::move(wtx));
} }
// Copied from src/wallet/test/coinselector_tests.cpp // Copied from src/wallet/test/coinselector_tests.cpp

View File

@ -9,6 +9,7 @@
#include <test/util/setup_common.h> #include <test/util/setup_common.h>
#include <test/util/wallet.h> #include <test/util/wallet.h>
#include <validationinterface.h> #include <validationinterface.h>
#include <wallet/receive.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
#include <optional> #include <optional>
@ -35,11 +36,11 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
} }
SyncWithValidationInterfaceQueue(); SyncWithValidationInterfaceQueue();
auto bal = wallet.GetBalance(); // Cache auto bal = GetBalance(wallet); // Cache
bench.run([&] { bench.run([&] {
if (set_dirty) wallet.MarkDirty(); if (set_dirty) wallet.MarkDirty();
bal = wallet.GetBalance(); bal = GetBalance(wallet);
if (add_mine) assert(bal.m_mine_trusted > 0); if (add_mine) assert(bal.m_mine_trusted > 0);
if (add_watchonly) assert(bal.m_watchonly_trusted > 0); if (add_watchonly) assert(bal.m_watchonly_trusted > 0);
}); });

View File

@ -12,6 +12,8 @@
#include <wallet/coincontrol.h> #include <wallet/coincontrol.h>
#include <wallet/feebumper.h> #include <wallet/feebumper.h>
#include <wallet/fees.h> #include <wallet/fees.h>
#include <wallet/receive.h>
#include <wallet/spend.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
//! Check whether transaction has descendant in wallet or mempool, or has been //! Check whether transaction has descendant in wallet or mempool, or has been
@ -30,7 +32,7 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet
} }
} }
if (wtx.GetDepthInMainChain() != 0) { if (wallet.GetTxDepthInMainChain(wtx) != 0) {
errors.push_back(Untranslated("Transaction has been mined, or is conflicted with a mined transaction")); errors.push_back(Untranslated("Transaction has been mined, or is conflicted with a mined transaction"));
return feebumper::Result::WALLET_ERROR; return feebumper::Result::WALLET_ERROR;
} }
@ -48,7 +50,7 @@ static feebumper::Result PreconditionChecks(const CWallet& wallet, const CWallet
// check that original tx consists entirely of our inputs // check that original tx consists entirely of our inputs
// if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee) // if not, we can't bump the fee, because the wallet has no way of knowing the value of the other inputs (thus the fee)
isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
if (!wallet.IsAllFromMe(*wtx.tx, filter)) { if (!AllInputsMine(wallet, *wtx.tx, filter)) {
errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet")); errors.push_back(Untranslated("Transaction contains inputs that don't belong to this wallet"));
return feebumper::Result::WALLET_ERROR; return feebumper::Result::WALLET_ERROR;
} }
@ -81,7 +83,7 @@ static feebumper::Result CheckFeeRate(const CWallet& wallet, const CWalletTx& wt
// Given old total fee and transaction size, calculate the old feeRate // Given old total fee and transaction size, calculate the old feeRate
isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
CAmount old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut(); CAmount old_fee = CachedTxGetDebit(wallet, wtx, filter) - wtx.tx->GetValueOut();
const int64_t txSize = GetVirtualTransactionSize(*(wtx.tx)); const int64_t txSize = GetVirtualTransactionSize(*(wtx.tx));
CFeeRate nOldFeeRate(old_fee, txSize); CFeeRate nOldFeeRate(old_fee, txSize);
// Min total fee is old fee + relay fee // Min total fee is old fee + relay fee
@ -174,7 +176,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
// Fill in recipients(and preserve a single change key if there is one) // Fill in recipients(and preserve a single change key if there is one)
std::vector<CRecipient> recipients; std::vector<CRecipient> recipients;
for (const auto& output : wtx.tx->vout) { for (const auto& output : wtx.tx->vout) {
if (!wallet.IsChange(output)) { if (!OutputIsChange(wallet, output)) {
CRecipient recipient = {output.scriptPubKey, output.nValue, false}; CRecipient recipient = {output.scriptPubKey, output.nValue, false};
recipients.push_back(recipient); recipients.push_back(recipient);
} else { } else {
@ -185,7 +187,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
} }
isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; isminefilter filter = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut(); old_fee = CachedTxGetDebit(wallet, wtx, filter) - wtx.tx->GetValueOut();
if (coin_control.m_feerate) { if (coin_control.m_feerate) {
// The user provided a feeRate argument. // The user provided a feeRate argument.
@ -220,7 +222,7 @@ Result CreateRateBumpTransaction(CWallet& wallet, const uint256& txid, const CCo
int change_pos_in_out = -1; // No requested location for change int change_pos_in_out = -1; // No requested location for change
bilingual_str fail_reason; bilingual_str fail_reason;
FeeCalculation fee_calc_out; FeeCalculation fee_calc_out;
if (!wallet.CreateTransaction(recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) { if (!CreateTransaction(wallet, recipients, tx_new, fee_ret, change_pos_in_out, fail_reason, new_coin_control, fee_calc_out, false)) {
errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason); errors.push_back(Untranslated("Unable to create transaction.") + Untranslated(" ") + fail_reason);
return Result::WALLET_ERROR; return Result::WALLET_ERROR;
} }

View File

@ -23,7 +23,9 @@
#include <wallet/fees.h> #include <wallet/fees.h>
#include <wallet/ismine.h> #include <wallet/ismine.h>
#include <wallet/load.h> #include <wallet/load.h>
#include <wallet/receive.h>
#include <wallet/rpcwallet.h> #include <wallet/rpcwallet.h>
#include <wallet/spend.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
#include <memory> #include <memory>
@ -55,7 +57,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
result.tx = wtx.tx; result.tx = wtx.tx;
result.txin_is_mine.reserve(wtx.tx->vin.size()); result.txin_is_mine.reserve(wtx.tx->vin.size());
for (const auto& txin : wtx.tx->vin) { for (const auto& txin : wtx.tx->vin) {
result.txin_is_mine.emplace_back(wallet.IsMine(txin)); result.txin_is_mine.emplace_back(InputIsMine(wallet, txin));
} }
result.txout_is_mine.reserve(wtx.tx->vout.size()); result.txout_is_mine.reserve(wtx.tx->vout.size());
result.txout_address.reserve(wtx.tx->vout.size()); result.txout_address.reserve(wtx.tx->vout.size());
@ -67,9 +69,9 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
wallet.IsMine(result.txout_address.back()) : wallet.IsMine(result.txout_address.back()) :
ISMINE_NO); ISMINE_NO);
} }
result.credit = wtx.GetCredit(ISMINE_ALL); result.credit = CachedTxGetCredit(wallet, wtx, ISMINE_ALL);
result.debit = wtx.GetDebit(ISMINE_ALL); result.debit = CachedTxGetDebit(wallet, wtx, ISMINE_ALL);
result.change = wtx.GetChange(); result.change = CachedTxGetChange(wallet, wtx);
result.time = wtx.GetTxTime(); result.time = wtx.GetTxTime();
result.value_map = wtx.mapValue; result.value_map = wtx.mapValue;
result.is_coinbase = wtx.IsCoinBase(); result.is_coinbase = wtx.IsCoinBase();
@ -81,15 +83,15 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
{ {
WalletTxStatus result; WalletTxStatus result;
result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits<int>::max(); result.block_height = wtx.m_confirm.block_height > 0 ? wtx.m_confirm.block_height : std::numeric_limits<int>::max();
result.blocks_to_maturity = wtx.GetBlocksToMaturity(); result.blocks_to_maturity = wallet.GetTxBlocksToMaturity(wtx);
result.depth_in_main_chain = wtx.GetDepthInMainChain(); result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx);
result.time_received = wtx.nTimeReceived; result.time_received = wtx.nTimeReceived;
result.lock_time = wtx.tx->nLockTime; result.lock_time = wtx.tx->nLockTime;
result.is_final = wallet.chain().checkFinalTx(*wtx.tx); result.is_final = wallet.chain().checkFinalTx(*wtx.tx);
result.is_trusted = wtx.IsTrusted(); result.is_trusted = CachedTxIsTrusted(wallet, wtx);
result.is_abandoned = wtx.isAbandoned(); result.is_abandoned = wtx.isAbandoned();
result.is_coinbase = wtx.IsCoinBase(); result.is_coinbase = wtx.IsCoinBase();
result.is_in_main_chain = wtx.IsInMainChain(); result.is_in_main_chain = wallet.IsTxInMainChain(wtx);
return result; return result;
} }
@ -242,7 +244,7 @@ public:
LOCK(m_wallet->cs_wallet); LOCK(m_wallet->cs_wallet);
CTransactionRef tx; CTransactionRef tx;
FeeCalculation fee_calc_out; FeeCalculation fee_calc_out;
if (!m_wallet->CreateTransaction(recipients, tx, fee, change_pos, if (!CreateTransaction(*m_wallet, recipients, tx, fee, change_pos,
fail_reason, coin_control, fee_calc_out, sign)) { fail_reason, coin_control, fee_calc_out, sign)) {
return {}; return {};
} }
@ -358,7 +360,7 @@ public:
} }
WalletBalances getBalances() override WalletBalances getBalances() override
{ {
const auto bal = m_wallet->GetBalance(); const auto bal = GetBalance(*m_wallet);
WalletBalances result; WalletBalances result;
result.balance = bal.m_mine_trusted; result.balance = bal.m_mine_trusted;
result.unconfirmed_balance = bal.m_mine_untrusted_pending; result.unconfirmed_balance = bal.m_mine_untrusted_pending;
@ -381,15 +383,15 @@ public:
balances = getBalances(); balances = getBalances();
return true; return true;
} }
CAmount getBalance() override { return m_wallet->GetBalance().m_mine_trusted; } CAmount getBalance() override { return GetBalance(*m_wallet).m_mine_trusted; }
CAmount getAvailableBalance(const CCoinControl& coin_control) override CAmount getAvailableBalance(const CCoinControl& coin_control) override
{ {
return m_wallet->GetAvailableBalance(&coin_control); return GetAvailableBalance(*m_wallet, &coin_control);
} }
isminetype txinIsMine(const CTxIn& txin) override isminetype txinIsMine(const CTxIn& txin) override
{ {
LOCK(m_wallet->cs_wallet); LOCK(m_wallet->cs_wallet);
return m_wallet->IsMine(txin); return InputIsMine(*m_wallet, txin);
} }
isminetype txoutIsMine(const CTxOut& txout) override isminetype txoutIsMine(const CTxOut& txout) override
{ {
@ -404,13 +406,13 @@ public:
CAmount getCredit(const CTxOut& txout, isminefilter filter) override CAmount getCredit(const CTxOut& txout, isminefilter filter) override
{ {
LOCK(m_wallet->cs_wallet); LOCK(m_wallet->cs_wallet);
return m_wallet->GetCredit(txout, filter); return OutputGetCredit(*m_wallet, txout, filter);
} }
CoinsList listCoins() override CoinsList listCoins() override
{ {
LOCK(m_wallet->cs_wallet); LOCK(m_wallet->cs_wallet);
CoinsList result; CoinsList result;
for (const auto& entry : m_wallet->ListCoins()) { for (const auto& entry : ListCoins(*m_wallet)) {
auto& group = result[entry.first]; auto& group = result[entry.first];
for (const auto& coin : entry.second) { for (const auto& coin : entry.second) {
group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i), group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i),
@ -428,7 +430,7 @@ public:
result.emplace_back(); result.emplace_back();
auto it = m_wallet->mapWallet.find(output.hash); auto it = m_wallet->mapWallet.find(output.hash);
if (it != m_wallet->mapWallet.end()) { if (it != m_wallet->mapWallet.end()) {
int depth = it->second.GetDepthInMainChain(); int depth = m_wallet->GetTxDepthInMainChain(it->second);
if (depth >= 0) { if (depth >= 0) {
result.back() = MakeWalletTxOut(*m_wallet, it->second, output.n, depth); result.back() = MakeWalletTxOut(*m_wallet, it->second, output.n, depth);
} }

View File

@ -13,6 +13,7 @@
#include <util/system.h> #include <util/system.h>
#include <util/translation.h> #include <util/translation.h>
#include <wallet/context.h> #include <wallet/context.h>
#include <wallet/spend.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
#include <wallet/walletdb.h> #include <wallet/walletdb.h>

View File

@ -7,27 +7,27 @@
#include <wallet/transaction.h> #include <wallet/transaction.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
isminetype CWallet::IsMine(const CTxIn &txin) const isminetype InputIsMine(const CWallet& wallet, const CTxIn &txin)
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(wallet.cs_wallet);
std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash); std::map<uint256, CWalletTx>::const_iterator mi = wallet.mapWallet.find(txin.prevout.hash);
if (mi != mapWallet.end()) if (mi != wallet.mapWallet.end())
{ {
const CWalletTx& prev = (*mi).second; const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.tx->vout.size()) if (txin.prevout.n < prev.tx->vout.size())
return IsMine(prev.tx->vout[txin.prevout.n]); return wallet.IsMine(prev.tx->vout[txin.prevout.n]);
} }
return ISMINE_NO; return ISMINE_NO;
} }
bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter)
{ {
LOCK(cs_wallet); LOCK(wallet.cs_wallet);
for (const CTxIn& txin : tx.vin) for (const CTxIn& txin : tx.vin)
{ {
auto mi = mapWallet.find(txin.prevout.hash); auto mi = wallet.mapWallet.find(txin.prevout.hash);
if (mi == mapWallet.end()) if (mi == wallet.mapWallet.end())
return false; // any unknown inputs can't be from us return false; // any unknown inputs can't be from us
const CWalletTx& prev = (*mi).second; const CWalletTx& prev = (*mi).second;
@ -35,33 +35,33 @@ bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) co
if (txin.prevout.n >= prev.tx->vout.size()) if (txin.prevout.n >= prev.tx->vout.size())
return false; // invalid input! return false; // invalid input!
if (!(IsMine(prev.tx->vout[txin.prevout.n]) & filter)) if (!(wallet.IsMine(prev.tx->vout[txin.prevout.n]) & filter))
return false; return false;
} }
return true; return true;
} }
CAmount CWallet::GetCredit(const CTxOut& txout, const isminefilter& filter) const CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter)
{ {
if (!MoneyRange(txout.nValue)) if (!MoneyRange(txout.nValue))
throw std::runtime_error(std::string(__func__) + ": value out of range"); throw std::runtime_error(std::string(__func__) + ": value out of range");
LOCK(cs_wallet); LOCK(wallet.cs_wallet);
return ((IsMine(txout) & filter) ? txout.nValue : 0); return ((wallet.IsMine(txout) & filter) ? txout.nValue : 0);
} }
CAmount CWallet::GetCredit(const CTransaction& tx, const isminefilter& filter) const CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter)
{ {
CAmount nCredit = 0; CAmount nCredit = 0;
for (const CTxOut& txout : tx.vout) for (const CTxOut& txout : tx.vout)
{ {
nCredit += GetCredit(txout, filter); nCredit += OutputGetCredit(wallet, txout, filter);
if (!MoneyRange(nCredit)) if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range"); throw std::runtime_error(std::string(__func__) + ": value out of range");
} }
return nCredit; return nCredit;
} }
bool CWallet::IsChange(const CScript& script) const bool ScriptIsChange(const CWallet& wallet, const CScript& script)
{ {
// TODO: fix handling of 'change' outputs. The assumption is that any // 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 // payment to a script that is ours, but is not in the address book
@ -70,179 +70,177 @@ bool CWallet::IsChange(const CScript& script) const
// a better way of identifying which outputs are 'the send' and which are // 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 // 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change). // which output, if any, was change).
AssertLockHeld(cs_wallet); AssertLockHeld(wallet.cs_wallet);
if (IsMine(script)) if (wallet.IsMine(script))
{ {
CTxDestination address; CTxDestination address;
if (!ExtractDestination(script, address)) if (!ExtractDestination(script, address))
return true; return true;
if (!FindAddressBookEntry(address)) { if (!wallet.FindAddressBookEntry(address)) {
return true; return true;
} }
} }
return false; return false;
} }
bool CWallet::IsChange(const CTxOut& txout) const bool OutputIsChange(const CWallet& wallet, const CTxOut& txout)
{ {
return IsChange(txout.scriptPubKey); return ScriptIsChange(wallet, txout.scriptPubKey);
} }
CAmount CWallet::GetChange(const CTxOut& txout) const CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout)
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(wallet.cs_wallet);
if (!MoneyRange(txout.nValue)) if (!MoneyRange(txout.nValue))
throw std::runtime_error(std::string(__func__) + ": value out of range"); throw std::runtime_error(std::string(__func__) + ": value out of range");
return (IsChange(txout) ? txout.nValue : 0); return (OutputIsChange(wallet, txout) ? txout.nValue : 0);
} }
CAmount CWallet::GetChange(const CTransaction& tx) const CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx)
{ {
LOCK(cs_wallet); LOCK(wallet.cs_wallet);
CAmount nChange = 0; CAmount nChange = 0;
for (const CTxOut& txout : tx.vout) for (const CTxOut& txout : tx.vout)
{ {
nChange += GetChange(txout); nChange += OutputGetChange(wallet, txout);
if (!MoneyRange(nChange)) if (!MoneyRange(nChange))
throw std::runtime_error(std::string(__func__) + ": value out of range"); throw std::runtime_error(std::string(__func__) + ": value out of range");
} }
return nChange; return nChange;
} }
CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate) const static CAmount GetCachableAmount(const CWallet& wallet, const CWalletTx& wtx, CWalletTx::AmountType type, const isminefilter& filter, bool recalculate = false)
{ {
auto& amount = m_amounts[type]; auto& amount = wtx.m_amounts[type];
if (recalculate || !amount.m_cached[filter]) { if (recalculate || !amount.m_cached[filter]) {
amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter)); amount.Set(filter, type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx, filter) : TxGetCredit(wallet, *wtx.tx, filter));
m_is_cache_empty = false; wtx.m_is_cache_empty = false;
} }
return amount.m_value[filter]; return amount.m_value[filter];
} }
CAmount CWalletTx::GetCredit(const isminefilter& filter) const CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
{ {
// Must wait until coinbase is safely deep enough in the chain before valuing it // Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsImmatureCoinBase()) if (wallet.IsTxImmatureCoinBase(wtx))
return 0; return 0;
CAmount credit = 0; CAmount credit = 0;
if (filter & ISMINE_SPENDABLE) { if (filter & ISMINE_SPENDABLE) {
// GetBalance can assume transactions in mapWallet won't change // GetBalance can assume transactions in mapWallet won't change
credit += GetCachableAmount(CREDIT, ISMINE_SPENDABLE); credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_SPENDABLE);
} }
if (filter & ISMINE_WATCH_ONLY) { if (filter & ISMINE_WATCH_ONLY) {
credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY); credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_WATCH_ONLY);
} }
return credit; return credit;
} }
CAmount CWalletTx::GetDebit(const isminefilter& filter) const CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
{ {
if (tx->vin.empty()) if (wtx.tx->vin.empty())
return 0; return 0;
CAmount debit = 0; CAmount debit = 0;
if (filter & ISMINE_SPENDABLE) { if (filter & ISMINE_SPENDABLE) {
debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE); debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_SPENDABLE);
} }
if (filter & ISMINE_WATCH_ONLY) { if (filter & ISMINE_WATCH_ONLY) {
debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY); debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_WATCH_ONLY);
} }
return debit; return debit;
} }
CAmount CWalletTx::GetChange() const CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx)
{ {
if (fChangeCached) if (wtx.fChangeCached)
return nChangeCached; return wtx.nChangeCached;
nChangeCached = pwallet->GetChange(*tx); wtx.nChangeCached = TxGetChange(wallet, *wtx.tx);
fChangeCached = true; wtx.fChangeCached = true;
return nChangeCached; return wtx.nChangeCached;
} }
CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache)
{ {
if (IsImmatureCoinBase() && IsInMainChain()) { if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache); return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
} }
return 0; return 0;
} }
CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache)
{ {
if (IsImmatureCoinBase() && IsInMainChain()) { if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) {
return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache); return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache);
} }
return 0; return 0;
} }
CAmount CWalletTx::GetAvailableCredit(bool fUseCache, const isminefilter& filter) const CAmount CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache, const isminefilter& filter)
{ {
if (pwallet == nullptr)
return 0;
// Avoid caching ismine for NO or ALL cases (could remove this check and simplify in the future). // 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; 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 // Must wait until coinbase is safely deep enough in the chain before valuing it
if (IsImmatureCoinBase()) if (wallet.IsTxImmatureCoinBase(wtx))
return 0; return 0;
if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) { if (fUseCache && allow_cache && wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_cached[filter]) {
return m_amounts[AVAILABLE_CREDIT].m_value[filter]; return wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_value[filter];
} }
bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE); bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
CAmount nCredit = 0; CAmount nCredit = 0;
uint256 hashTx = GetHash(); uint256 hashTx = wtx.GetHash();
for (unsigned int i = 0; i < tx->vout.size(); i++) for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
{ {
if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) { if (!wallet.IsSpent(hashTx, i) && (allow_used_addresses || !wallet.IsSpentKey(hashTx, i))) {
const CTxOut &txout = tx->vout[i]; const CTxOut &txout = wtx.tx->vout[i];
nCredit += pwallet->GetCredit(txout, filter); nCredit += OutputGetCredit(wallet, txout, filter);
if (!MoneyRange(nCredit)) if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + " : value out of range"); throw std::runtime_error(std::string(__func__) + " : value out of range");
} }
} }
if (allow_cache) { if (allow_cache) {
m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit); wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].Set(filter, nCredit);
m_is_cache_empty = false; wtx.m_is_cache_empty = false;
} }
return nCredit; return nCredit;
} }
void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived, void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const std::list<COutputEntry>& listReceived,
std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter)
{ {
nFee = 0; nFee = 0;
listReceived.clear(); listReceived.clear();
listSent.clear(); listSent.clear();
// Compute fee: // Compute fee:
CAmount nDebit = GetDebit(filter); CAmount nDebit = CachedTxGetDebit(wallet, wtx, filter);
if (nDebit > 0) // debit>0 means we signed/sent this transaction if (nDebit > 0) // debit>0 means we signed/sent this transaction
{ {
CAmount nValueOut = tx->GetValueOut(); CAmount nValueOut = wtx.tx->GetValueOut();
nFee = nDebit - nValueOut; nFee = nDebit - nValueOut;
} }
LOCK(pwallet->cs_wallet); LOCK(wallet.cs_wallet);
// Sent/received. // Sent/received.
for (unsigned int i = 0; i < tx->vout.size(); ++i) for (unsigned int i = 0; i < wtx.tx->vout.size(); ++i)
{ {
const CTxOut& txout = tx->vout[i]; const CTxOut& txout = wtx.tx->vout[i];
isminetype fIsMine = pwallet->IsMine(txout); isminetype fIsMine = wallet.IsMine(txout);
// Only need to handle txouts if AT LEAST one of these is true: // Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent) // 1) they debit from us (sent)
// 2) the output is to us (received) // 2) the output is to us (received)
if (nDebit > 0) if (nDebit > 0)
{ {
// Don't report 'change' txouts // Don't report 'change' txouts
if (pwallet->IsChange(txout)) if (OutputIsChange(wallet, txout))
continue; continue;
} }
else if (!(fIsMine & filter)) else if (!(fIsMine & filter))
@ -253,8 +251,8 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable()) if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable())
{ {
pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n", wallet.WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
this->GetHash().ToString()); wtx.GetHash().ToString());
address = CNoDestination(); address = CNoDestination();
} }
@ -271,16 +269,21 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
} }
bool CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter)
{ {
AssertLockHeld(cs_wallet); return (CachedTxGetDebit(wallet, wtx, filter) > 0);
}
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents)
{
AssertLockHeld(wallet.cs_wallet);
// Quick answer in most cases // Quick answer in most cases
if (!chain().checkFinalTx(*wtx.tx)) return false; if (!wallet.chain().checkFinalTx(*wtx.tx)) return false;
int nDepth = wtx.GetDepthInMainChain(); int nDepth = wallet.GetTxDepthInMainChain(wtx);
if (nDepth >= 1) return true; if (nDepth >= 1) return true;
if (nDepth < 0) return false; if (nDepth < 0) return false;
// using wtx's cached debit // using wtx's cached debit
if (!m_spend_zero_conf_change || !wtx.IsFromMe(ISMINE_ALL)) return false; if (!wallet.m_spend_zero_conf_change || !CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)) return false;
// Don't trust unconfirmed transactions from us unless they are in the mempool. // Don't trust unconfirmed transactions from us unless they are in the mempool.
if (!wtx.InMempool()) return false; if (!wtx.InMempool()) return false;
@ -289,41 +292,41 @@ bool CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents
for (const CTxIn& txin : wtx.tx->vin) for (const CTxIn& txin : wtx.tx->vin)
{ {
// Transactions not sent by us: not trusted // Transactions not sent by us: not trusted
const CWalletTx* parent = GetWalletTx(txin.prevout.hash); const CWalletTx* parent = wallet.GetWalletTx(txin.prevout.hash);
if (parent == nullptr) return false; if (parent == nullptr) return false;
const CTxOut& parentOut = parent->tx->vout[txin.prevout.n]; const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
// Check that this specific input being spent is trusted // Check that this specific input being spent is trusted
if (IsMine(parentOut) != ISMINE_SPENDABLE) return false; if (wallet.IsMine(parentOut) != ISMINE_SPENDABLE) return false;
// If we've already trusted this parent, continue // If we've already trusted this parent, continue
if (trusted_parents.count(parent->GetHash())) continue; if (trusted_parents.count(parent->GetHash())) continue;
// Recurse to check that the parent is also trusted // Recurse to check that the parent is also trusted
if (!IsTrusted(*parent, trusted_parents)) return false; if (!CachedTxIsTrusted(wallet, *parent, trusted_parents)) return false;
trusted_parents.insert(parent->GetHash()); trusted_parents.insert(parent->GetHash());
} }
return true; return true;
} }
bool CWalletTx::IsTrusted() const bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx)
{ {
std::set<uint256> trusted_parents; std::set<uint256> trusted_parents;
LOCK(pwallet->cs_wallet); LOCK(wallet.cs_wallet);
return pwallet->IsTrusted(*this, trusted_parents); return CachedTxIsTrusted(wallet, wtx, trusted_parents);
} }
CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) const Balance GetBalance(const CWallet& wallet, const int min_depth, bool avoid_reuse)
{ {
Balance ret; Balance ret;
isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED; isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED;
{ {
LOCK(cs_wallet); LOCK(wallet.cs_wallet);
std::set<uint256> trusted_parents; std::set<uint256> trusted_parents;
for (const auto& entry : mapWallet) for (const auto& entry : wallet.mapWallet)
{ {
const CWalletTx& wtx = entry.second; const CWalletTx& wtx = entry.second;
const bool is_trusted{IsTrusted(wtx, trusted_parents)}; const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)};
const int tx_depth{wtx.GetDepthInMainChain()}; const int tx_depth{wallet.GetTxDepthInMainChain(wtx)};
const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)}; const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)}; const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
if (is_trusted && tx_depth >= min_depth) { if (is_trusted && tx_depth >= min_depth) {
ret.m_mine_trusted += tx_credit_mine; ret.m_mine_trusted += tx_credit_mine;
ret.m_watchonly_trusted += tx_credit_watchonly; ret.m_watchonly_trusted += tx_credit_watchonly;
@ -332,43 +335,43 @@ CWallet::Balance CWallet::GetBalance(const int min_depth, bool avoid_reuse) cons
ret.m_mine_untrusted_pending += tx_credit_mine; ret.m_mine_untrusted_pending += tx_credit_mine;
ret.m_watchonly_untrusted_pending += tx_credit_watchonly; ret.m_watchonly_untrusted_pending += tx_credit_watchonly;
} }
ret.m_mine_immature += wtx.GetImmatureCredit(); ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx);
ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit(); ret.m_watchonly_immature += CachedTxGetImmatureWatchOnlyCredit(wallet, wtx);
} }
} }
return ret; return ret;
} }
std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet)
{ {
std::map<CTxDestination, CAmount> balances; std::map<CTxDestination, CAmount> balances;
{ {
LOCK(cs_wallet); LOCK(wallet.cs_wallet);
std::set<uint256> trusted_parents; std::set<uint256> trusted_parents;
for (const auto& walletEntry : mapWallet) for (const auto& walletEntry : wallet.mapWallet)
{ {
const CWalletTx& wtx = walletEntry.second; const CWalletTx& wtx = walletEntry.second;
if (!IsTrusted(wtx, trusted_parents)) if (!CachedTxIsTrusted(wallet, wtx, trusted_parents))
continue; continue;
if (wtx.IsImmatureCoinBase()) if (wallet.IsTxImmatureCoinBase(wtx))
continue; continue;
int nDepth = wtx.GetDepthInMainChain(); int nDepth = wallet.GetTxDepthInMainChain(wtx);
if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1)) if (nDepth < (CachedTxIsFromMe(wallet, wtx, ISMINE_ALL) ? 0 : 1))
continue; continue;
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++) for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
{ {
CTxDestination addr; CTxDestination addr;
if (!IsMine(wtx.tx->vout[i])) if (!wallet.IsMine(wtx.tx->vout[i]))
continue; continue;
if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr)) if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr))
continue; continue;
CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue; CAmount n = wallet.IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue;
balances[addr] += n; balances[addr] += n;
} }
} }
@ -377,13 +380,13 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const
return balances; return balances;
} }
std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const std::set< std::set<CTxDestination> > GetAddressGroupings(const CWallet& wallet)
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(wallet.cs_wallet);
std::set< std::set<CTxDestination> > groupings; std::set< std::set<CTxDestination> > groupings;
std::set<CTxDestination> grouping; std::set<CTxDestination> grouping;
for (const auto& walletEntry : mapWallet) for (const auto& walletEntry : wallet.mapWallet)
{ {
const CWalletTx& wtx = walletEntry.second; const CWalletTx& wtx = walletEntry.second;
@ -394,9 +397,9 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const
for (const CTxIn& txin : wtx.tx->vin) for (const CTxIn& txin : wtx.tx->vin)
{ {
CTxDestination address; CTxDestination address;
if(!IsMine(txin)) /* If this input isn't mine, ignore it */ if(!InputIsMine(wallet, txin)) /* If this input isn't mine, ignore it */
continue; continue;
if(!ExtractDestination(mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address)) if(!ExtractDestination(wallet.mapWallet.at(txin.prevout.hash).tx->vout[txin.prevout.n].scriptPubKey, address))
continue; continue;
grouping.insert(address); grouping.insert(address);
any_mine = true; any_mine = true;
@ -406,7 +409,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const
if (any_mine) if (any_mine)
{ {
for (const CTxOut& txout : wtx.tx->vout) for (const CTxOut& txout : wtx.tx->vout)
if (IsChange(txout)) if (OutputIsChange(wallet, txout))
{ {
CTxDestination txoutAddr; CTxDestination txoutAddr;
if(!ExtractDestination(txout.scriptPubKey, txoutAddr)) if(!ExtractDestination(txout.scriptPubKey, txoutAddr))
@ -423,7 +426,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const
// group lone addrs by themselves // group lone addrs by themselves
for (const auto& txout : wtx.tx->vout) for (const auto& txout : wtx.tx->vout)
if (IsMine(txout)) if (wallet.IsMine(txout))
{ {
CTxDestination address; CTxDestination address;
if(!ExtractDestination(txout.scriptPubKey, address)) if(!ExtractDestination(txout.scriptPubKey, address))

View File

@ -10,11 +10,55 @@
#include <wallet/transaction.h> #include <wallet/transaction.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
isminetype InputIsMine(const CWallet& wallet, const CTxIn& txin) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
/** Returns whether all of the inputs match the filter */
bool AllInputsMine(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter);
CAmount OutputGetCredit(const CWallet& wallet, const CTxOut& txout, const isminefilter& filter);
CAmount TxGetCredit(const CWallet& wallet, const CTransaction& tx, const isminefilter& filter);
bool ScriptIsChange(const CWallet& wallet, const CScript& script) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
bool OutputIsChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
CAmount OutputGetChange(const CWallet& wallet, const CTxOut& txout) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
CAmount TxGetChange(const CWallet& wallet, const CTransaction& tx);
CAmount CachedTxGetCredit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter);
//! filter decides which addresses will count towards the debit
CAmount CachedTxGetDebit(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter);
CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx);
CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true);
CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache = true);
// 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 CachedTxGetAvailableCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) NO_THREAD_SAFETY_ANALYSIS;
struct COutputEntry struct COutputEntry
{ {
CTxDestination destination; CTxDestination destination;
CAmount amount; CAmount amount;
int vout; int vout;
}; };
void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
std::list<COutputEntry>& listReceived,
std::list<COutputEntry>& listSent,
CAmount& nFee, const isminefilter& filter);
bool CachedTxIsFromMe(const CWallet& wallet, const CWalletTx& wtx, const isminefilter& filter);
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx);
struct Balance {
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending)
CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain
CAmount m_watchonly_trusted{0};
CAmount m_watchonly_untrusted_pending{0};
CAmount m_watchonly_immature{0};
};
Balance GetBalance(const CWallet& wallet, int min_depth = 0, bool avoid_reuse = true);
std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet);
std::set<std::set<CTxDestination>> GetAddressGroupings(const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
#endif // BITCOIN_WALLET_RECEIVE_H #endif // BITCOIN_WALLET_RECEIVE_H

View File

@ -31,7 +31,9 @@
#include <wallet/context.h> #include <wallet/context.h>
#include <wallet/feebumper.h> #include <wallet/feebumper.h>
#include <wallet/load.h> #include <wallet/load.h>
#include <wallet/receive.h>
#include <wallet/rpcwallet.h> #include <wallet/rpcwallet.h>
#include <wallet/spend.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
#include <wallet/walletdb.h> #include <wallet/walletdb.h>
#include <wallet/walletutil.h> #include <wallet/walletutil.h>
@ -147,9 +149,10 @@ LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_cr
return *spk_man; return *spk_man;
} }
static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniValue& entry) static void WalletTxToJSON(const CWallet& wallet, const CWalletTx& wtx, UniValue& entry)
{ {
int confirms = wtx.GetDepthInMainChain(); interfaces::Chain& chain = wallet.chain();
int confirms = wallet.GetTxDepthInMainChain(wtx);
entry.pushKV("confirmations", confirms); entry.pushKV("confirmations", confirms);
if (wtx.IsCoinBase()) if (wtx.IsCoinBase())
entry.pushKV("generated", true); entry.pushKV("generated", true);
@ -162,12 +165,12 @@ static void WalletTxToJSON(interfaces::Chain& chain, const CWalletTx& wtx, UniVa
CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(block_time))); CHECK_NONFATAL(chain.findBlock(wtx.m_confirm.hashBlock, FoundBlock().time(block_time)));
entry.pushKV("blocktime", block_time); entry.pushKV("blocktime", block_time);
} else { } else {
entry.pushKV("trusted", wtx.IsTrusted()); entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx));
} }
uint256 hash = wtx.GetHash(); uint256 hash = wtx.GetHash();
entry.pushKV("txid", hash.GetHex()); entry.pushKV("txid", hash.GetHex());
UniValue conflicts(UniValue::VARR); UniValue conflicts(UniValue::VARR);
for (const uint256& conflict : wtx.GetConflicts()) for (const uint256& conflict : wallet.GetTxConflicts(wtx))
conflicts.push_back(conflict.GetHex()); conflicts.push_back(conflict.GetHex());
entry.pushKV("walletconflicts", conflicts); entry.pushKV("walletconflicts", conflicts);
entry.pushKV("time", wtx.GetTxTime()); entry.pushKV("time", wtx.GetTxTime());
@ -423,7 +426,7 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto
bilingual_str error; bilingual_str error;
CTransactionRef tx; CTransactionRef tx;
FeeCalculation fee_calc_out; FeeCalculation fee_calc_out;
const bool fCreated = wallet.CreateTransaction(recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true); const bool fCreated = CreateTransaction(wallet, recipients, tx, nFeeRequired, nChangePosRet, error, coin_control, fee_calc_out, true);
if (!fCreated) { if (!fCreated) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original); throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
} }
@ -576,8 +579,8 @@ static RPCHelpMan listaddressgroupings()
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
UniValue jsonGroupings(UniValue::VARR); UniValue jsonGroupings(UniValue::VARR);
std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances(); std::map<CTxDestination, CAmount> balances = GetAddressBalances(*pwallet);
for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) { for (const std::set<CTxDestination>& grouping : GetAddressGroupings(*pwallet)) {
UniValue jsonGrouping(UniValue::VARR); UniValue jsonGrouping(UniValue::VARR);
for (const CTxDestination& address : grouping) for (const CTxDestination& address : grouping)
{ {
@ -686,7 +689,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b
CAmount amount = 0; CAmount amount = 0;
for (const std::pair<const uint256, CWalletTx>& wtx_pair : wallet.mapWallet) { for (const std::pair<const uint256, CWalletTx>& wtx_pair : wallet.mapWallet) {
const CWalletTx& wtx = wtx_pair.second; const CWalletTx& wtx = wtx_pair.second;
if (wtx.IsCoinBase() || !wallet.chain().checkFinalTx(*wtx.tx) || wtx.GetDepthInMainChain() < min_depth) { if (wtx.IsCoinBase() || !wallet.chain().checkFinalTx(*wtx.tx) || wallet.GetTxDepthInMainChain(wtx) < min_depth) {
continue; continue;
} }
@ -826,7 +829,7 @@ static RPCHelpMan getbalance()
bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[3]); bool avoid_reuse = GetAvoidReuseFlag(*pwallet, request.params[3]);
const auto bal = pwallet->GetBalance(min_depth, avoid_reuse); const auto bal = GetBalance(*pwallet, min_depth, avoid_reuse);
return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0)); return ValueFromAmount(bal.m_mine_trusted + (include_watchonly ? bal.m_watchonly_trusted : 0));
}, },
@ -851,7 +854,7 @@ static RPCHelpMan getunconfirmedbalance()
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
return ValueFromAmount(pwallet->GetBalance().m_mine_untrusted_pending); return ValueFromAmount(GetBalance(*pwallet).m_mine_untrusted_pending);
}, },
}; };
} }
@ -1085,7 +1088,7 @@ static UniValue ListReceived(const CWallet& wallet, const UniValue& params, bool
continue; continue;
} }
int nDepth = wtx.GetDepthInMainChain(); int nDepth = wallet.GetTxDepthInMainChain(wtx);
if (nDepth < nMinDepth) if (nDepth < nMinDepth)
continue; continue;
@ -1310,9 +1313,9 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
std::list<COutputEntry> listReceived; std::list<COutputEntry> listReceived;
std::list<COutputEntry> listSent; std::list<COutputEntry> listSent;
wtx.GetAmounts(listReceived, listSent, nFee, filter_ismine); CachedTxGetAmounts(wallet, wtx, listReceived, listSent, nFee, filter_ismine);
bool involvesWatchonly = wtx.IsFromMe(ISMINE_WATCH_ONLY); bool involvesWatchonly = CachedTxIsFromMe(wallet, wtx, ISMINE_WATCH_ONLY);
// Sent // Sent
if (!filter_label) if (!filter_label)
@ -1333,14 +1336,14 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
entry.pushKV("vout", s.vout); entry.pushKV("vout", s.vout);
entry.pushKV("fee", ValueFromAmount(-nFee)); entry.pushKV("fee", ValueFromAmount(-nFee));
if (fLong) if (fLong)
WalletTxToJSON(wallet.chain(), wtx, entry); WalletTxToJSON(wallet, wtx, entry);
entry.pushKV("abandoned", wtx.isAbandoned()); entry.pushKV("abandoned", wtx.isAbandoned());
ret.push_back(entry); ret.push_back(entry);
} }
} }
// Received // Received
if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) { if (listReceived.size() > 0 && wallet.GetTxDepthInMainChain(wtx) >= nMinDepth) {
for (const COutputEntry& r : listReceived) for (const COutputEntry& r : listReceived)
{ {
std::string label; std::string label;
@ -1358,9 +1361,9 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
MaybePushAddress(entry, r.destination); MaybePushAddress(entry, r.destination);
if (wtx.IsCoinBase()) if (wtx.IsCoinBase())
{ {
if (wtx.GetDepthInMainChain() < 1) if (wallet.GetTxDepthInMainChain(wtx) < 1)
entry.pushKV("category", "orphan"); entry.pushKV("category", "orphan");
else if (wtx.IsImmatureCoinBase()) else if (wallet.IsTxImmatureCoinBase(wtx))
entry.pushKV("category", "immature"); entry.pushKV("category", "immature");
else else
entry.pushKV("category", "generate"); entry.pushKV("category", "generate");
@ -1375,7 +1378,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
} }
entry.pushKV("vout", r.vout); entry.pushKV("vout", r.vout);
if (fLong) if (fLong)
WalletTxToJSON(wallet.chain(), wtx, entry); WalletTxToJSON(wallet, wtx, entry);
ret.push_back(entry); ret.push_back(entry);
} }
} }
@ -1615,7 +1618,7 @@ static RPCHelpMan listsinceblock()
for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) { for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) {
const CWalletTx& tx = pairWtx.second; const CWalletTx& tx = pairWtx.second;
if (depth == -1 || abs(tx.GetDepthInMainChain()) < depth) { if (depth == -1 || abs(wallet.GetTxDepthInMainChain(tx)) < depth) {
ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */); ListTransactions(wallet, tx, 0, true, transactions, filter, nullptr /* filter_label */);
} }
} }
@ -1736,16 +1739,16 @@ static RPCHelpMan gettransaction()
} }
const CWalletTx& wtx = it->second; const CWalletTx& wtx = it->second;
CAmount nCredit = wtx.GetCredit(filter); CAmount nCredit = CachedTxGetCredit(*pwallet, wtx, filter);
CAmount nDebit = wtx.GetDebit(filter); CAmount nDebit = CachedTxGetDebit(*pwallet, wtx, filter);
CAmount nNet = nCredit - nDebit; CAmount nNet = nCredit - nDebit;
CAmount nFee = (wtx.IsFromMe(filter) ? wtx.tx->GetValueOut() - nDebit : 0); CAmount nFee = (CachedTxIsFromMe(*pwallet, wtx, filter) ? wtx.tx->GetValueOut() - nDebit : 0);
entry.pushKV("amount", ValueFromAmount(nNet - nFee)); entry.pushKV("amount", ValueFromAmount(nNet - nFee));
if (wtx.IsFromMe(filter)) if (CachedTxIsFromMe(*pwallet, wtx, filter))
entry.pushKV("fee", ValueFromAmount(nFee)); entry.pushKV("fee", ValueFromAmount(nFee));
WalletTxToJSON(pwallet->chain(), wtx, entry); WalletTxToJSON(*pwallet, wtx, entry);
UniValue details(UniValue::VARR); UniValue details(UniValue::VARR);
ListTransactions(*pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */); ListTransactions(*pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */);
@ -2384,7 +2387,7 @@ static RPCHelpMan getbalances()
LOCK(wallet.cs_wallet); LOCK(wallet.cs_wallet);
const auto bal = wallet.GetBalance(); const auto bal = GetBalance(wallet);
UniValue balances{UniValue::VOBJ}; UniValue balances{UniValue::VOBJ};
{ {
UniValue balances_mine{UniValue::VOBJ}; UniValue balances_mine{UniValue::VOBJ};
@ -2394,7 +2397,7 @@ static RPCHelpMan getbalances()
if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) { if (wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE)) {
// If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get // If the AVOID_REUSE flag is set, bal has been set to just the un-reused address balance. Get
// the total balance, and then subtract bal to get the reused address balance. // the total balance, and then subtract bal to get the reused address balance.
const auto full_bal = wallet.GetBalance(0, false); const auto full_bal = GetBalance(wallet, 0, false);
balances_mine.pushKV("used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending)); balances_mine.pushKV("used", ValueFromAmount(full_bal.m_mine_trusted + full_bal.m_mine_untrusted_pending - bal.m_mine_trusted - bal.m_mine_untrusted_pending));
} }
balances.pushKV("mine", balances_mine); balances.pushKV("mine", balances_mine);
@ -2462,7 +2465,7 @@ static RPCHelpMan getwalletinfo()
UniValue obj(UniValue::VOBJ); UniValue obj(UniValue::VOBJ);
size_t kpExternalSize = pwallet->KeypoolCountExternalKeys(); size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
const auto bal = pwallet->GetBalance(); const auto bal = GetBalance(*pwallet);
int64_t kp_oldest = pwallet->GetOldestKeyPoolTime(); int64_t kp_oldest = pwallet->GetOldestKeyPoolTime();
obj.pushKV("walletname", pwallet->GetName()); obj.pushKV("walletname", pwallet->GetName());
obj.pushKV("walletversion", pwallet->GetVersion()); obj.pushKV("walletversion", pwallet->GetVersion());
@ -3058,7 +3061,7 @@ static RPCHelpMan listunspent()
cctl.m_max_depth = nMaxDepth; cctl.m_max_depth = nMaxDepth;
cctl.m_include_unsafe_inputs = include_unsafe; cctl.m_include_unsafe_inputs = include_unsafe;
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
pwallet->AvailableCoins(vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount); AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
} }
LOCK(pwallet->cs_wallet); LOCK(pwallet->cs_wallet);
@ -3274,7 +3277,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
bilingual_str error; bilingual_str error;
if (!wallet.FundTransaction(tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) { if (!FundTransaction(wallet, tx, fee_out, change_position, error, lockUnspents, setSubtractFeeFromOutputs, coinControl)) {
throw JSONRPCError(RPC_WALLET_ERROR, error.original); throw JSONRPCError(RPC_WALLET_ERROR, error.original);
} }
} }
@ -3959,7 +3962,7 @@ RPCHelpMan getaddressinfo()
UniValue detail = DescribeWalletAddress(*pwallet, dest); UniValue detail = DescribeWalletAddress(*pwallet, dest);
ret.pushKVs(detail); ret.pushKVs(detail);
ret.pushKV("ischange", pwallet->IsChange(scriptPubKey)); ret.pushKV("ischange", ScriptIsChange(*pwallet, scriptPubKey));
ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey); ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey);
if (spk_man) { if (spk_man) {

View File

@ -21,6 +21,11 @@ using interfaces::FoundBlock;
static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100}; static constexpr size_t OUTPUT_GROUP_MAX_ENTRIES{100};
int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig)
{
return CalculateMaximumSignedInputSize(wtx.tx->vout[out], &wallet, use_max_sig);
}
std::string COutput::ToString() const std::string COutput::ToString() const
{ {
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue)); return strprintf("COutput(%s, %d, %d) [%s]", tx->GetHash().ToString(), i, nDepth, FormatMoney(tx->tx->vout[i].nValue));
@ -64,33 +69,33 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
return CalculateMaximumSignedTxSize(tx, wallet, txouts, use_max_sig); 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 void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl, const CAmount& nMinimumAmount, const CAmount& nMaximumAmount, const CAmount& nMinimumSumAmount, const uint64_t nMaximumCount)
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(wallet.cs_wallet);
vCoins.clear(); vCoins.clear();
CAmount nTotal = 0; 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 // 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 // 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); bool allow_used_addresses = !wallet.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 min_depth = {coinControl ? coinControl->m_min_depth : DEFAULT_MIN_DEPTH};
const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH}; const int max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true}; const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true};
std::set<uint256> trusted_parents; std::set<uint256> trusted_parents;
for (const auto& entry : mapWallet) for (const auto& entry : wallet.mapWallet)
{ {
const uint256& wtxid = entry.first; const uint256& wtxid = entry.first;
const CWalletTx& wtx = entry.second; const CWalletTx& wtx = entry.second;
if (!chain().checkFinalTx(*wtx.tx)) { if (!wallet.chain().checkFinalTx(*wtx.tx)) {
continue; continue;
} }
if (wtx.IsImmatureCoinBase()) if (wallet.IsTxImmatureCoinBase(wtx))
continue; continue;
int nDepth = wtx.GetDepthInMainChain(); int nDepth = wallet.GetTxDepthInMainChain(wtx);
if (nDepth < 0) if (nDepth < 0)
continue; continue;
@ -99,7 +104,7 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* c
if (nDepth == 0 && !wtx.InMempool()) if (nDepth == 0 && !wtx.InMempool())
continue; continue;
bool safeTx = IsTrusted(wtx, trusted_parents); bool safeTx = CachedTxIsTrusted(wallet, wtx, trusted_parents);
// We should not consider coins from transactions that are replacing // We should not consider coins from transactions that are replacing
// other transactions. // other transactions.
@ -152,28 +157,28 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* c
if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i))) if (coinControl && coinControl->HasSelected() && !coinControl->fAllowOtherInputs && !coinControl->IsSelected(COutPoint(entry.first, i)))
continue; continue;
if (IsLockedCoin(entry.first, i)) if (wallet.IsLockedCoin(entry.first, i))
continue; continue;
if (IsSpent(wtxid, i)) if (wallet.IsSpent(wtxid, i))
continue; continue;
isminetype mine = IsMine(wtx.tx->vout[i]); isminetype mine = wallet.IsMine(wtx.tx->vout[i]);
if (mine == ISMINE_NO) { if (mine == ISMINE_NO) {
continue; continue;
} }
if (!allow_used_addresses && IsSpentKey(wtxid, i)) { if (!allow_used_addresses && wallet.IsSpentKey(wtxid, i)) {
continue; continue;
} }
std::unique_ptr<SigningProvider> provider = GetSolvingProvider(wtx.tx->vout[i].scriptPubKey); std::unique_ptr<SigningProvider> provider = wallet.GetSolvingProvider(wtx.tx->vout[i].scriptPubKey);
bool solvable = provider ? IsSolvable(*provider, wtx.tx->vout[i].scriptPubKey) : false; 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)); 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))); vCoins.push_back(COutput(wallet, wtx, i, nDepth, spendable, solvable, safeTx, (coinControl && coinControl->fAllowWatchOnly)));
// Checks the sum amount of all UTXO's. // Checks the sum amount of all UTXO's.
if (nMinimumSumAmount != MAX_MONEY) { if (nMinimumSumAmount != MAX_MONEY) {
@ -192,13 +197,13 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* c
} }
} }
CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl)
{ {
LOCK(cs_wallet); LOCK(wallet.cs_wallet);
CAmount balance = 0; CAmount balance = 0;
std::vector<COutput> vCoins; std::vector<COutput> vCoins;
AvailableCoins(vCoins, coinControl); AvailableCoins(wallet, vCoins, coinControl);
for (const COutput& out : vCoins) { for (const COutput& out : vCoins) {
if (out.fSpendable) { if (out.fSpendable) {
balance += out.tx->tx->vout[out.i].nValue; balance += out.tx->tx->vout[out.i].nValue;
@ -207,16 +212,16 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
return balance; return balance;
} }
const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int output) const const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output)
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(wallet.cs_wallet);
const CTransaction* ptx = &tx; const CTransaction* ptx = &tx;
int n = output; int n = output;
while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) { while (OutputIsChange(wallet, ptx->vout[n]) && ptx->vin.size() > 0) {
const COutPoint& prevout = ptx->vin[0].prevout; const COutPoint& prevout = ptx->vin[0].prevout;
auto it = mapWallet.find(prevout.hash); auto it = wallet.mapWallet.find(prevout.hash);
if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n || if (it == wallet.mapWallet.end() || it->second.tx->vout.size() <= prevout.n ||
!IsMine(it->second.tx->vout[prevout.n])) { !wallet.IsMine(it->second.tx->vout[prevout.n])) {
break; break;
} }
ptx = it->second.tx.get(); ptx = it->second.tx.get();
@ -225,39 +230,39 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out
return ptx->vout[n]; return ptx->vout[n];
} }
std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(wallet.cs_wallet);
std::map<CTxDestination, std::vector<COutput>> result; std::map<CTxDestination, std::vector<COutput>> result;
std::vector<COutput> availableCoins; std::vector<COutput> availableCoins;
AvailableCoins(availableCoins); AvailableCoins(wallet, availableCoins);
for (const COutput& coin : availableCoins) { for (const COutput& coin : availableCoins) {
CTxDestination address; CTxDestination address;
if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) && if ((coin.fSpendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) { ExtractDestination(FindNonChangeParentOutput(wallet, *coin.tx->tx, coin.i).scriptPubKey, address)) {
result[address].emplace_back(std::move(coin)); result[address].emplace_back(std::move(coin));
} }
} }
std::vector<COutPoint> lockedCoins; std::vector<COutPoint> lockedCoins;
ListLockedCoins(lockedCoins); wallet.ListLockedCoins(lockedCoins);
// Include watch-only for LegacyScriptPubKeyMan wallets without private keys // Include watch-only for LegacyScriptPubKeyMan wallets without private keys
const bool include_watch_only = GetLegacyScriptPubKeyMan() && IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); const bool include_watch_only = wallet.GetLegacyScriptPubKeyMan() && wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS);
const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE; const isminetype is_mine_filter = include_watch_only ? ISMINE_WATCH_ONLY : ISMINE_SPENDABLE;
for (const COutPoint& output : lockedCoins) { for (const COutPoint& output : lockedCoins) {
auto it = mapWallet.find(output.hash); auto it = wallet.mapWallet.find(output.hash);
if (it != mapWallet.end()) { if (it != wallet.mapWallet.end()) {
int depth = it->second.GetDepthInMainChain(); int depth = wallet.GetTxDepthInMainChain(it->second);
if (depth >= 0 && output.n < it->second.tx->vout.size() && if (depth >= 0 && output.n < it->second.tx->vout.size() &&
IsMine(it->second.tx->vout[output.n]) == is_mine_filter wallet.IsMine(it->second.tx->vout[output.n]) == is_mine_filter
) { ) {
CTxDestination address; CTxDestination address;
if (ExtractDestination(FindNonChangeParentOutput(*it->second.tx, output.n).scriptPubKey, address)) { if (ExtractDestination(FindNonChangeParentOutput(wallet, *it->second.tx, output.n).scriptPubKey, address)) {
result[address].emplace_back( result[address].emplace_back(
&it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */); wallet, it->second, output.n, depth, true /* spendable */, true /* solvable */, false /* safe */);
} }
} }
} }
@ -266,7 +271,7 @@ std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const
return result; 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> GroupOutputs(const CWallet& wallet, const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only)
{ {
std::vector<OutputGroup> groups_out; std::vector<OutputGroup> groups_out;
@ -277,12 +282,12 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
if (!output.fSpendable) continue; if (!output.fSpendable) continue;
size_t ancestors, descendants; size_t ancestors, descendants;
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
CInputCoin input_coin = output.GetInputCoin(); CInputCoin input_coin = output.GetInputCoin();
// Make an OutputGroup containing just this output // Make an OutputGroup containing just this output
OutputGroup group{coin_sel_params}; OutputGroup group{coin_sel_params};
group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); group.Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only);
// Check the OutputGroup's eligibility. Only add the eligible ones. // Check the OutputGroup's eligibility. Only add the eligible ones.
if (positive_only && group.GetSelectionAmount() <= 0) continue; if (positive_only && group.GetSelectionAmount() <= 0) continue;
@ -303,7 +308,7 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
if (!output.fSpendable) continue; if (!output.fSpendable) continue;
size_t ancestors, descendants; size_t ancestors, descendants;
chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants); wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
CInputCoin input_coin = output.GetInputCoin(); CInputCoin input_coin = output.GetInputCoin();
CScript spk = input_coin.txout.scriptPubKey; CScript spk = input_coin.txout.scriptPubKey;
@ -327,7 +332,7 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
} }
// Add the input_coin to group // Add the input_coin to group
group->Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only); group->Insert(input_coin, output.nDepth, CachedTxIsFromMe(wallet, *output.tx, ISMINE_ALL), ancestors, descendants, positive_only);
} }
// Now we go through the entire map and pull out the OutputGroups // Now we go through the entire map and pull out the OutputGroups
@ -352,8 +357,8 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
return groups_out; return groups_out;
} }
bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins, bool AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params)
{ {
setCoinsRet.clear(); setCoinsRet.clear();
nValueRet = 0; nValueRet = 0;
@ -363,7 +368,7 @@ bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilit
std::vector<std::tuple<CAmount, std::set<CInputCoin>, CAmount>> results; std::vector<std::tuple<CAmount, std::set<CInputCoin>, CAmount>> results;
// Note that unlike KnapsackSolver, we do not include the fee for creating a change output as BnB will not create a change output. // 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 */); std::vector<OutputGroup> positive_groups = GroupOutputs(wallet, coins, coin_selection_params, eligibility_filter, true /* positive_only */);
std::set<CInputCoin> bnb_coins; std::set<CInputCoin> bnb_coins;
CAmount bnb_value; CAmount bnb_value;
if (SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change, bnb_coins, bnb_value)) { if (SelectCoinsBnB(positive_groups, nTargetValue, coin_selection_params.m_cost_of_change, bnb_coins, bnb_value)) {
@ -372,7 +377,7 @@ bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilit
} }
// 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. // 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 */); std::vector<OutputGroup> all_groups = GroupOutputs(wallet, 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. // 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. // So we need to include that for KnapsackSolver as well, as we are expecting to create a change output.
std::set<CInputCoin> knapsack_coins; std::set<CInputCoin> knapsack_coins;
@ -397,7 +402,7 @@ bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilit
return true; return true;
} }
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 bool SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params)
{ {
std::vector<COutput> vCoins(vAvailableCoins); std::vector<COutput> vCoins(vAvailableCoins);
CAmount value_to_select = nTargetValue; CAmount value_to_select = nTargetValue;
@ -423,8 +428,8 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
coin_control.ListSelected(vPresetInputs); coin_control.ListSelected(vPresetInputs);
for (const COutPoint& outpoint : vPresetInputs) for (const COutPoint& outpoint : vPresetInputs)
{ {
std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash); std::map<uint256, CWalletTx>::const_iterator it = wallet.mapWallet.find(outpoint.hash);
if (it != mapWallet.end()) if (it != wallet.mapWallet.end())
{ {
const CWalletTx& wtx = it->second; const CWalletTx& wtx = it->second;
// Clearly invalid input, fail // Clearly invalid input, fail
@ -432,7 +437,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
return false; return false;
} }
// Just to calculate the marginal byte size // Just to calculate the marginal byte size
CInputCoin coin(wtx.tx, outpoint.n, wtx.GetSpendSize(outpoint.n, false)); CInputCoin coin(wtx.tx, outpoint.n, GetTxSpendSize(wallet, wtx, outpoint.n, false));
nValueFromPresetInputs += coin.txout.nValue; nValueFromPresetInputs += coin.txout.nValue;
if (coin.m_input_bytes <= 0) { if (coin.m_input_bytes <= 0) {
return false; // Not solvable, can't estimate size for fee return false; // Not solvable, can't estimate size for fee
@ -460,7 +465,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
unsigned int limit_ancestor_count = 0; unsigned int limit_ancestor_count = 0;
unsigned int limit_descendant_count = 0; unsigned int limit_descendant_count = 0;
chain().getPackageLimits(limit_ancestor_count, limit_descendant_count); wallet.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_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 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); const bool fRejectLongChains = gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
@ -483,32 +488,32 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// If possible, fund the transaction with confirmed UTXOs only. Prefer at least six // 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. // confirmations on outputs received from other wallets and only spend confirmed change.
if (AttemptSelection(value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(1, 6, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
if (AttemptSelection(value_to_select, CoinEligibilityFilter(1, 1, 0), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; if (AttemptSelection(wallet, 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 // Fall back to using zero confirmation change (but with as few ancestors in the mempool as
// possible) if we cannot fund the transaction otherwise. // possible) if we cannot fund the transaction otherwise.
if (m_spend_zero_conf_change) { if (wallet.m_spend_zero_conf_change) {
if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true; if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, std::min((size_t)4, max_ancestors/3), std::min((size_t)4, max_descendants/3)), if (AttemptSelection(wallet, 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)) { vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true; return true;
} }
if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2), if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors/2, max_descendants/2),
vCoins, setCoinsRet, nValueRet, coin_selection_params)) { vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true; return true;
} }
// If partial groups are allowed, relax the requirement of spending OutputGroups (groups // 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) // of UTXOs sent to the same address, which are obviously controlled by a single wallet)
// in their entirety. // in their entirety.
if (AttemptSelection(value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
vCoins, setCoinsRet, nValueRet, coin_selection_params)) { vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true; return true;
} }
// Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs // Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
// received from other wallets. // received from other wallets.
if (coin_control.m_include_unsafe_inputs if (coin_control.m_include_unsafe_inputs
&& AttemptSelection(value_to_select, && AttemptSelection(wallet, value_to_select,
CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */), CoinEligibilityFilter(0 /* conf_mine */, 0 /* conf_theirs */, max_ancestors-1, max_descendants-1, true /* include_partial_groups */),
vCoins, setCoinsRet, nValueRet, coin_selection_params)) { vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true; return true;
@ -516,7 +521,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
// Try with unlimited ancestors/descendants. The transaction will still need to meet // Try with unlimited ancestors/descendants. The transaction will still need to meet
// mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but // mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but
// OutputGroups use heuristics that may overestimate ancestor/descendant counts. // OutputGroups use heuristics that may overestimate ancestor/descendant counts.
if (!fRejectLongChains && AttemptSelection(value_to_select, if (!fRejectLongChains && AttemptSelection(wallet, value_to_select,
CoinEligibilityFilter(0, 1, std::numeric_limits<uint64_t>::max(), std::numeric_limits<uint64_t>::max(), true /* include_partial_groups */), 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)) { vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true; return true;
@ -595,7 +600,8 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uin
return locktime; return locktime;
} }
bool CWallet::CreateTransactionInternal( static bool CreateTransactionInternal(
CWallet& wallet,
const std::vector<CRecipient>& vecSend, const std::vector<CRecipient>& vecSend,
CTransactionRef& tx, CTransactionRef& tx,
CAmount& nFeeRet, CAmount& nFeeRet,
@ -603,22 +609,22 @@ bool CWallet::CreateTransactionInternal(
bilingual_str& error, bilingual_str& error,
const CCoinControl& coin_control, const CCoinControl& coin_control,
FeeCalculation& fee_calc_out, FeeCalculation& fee_calc_out,
bool sign) bool sign) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
{ {
AssertLockHeld(cs_wallet); AssertLockHeld(wallet.cs_wallet);
CMutableTransaction txNew; // The resulting transaction that we make CMutableTransaction txNew; // The resulting transaction that we make
txNew.nLockTime = GetLocktimeForNewTransaction(chain(), GetLastBlockHash(), GetLastBlockHeight()); txNew.nLockTime = GetLocktimeForNewTransaction(wallet.chain(), wallet.GetLastBlockHash(), wallet.GetLastBlockHeight());
CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy CoinSelectionParams coin_selection_params; // Parameters for coin selection, init with dummy
coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends; coin_selection_params.m_avoid_partial_spends = coin_control.m_avoid_partial_spends;
// Set the long term feerate estimate to the wallet's consolidate feerate // Set the long term feerate estimate to the wallet's consolidate feerate
coin_selection_params.m_long_term_feerate = m_consolidate_feerate; coin_selection_params.m_long_term_feerate = wallet.m_consolidate_feerate;
CAmount recipients_sum = 0; CAmount recipients_sum = 0;
const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend); const OutputType change_type = wallet.TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : wallet.m_default_change_type, vecSend);
ReserveDestination reservedest(this, change_type); ReserveDestination reservedest(&wallet, change_type);
unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from
for (const auto& recipient : vecSend) { for (const auto& recipient : vecSend) {
recipients_sum += recipient.nAmount; recipients_sum += recipient.nAmount;
@ -662,7 +668,7 @@ bool CWallet::CreateTransactionInternal(
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout); coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
// Get size of spending the change output // Get size of spending the change output
int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this); int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, &wallet);
// If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh // 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 // as lower-bound to allow BnB to do it's thing
if (change_spend_size == -1) { if (change_spend_size == -1) {
@ -672,18 +678,18 @@ bool CWallet::CreateTransactionInternal(
} }
// Set discard feerate // Set discard feerate
coin_selection_params.m_discard_feerate = GetDiscardRate(*this); coin_selection_params.m_discard_feerate = GetDiscardRate(wallet);
// Get the fee rate to use effective values in coin selection // Get the fee rate to use effective values in coin selection
FeeCalculation feeCalc; FeeCalculation feeCalc;
coin_selection_params.m_effective_feerate = GetMinimumFeeRate(*this, coin_control, &feeCalc); coin_selection_params.m_effective_feerate = GetMinimumFeeRate(wallet, coin_control, &feeCalc);
// Do not, ever, assume that it's fine to change the fee rate if the user has explicitly // Do not, ever, assume that it's fine to change the fee rate if the user has explicitly
// provided one // provided one
if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) { 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)); 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; return false;
} }
if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) { if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) {
// eventually allow a fallback fee // eventually allow a fallback fee
error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee."); error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
return false; return false;
@ -710,7 +716,7 @@ bool CWallet::CreateTransactionInternal(
coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION); coin_selection_params.tx_noinputs_size += ::GetSerializeSize(txout, PROTOCOL_VERSION);
} }
if (IsDust(txout, chain().relayDustFee())) if (IsDust(txout, wallet.chain().relayDustFee()))
{ {
error = _("Transaction amount too small"); error = _("Transaction amount too small");
return false; return false;
@ -724,12 +730,12 @@ bool CWallet::CreateTransactionInternal(
// Get available coins // Get available coins
std::vector<COutput> vAvailableCoins; std::vector<COutput> vAvailableCoins;
AvailableCoins(vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0); AvailableCoins(wallet, vAvailableCoins, &coin_control, 1, MAX_MONEY, MAX_MONEY, 0);
// Choose coins to use // Choose coins to use
CAmount inputs_sum = 0; CAmount inputs_sum = 0;
std::set<CInputCoin> setCoins; std::set<CInputCoin> setCoins;
if (!SelectCoins(vAvailableCoins, /* nTargetValue */ selection_target, setCoins, inputs_sum, coin_control, coin_selection_params)) if (!SelectCoins(wallet, vAvailableCoins, /* nTargetValue */ selection_target, setCoins, inputs_sum, coin_control, coin_selection_params))
{ {
error = _("Insufficient funds"); error = _("Insufficient funds");
return false; return false;
@ -767,13 +773,13 @@ bool CWallet::CreateTransactionInternal(
// to avoid conflicting with other possible uses of nSequence, // to avoid conflicting with other possible uses of nSequence,
// and in the spirit of "smallest possible change from prior // and in the spirit of "smallest possible change from prior
// behavior." // behavior."
const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1); const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(wallet.m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
for (const auto& coin : selected_coins) { for (const auto& coin : selected_coins) {
txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence)); txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
} }
// Calculate the transaction fee // Calculate the transaction fee
TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly); TxSize tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly);
int nBytes = tx_sizes.vsize; int nBytes = tx_sizes.vsize;
if (nBytes < 0) { if (nBytes < 0) {
error = _("Signing transaction failed"); error = _("Signing transaction failed");
@ -798,7 +804,7 @@ bool CWallet::CreateTransactionInternal(
txNew.vout.erase(change_position); 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 // 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); tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly);
nBytes = tx_sizes.vsize; nBytes = tx_sizes.vsize;
fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes); fee_needed = coin_selection_params.m_effective_feerate.GetFee(nBytes);
} }
@ -835,7 +841,7 @@ bool CWallet::CreateTransactionInternal(
} }
// Error if this output is reduced to be below dust // Error if this output is reduced to be below dust
if (IsDust(txout, chain().relayDustFee())) { if (IsDust(txout, wallet.chain().relayDustFee())) {
if (txout.nValue < 0) { if (txout.nValue < 0) {
error = _("The transaction amount is too small to pay the fee"); error = _("The transaction amount is too small to pay the fee");
} else { } else {
@ -854,7 +860,7 @@ bool CWallet::CreateTransactionInternal(
return false; return false;
} }
if (sign && !SignTransaction(txNew)) { if (sign && !wallet.SignTransaction(txNew)) {
error = _("Signing transaction failed"); error = _("Signing transaction failed");
return false; return false;
} }
@ -870,14 +876,14 @@ bool CWallet::CreateTransactionInternal(
return false; return false;
} }
if (nFeeRet > m_default_max_tx_fee) { if (nFeeRet > wallet.m_default_max_tx_fee) {
error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED); error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
return false; return false;
} }
if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) { if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits // Lastly, ensure this tx will pass the mempool's chain limits
if (!chain().checkChainLimits(tx)) { if (!wallet.chain().checkChainLimits(tx)) {
error = _("Transaction has too long of a mempool chain"); error = _("Transaction has too long of a mempool chain");
return false; return false;
} }
@ -888,7 +894,7 @@ bool CWallet::CreateTransactionInternal(
reservedest.KeepDestination(); reservedest.KeepDestination();
fee_calc_out = feeCalc; 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", wallet.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, nFeeRet, nBytes, feeCalc.returnedTarget, feeCalc.desiredTarget, StringForFeeReason(feeCalc.reason), feeCalc.est.decay,
feeCalc.est.pass.start, feeCalc.est.pass.end, 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.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,
@ -899,7 +905,8 @@ bool CWallet::CreateTransactionInternal(
return true; return true;
} }
bool CWallet::CreateTransaction( bool CreateTransaction(
CWallet& wallet,
const std::vector<CRecipient>& vecSend, const std::vector<CRecipient>& vecSend,
CTransactionRef& tx, CTransactionRef& tx,
CAmount& nFeeRet, CAmount& nFeeRet,
@ -919,23 +926,23 @@ bool CWallet::CreateTransaction(
return false; return false;
} }
LOCK(cs_wallet); LOCK(wallet.cs_wallet);
int nChangePosIn = nChangePosInOut; int nChangePosIn = nChangePosInOut;
Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr) 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); bool res = CreateTransactionInternal(wallet, vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
// try with avoidpartialspends unless it's enabled already // 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) { if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && wallet.m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
CCoinControl tmp_cc = coin_control; CCoinControl tmp_cc = coin_control;
tmp_cc.m_avoid_partial_spends = true; tmp_cc.m_avoid_partial_spends = true;
CAmount nFeeRet2; CAmount nFeeRet2;
CTransactionRef tx2; CTransactionRef tx2;
int nChangePosInOut2 = nChangePosIn; int nChangePosInOut2 = nChangePosIn;
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results 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 (CreateTransactionInternal(wallet, 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 // 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; const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee;
WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped"); wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
if (use_aps) { if (use_aps) {
tx = tx2; tx = tx2;
nFeeRet = nFeeRet2; nFeeRet = nFeeRet2;
@ -946,7 +953,7 @@ bool CWallet::CreateTransaction(
return res; return res;
} }
bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl) bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl coinControl)
{ {
std::vector<CRecipient> vecSend; std::vector<CRecipient> vecSend;
@ -965,11 +972,11 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
// Acquire the locks to prevent races to the new locked unspents between the // Acquire the locks to prevent races to the new locked unspents between the
// CreateTransaction call and LockCoin calls (when lockUnspents is true). // CreateTransaction call and LockCoin calls (when lockUnspents is true).
LOCK(cs_wallet); LOCK(wallet.cs_wallet);
CTransactionRef tx_new; CTransactionRef tx_new;
FeeCalculation fee_calc_out; FeeCalculation fee_calc_out;
if (!CreateTransaction(vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) { if (!CreateTransaction(wallet, vecSend, tx_new, nFeeRet, nChangePosInOut, error, coinControl, fee_calc_out, false)) {
return false; return false;
} }
@ -990,7 +997,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
} }
if (lockUnspents) { if (lockUnspents) {
LockCoin(txin.prevout); wallet.LockCoin(txin.prevout);
} }
} }

View File

@ -9,6 +9,9 @@
#include <wallet/transaction.h> #include <wallet/transaction.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
/** Get the marginal bytes if spending the specified output from this transaction */
int GetTxSpendSize(const CWallet& wallet, const CWalletTx& wtx, unsigned int out, bool use_max_sig = false);
class COutput class COutput
{ {
public: public:
@ -43,13 +46,13 @@ public:
*/ */
bool fSafe; bool fSafe;
COutput(const CWalletTx *txIn, int iIn, int nDepthIn, bool fSpendableIn, bool fSolvableIn, bool fSafeIn, bool use_max_sig_in = false) COutput(const CWallet& wallet, const CWalletTx& wtx, 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; tx = &wtx; 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 // If known and signable by the given wallet, compute nInputBytes
// Failure will keep this value -1 // Failure will keep this value -1
if (fSpendable && tx) { if (fSpendable) {
nInputBytes = tx->GetSpendSize(i, use_max_sig); nInputBytes = GetTxSpendSize(wallet, wtx, i, use_max_sig);
} }
} }
@ -61,4 +64,76 @@ public:
} }
}; };
//Get the marginal bytes of spending the specified output
int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
struct TxSize {
int64_t vsize{-1};
int64_t weight{-1};
};
/** Calculate the size of the transaction assuming all signatures are max size
* Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
* NOTE: this requires that all inputs must be in mapWallet (eg the tx should
* be AllInputsMine). */
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
/**
* populate vCoins with vector of available COutputs.
*/
void AvailableCoins(const CWallet& wallet, std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
CAmount GetAvailableBalance(const CWallet& wallet, const CCoinControl* coinControl = nullptr);
/**
* Find non-change parent output.
*/
const CTxOut& FindNonChangeParentOutput(const CWallet& wallet, const CTransaction& tx, int output) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
/**
* Return list of available coins and locked coins grouped by non-change output address.
*/
std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
std::vector<OutputGroup> GroupOutputs(const CWallet& wallet, const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only);
/**
* Shuffle and select coins until nTargetValue is reached while avoiding
* small change; This method is stochastic for some inputs and upon
* completion the coin set and corresponding actual target value is
* assembled
* param@[in] coins Set of UTXOs to consider. These will be categorized into
* OutputGroups and filtered using eligibility_filter before
* selecting coins.
* param@[out] setCoinsRet Populated with the coins selected if successful.
* param@[out] nValueRet Used to return the total value of selected coins.
*/
bool AttemptSelection(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params);
/**
* Select a set of coins such that nValueRet >= nTargetValue and at least
* all coins from coin_control are selected; never select unconfirmed coins if they are not ours
* param@[out] setCoinsRet Populated with inputs including pre-selected inputs from
* coin_control and Coin Selection if successful.
* param@[out] nValueRet Total value of selected coins including pre-selected ones
* from coin_control and Coin Selection if successful.
*/
bool SelectCoins(const CWallet& wallet, const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet,
const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet);
/**
* Create a new transaction paying the recipients with a set of coins
* selected by SelectCoins(); Also create the change output, when needed
* @note passing nChangePosInOut as -1 will result in setting a random position
*/
bool CreateTransaction(CWallet& wallet, const std::vector<CRecipient>& vecSend, CTransactionRef& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, const CCoinControl& coin_control, FeeCalculation& fee_calc_out, bool sign = true);
/**
* Insert additional inputs into the transaction by
* calling CreateTransaction();
*/
bool FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl);
#endif // BITCOIN_WALLET_SPEND_H #endif // BITCOIN_WALLET_SPEND_H

View File

@ -10,6 +10,7 @@
#include <util/translation.h> #include <util/translation.h>
#include <wallet/coincontrol.h> #include <wallet/coincontrol.h>
#include <wallet/coinselection.h> #include <wallet/coinselection.h>
#include <wallet/spend.h>
#include <wallet/test/wallet_test_fixture.h> #include <wallet/test/wallet_test_fixture.h>
#include <wallet/wallet.h> #include <wallet/wallet.h>
@ -87,7 +88,7 @@ static void add_coin(CWallet& wallet, const CAmount& nValue, int nAge = 6*24, bo
wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1); wtx->m_amounts[CWalletTx::DEBIT].Set(ISMINE_SPENDABLE, 1);
wtx->m_is_cache_empty = false; wtx->m_is_cache_empty = false;
} }
COutput output(wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */); COutput output(wallet, *wtx, nInput, nAge, true /* spendable */, true /* solvable */, true /* safe */);
vCoins.push_back(output); vCoins.push_back(output);
} }
static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false) static void add_coin(const CAmount& nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0, bool spendable = false)
@ -144,7 +145,7 @@ inline std::vector<OutputGroup>& GroupCoins(const std::vector<COutput>& coins)
inline std::vector<OutputGroup>& KnapsackGroupOutputs(const CoinEligibilityFilter& filter) inline std::vector<OutputGroup>& KnapsackGroupOutputs(const CoinEligibilityFilter& filter)
{ {
static std::vector<OutputGroup> static_groups; static std::vector<OutputGroup> static_groups;
static_groups = testWallet.GroupOutputs(vCoins, coin_selection_params, filter, /* positive_only */false); static_groups = GroupOutputs(testWallet, vCoins, coin_selection_params, filter, /* positive_only */false);
return static_groups; return static_groups;
} }
@ -316,7 +317,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
coin_control.fAllowOtherInputs = true; coin_control.fAllowOtherInputs = true;
coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i)); coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i));
coin_selection_params_bnb.m_effective_feerate = CFeeRate(0); coin_selection_params_bnb.m_effective_feerate = CFeeRate(0);
BOOST_CHECK(wallet->SelectCoins(vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb)); BOOST_CHECK(SelectCoins(*wallet, vCoins, 10 * CENT, setCoinsRet, nValueRet, coin_control, coin_selection_params_bnb));
} }
} }
@ -657,7 +658,7 @@ BOOST_AUTO_TEST_CASE(SelectCoins_test)
CoinSet out_set; CoinSet out_set;
CAmount out_value = 0; CAmount out_value = 0;
CCoinControl cc; CCoinControl cc;
BOOST_CHECK(testWallet.SelectCoins(vCoins, target, out_set, out_value, cc, cs_params)); BOOST_CHECK(SelectCoins(testWallet, vCoins, target, out_set, out_value, cc, cs_params));
BOOST_CHECK_GE(out_value, target); BOOST_CHECK_GE(out_value, target);
} }
} }

View File

@ -22,12 +22,12 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION); CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION);
CTransactionRef prev_tx1; CTransactionRef prev_tx1;
s_prev_tx1 >> prev_tx1; s_prev_tx1 >> prev_tx1;
m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(&m_wallet, prev_tx1)); m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx1->GetHash()), std::forward_as_tuple(prev_tx1));
CDataStream s_prev_tx2(ParseHex("0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000"), SER_NETWORK, PROTOCOL_VERSION); CDataStream s_prev_tx2(ParseHex("0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f618765000000"), SER_NETWORK, PROTOCOL_VERSION);
CTransactionRef prev_tx2; CTransactionRef prev_tx2;
s_prev_tx2 >> prev_tx2; s_prev_tx2 >> prev_tx2;
m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(&m_wallet, prev_tx2)); m_wallet.mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(prev_tx2->GetHash()), std::forward_as_tuple(prev_tx2));
// Add scripts // Add scripts
CScript rs1; CScript rs1;

View File

@ -5,6 +5,7 @@
#include <policy/fees.h> #include <policy/fees.h>
#include <validation.h> #include <validation.h>
#include <wallet/coincontrol.h> #include <wallet/coincontrol.h>
#include <wallet/spend.h>
#include <wallet/test/util.h> #include <wallet/test/util.h>
#include <wallet/test/wallet_test_fixture.h> #include <wallet/test/wallet_test_fixture.h>
@ -32,7 +33,7 @@ BOOST_FIXTURE_TEST_CASE(SubtractFee, TestChain100Setup)
coin_control.m_feerate.emplace(10000); coin_control.m_feerate.emplace(10000);
coin_control.fOverrideFeeRate = true; coin_control.fOverrideFeeRate = true;
FeeCalculation fee_calc; FeeCalculation fee_calc;
BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, change_pos, error, coin_control, fee_calc)); BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, change_pos, error, coin_control, fee_calc));
BOOST_CHECK_EQUAL(tx->vout.size(), 1); BOOST_CHECK_EQUAL(tx->vout.size(), 1);
BOOST_CHECK_EQUAL(tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - fee); BOOST_CHECK_EQUAL(tx->vout[0].nValue, recipient.nAmount + leftover_input_amount - fee);
BOOST_CHECK_GT(fee, 0); BOOST_CHECK_GT(fee, 0);

View File

@ -21,6 +21,8 @@
#include <validation.h> #include <validation.h>
#include <wallet/coincontrol.h> #include <wallet/coincontrol.h>
#include <wallet/context.h> #include <wallet/context.h>
#include <wallet/receive.h>
#include <wallet/spend.h>
#include <wallet/test/util.h> #include <wallet/test/util.h>
#include <wallet/test/wallet_test_fixture.h> #include <wallet/test/wallet_test_fixture.h>
@ -103,7 +105,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull());
BOOST_CHECK(!result.last_scanned_height); BOOST_CHECK(!result.last_scanned_height);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0);
} }
// Verify ScanForWalletTransactions picks up transactions in both the old // Verify ScanForWalletTransactions picks up transactions in both the old
@ -122,7 +124,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK(result.last_failed_block.IsNull()); BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 100 * COIN); BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 100 * COIN);
} }
// Prune the older block file. // Prune the older block file.
@ -148,7 +150,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_failed_block, oldTip->GetBlockHash());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight); BOOST_CHECK_EQUAL(*result.last_scanned_height, newTip->nHeight);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 50 * COIN); BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 50 * COIN);
} }
// Prune the remaining block file. // Prune the remaining block file.
@ -173,7 +175,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash()); BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash());
BOOST_CHECK(result.last_scanned_block.IsNull()); BOOST_CHECK(result.last_scanned_block.IsNull());
BOOST_CHECK(!result.last_scanned_height); BOOST_CHECK(!result.last_scanned_height);
BOOST_CHECK_EQUAL(wallet.GetBalance().m_mine_immature, 0); BOOST_CHECK_EQUAL(GetBalance(wallet).m_mine_immature, 0);
} }
} }
@ -319,7 +321,7 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
{ {
CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase()); CWallet wallet(m_node.chain.get(), "", CreateDummyWalletDatabase());
auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan(); auto spk_man = wallet.GetOrCreateLegacyScriptPubKeyMan();
CWalletTx wtx(&wallet, m_coinbase_txns.back()); CWalletTx wtx(m_coinbase_txns.back());
LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore); LOCK2(wallet.cs_wallet, spk_man->cs_KeyStore);
wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash()); wallet.SetLastBlockProcessed(m_node.chainman->ActiveChain().Height(), m_node.chainman->ActiveChain().Tip()->GetBlockHash());
@ -329,13 +331,13 @@ BOOST_FIXTURE_TEST_CASE(coin_mark_dirty_immature_credit, TestChain100Setup)
// Call GetImmatureCredit() once before adding the key to the wallet to // Call GetImmatureCredit() once before adding the key to the wallet to
// cache the current immature credit amount, which is 0. // cache the current immature credit amount, which is 0.
BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 0); BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 0);
// Invalidate the cached value, add the key, and make sure a new immature // Invalidate the cached value, add the key, and make sure a new immature
// credit amount is calculated. // credit amount is calculated.
wtx.MarkDirty(); wtx.MarkDirty();
BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey())); BOOST_CHECK(spk_man->AddKeyPubKey(coinbaseKey, coinbaseKey.GetPubKey()));
BOOST_CHECK_EQUAL(wtx.GetImmatureCredit(), 50*COIN); BOOST_CHECK_EQUAL(CachedTxGetImmatureCredit(wallet, wtx), 50*COIN);
} }
static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime) static int64_t AddTx(ChainstateManager& chainman, CWallet& wallet, uint32_t lockTime, int64_t mockTime, int64_t blockTime)
@ -506,7 +508,7 @@ public:
CCoinControl dummy; CCoinControl dummy;
FeeCalculation fee_calc_out; FeeCalculation fee_calc_out;
{ {
BOOST_CHECK(wallet->CreateTransaction({recipient}, tx, fee, changePos, error, dummy, fee_calc_out)); BOOST_CHECK(CreateTransaction(*wallet, {recipient}, tx, fee, changePos, error, dummy, fee_calc_out));
} }
wallet->CommitTransaction(tx, {}, {}); wallet->CommitTransaction(tx, {}, {});
CMutableTransaction blocktx; CMutableTransaction blocktx;
@ -528,7 +530,7 @@ public:
std::unique_ptr<CWallet> wallet; std::unique_ptr<CWallet> wallet;
}; };
BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup) BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
{ {
std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString(); std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
@ -537,14 +539,14 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
std::map<CTxDestination, std::vector<COutput>> list; std::map<CTxDestination, std::vector<COutput>> list;
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
list = wallet->ListCoins(); list = ListCoins(*wallet);
} }
BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U); BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U);
// Check initial balance from one mature coinbase transaction. // Check initial balance from one mature coinbase transaction.
BOOST_CHECK_EQUAL(50 * COIN, wallet->GetAvailableBalance()); BOOST_CHECK_EQUAL(50 * COIN, GetAvailableBalance(*wallet));
// Add a transaction creating a change address, and confirm ListCoins still // Add a transaction creating a change address, and confirm ListCoins still
// returns the coin associated with the change address underneath the // returns the coin associated with the change address underneath the
@ -553,7 +555,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */}); AddTx(CRecipient{GetScriptForRawPubKey({}), 1 * COIN, false /* subtract fee */});
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
list = wallet->ListCoins(); list = ListCoins(*wallet);
} }
BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
@ -563,7 +565,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
std::vector<COutput> available; std::vector<COutput> available;
wallet->AvailableCoins(available); AvailableCoins(*wallet, available);
BOOST_CHECK_EQUAL(available.size(), 2U); BOOST_CHECK_EQUAL(available.size(), 2U);
} }
for (const auto& group : list) { for (const auto& group : list) {
@ -575,14 +577,14 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
std::vector<COutput> available; std::vector<COutput> available;
wallet->AvailableCoins(available); AvailableCoins(*wallet, available);
BOOST_CHECK_EQUAL(available.size(), 0U); BOOST_CHECK_EQUAL(available.size(), 0U);
} }
// Confirm ListCoins still returns same result as before, despite coins // Confirm ListCoins still returns same result as before, despite coins
// being locked. // being locked.
{ {
LOCK(wallet->cs_wallet); LOCK(wallet->cs_wallet);
list = wallet->ListCoins(); list = ListCoins(*wallet);
} }
BOOST_CHECK_EQUAL(list.size(), 1U); BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress); BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);

View File

@ -17,12 +17,8 @@
#include <list> #include <list>
#include <vector> #include <vector>
struct COutputEntry;
typedef std::map<std::string, std::string> mapValue_t; 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) static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue)
{ {
@ -34,6 +30,7 @@ static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue)
nOrderPos = atoi64(mapValue["n"]); nOrderPos = atoi64(mapValue["n"]);
} }
static inline void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue) static inline void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue)
{ {
if (nOrderPos == -1) if (nOrderPos == -1)
@ -68,8 +65,6 @@ public:
class CWalletTx class CWalletTx
{ {
private: private:
const CWallet* const pwallet;
/** Constant used in hashBlock to indicate tx has been abandoned, only used at /** Constant used in hashBlock to indicate tx has been abandoned, only used at
* serialization/deserialization to avoid ambiguity with conflicted. * serialization/deserialization to avoid ambiguity with conflicted.
*/ */
@ -126,7 +121,6 @@ public:
// memory only // memory only
enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS }; 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]; mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS];
/** /**
* This flag is true if all m_amounts caches are empty. This is particularly * This flag is true if all m_amounts caches are empty. This is particularly
@ -139,9 +133,8 @@ public:
mutable bool fInMempool; mutable bool fInMempool;
mutable CAmount nChangeCached; mutable CAmount nChangeCached;
CWalletTx(const CWallet* wallet, CTransactionRef arg) CWalletTx(CTransactionRef arg)
: pwallet(wallet), : tx(std::move(arg))
tx(std::move(arg))
{ {
Init(); Init();
} }
@ -264,72 +257,13 @@ public:
m_is_cache_empty = true; 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 */ /** True if only scriptSigs are different */
bool IsEquivalentTo(const CWalletTx& tx) const; bool IsEquivalentTo(const CWalletTx& tx) const;
bool InMempool() const; bool InMempool() const;
bool IsTrusted() const;
int64_t GetTxTime() 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; } bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; }
void setAbandoned() void setAbandoned()
{ {
@ -346,7 +280,6 @@ public:
void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; } void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; }
const uint256& GetHash() const { return tx->GetHash(); } const uint256& GetHash() const { return tx->GetHash(); }
bool IsCoinBase() const { return tx->IsCoinBase(); } bool IsCoinBase() const { return tx->IsCoinBase(); }
bool IsImmatureCoinBase() const;
// Disable copying of CWalletTx objects to prevent bugs where instances get // 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 // copied in and out of the mapWallet map, and fields are updated in the

View File

@ -581,7 +581,7 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
const uint256& wtxid = it->second; const uint256& wtxid = it->second;
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid); std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) { if (mit != mapWallet.end()) {
int depth = mit->second.GetDepthInMainChain(); int depth = GetTxDepthInMainChain(mit->second);
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
return true; // Spent return true; // Spent
} }
@ -900,7 +900,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
} }
// Inserts only if not already there, returns tx inserted or tx found // Inserts only if not already there, returns tx inserted or tx found
auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(this, tx)); auto ret = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(tx));
CWalletTx& wtx = (*ret.first).second; CWalletTx& wtx = (*ret.first).second;
bool fInsertedNew = ret.second; bool fInsertedNew = ret.second;
bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew); bool fUpdated = update_wtx && update_wtx(wtx, fInsertedNew);
@ -984,7 +984,7 @@ CWalletTx* CWallet::AddToWallet(CTransactionRef tx, const CWalletTx::Confirmatio
bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) bool CWallet::LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx)
{ {
const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(this, nullptr)); const auto& ins = mapWallet.emplace(std::piecewise_construct, std::forward_as_tuple(hash), std::forward_as_tuple(nullptr));
CWalletTx& wtx = ins.first->second; CWalletTx& wtx = ins.first->second;
if (!fill_wtx(wtx, ins.second)) { if (!fill_wtx(wtx, ins.second)) {
return false; return false;
@ -1074,7 +1074,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
{ {
LOCK(cs_wallet); LOCK(cs_wallet);
const CWalletTx* wtx = GetWalletTx(hashTx); const CWalletTx* wtx = GetWalletTx(hashTx);
return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() == 0 && !wtx->InMempool(); return wtx && !wtx->isAbandoned() && GetTxDepthInMainChain(*wtx) == 0 && !wtx->InMempool();
} }
void CWallet::MarkInputsDirty(const CTransactionRef& tx) void CWallet::MarkInputsDirty(const CTransactionRef& tx)
@ -1100,7 +1100,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
auto it = mapWallet.find(hashTx); auto it = mapWallet.find(hashTx);
assert(it != mapWallet.end()); assert(it != mapWallet.end());
const CWalletTx& origtx = it->second; const CWalletTx& origtx = it->second;
if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) { if (GetTxDepthInMainChain(origtx) != 0 || origtx.InMempool()) {
return false; return false;
} }
@ -1113,7 +1113,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
auto it = mapWallet.find(now); auto it = mapWallet.find(now);
assert(it != mapWallet.end()); assert(it != mapWallet.end());
CWalletTx& wtx = it->second; CWalletTx& wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain(); int currentconfirm = GetTxDepthInMainChain(wtx);
// If the orig tx was not in block, none of its spends can be // If the orig tx was not in block, none of its spends can be
assert(currentconfirm <= 0); assert(currentconfirm <= 0);
// if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon} // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon}
@ -1168,7 +1168,7 @@ void CWallet::MarkConflicted(const uint256& hashBlock, int conflicting_height, c
auto it = mapWallet.find(now); auto it = mapWallet.find(now);
assert(it != mapWallet.end()); assert(it != mapWallet.end());
CWalletTx& wtx = it->second; CWalletTx& wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain(); int currentconfirm = GetTxDepthInMainChain(wtx);
if (conflictconfirms < currentconfirm) { if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update. // Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block. // Mark transaction as conflicted with this block.
@ -1698,7 +1698,7 @@ void CWallet::ReacceptWalletTransactions()
CWalletTx& wtx = item.second; CWalletTx& wtx = item.second;
assert(wtx.GetHash() == wtxid); assert(wtx.GetHash() == wtxid);
int nDepth = wtx.GetDepthInMainChain(); int nDepth = GetTxDepthInMainChain(wtx);
if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) { if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx)); mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
@ -1709,24 +1709,24 @@ void CWallet::ReacceptWalletTransactions()
for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) { for (const std::pair<const int64_t, CWalletTx*>& item : mapSorted) {
CWalletTx& wtx = *(item.second); CWalletTx& wtx = *(item.second);
std::string unused_err_string; std::string unused_err_string;
wtx.SubmitMemoryPoolAndRelay(unused_err_string, false); SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, false);
} }
} }
bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay) bool CWallet::SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const
{ {
// Can't relay if wallet is not broadcasting // Can't relay if wallet is not broadcasting
if (!pwallet->GetBroadcastTransactions()) return false; if (!GetBroadcastTransactions()) return false;
// Don't relay abandoned transactions // Don't relay abandoned transactions
if (isAbandoned()) return false; if (wtx.isAbandoned()) return false;
// Don't try to submit coinbase transactions. These would fail anyway but would // Don't try to submit coinbase transactions. These would fail anyway but would
// cause log spam. // cause log spam.
if (IsCoinBase()) return false; if (wtx.IsCoinBase()) return false;
// Don't try to submit conflicted or confirmed transactions. // Don't try to submit conflicted or confirmed transactions.
if (GetDepthInMainChain() != 0) return false; if (GetTxDepthInMainChain(wtx) != 0) return false;
// Submit transaction to mempool for relay // Submit transaction to mempool for relay
pwallet->WalletLogPrintf("Submitting wtx %s to mempool for relay\n", GetHash().ToString()); WalletLogPrintf("Submitting wtx %s to mempool for relay\n", wtx.GetHash().ToString());
// We must set fInMempool here - while it will be re-set to true by the // We must set fInMempool here - while it will be re-set to true by the
// entered-mempool callback, if we did not there would be a race where a // entered-mempool callback, if we did not there would be a race where a
// user could call sendmoney in a loop and hit spurious out of funds errors // user could call sendmoney in a loop and hit spurious out of funds errors
@ -1736,18 +1736,17 @@ bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay)
// Irrespective of the failure reason, un-marking fInMempool // Irrespective of the failure reason, un-marking fInMempool
// out-of-order is incorrect - it should be unmarked when // out-of-order is incorrect - it should be unmarked when
// TransactionRemovedFromMempool fires. // TransactionRemovedFromMempool fires.
bool ret = pwallet->chain().broadcastTransaction(tx, pwallet->m_default_max_tx_fee, relay, err_string); bool ret = chain().broadcastTransaction(wtx.tx, m_default_max_tx_fee, relay, err_string);
fInMempool |= ret; wtx.fInMempool |= ret;
return ret; return ret;
} }
std::set<uint256> CWalletTx::GetConflicts() const std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const
{ {
std::set<uint256> result; std::set<uint256> result;
if (pwallet != nullptr)
{ {
uint256 myHash = GetHash(); uint256 myHash = wtx.GetHash();
result = pwallet->GetConflicts(myHash); result = GetConflicts(myHash);
result.erase(myHash); result.erase(myHash);
} }
return result; return result;
@ -1785,11 +1784,11 @@ void CWallet::ResendWalletTransactions()
for (std::pair<const uint256, CWalletTx>& item : mapWallet) { for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
CWalletTx& wtx = item.second; CWalletTx& wtx = item.second;
// Attempt to rebroadcast all txes more than 5 minutes older than // Attempt to rebroadcast all txes more than 5 minutes older than
// the last block. SubmitMemoryPoolAndRelay() will not rebroadcast // the last block. SubmitTxMemoryPoolAndRelay() will not rebroadcast
// any confirmed or conflicting txs. // any confirmed or conflicting txs.
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue; if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
std::string unused_err_string; std::string unused_err_string;
if (wtx.SubmitMemoryPoolAndRelay(unused_err_string, true)) ++submitted_tx_count; if (SubmitTxMemoryPoolAndRelay(wtx, unused_err_string, true)) ++submitted_tx_count;
} }
} // cs_wallet } // cs_wallet
@ -1977,7 +1976,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
} }
std::string err_string; std::string err_string;
if (!wtx.SubmitMemoryPoolAndRelay(err_string, true)) { if (!SubmitTxMemoryPoolAndRelay(wtx, err_string, true)) {
WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string); WalletLogPrintf("CommitTransaction(): Transaction cannot be broadcast immediately, %s\n", err_string);
// TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure. // TODO: if we expect the failure to be long term or permanent, instead delete wtx from the wallet and return failure.
} }
@ -2911,28 +2910,27 @@ CKeyPool::CKeyPool(const CPubKey& vchPubKeyIn, bool internalIn)
m_pre_split = false; m_pre_split = false;
} }
int CWalletTx::GetDepthInMainChain() const int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const
{ {
assert(pwallet != nullptr); AssertLockHeld(cs_wallet);
AssertLockHeld(pwallet->cs_wallet); if (wtx.isUnconfirmed() || wtx.isAbandoned()) return 0;
if (isUnconfirmed() || isAbandoned()) return 0;
return (pwallet->GetLastBlockHeight() - m_confirm.block_height + 1) * (isConflicted() ? -1 : 1); return (GetLastBlockHeight() - wtx.m_confirm.block_height + 1) * (wtx.isConflicted() ? -1 : 1);
} }
int CWalletTx::GetBlocksToMaturity() const int CWallet::GetTxBlocksToMaturity(const CWalletTx& wtx) const
{ {
if (!IsCoinBase()) if (!wtx.IsCoinBase())
return 0; return 0;
int chain_depth = GetDepthInMainChain(); int chain_depth = GetTxDepthInMainChain(wtx);
assert(chain_depth >= 0); // coinbase tx should not be conflicted assert(chain_depth >= 0); // coinbase tx should not be conflicted
return std::max(0, (COINBASE_MATURITY+1) - chain_depth); return std::max(0, (COINBASE_MATURITY+1) - chain_depth);
} }
bool CWalletTx::IsImmatureCoinBase() const bool CWallet::IsTxImmatureCoinBase(const CWalletTx& wtx) const
{ {
// note GetBlocksToMaturity is 0 for non-coinbase tx // note GetBlocksToMaturity is 0 for non-coinbase tx
return GetBlocksToMaturity() > 0; return GetTxBlocksToMaturity(wtx) > 0;
} }
bool CWallet::IsCrypted() const bool CWallet::IsCrypted() const

View File

@ -21,9 +21,7 @@
#include <validationinterface.h> #include <validationinterface.h>
#include <wallet/coinselection.h> #include <wallet/coinselection.h>
#include <wallet/crypter.h> #include <wallet/crypter.h>
#include <wallet/receive.h>
#include <wallet/scriptpubkeyman.h> #include <wallet/scriptpubkeyman.h>
#include <wallet/spend.h>
#include <wallet/transaction.h> #include <wallet/transaction.h>
#include <wallet/walletdb.h> #include <wallet/walletdb.h>
#include <wallet/walletutil.h> #include <wallet/walletutil.h>
@ -331,8 +329,6 @@ private:
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure // ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers; std::map<uint256, std::unique_ptr<ScriptPubKeyMan>> m_spk_managers;
bool 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) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** /**
* Catch wallet up to current chain, scanning new blocks, updating the best * Catch wallet up to current chain, scanning new blocks, updating the best
* block locator and m_last_block_processed, and registering for * block locator and m_last_block_processed, and registering for
@ -353,17 +349,6 @@ public:
return *m_database; return *m_database;
} }
/**
* Select a set of coins such that nValueRet >= nTargetValue and at least
* all coins from coin_control are selected; never select unconfirmed coins if they are not ours
* param@[out] setCoinsRet Populated with inputs including pre-selected inputs from
* coin_control and Coin Selection if successful.
* param@[out] nValueRet Total value of selected coins including pre-selected ones
* from coin_control and Coin Selection if successful.
*/
bool SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet,
const CCoinControl& coin_control, CoinSelectionParams& coin_selection_params) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Get a name for this wallet for logging/debugging purposes. /** Get a name for this wallet for logging/debugging purposes.
*/ */
const std::string& GetName() const { return m_name; } const std::string& GetName() const { return m_name; }
@ -419,48 +404,47 @@ public:
interfaces::Chain& chain() const { assert(m_chain); return *m_chain; } interfaces::Chain& chain() const { assert(m_chain); return *m_chain; }
const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); const CWalletTx* GetWalletTx(const uint256& hash) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
// 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> GetTxConflicts(const CWalletTx& wtx) 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 GetTxDepthInMainChain(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS;
bool IsTxInMainChain(const CWalletTx& wtx) const { return GetTxDepthInMainChain(wtx) > 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 GetTxBlocksToMaturity(const CWalletTx& wtx) const;
bool IsTxImmatureCoinBase(const CWalletTx& wtx) const;
//! check whether we support the named feature //! check whether we support the named feature
bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); } bool CanSupportFeature(enum WalletFeature wf) const override EXCLUSIVE_LOCKS_REQUIRED(cs_wallet) { AssertLockHeld(cs_wallet); return IsFeatureSupported(nWalletVersion, wf); }
/**
* populate vCoins with vector of available COutputs.
*/
void AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* coinControl = nullptr, const CAmount& nMinimumAmount = 1, const CAmount& nMaximumAmount = MAX_MONEY, const CAmount& nMinimumSumAmount = MAX_MONEY, const uint64_t nMaximumCount = 0) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Return list of available coins and locked coins grouped by non-change output address.
*/
std::map<CTxDestination, std::vector<COutput>> ListCoins() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Find non-change parent output.
*/
const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Shuffle and select coins until nTargetValue is reached while avoiding
* small change; This method is stochastic for some inputs and upon
* completion the coin set and corresponding actual target value is
* assembled
* param@[in] coins Set of UTXOs to consider. These will be categorized into
* OutputGroups and filtered using eligibility_filter before
* selecting coins.
* param@[out] setCoinsRet Populated with the coins selected if successful.
* param@[out] nValueRet Used to return the total value of selected coins.
*/
bool AttemptSelection(const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, std::vector<COutput> coins,
std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CoinSelectionParams& coin_selection_params) const;
bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
// Whether this or any known UTXO with the same single key has been spent. // Whether this or any known UTXO with the same single key has been spent.
bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsSpentKey(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void SetSpentKeyState(WalletBatch& batch, const uint256& hash, unsigned int n, bool used, std::set<CTxDestination>& tx_destinations) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::vector<OutputGroup> GroupOutputs(const std::vector<COutput>& outputs, const CoinSelectionParams& coin_sel_params, const CoinEligibilityFilter& filter, bool positive_only) const;
/** Display address on an external signer. Returns false if external signer support is not compiled */ /** Display address on an external signer. Returns false if external signer support is not compiled */
bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool DisplayAddress(const CTxDestination& dest) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@ -546,24 +530,9 @@ public:
void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override; void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override;
void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
void ResendWalletTransactions(); void ResendWalletTransactions();
struct Balance {
CAmount m_mine_trusted{0}; //!< Trusted, at depth=GetBalance.min_depth or more
CAmount m_mine_untrusted_pending{0}; //!< Untrusted, but in mempool (pending)
CAmount m_mine_immature{0}; //!< Immature coinbases in the main chain
CAmount m_watchonly_trusted{0};
CAmount m_watchonly_untrusted_pending{0};
CAmount m_watchonly_immature{0};
};
Balance GetBalance(int min_depth = 0, bool avoid_reuse = true) const;
CAmount GetAvailableBalance(const CCoinControl* coinControl = nullptr) const;
OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const; OutputType TransactionChangeType(const std::optional<OutputType>& change_type, const std::vector<CRecipient>& vecSend) const;
/**
* Insert additional inputs into the transaction by
* calling CreateTransaction();
*/
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosInOut, bilingual_str& error, bool lockUnspents, const std::set<int>& setSubtractFeeFromOutputs, CCoinControl);
/** Fetch the inputs and sign with SIGHASH_ALL. */ /** Fetch the inputs and sign with SIGHASH_ALL. */
bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Sign the tx given the input coins and sighash. */ /** Sign the tx given the input coins and sighash. */
@ -590,12 +559,6 @@ public:
bool bip32derivs = true, bool bip32derivs = true,
size_t* n_signed = nullptr) const; size_t* n_signed = nullptr) const;
/**
* Create a new transaction paying the recipients with a set of coins
* selected by SelectCoins(); Also create the change output, when needed
* @note passing nChangePosInOut as -1 will result in setting a random position
*/
bool 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 = true);
/** /**
* Submit the transaction to the node's mempool and then relay to peers. * Submit the transaction to the node's mempool and then relay to peers.
* Should be called after CreateTransaction unless you want to abort * Should be called after CreateTransaction unless you want to abort
@ -607,6 +570,9 @@ public:
*/ */
void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm); void CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::vector<std::pair<std::string, std::string>> orderForm);
/** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */
bool SubmitTxMemoryPoolAndRelay(const CWalletTx& wtx, std::string& err_string, bool relay) const;
bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const bool DummySignTx(CMutableTransaction &txNew, const std::set<CTxOut> &txouts, bool use_max_sig = false) const
{ {
std::vector<CTxOut> v_txouts(txouts.size()); std::vector<CTxOut> v_txouts(txouts.size());
@ -664,9 +630,6 @@ public:
int64_t GetOldestKeyPoolTime() const; int64_t GetOldestKeyPoolTime() const;
std::set<std::set<CTxDestination>> GetAddressGroupings() const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
std::map<CTxDestination, CAmount> GetAddressBalances() const;
std::set<CTxDestination> GetLabelAddresses(const std::string& label) const; std::set<CTxDestination> GetLabelAddresses(const std::string& label) const;
/** /**
@ -680,25 +643,16 @@ public:
isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); isminetype IsMine(const CTxDestination& dest) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); isminetype IsMine(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
isminetype IsMine(const CTxIn& txin) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** /**
* Returns amount of debit if the input matches the * Returns amount of debit if the input matches the
* filter, otherwise returns 0 * filter, otherwise returns 0
*/ */
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const; CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
isminetype IsMine(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); isminetype IsMine(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
CAmount GetCredit(const CTxOut& txout, const isminefilter& filter) const;
bool IsChange(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool IsChange(const CScript& script) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
CAmount GetChange(const CTxOut& txout) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); bool IsMine(const CTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** should probably be renamed to IsRelevantToMe */ /** should probably be renamed to IsRelevantToMe */
bool IsFromMe(const CTransaction& tx) const; bool IsFromMe(const CTransaction& tx) const;
CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const; CAmount GetDebit(const CTransaction& tx, const isminefilter& filter) const;
/** Returns whether all of the inputs match the filter */
bool IsAllFromMe(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetCredit(const CTransaction& tx, const isminefilter& filter) const;
CAmount GetChange(const CTransaction& tx) const;
void chainStateFlushed(const CBlockLocator& loc) override; void chainStateFlushed(const CBlockLocator& loc) override;
DBErrors LoadWallet(); DBErrors LoadWallet();
@ -965,18 +919,6 @@ public:
} }
}; };
struct TxSize {
int64_t vsize{-1};
int64_t weight{-1};
};
/** Calculate the size of the transaction assuming all signatures are max size
* Use DummySignatureCreator, which inserts 71 byte signatures everywhere.
* NOTE: this requires that all inputs must be in mapWallet (eg the tx should
* be IsAllFromMe). */
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, bool use_max_sig = false) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, bool use_max_sig = false);
//! Add wallet name to persistent configuration so it will be loaded on startup. //! Add wallet name to persistent configuration so it will be loaded on startup.
bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name); bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);

View File

@ -954,7 +954,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal
uint256 hash; uint256 hash;
ssKey >> hash; ssKey >> hash;
vTxHash.push_back(hash); vTxHash.push_back(hash);
vWtx.emplace_back(nullptr /* wallet */, nullptr /* tx */); vWtx.emplace_back(nullptr /* tx */);
ssValue >> vWtx.back(); ssValue >> vWtx.back();
} }
} }

View File

@ -600,7 +600,7 @@ class WalletTest(BitcoinTestFramework):
total_txs = len(self.nodes[0].listtransactions("*", 99999)) total_txs = len(self.nodes[0].listtransactions("*", 99999))
# Try with walletrejectlongchains # Try with walletrejectlongchains
# Double chain limit but require combining inputs, so we pass SelectCoinsMinConf # Double chain limit but require combining inputs, so we pass AttemptSelection
self.stop_node(0) self.stop_node(0)
extra_args = ["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)] extra_args = ["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)]
self.start_node(0, extra_args=extra_args) self.start_node(0, extra_args=extra_args)

View File

@ -24,10 +24,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"wallet/fees -> wallet/wallet -> wallet/fees" "wallet/fees -> wallet/wallet -> wallet/fees"
"wallet/wallet -> wallet/walletdb -> wallet/wallet" "wallet/wallet -> wallet/walletdb -> wallet/wallet"
"node/coinstats -> validation -> node/coinstats" "node/coinstats -> validation -> node/coinstats"
# Temporary circular dependencies that allow wallet.h/wallet.cpp to be
# split up in a MOVEONLY commit. These are removed in #21206.
"wallet/receive -> wallet/wallet -> wallet/receive"
"wallet/spend -> wallet/wallet -> wallet/spend"
) )
EXIT_CODE=0 EXIT_CODE=0