aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Dobson <dobsonsa68@gmail.com>2021-09-03 21:05:00 +1200
committerSamuel Dobson <dobsonsa68@gmail.com>2021-09-03 21:21:58 +1200
commit629c4ab2e31325614c5b5bb9636705dde4659bb3 (patch)
treeb06de8ce5810c22477a569821c5f9c9434f455de
parentdd097c42df7378c9d0114c04c82b3c38c18e13dc (diff)
parentb11a195ef450bd138aa03204a5e74fdd3ddced26 (diff)
Merge bitcoin/bitcoin#22100: refactor: Clean up new wallet spend, receive files added #21207
b11a195ef450bd138aa03204a5e74fdd3ddced26 refactor: Detach wallet transaction methods (followup for move-only) (Russell Yanofsky) Pull request description: This makes `CWallet` and `CWalletTx` methods in `spend.cpp` and `receive.cpp` files into standalone functions. It's a followup to [#21207 MOVEONLY: CWallet transaction code out of wallet.cpp/.h](https://github.com/bitcoin/bitcoin/pull/21207), which moved code from `wallet.cpp` to new `spend.cpp` and `receive.cpp` files. There are no changes in behavior. This is just making methods into functions and removing circular dependencies created by #21207. There are no comment or documentation changes, either. Removed comments from `transaction.h` are just migrated to `spend.h`, `receive.h`, and `wallet.h`. --- This commit was split off from #21206 so there are a few earlier review comments there ACKs for top commit: achow101: ACK b11a195ef450bd138aa03204a5e74fdd3ddced26 Sjors: utACK b11a195ef450bd138aa03204a5e74fdd3ddced26 meshcollider: light ACK b11a195ef450bd138aa03204a5e74fdd3ddced26 Tree-SHA512: 75ce818d3f03b728b14b12e2d21bd20b7be73978601989cb37ff98254393300d1bb7823281449cd3d9e40756d67d42bd9a46bbdafd2e8baa95aaf2cb1c84549f
-rw-r--r--src/bench/coin_selection.cpp11
-rw-r--r--src/bench/wallet_balance.cpp5
-rw-r--r--src/wallet/feebumper.cpp14
-rw-r--r--src/wallet/interfaces.cpp34
-rw-r--r--src/wallet/load.cpp1
-rw-r--r--src/wallet/receive.cpp229
-rw-r--r--src/wallet/receive.h44
-rw-r--r--src/wallet/rpcwallet.cpp63
-rw-r--r--src/wallet/spend.cpp191
-rw-r--r--src/wallet/spend.h83
-rw-r--r--src/wallet/test/coinselector_tests.cpp9
-rw-r--r--src/wallet/test/psbt_wallet_tests.cpp4
-rw-r--r--src/wallet/test/spend_tests.cpp3
-rw-r--r--src/wallet/test/wallet_tests.cpp32
-rw-r--r--src/wallet/transaction.h73
-rw-r--r--src/wallet/wallet.cpp66
-rw-r--r--src/wallet/wallet.h116
-rw-r--r--src/wallet/walletdb.cpp2
-rwxr-xr-xtest/functional/wallet_basic.py2
-rwxr-xr-xtest/lint/lint-circular-dependencies.sh4
20 files changed, 499 insertions, 487 deletions
diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp
index 5beb833b48..aa79aab755 100644
--- a/src/bench/coin_selection.cpp
+++ b/src/bench/coin_selection.cpp
@@ -6,6 +6,7 @@
#include <interfaces/chain.h>
#include <node/context.h>
#include <wallet/coinselection.h>
+#include <wallet/spend.h>
#include <wallet/wallet.h>
#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.vout.resize(1);
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
@@ -45,7 +46,7 @@ static void CoinSelection(benchmark::Bench& bench)
// Create coins
std::vector<COutput> coins;
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);
@@ -56,7 +57,7 @@ static void CoinSelection(benchmark::Bench& bench)
bench.run([&] {
std::set<CInputCoin> setCoinsRet;
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(nValueRet == 1003 * COIN);
assert(setCoinsRet.size() == 2);
@@ -75,9 +76,9 @@ static void add_coin(const CAmount& nValue, int nInput, std::vector<OutputGroup>
CMutableTransaction tx;
tx.vout.resize(nInput + 1);
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.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));
}
// Copied from src/wallet/test/coinselector_tests.cpp
diff --git a/src/bench/wallet_balance.cpp b/src/bench/wallet_balance.cpp
index 362b7c1e15..a205d8b6e7 100644
--- a/src/bench/wallet_balance.cpp
+++ b/src/bench/wallet_balance.cpp
@@ -9,6 +9,7 @@
#include <test/util/setup_common.h>
#include <test/util/wallet.h>
#include <validationinterface.h>
+#include <wallet/receive.h>
#include <wallet/wallet.h>
#include <optional>
@@ -35,11 +36,11 @@ static void WalletBalance(benchmark::Bench& bench, const bool set_dirty, const b
}
SyncWithValidationInterfaceQueue();
- auto bal = wallet.GetBalance(); // Cache
+ auto bal = GetBalance(wallet); // Cache
bench.run([&] {
if (set_dirty) wallet.MarkDirty();
- bal = wallet.GetBalance();
+ bal = GetBalance(wallet);
if (add_mine) assert(bal.m_mine_trusted > 0);
if (add_watchonly) assert(bal.m_watchonly_trusted > 0);
});
diff --git a/src/wallet/feebumper.cpp b/src/wallet/feebumper.cpp
index 30fef50c3b..f2de68295e 100644
--- a/src/wallet/feebumper.cpp
+++ b/src/wallet/feebumper.cpp
@@ -12,6 +12,8 @@
#include <wallet/coincontrol.h>
#include <wallet/feebumper.h>
#include <wallet/fees.h>
+#include <wallet/receive.h>
+#include <wallet/spend.h>
#include <wallet/wallet.h>
//! 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"));
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
// 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;
- 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"));
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
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));
CFeeRate nOldFeeRate(old_fee, txSize);
// 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)
std::vector<CRecipient> recipients;
for (const auto& output : wtx.tx->vout) {
- if (!wallet.IsChange(output)) {
+ if (!OutputIsChange(wallet, output)) {
CRecipient recipient = {output.scriptPubKey, output.nValue, false};
recipients.push_back(recipient);
} 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;
- old_fee = wtx.GetDebit(filter) - wtx.tx->GetValueOut();
+ old_fee = CachedTxGetDebit(wallet, wtx, filter) - wtx.tx->GetValueOut();
if (coin_control.m_feerate) {
// 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
bilingual_str fail_reason;
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);
return Result::WALLET_ERROR;
}
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 0d4b98ecaf..9a8c1e3c02 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -23,7 +23,9 @@
#include <wallet/fees.h>
#include <wallet/ismine.h>
#include <wallet/load.h>
+#include <wallet/receive.h>
#include <wallet/rpcwallet.h>
+#include <wallet/spend.h>
#include <wallet/wallet.h>
#include <memory>
@@ -55,7 +57,7 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
result.tx = wtx.tx;
result.txin_is_mine.reserve(wtx.tx->vin.size());
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_address.reserve(wtx.tx->vout.size());
@@ -67,9 +69,9 @@ WalletTx MakeWalletTx(CWallet& wallet, const CWalletTx& wtx)
wallet.IsMine(result.txout_address.back()) :
ISMINE_NO);
}
- result.credit = wtx.GetCredit(ISMINE_ALL);
- result.debit = wtx.GetDebit(ISMINE_ALL);
- result.change = wtx.GetChange();
+ result.credit = CachedTxGetCredit(wallet, wtx, ISMINE_ALL);
+ result.debit = CachedTxGetDebit(wallet, wtx, ISMINE_ALL);
+ result.change = CachedTxGetChange(wallet, wtx);
result.time = wtx.GetTxTime();
result.value_map = wtx.mapValue;
result.is_coinbase = wtx.IsCoinBase();
@@ -81,15 +83,15 @@ WalletTxStatus MakeWalletTxStatus(const CWallet& wallet, const CWalletTx& wtx)
{
WalletTxStatus result;
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.depth_in_main_chain = wtx.GetDepthInMainChain();
+ result.blocks_to_maturity = wallet.GetTxBlocksToMaturity(wtx);
+ result.depth_in_main_chain = wallet.GetTxDepthInMainChain(wtx);
result.time_received = wtx.nTimeReceived;
result.lock_time = wtx.tx->nLockTime;
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_coinbase = wtx.IsCoinBase();
- result.is_in_main_chain = wtx.IsInMainChain();
+ result.is_in_main_chain = wallet.IsTxInMainChain(wtx);
return result;
}
@@ -242,7 +244,7 @@ public:
LOCK(m_wallet->cs_wallet);
CTransactionRef tx;
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)) {
return {};
}
@@ -358,7 +360,7 @@ public:
}
WalletBalances getBalances() override
{
- const auto bal = m_wallet->GetBalance();
+ const auto bal = GetBalance(*m_wallet);
WalletBalances result;
result.balance = bal.m_mine_trusted;
result.unconfirmed_balance = bal.m_mine_untrusted_pending;
@@ -381,15 +383,15 @@ public:
balances = getBalances();
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
{
- return m_wallet->GetAvailableBalance(&coin_control);
+ return GetAvailableBalance(*m_wallet, &coin_control);
}
isminetype txinIsMine(const CTxIn& txin) override
{
LOCK(m_wallet->cs_wallet);
- return m_wallet->IsMine(txin);
+ return InputIsMine(*m_wallet, txin);
}
isminetype txoutIsMine(const CTxOut& txout) override
{
@@ -404,13 +406,13 @@ public:
CAmount getCredit(const CTxOut& txout, isminefilter filter) override
{
LOCK(m_wallet->cs_wallet);
- return m_wallet->GetCredit(txout, filter);
+ return OutputGetCredit(*m_wallet, txout, filter);
}
CoinsList listCoins() override
{
LOCK(m_wallet->cs_wallet);
CoinsList result;
- for (const auto& entry : m_wallet->ListCoins()) {
+ for (const auto& entry : ListCoins(*m_wallet)) {
auto& group = result[entry.first];
for (const auto& coin : entry.second) {
group.emplace_back(COutPoint(coin.tx->GetHash(), coin.i),
@@ -428,7 +430,7 @@ public:
result.emplace_back();
auto it = m_wallet->mapWallet.find(output.hash);
if (it != m_wallet->mapWallet.end()) {
- int depth = it->second.GetDepthInMainChain();
+ int depth = m_wallet->GetTxDepthInMainChain(it->second);
if (depth >= 0) {
result.back() = MakeWalletTxOut(*m_wallet, it->second, output.n, depth);
}
diff --git a/src/wallet/load.cpp b/src/wallet/load.cpp
index 85cdbb67c9..a994976394 100644
--- a/src/wallet/load.cpp
+++ b/src/wallet/load.cpp
@@ -13,6 +13,7 @@
#include <util/system.h>
#include <util/translation.h>
#include <wallet/context.h>
+#include <wallet/spend.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
diff --git a/src/wallet/receive.cpp b/src/wallet/receive.cpp
index de81dbf324..98706dcdf8 100644
--- a/src/wallet/receive.cpp
+++ b/src/wallet/receive.cpp
@@ -7,27 +7,27 @@
#include <wallet/transaction.h>
#include <wallet/wallet.h>
-isminetype CWallet::IsMine(const CTxIn &txin) const
+isminetype InputIsMine(const CWallet& wallet, const CTxIn &txin)
{
- AssertLockHeld(cs_wallet);
- std::map<uint256, CWalletTx>::const_iterator mi = mapWallet.find(txin.prevout.hash);
- if (mi != mapWallet.end())
+ AssertLockHeld(wallet.cs_wallet);
+ std::map<uint256, CWalletTx>::const_iterator mi = wallet.mapWallet.find(txin.prevout.hash);
+ if (mi != wallet.mapWallet.end())
{
const CWalletTx& prev = (*mi).second;
if (txin.prevout.n < prev.tx->vout.size())
- return IsMine(prev.tx->vout[txin.prevout.n]);
+ return wallet.IsMine(prev.tx->vout[txin.prevout.n]);
}
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)
{
- auto mi = mapWallet.find(txin.prevout.hash);
- if (mi == mapWallet.end())
+ auto mi = wallet.mapWallet.find(txin.prevout.hash);
+ if (mi == wallet.mapWallet.end())
return false; // any unknown inputs can't be from us
const CWalletTx& prev = (*mi).second;
@@ -35,33 +35,33 @@ bool CWallet::IsAllFromMe(const CTransaction& tx, const isminefilter& filter) co
if (txin.prevout.n >= prev.tx->vout.size())
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 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))
throw std::runtime_error(std::string(__func__) + ": value out of range");
- LOCK(cs_wallet);
- return ((IsMine(txout) & filter) ? txout.nValue : 0);
+ LOCK(wallet.cs_wallet);
+ 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;
for (const CTxOut& txout : tx.vout)
{
- nCredit += GetCredit(txout, filter);
+ nCredit += OutputGetCredit(wallet, txout, filter);
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
return nCredit;
}
-bool CWallet::IsChange(const CScript& script) const
+bool ScriptIsChange(const CWallet& wallet, const CScript& script)
{
// TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a script that is ours, but is not in the address book
@@ -70,179 +70,177 @@ bool CWallet::IsChange(const CScript& script) const
// a better way of identifying which outputs are 'the send' and which are
// 'the change' will need to be implemented (maybe extend CWalletTx to remember
// which output, if any, was change).
- AssertLockHeld(cs_wallet);
- if (IsMine(script))
+ AssertLockHeld(wallet.cs_wallet);
+ if (wallet.IsMine(script))
{
CTxDestination address;
if (!ExtractDestination(script, address))
return true;
- if (!FindAddressBookEntry(address)) {
+ if (!wallet.FindAddressBookEntry(address)) {
return true;
}
}
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))
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;
for (const CTxOut& txout : tx.vout)
{
- nChange += GetChange(txout);
+ nChange += OutputGetChange(wallet, txout);
if (!MoneyRange(nChange))
throw std::runtime_error(std::string(__func__) + ": value out of range");
}
return nChange;
}
-CAmount CWalletTx::GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate) const
+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]) {
- amount.Set(filter, type == DEBIT ? pwallet->GetDebit(*tx, filter) : pwallet->GetCredit(*tx, filter));
- m_is_cache_empty = false;
+ amount.Set(filter, type == CWalletTx::DEBIT ? wallet.GetDebit(*wtx.tx, filter) : TxGetCredit(wallet, *wtx.tx, filter));
+ wtx.m_is_cache_empty = false;
}
return amount.m_value[filter];
}
-CAmount 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
- if (IsImmatureCoinBase())
+ if (wallet.IsTxImmatureCoinBase(wtx))
return 0;
CAmount credit = 0;
if (filter & ISMINE_SPENDABLE) {
// 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) {
- credit += GetCachableAmount(CREDIT, ISMINE_WATCH_ONLY);
+ credit += GetCachableAmount(wallet, wtx, CWalletTx::CREDIT, ISMINE_WATCH_ONLY);
}
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;
CAmount debit = 0;
if (filter & ISMINE_SPENDABLE) {
- debit += GetCachableAmount(DEBIT, ISMINE_SPENDABLE);
+ debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_SPENDABLE);
}
if (filter & ISMINE_WATCH_ONLY) {
- debit += GetCachableAmount(DEBIT, ISMINE_WATCH_ONLY);
+ debit += GetCachableAmount(wallet, wtx, CWalletTx::DEBIT, ISMINE_WATCH_ONLY);
}
return debit;
}
-CAmount CWalletTx::GetChange() const
+CAmount CachedTxGetChange(const CWallet& wallet, const CWalletTx& wtx)
{
- if (fChangeCached)
- return nChangeCached;
- nChangeCached = pwallet->GetChange(*tx);
- fChangeCached = true;
- return nChangeCached;
+ if (wtx.fChangeCached)
+ return wtx.nChangeCached;
+ wtx.nChangeCached = TxGetChange(wallet, *wtx.tx);
+ wtx.fChangeCached = true;
+ return wtx.nChangeCached;
}
-CAmount CWalletTx::GetImmatureCredit(bool fUseCache) const
+CAmount CachedTxGetImmatureCredit(const CWallet& wallet, const CWalletTx& wtx, bool fUseCache)
{
- if (IsImmatureCoinBase() && IsInMainChain()) {
- return GetCachableAmount(IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
+ if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) {
+ return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_SPENDABLE, !fUseCache);
}
return 0;
}
-CAmount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const
+CAmount CachedTxGetImmatureWatchOnlyCredit(const CWallet& wallet, const CWalletTx& wtx, const bool fUseCache)
{
- if (IsImmatureCoinBase() && IsInMainChain()) {
- return GetCachableAmount(IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache);
+ if (wallet.IsTxImmatureCoinBase(wtx) && wallet.IsTxInMainChain(wtx)) {
+ return GetCachableAmount(wallet, wtx, CWalletTx::IMMATURE_CREDIT, ISMINE_WATCH_ONLY, !fUseCache);
}
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).
bool allow_cache = (filter & ISMINE_ALL) && (filter & ISMINE_ALL) != ISMINE_ALL;
// Must wait until coinbase is safely deep enough in the chain before valuing it
- if (IsImmatureCoinBase())
+ if (wallet.IsTxImmatureCoinBase(wtx))
return 0;
- if (fUseCache && allow_cache && m_amounts[AVAILABLE_CREDIT].m_cached[filter]) {
- return m_amounts[AVAILABLE_CREDIT].m_value[filter];
+ if (fUseCache && allow_cache && wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_cached[filter]) {
+ return wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].m_value[filter];
}
- bool allow_used_addresses = (filter & ISMINE_USED) || !pwallet->IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
+ bool allow_used_addresses = (filter & ISMINE_USED) || !wallet.IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE);
CAmount nCredit = 0;
- uint256 hashTx = GetHash();
- for (unsigned int i = 0; i < tx->vout.size(); i++)
+ uint256 hashTx = wtx.GetHash();
+ for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
{
- if (!pwallet->IsSpent(hashTx, i) && (allow_used_addresses || !pwallet->IsSpentKey(hashTx, i))) {
- const CTxOut &txout = tx->vout[i];
- nCredit += pwallet->GetCredit(txout, filter);
+ if (!wallet.IsSpent(hashTx, i) && (allow_used_addresses || !wallet.IsSpentKey(hashTx, i))) {
+ const CTxOut &txout = wtx.tx->vout[i];
+ nCredit += OutputGetCredit(wallet, txout, filter);
if (!MoneyRange(nCredit))
throw std::runtime_error(std::string(__func__) + " : value out of range");
}
}
if (allow_cache) {
- m_amounts[AVAILABLE_CREDIT].Set(filter, nCredit);
- m_is_cache_empty = false;
+ wtx.m_amounts[CWalletTx::AVAILABLE_CREDIT].Set(filter, nCredit);
+ wtx.m_is_cache_empty = false;
}
return nCredit;
}
-void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
- std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const
+void CachedTxGetAmounts(const CWallet& wallet, const CWalletTx& wtx,
+ std::list<COutputEntry>& listReceived,
+ std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter)
{
nFee = 0;
listReceived.clear();
listSent.clear();
// Compute fee:
- CAmount nDebit = GetDebit(filter);
+ CAmount nDebit = CachedTxGetDebit(wallet, wtx, filter);
if (nDebit > 0) // debit>0 means we signed/sent this transaction
{
- CAmount nValueOut = tx->GetValueOut();
+ CAmount nValueOut = wtx.tx->GetValueOut();
nFee = nDebit - nValueOut;
}
- LOCK(pwallet->cs_wallet);
+ LOCK(wallet.cs_wallet);
// 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];
- isminetype fIsMine = pwallet->IsMine(txout);
+ const CTxOut& txout = wtx.tx->vout[i];
+ isminetype fIsMine = wallet.IsMine(txout);
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
// 2) the output is to us (received)
if (nDebit > 0)
{
// Don't report 'change' txouts
- if (pwallet->IsChange(txout))
+ if (OutputIsChange(wallet, txout))
continue;
}
else if (!(fIsMine & filter))
@@ -253,8 +251,8 @@ void CWalletTx::GetAmounts(std::list<COutputEntry>& listReceived,
if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable())
{
- pwallet->WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
- this->GetHash().ToString());
+ wallet.WalletLogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
+ wtx.GetHash().ToString());
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)
+{
+ return (CachedTxGetDebit(wallet, wtx, filter) > 0);
+}
+
+bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx, std::set<uint256>& trusted_parents)
{
- AssertLockHeld(cs_wallet);
+ AssertLockHeld(wallet.cs_wallet);
// Quick answer in most cases
- if (!chain().checkFinalTx(*wtx.tx)) return false;
- int nDepth = wtx.GetDepthInMainChain();
+ if (!wallet.chain().checkFinalTx(*wtx.tx)) return false;
+ int nDepth = wallet.GetTxDepthInMainChain(wtx);
if (nDepth >= 1) return true;
if (nDepth < 0) return false;
// using wtx's cached debit
- if (!m_spend_zero_conf_change || !wtx.IsFromMe(ISMINE_ALL)) return false;
+ if (!wallet.m_spend_zero_conf_change || !CachedTxIsFromMe(wallet, wtx, ISMINE_ALL)) return false;
// Don't trust unconfirmed transactions from us unless they are in the mempool.
if (!wtx.InMempool()) return false;
@@ -289,41 +292,41 @@ bool CWallet::IsTrusted(const CWalletTx& wtx, std::set<uint256>& trusted_parents
for (const CTxIn& txin : wtx.tx->vin)
{
// 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;
const CTxOut& parentOut = parent->tx->vout[txin.prevout.n];
// Check that this specific input being spent is trusted
- if (IsMine(parentOut) != ISMINE_SPENDABLE) return false;
+ if (wallet.IsMine(parentOut) != ISMINE_SPENDABLE) return false;
// If we've already trusted this parent, continue
if (trusted_parents.count(parent->GetHash())) continue;
// Recurse to check that the parent is also trusted
- if (!IsTrusted(*parent, trusted_parents)) return false;
+ if (!CachedTxIsTrusted(wallet, *parent, trusted_parents)) return false;
trusted_parents.insert(parent->GetHash());
}
return true;
}
-bool CWalletTx::IsTrusted() const
+bool CachedTxIsTrusted(const CWallet& wallet, const CWalletTx& wtx)
{
std::set<uint256> trusted_parents;
- LOCK(pwallet->cs_wallet);
- return pwallet->IsTrusted(*this, trusted_parents);
+ LOCK(wallet.cs_wallet);
+ 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;
isminefilter reuse_filter = avoid_reuse ? ISMINE_NO : ISMINE_USED;
{
- LOCK(cs_wallet);
+ LOCK(wallet.cs_wallet);
std::set<uint256> trusted_parents;
- for (const auto& entry : mapWallet)
+ for (const auto& entry : wallet.mapWallet)
{
const CWalletTx& wtx = entry.second;
- const bool is_trusted{IsTrusted(wtx, trusted_parents)};
- const int tx_depth{wtx.GetDepthInMainChain()};
- const CAmount tx_credit_mine{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
- const CAmount tx_credit_watchonly{wtx.GetAvailableCredit(/* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
+ const bool is_trusted{CachedTxIsTrusted(wallet, wtx, trusted_parents)};
+ const int tx_depth{wallet.GetTxDepthInMainChain(wtx)};
+ const CAmount tx_credit_mine{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_SPENDABLE | reuse_filter)};
+ const CAmount tx_credit_watchonly{CachedTxGetAvailableCredit(wallet, wtx, /* fUseCache */ true, ISMINE_WATCH_ONLY | reuse_filter)};
if (is_trusted && tx_depth >= min_depth) {
ret.m_mine_trusted += tx_credit_mine;
ret.m_watchonly_trusted += tx_credit_watchonly;
@@ -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_watchonly_untrusted_pending += tx_credit_watchonly;
}
- ret.m_mine_immature += wtx.GetImmatureCredit();
- ret.m_watchonly_immature += wtx.GetImmatureWatchOnlyCredit();
+ ret.m_mine_immature += CachedTxGetImmatureCredit(wallet, wtx);
+ ret.m_watchonly_immature += CachedTxGetImmatureWatchOnlyCredit(wallet, wtx);
}
}
return ret;
}
-std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const
+std::map<CTxDestination, CAmount> GetAddressBalances(const CWallet& wallet)
{
std::map<CTxDestination, CAmount> balances;
{
- LOCK(cs_wallet);
+ LOCK(wallet.cs_wallet);
std::set<uint256> trusted_parents;
- for (const auto& walletEntry : mapWallet)
+ for (const auto& walletEntry : wallet.mapWallet)
{
const CWalletTx& wtx = walletEntry.second;
- if (!IsTrusted(wtx, trusted_parents))
+ if (!CachedTxIsTrusted(wallet, wtx, trusted_parents))
continue;
- if (wtx.IsImmatureCoinBase())
+ if (wallet.IsTxImmatureCoinBase(wtx))
continue;
- int nDepth = wtx.GetDepthInMainChain();
- if (nDepth < (wtx.IsFromMe(ISMINE_ALL) ? 0 : 1))
+ int nDepth = wallet.GetTxDepthInMainChain(wtx);
+ if (nDepth < (CachedTxIsFromMe(wallet, wtx, ISMINE_ALL) ? 0 : 1))
continue;
for (unsigned int i = 0; i < wtx.tx->vout.size(); i++)
{
CTxDestination addr;
- if (!IsMine(wtx.tx->vout[i]))
+ if (!wallet.IsMine(wtx.tx->vout[i]))
continue;
if(!ExtractDestination(wtx.tx->vout[i].scriptPubKey, addr))
continue;
- CAmount n = IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue;
+ CAmount n = wallet.IsSpent(walletEntry.first, i) ? 0 : wtx.tx->vout[i].nValue;
balances[addr] += n;
}
}
@@ -377,13 +380,13 @@ std::map<CTxDestination, CAmount> CWallet::GetAddressBalances() const
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<CTxDestination> grouping;
- for (const auto& walletEntry : mapWallet)
+ for (const auto& walletEntry : wallet.mapWallet)
{
const CWalletTx& wtx = walletEntry.second;
@@ -394,9 +397,9 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const
for (const CTxIn& txin : wtx.tx->vin)
{
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;
- 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;
grouping.insert(address);
any_mine = true;
@@ -406,7 +409,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const
if (any_mine)
{
for (const CTxOut& txout : wtx.tx->vout)
- if (IsChange(txout))
+ if (OutputIsChange(wallet, txout))
{
CTxDestination txoutAddr;
if(!ExtractDestination(txout.scriptPubKey, txoutAddr))
@@ -423,7 +426,7 @@ std::set< std::set<CTxDestination> > CWallet::GetAddressGroupings() const
// group lone addrs by themselves
for (const auto& txout : wtx.tx->vout)
- if (IsMine(txout))
+ if (wallet.IsMine(txout))
{
CTxDestination address;
if(!ExtractDestination(txout.scriptPubKey, address))
diff --git a/src/wallet/receive.h b/src/wallet/receive.h
index 8eead32413..b4b311636b 100644
--- a/src/wallet/receive.h
+++ b/src/wallet/receive.h
@@ -10,11 +10,55 @@
#include <wallet/transaction.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
{
CTxDestination destination;
CAmount amount;
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
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 916f811f9b..ff9e10c5ad 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -31,7 +31,9 @@
#include <wallet/context.h>
#include <wallet/feebumper.h>
#include <wallet/load.h>
+#include <wallet/receive.h>
#include <wallet/rpcwallet.h>
+#include <wallet/spend.h>
#include <wallet/wallet.h>
#include <wallet/walletdb.h>
#include <wallet/walletutil.h>
@@ -147,9 +149,10 @@ LegacyScriptPubKeyMan& EnsureLegacyScriptPubKeyMan(CWallet& wallet, bool also_cr
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);
if (wtx.IsCoinBase())
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)));
entry.pushKV("blocktime", block_time);
} else {
- entry.pushKV("trusted", wtx.IsTrusted());
+ entry.pushKV("trusted", CachedTxIsTrusted(wallet, wtx));
}
uint256 hash = wtx.GetHash();
entry.pushKV("txid", hash.GetHex());
UniValue conflicts(UniValue::VARR);
- for (const uint256& conflict : wtx.GetConflicts())
+ for (const uint256& conflict : wallet.GetTxConflicts(wtx))
conflicts.push_back(conflict.GetHex());
entry.pushKV("walletconflicts", conflicts);
entry.pushKV("time", wtx.GetTxTime());
@@ -423,7 +426,7 @@ UniValue SendMoney(CWallet& wallet, const CCoinControl &coin_control, std::vecto
bilingual_str error;
CTransactionRef tx;
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) {
throw JSONRPCError(RPC_WALLET_INSUFFICIENT_FUNDS, error.original);
}
@@ -576,8 +579,8 @@ static RPCHelpMan listaddressgroupings()
LOCK(pwallet->cs_wallet);
UniValue jsonGroupings(UniValue::VARR);
- std::map<CTxDestination, CAmount> balances = pwallet->GetAddressBalances();
- for (const std::set<CTxDestination>& grouping : pwallet->GetAddressGroupings()) {
+ std::map<CTxDestination, CAmount> balances = GetAddressBalances(*pwallet);
+ for (const std::set<CTxDestination>& grouping : GetAddressGroupings(*pwallet)) {
UniValue jsonGrouping(UniValue::VARR);
for (const CTxDestination& address : grouping)
{
@@ -686,7 +689,7 @@ static CAmount GetReceived(const CWallet& wallet, const UniValue& params, bool b
CAmount amount = 0;
for (const std::pair<const uint256, CWalletTx>& wtx_pair : wallet.mapWallet) {
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;
}
@@ -826,7 +829,7 @@ static RPCHelpMan getbalance()
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));
},
@@ -851,7 +854,7 @@ static RPCHelpMan getunconfirmedbalance()
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;
}
- int nDepth = wtx.GetDepthInMainChain();
+ int nDepth = wallet.GetTxDepthInMainChain(wtx);
if (nDepth < nMinDepth)
continue;
@@ -1310,9 +1313,9 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
std::list<COutputEntry> listReceived;
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
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("fee", ValueFromAmount(-nFee));
if (fLong)
- WalletTxToJSON(wallet.chain(), wtx, entry);
+ WalletTxToJSON(wallet, wtx, entry);
entry.pushKV("abandoned", wtx.isAbandoned());
ret.push_back(entry);
}
}
// Received
- if (listReceived.size() > 0 && wtx.GetDepthInMainChain() >= nMinDepth) {
+ if (listReceived.size() > 0 && wallet.GetTxDepthInMainChain(wtx) >= nMinDepth) {
for (const COutputEntry& r : listReceived)
{
std::string label;
@@ -1358,9 +1361,9 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
MaybePushAddress(entry, r.destination);
if (wtx.IsCoinBase())
{
- if (wtx.GetDepthInMainChain() < 1)
+ if (wallet.GetTxDepthInMainChain(wtx) < 1)
entry.pushKV("category", "orphan");
- else if (wtx.IsImmatureCoinBase())
+ else if (wallet.IsTxImmatureCoinBase(wtx))
entry.pushKV("category", "immature");
else
entry.pushKV("category", "generate");
@@ -1375,7 +1378,7 @@ static void ListTransactions(const CWallet& wallet, const CWalletTx& wtx, int nM
}
entry.pushKV("vout", r.vout);
if (fLong)
- WalletTxToJSON(wallet.chain(), wtx, entry);
+ WalletTxToJSON(wallet, wtx, entry);
ret.push_back(entry);
}
}
@@ -1615,7 +1618,7 @@ static RPCHelpMan listsinceblock()
for (const std::pair<const uint256, CWalletTx>& pairWtx : wallet.mapWallet) {
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 */);
}
}
@@ -1736,16 +1739,16 @@ static RPCHelpMan gettransaction()
}
const CWalletTx& wtx = it->second;
- CAmount nCredit = wtx.GetCredit(filter);
- CAmount nDebit = wtx.GetDebit(filter);
+ CAmount nCredit = CachedTxGetCredit(*pwallet, wtx, filter);
+ CAmount nDebit = CachedTxGetDebit(*pwallet, wtx, filter);
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));
- if (wtx.IsFromMe(filter))
+ if (CachedTxIsFromMe(*pwallet, wtx, filter))
entry.pushKV("fee", ValueFromAmount(nFee));
- WalletTxToJSON(pwallet->chain(), wtx, entry);
+ WalletTxToJSON(*pwallet, wtx, entry);
UniValue details(UniValue::VARR);
ListTransactions(*pwallet, wtx, 0, false, details, filter, nullptr /* filter_label */);
@@ -2384,7 +2387,7 @@ static RPCHelpMan getbalances()
LOCK(wallet.cs_wallet);
- const auto bal = wallet.GetBalance();
+ const auto bal = GetBalance(wallet);
UniValue balances{UniValue::VOBJ};
{
UniValue balances_mine{UniValue::VOBJ};
@@ -2394,7 +2397,7 @@ static RPCHelpMan getbalances()
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
// 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.pushKV("mine", balances_mine);
@@ -2462,7 +2465,7 @@ static RPCHelpMan getwalletinfo()
UniValue obj(UniValue::VOBJ);
size_t kpExternalSize = pwallet->KeypoolCountExternalKeys();
- const auto bal = pwallet->GetBalance();
+ const auto bal = GetBalance(*pwallet);
int64_t kp_oldest = pwallet->GetOldestKeyPoolTime();
obj.pushKV("walletname", pwallet->GetName());
obj.pushKV("walletversion", pwallet->GetVersion());
@@ -3058,7 +3061,7 @@ static RPCHelpMan listunspent()
cctl.m_max_depth = nMaxDepth;
cctl.m_include_unsafe_inputs = include_unsafe;
LOCK(pwallet->cs_wallet);
- pwallet->AvailableCoins(vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
+ AvailableCoins(*pwallet, vecOutputs, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount);
}
LOCK(pwallet->cs_wallet);
@@ -3274,7 +3277,7 @@ void FundTransaction(CWallet& wallet, CMutableTransaction& tx, CAmount& fee_out,
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);
}
}
@@ -3959,7 +3962,7 @@ RPCHelpMan getaddressinfo()
UniValue detail = DescribeWalletAddress(*pwallet, dest);
ret.pushKVs(detail);
- ret.pushKV("ischange", pwallet->IsChange(scriptPubKey));
+ ret.pushKV("ischange", ScriptIsChange(*pwallet, scriptPubKey));
ScriptPubKeyMan* spk_man = pwallet->GetScriptPubKeyMan(scriptPubKey);
if (spk_man) {
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index 928335da2b..4a7a268982 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -21,6 +21,11 @@ using interfaces::FoundBlock;
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
{
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);
}
-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();
CAmount nTotal = 0;
// Either the WALLET_FLAG_AVOID_REUSE flag is not set (in which case we always allow), or we default to avoiding, and only in the case where
// a coin control object is provided, and has the avoid address reuse flag set to false, do we allow already used addresses
- bool allow_used_addresses = !IsWalletFlagSet(WALLET_FLAG_AVOID_REUSE) || (coinControl && !coinControl->m_avoid_address_reuse);
+ 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 max_depth = {coinControl ? coinControl->m_max_depth : DEFAULT_MAX_DEPTH};
const bool only_safe = {coinControl ? !coinControl->m_include_unsafe_inputs : true};
std::set<uint256> trusted_parents;
- for (const auto& entry : mapWallet)
+ for (const auto& entry : wallet.mapWallet)
{
const uint256& wtxid = entry.first;
const CWalletTx& wtx = entry.second;
- if (!chain().checkFinalTx(*wtx.tx)) {
+ if (!wallet.chain().checkFinalTx(*wtx.tx)) {
continue;
}
- if (wtx.IsImmatureCoinBase())
+ if (wallet.IsTxImmatureCoinBase(wtx))
continue;
- int nDepth = wtx.GetDepthInMainChain();
+ int nDepth = wallet.GetTxDepthInMainChain(wtx);
if (nDepth < 0)
continue;
@@ -99,7 +104,7 @@ void CWallet::AvailableCoins(std::vector<COutput>& vCoins, const CCoinControl* c
if (nDepth == 0 && !wtx.InMempool())
continue;
- bool safeTx = IsTrusted(wtx, trusted_parents);
+ bool safeTx = CachedTxIsTrusted(wallet, wtx, trusted_parents);
// We should not consider coins from transactions that are replacing
// 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)))
continue;
- if (IsLockedCoin(entry.first, i))
+ if (wallet.IsLockedCoin(entry.first, i))
continue;
- if (IsSpent(wtxid, i))
+ if (wallet.IsSpent(wtxid, i))
continue;
- isminetype mine = IsMine(wtx.tx->vout[i]);
+ isminetype mine = wallet.IsMine(wtx.tx->vout[i]);
if (mine == ISMINE_NO) {
continue;
}
- if (!allow_used_addresses && IsSpentKey(wtxid, i)) {
+ if (!allow_used_addresses && wallet.IsSpentKey(wtxid, i)) {
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 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.
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;
std::vector<COutput> vCoins;
- AvailableCoins(vCoins, coinControl);
+ AvailableCoins(wallet, vCoins, coinControl);
for (const COutput& out : vCoins) {
if (out.fSpendable) {
balance += out.tx->tx->vout[out.i].nValue;
@@ -207,16 +212,16 @@ CAmount CWallet::GetAvailableBalance(const CCoinControl* coinControl) const
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;
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;
- auto it = mapWallet.find(prevout.hash);
- if (it == mapWallet.end() || it->second.tx->vout.size() <= prevout.n ||
- !IsMine(it->second.tx->vout[prevout.n])) {
+ auto it = wallet.mapWallet.find(prevout.hash);
+ if (it == wallet.mapWallet.end() || it->second.tx->vout.size() <= prevout.n ||
+ !wallet.IsMine(it->second.tx->vout[prevout.n])) {
break;
}
ptx = it->second.tx.get();
@@ -225,39 +230,39 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out
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::vector<COutput> availableCoins;
- AvailableCoins(availableCoins);
+ AvailableCoins(wallet, availableCoins);
for (const COutput& coin : availableCoins) {
CTxDestination address;
- if ((coin.fSpendable || (IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
- ExtractDestination(FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey, address)) {
+ if ((coin.fSpendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.fSolvable)) &&
+ ExtractDestination(FindNonChangeParentOutput(wallet, *coin.tx->tx, coin.i).scriptPubKey, address)) {
result[address].emplace_back(std::move(coin));
}
}
std::vector<COutPoint> lockedCoins;
- ListLockedCoins(lockedCoins);
+ wallet.ListLockedCoins(lockedCoins);
// 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;
for (const COutPoint& output : lockedCoins) {
- auto it = mapWallet.find(output.hash);
- if (it != mapWallet.end()) {
- int depth = it->second.GetDepthInMainChain();
+ auto it = wallet.mapWallet.find(output.hash);
+ if (it != wallet.mapWallet.end()) {
+ int depth = wallet.GetTxDepthInMainChain(it->second);
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;
- 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(
- &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;
}
-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;
@@ -277,12 +282,12 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
if (!output.fSpendable) continue;
size_t ancestors, descendants;
- chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
+ wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
CInputCoin input_coin = output.GetInputCoin();
// Make an OutputGroup containing just this output
OutputGroup group{coin_sel_params};
- group.Insert(input_coin, output.nDepth, output.tx->IsFromMe(ISMINE_ALL), ancestors, descendants, positive_only);
+ 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.
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;
size_t ancestors, descendants;
- chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
+ wallet.chain().getTransactionAncestry(output.tx->GetHash(), ancestors, descendants);
CInputCoin input_coin = output.GetInputCoin();
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
- 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
@@ -352,8 +357,8 @@ std::vector<OutputGroup> CWallet::GroupOutputs(const std::vector<COutput>& outpu
return groups_out;
}
-bool CWallet::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 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)
{
setCoinsRet.clear();
nValueRet = 0;
@@ -363,7 +368,7 @@ bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilit
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.
- 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;
CAmount 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.
- 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.
// So we need to include that for KnapsackSolver as well, as we are expecting to create a change output.
std::set<CInputCoin> knapsack_coins;
@@ -397,7 +402,7 @@ bool CWallet::AttemptSelection(const CAmount& nTargetValue, const CoinEligibilit
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);
CAmount value_to_select = nTargetValue;
@@ -423,8 +428,8 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
coin_control.ListSelected(vPresetInputs);
for (const COutPoint& outpoint : vPresetInputs)
{
- std::map<uint256, CWalletTx>::const_iterator it = mapWallet.find(outpoint.hash);
- if (it != mapWallet.end())
+ std::map<uint256, CWalletTx>::const_iterator it = wallet.mapWallet.find(outpoint.hash);
+ if (it != wallet.mapWallet.end())
{
const CWalletTx& wtx = it->second;
// Clearly invalid input, fail
@@ -432,7 +437,7 @@ bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAm
return false;
}
// 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;
if (coin.m_input_bytes <= 0) {
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_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_descendants = (size_t)std::max<int64_t>(1, limit_descendant_count);
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
// 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(value_to_select, CoinEligibilityFilter(1, 1, 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(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
// possible) if we cannot fund the transaction otherwise.
- if (m_spend_zero_conf_change) {
- if (AttemptSelection(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 (wallet.m_spend_zero_conf_change) {
+ if (AttemptSelection(wallet, value_to_select, CoinEligibilityFilter(0, 1, 2), vCoins, setCoinsRet, nValueRet, coin_selection_params)) return true;
+ 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)) {
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)) {
return true;
}
// If partial groups are allowed, relax the requirement of spending OutputGroups (groups
// of UTXOs sent to the same address, which are obviously controlled by a single wallet)
// in their entirety.
- if (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)) {
return true;
}
// Try with unsafe inputs if they are allowed. This may spend unconfirmed outputs
// received from other wallets.
if (coin_control.m_include_unsafe_inputs
- && 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 */),
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
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
// mempool ancestor/descendant policy to be accepted to mempool and broadcasted, but
// 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 */),
vCoins, setCoinsRet, nValueRet, coin_selection_params)) {
return true;
@@ -595,7 +600,8 @@ static uint32_t GetLocktimeForNewTransaction(interfaces::Chain& chain, const uin
return locktime;
}
-bool CWallet::CreateTransactionInternal(
+static bool CreateTransactionInternal(
+ CWallet& wallet,
const std::vector<CRecipient>& vecSend,
CTransactionRef& tx,
CAmount& nFeeRet,
@@ -603,22 +609,22 @@ bool CWallet::CreateTransactionInternal(
bilingual_str& error,
const CCoinControl& coin_control,
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
- 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
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
- 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;
- const OutputType change_type = TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : m_default_change_type, vecSend);
- ReserveDestination reservedest(this, change_type);
+ const OutputType change_type = wallet.TransactionChangeType(coin_control.m_change_type ? *coin_control.m_change_type : wallet.m_default_change_type, vecSend);
+ ReserveDestination reservedest(&wallet, change_type);
unsigned int outputs_to_subtract_fee_from = 0; // The number of outputs which we are subtracting the fee from
for (const auto& recipient : vecSend) {
recipients_sum += recipient.nAmount;
@@ -662,7 +668,7 @@ bool CWallet::CreateTransactionInternal(
coin_selection_params.change_output_size = GetSerializeSize(change_prototype_txout);
// Get size of spending the change output
- int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, this);
+ int change_spend_size = CalculateMaximumSignedInputSize(change_prototype_txout, &wallet);
// If the wallet doesn't know how to sign change output, assume p2sh-p2wpkh
// as lower-bound to allow BnB to do it's thing
if (change_spend_size == -1) {
@@ -672,18 +678,18 @@ bool CWallet::CreateTransactionInternal(
}
// 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
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
// provided one
if (coin_control.m_feerate && coin_selection_params.m_effective_feerate > *coin_control.m_feerate) {
error = strprintf(_("Fee rate (%s) is lower than the minimum fee rate setting (%s)"), coin_control.m_feerate->ToString(FeeEstimateMode::SAT_VB), coin_selection_params.m_effective_feerate.ToString(FeeEstimateMode::SAT_VB));
return false;
}
- if (feeCalc.reason == FeeReason::FALLBACK && !m_allow_fallback_fee) {
+ if (feeCalc.reason == FeeReason::FALLBACK && !wallet.m_allow_fallback_fee) {
// eventually allow a fallback fee
error = _("Fee estimation failed. Fallbackfee is disabled. Wait a few blocks or enable -fallbackfee.");
return false;
@@ -710,7 +716,7 @@ bool CWallet::CreateTransactionInternal(
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");
return false;
@@ -724,12 +730,12 @@ bool CWallet::CreateTransactionInternal(
// Get available coins
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
CAmount inputs_sum = 0;
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");
return false;
@@ -767,13 +773,13 @@ bool CWallet::CreateTransactionInternal(
// to avoid conflicting with other possible uses of nSequence,
// and in the spirit of "smallest possible change from prior
// behavior."
- const uint32_t nSequence = coin_control.m_signal_bip125_rbf.value_or(m_signal_rbf) ? MAX_BIP125_RBF_SEQUENCE : (CTxIn::SEQUENCE_FINAL - 1);
+ 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) {
txNew.vin.push_back(CTxIn(coin.outpoint, CScript(), nSequence));
}
// 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;
if (nBytes < 0) {
error = _("Signing transaction failed");
@@ -798,7 +804,7 @@ bool CWallet::CreateTransactionInternal(
txNew.vout.erase(change_position);
// Because we have dropped this change, the tx size and required fee will be different, so let's recalculate those
- tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), this, coin_control.fAllowWatchOnly);
+ tx_sizes = CalculateMaximumSignedTxSize(CTransaction(txNew), &wallet, coin_control.fAllowWatchOnly);
nBytes = tx_sizes.vsize;
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
- if (IsDust(txout, chain().relayDustFee())) {
+ if (IsDust(txout, wallet.chain().relayDustFee())) {
if (txout.nValue < 0) {
error = _("The transaction amount is too small to pay the fee");
} else {
@@ -854,7 +860,7 @@ bool CWallet::CreateTransactionInternal(
return false;
}
- if (sign && !SignTransaction(txNew)) {
+ if (sign && !wallet.SignTransaction(txNew)) {
error = _("Signing transaction failed");
return false;
}
@@ -870,14 +876,14 @@ bool CWallet::CreateTransactionInternal(
return false;
}
- if (nFeeRet > m_default_max_tx_fee) {
+ if (nFeeRet > wallet.m_default_max_tx_fee) {
error = TransactionErrorString(TransactionError::MAX_FEE_EXCEEDED);
return false;
}
if (gArgs.GetBoolArg("-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits
- if (!chain().checkChainLimits(tx)) {
+ if (!wallet.chain().checkChainLimits(tx)) {
error = _("Transaction has too long of a mempool chain");
return false;
}
@@ -888,7 +894,7 @@ bool CWallet::CreateTransactionInternal(
reservedest.KeepDestination();
fee_calc_out = feeCalc;
- WalletLogPrintf("Fee Calculation: Fee:%d Bytes:%u Tgt:%d (requested %d) Reason:\"%s\" Decay %.5f: Estimation: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out) Fail: (%g - %g) %.2f%% %.1f/(%.1f %d mem %.1f out)\n",
+ 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,
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,
@@ -899,7 +905,8 @@ bool CWallet::CreateTransactionInternal(
return true;
}
-bool CWallet::CreateTransaction(
+bool CreateTransaction(
+ CWallet& wallet,
const std::vector<CRecipient>& vecSend,
CTransactionRef& tx,
CAmount& nFeeRet,
@@ -919,23 +926,23 @@ bool CWallet::CreateTransaction(
return false;
}
- LOCK(cs_wallet);
+ LOCK(wallet.cs_wallet);
int nChangePosIn = nChangePosInOut;
Assert(!tx); // tx is an out-param. TODO change the return type from bool to tx (or nullptr)
- bool res = CreateTransactionInternal(vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
+ bool res = CreateTransactionInternal(wallet, vecSend, tx, nFeeRet, nChangePosInOut, error, coin_control, fee_calc_out, sign);
// try with avoidpartialspends unless it's enabled already
- if (res && nFeeRet > 0 /* 0 means non-functional fee rate estimation */ && m_max_aps_fee > -1 && !coin_control.m_avoid_partial_spends) {
+ 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;
tmp_cc.m_avoid_partial_spends = true;
CAmount nFeeRet2;
CTransactionRef tx2;
int nChangePosInOut2 = nChangePosIn;
bilingual_str error2; // fired and forgotten; if an error occurs, we discard the results
- if (CreateTransactionInternal(vecSend, tx2, nFeeRet2, nChangePosInOut2, error2, tmp_cc, fee_calc_out, sign)) {
+ if (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
- const bool use_aps = nFeeRet2 <= nFeeRet + m_max_aps_fee;
- WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
+ const bool use_aps = nFeeRet2 <= nFeeRet + wallet.m_max_aps_fee;
+ wallet.WalletLogPrintf("Fee non-grouped = %lld, grouped = %lld, using %s\n", nFeeRet, nFeeRet2, use_aps ? "grouped" : "non-grouped");
if (use_aps) {
tx = tx2;
nFeeRet = nFeeRet2;
@@ -946,7 +953,7 @@ bool CWallet::CreateTransaction(
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;
@@ -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
// CreateTransaction call and LockCoin calls (when lockUnspents is true).
- LOCK(cs_wallet);
+ LOCK(wallet.cs_wallet);
CTransactionRef tx_new;
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;
}
@@ -990,7 +997,7 @@ bool CWallet::FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nC
}
if (lockUnspents) {
- LockCoin(txin.prevout);
+ wallet.LockCoin(txin.prevout);
}
}
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index 03f9a7c2b5..e39f134dc3 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -9,6 +9,9 @@
#include <wallet/transaction.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
{
public:
@@ -43,13 +46,13 @@ public:
*/
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
// Failure will keep this value -1
- if (fSpendable && tx) {
- nInputBytes = tx->GetSpendSize(i, use_max_sig);
+ if (fSpendable) {
+ 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
diff --git a/src/wallet/test/coinselector_tests.cpp b/src/wallet/test/coinselector_tests.cpp
index 7b2169a5b6..f901679efc 100644
--- a/src/wallet/test/coinselector_tests.cpp
+++ b/src/wallet/test/coinselector_tests.cpp
@@ -10,6 +10,7 @@
#include <util/translation.h>
#include <wallet/coincontrol.h>
#include <wallet/coinselection.h>
+#include <wallet/spend.h>
#include <wallet/test/wallet_test_fixture.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_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);
}
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)
{
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;
}
@@ -316,7 +317,7 @@ BOOST_AUTO_TEST_CASE(bnb_search_test)
coin_control.fAllowOtherInputs = true;
coin_control.Select(COutPoint(vCoins.at(0).tx->GetHash(), vCoins.at(0).i));
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;
CAmount out_value = 0;
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);
}
}
diff --git a/src/wallet/test/psbt_wallet_tests.cpp b/src/wallet/test/psbt_wallet_tests.cpp
index 1cefa386b7..8a97f7779d 100644
--- a/src/wallet/test/psbt_wallet_tests.cpp
+++ b/src/wallet/test/psbt_wallet_tests.cpp
@@ -22,12 +22,12 @@ BOOST_AUTO_TEST_CASE(psbt_updater_test)
CDataStream s_prev_tx1(ParseHex("0200000000010158e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7501000000171600145f275f436b09a8cc9a2eb2a2f528485c68a56323feffffff02d8231f1b0100000017a914aed962d6654f9a2b36608eb9d64d2b260db4f1118700c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e88702483045022100a22edcc6e5bc511af4cc4ae0de0fcd75c7e04d8c1c3a8aa9d820ed4b967384ec02200642963597b9b1bc22c75e9f3e117284a962188bf5e8a74c895089046a20ad770121035509a48eb623e10aace8bfd0212fdb8a8e5af3c94b0b133b95e114cab89e4f7965000000"), SER_NETWORK, PROTOCOL_VERSION);
CTransactionRef 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);
CTransactionRef 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
CScript rs1;
diff --git a/src/wallet/test/spend_tests.cpp b/src/wallet/test/spend_tests.cpp
index 8821f680b3..e779b2450f 100644
--- a/src/wallet/test/spend_tests.cpp
+++ b/src/wallet/test/spend_tests.cpp
@@ -5,6 +5,7 @@
#include <policy/fees.h>
#include <validation.h>
#include <wallet/coincontrol.h>
+#include <wallet/spend.h>
#include <wallet/test/util.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.fOverrideFeeRate = true;
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[0].nValue, recipient.nAmount + leftover_input_amount - fee);
BOOST_CHECK_GT(fee, 0);
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index bc5498c88e..12a22f458a 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -21,6 +21,8 @@
#include <validation.h>
#include <wallet/coincontrol.h>
#include <wallet/context.h>
+#include <wallet/receive.h>
+#include <wallet/spend.h>
#include <wallet/test/util.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_scanned_block.IsNull());
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
@@ -122,7 +124,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK(result.last_failed_block.IsNull());
BOOST_CHECK_EQUAL(result.last_scanned_block, newTip->GetBlockHash());
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.
@@ -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_scanned_block, newTip->GetBlockHash());
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.
@@ -173,7 +175,7 @@ BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup)
BOOST_CHECK_EQUAL(result.last_failed_block, newTip->GetBlockHash());
BOOST_CHECK(result.last_scanned_block.IsNull());
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());
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);
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
// 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
// credit amount is calculated.
wtx.MarkDirty();
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)
@@ -506,7 +508,7 @@ public:
CCoinControl dummy;
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, {}, {});
CMutableTransaction blocktx;
@@ -528,7 +530,7 @@ public:
std::unique_ptr<CWallet> wallet;
};
-BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
+BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
{
std::string coinbaseAddress = coinbaseKey.GetPubKey().GetID().ToString();
@@ -537,14 +539,14 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
std::map<CTxDestination, std::vector<COutput>> list;
{
LOCK(wallet->cs_wallet);
- list = wallet->ListCoins();
+ list = ListCoins(*wallet);
}
BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
BOOST_CHECK_EQUAL(list.begin()->second.size(), 1U);
// 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
// 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 */});
{
LOCK(wallet->cs_wallet);
- list = wallet->ListCoins();
+ list = ListCoins(*wallet);
}
BOOST_CHECK_EQUAL(list.size(), 1U);
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);
std::vector<COutput> available;
- wallet->AvailableCoins(available);
+ AvailableCoins(*wallet, available);
BOOST_CHECK_EQUAL(available.size(), 2U);
}
for (const auto& group : list) {
@@ -575,14 +577,14 @@ BOOST_FIXTURE_TEST_CASE(ListCoins, ListCoinsTestingSetup)
{
LOCK(wallet->cs_wallet);
std::vector<COutput> available;
- wallet->AvailableCoins(available);
+ AvailableCoins(*wallet, available);
BOOST_CHECK_EQUAL(available.size(), 0U);
}
// Confirm ListCoins still returns same result as before, despite coins
// being locked.
{
LOCK(wallet->cs_wallet);
- list = wallet->ListCoins();
+ list = ListCoins(*wallet);
}
BOOST_CHECK_EQUAL(list.size(), 1U);
BOOST_CHECK_EQUAL(std::get<PKHash>(list.begin()->first).ToString(), coinbaseAddress);
diff --git a/src/wallet/transaction.h b/src/wallet/transaction.h
index 131faefe0b..094221adf2 100644
--- a/src/wallet/transaction.h
+++ b/src/wallet/transaction.h
@@ -17,12 +17,8 @@
#include <list>
#include <vector>
-struct COutputEntry;
-
typedef std::map<std::string, std::string> mapValue_t;
-//Get the marginal bytes of spending the specified output
-int CalculateMaximumSignedInputSize(const CTxOut& txout, const CWallet* pwallet, bool use_max_sig = false);
static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue)
{
@@ -34,6 +30,7 @@ static inline void ReadOrderPos(int64_t& nOrderPos, mapValue_t& mapValue)
nOrderPos = atoi64(mapValue["n"]);
}
+
static inline void WriteOrderPos(const int64_t& nOrderPos, mapValue_t& mapValue)
{
if (nOrderPos == -1)
@@ -68,8 +65,6 @@ public:
class CWalletTx
{
private:
- const CWallet* const pwallet;
-
/** Constant used in hashBlock to indicate tx has been abandoned, only used at
* serialization/deserialization to avoid ambiguity with conflicted.
*/
@@ -126,7 +121,6 @@ public:
// memory only
enum AmountType { DEBIT, CREDIT, IMMATURE_CREDIT, AVAILABLE_CREDIT, AMOUNTTYPE_ENUM_ELEMENTS };
- CAmount GetCachableAmount(AmountType type, const isminefilter& filter, bool recalculate = false) const;
mutable CachableAmount m_amounts[AMOUNTTYPE_ENUM_ELEMENTS];
/**
* This flag is true if all m_amounts caches are empty. This is particularly
@@ -139,9 +133,8 @@ public:
mutable bool fInMempool;
mutable CAmount nChangeCached;
- CWalletTx(const CWallet* wallet, CTransactionRef arg)
- : pwallet(wallet),
- tx(std::move(arg))
+ CWalletTx(CTransactionRef arg)
+ : tx(std::move(arg))
{
Init();
}
@@ -264,72 +257,13 @@ public:
m_is_cache_empty = true;
}
- //! filter decides which addresses will count towards the debit
- CAmount GetDebit(const isminefilter& filter) const;
- CAmount GetCredit(const isminefilter& filter) const;
- CAmount GetImmatureCredit(bool fUseCache = true) const;
- // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
- // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The
- // annotation "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid
- // having to resolve the issue of member access into incomplete type CWallet.
- CAmount GetAvailableCredit(bool fUseCache = true, const isminefilter& filter = ISMINE_SPENDABLE) const NO_THREAD_SAFETY_ANALYSIS;
- CAmount GetImmatureWatchOnlyCredit(const bool fUseCache = true) const;
- CAmount GetChange() const;
-
- /** Get the marginal bytes if spending the specified output from this transaction */
- int GetSpendSize(unsigned int out, bool use_max_sig = false) const
- {
- return CalculateMaximumSignedInputSize(tx->vout[out], pwallet, use_max_sig);
- }
-
- void GetAmounts(std::list<COutputEntry>& listReceived,
- std::list<COutputEntry>& listSent, CAmount& nFee, const isminefilter& filter) const;
-
- bool IsFromMe(const isminefilter& filter) const
- {
- return (GetDebit(filter) > 0);
- }
-
/** True if only scriptSigs are different */
bool IsEquivalentTo(const CWalletTx& tx) const;
bool InMempool() const;
- bool IsTrusted() const;
int64_t GetTxTime() const;
- /** Pass this transaction to node for mempool insertion and relay to peers if flag set to true */
- bool SubmitMemoryPoolAndRelay(std::string& err_string, bool relay);
-
- // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
- // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation
- // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to
- // resolve the issue of member access into incomplete type CWallet. Note
- // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)"
- // in place.
- std::set<uint256> GetConflicts() const NO_THREAD_SAFETY_ANALYSIS;
-
- /**
- * Return depth of transaction in blockchain:
- * <0 : conflicts with a transaction this deep in the blockchain
- * 0 : in memory pool, waiting to be included in a block
- * >=1 : this many blocks deep in the main chain
- */
- // TODO: Remove "NO_THREAD_SAFETY_ANALYSIS" and replace it with the correct
- // annotation "EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet)". The annotation
- // "NO_THREAD_SAFETY_ANALYSIS" was temporarily added to avoid having to
- // resolve the issue of member access into incomplete type CWallet. Note
- // that we still have the runtime check "AssertLockHeld(pwallet->cs_wallet)"
- // in place.
- int GetDepthInMainChain() const NO_THREAD_SAFETY_ANALYSIS;
- bool IsInMainChain() const { return GetDepthInMainChain() > 0; }
-
- /**
- * @return number of blocks to maturity for this transaction:
- * 0 : is not a coinbase transaction, or is a mature coinbase transaction
- * >0 : is a coinbase transaction which matures in this many blocks
- */
- int GetBlocksToMaturity() const;
bool isAbandoned() const { return m_confirm.status == CWalletTx::ABANDONED; }
void setAbandoned()
{
@@ -346,7 +280,6 @@ public:
void setConfirmed() { m_confirm.status = CWalletTx::CONFIRMED; }
const uint256& GetHash() const { return tx->GetHash(); }
bool IsCoinBase() const { return tx->IsCoinBase(); }
- bool IsImmatureCoinBase() const;
// Disable copying of CWalletTx objects to prevent bugs where instances get
// copied in and out of the mapWallet map, and fields are updated in the
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 6f3dcf2afa..f3af9885a2 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -581,7 +581,7 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
const uint256& wtxid = it->second;
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) {
- int depth = mit->second.GetDepthInMainChain();
+ int depth = GetTxDepthInMainChain(mit->second);
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
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
- 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;
bool fInsertedNew = ret.second;
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)
{
- 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;
if (!fill_wtx(wtx, ins.second)) {
return false;
@@ -1074,7 +1074,7 @@ bool CWallet::TransactionCanBeAbandoned(const uint256& hashTx) const
{
LOCK(cs_wallet);
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)
@@ -1100,7 +1100,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
auto it = mapWallet.find(hashTx);
assert(it != mapWallet.end());
const CWalletTx& origtx = it->second;
- if (origtx.GetDepthInMainChain() != 0 || origtx.InMempool()) {
+ if (GetTxDepthInMainChain(origtx) != 0 || origtx.InMempool()) {
return false;
}
@@ -1113,7 +1113,7 @@ bool CWallet::AbandonTransaction(const uint256& hashTx)
auto it = mapWallet.find(now);
assert(it != mapWallet.end());
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
assert(currentconfirm <= 0);
// 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);
assert(it != mapWallet.end());
CWalletTx& wtx = it->second;
- int currentconfirm = wtx.GetDepthInMainChain();
+ int currentconfirm = GetTxDepthInMainChain(wtx);
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
@@ -1698,7 +1698,7 @@ void CWallet::ReacceptWalletTransactions()
CWalletTx& wtx = item.second;
assert(wtx.GetHash() == wtxid);
- int nDepth = wtx.GetDepthInMainChain();
+ int nDepth = GetTxDepthInMainChain(wtx);
if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
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) {
CWalletTx& wtx = *(item.second);
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
- if (!pwallet->GetBroadcastTransactions()) return false;
+ if (!GetBroadcastTransactions()) return false;
// 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
// cause log spam.
- if (IsCoinBase()) return false;
+ if (wtx.IsCoinBase()) return false;
// 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
- 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
// 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
@@ -1736,18 +1736,17 @@ bool CWalletTx::SubmitMemoryPoolAndRelay(std::string& err_string, bool relay)
// Irrespective of the failure reason, un-marking fInMempool
// out-of-order is incorrect - it should be unmarked when
// TransactionRemovedFromMempool fires.
- bool ret = pwallet->chain().broadcastTransaction(tx, pwallet->m_default_max_tx_fee, relay, err_string);
- fInMempool |= ret;
+ bool ret = chain().broadcastTransaction(wtx.tx, m_default_max_tx_fee, relay, err_string);
+ wtx.fInMempool |= ret;
return ret;
}
-std::set<uint256> CWalletTx::GetConflicts() const
+std::set<uint256> CWallet::GetTxConflicts(const CWalletTx& wtx) const
{
std::set<uint256> result;
- if (pwallet != nullptr)
{
- uint256 myHash = GetHash();
- result = pwallet->GetConflicts(myHash);
+ uint256 myHash = wtx.GetHash();
+ result = GetConflicts(myHash);
result.erase(myHash);
}
return result;
@@ -1785,11 +1784,11 @@ void CWallet::ResendWalletTransactions()
for (std::pair<const uint256, CWalletTx>& item : mapWallet) {
CWalletTx& wtx = item.second;
// 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.
if (wtx.nTimeReceived > m_best_block_time - 5 * 60) continue;
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
@@ -1977,7 +1976,7 @@ void CWallet::CommitTransaction(CTransactionRef tx, mapValue_t mapValue, std::ve
}
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);
// 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;
}
-int CWalletTx::GetDepthInMainChain() const
+int CWallet::GetTxDepthInMainChain(const CWalletTx& wtx) const
{
- assert(pwallet != nullptr);
- AssertLockHeld(pwallet->cs_wallet);
- if (isUnconfirmed() || isAbandoned()) return 0;
+ AssertLockHeld(cs_wallet);
+ if (wtx.isUnconfirmed() || wtx.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;
- int chain_depth = GetDepthInMainChain();
+ int chain_depth = GetTxDepthInMainChain(wtx);
assert(chain_depth >= 0); // coinbase tx should not be conflicted
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
- return GetBlocksToMaturity() > 0;
+ return GetTxBlocksToMaturity(wtx) > 0;
}
bool CWallet::IsCrypted() const
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 607af3efb0..fbeec2aa30 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -21,9 +21,7 @@
#include <validationinterface.h>
#include <wallet/coinselection.h>
#include <wallet/crypter.h>
-#include <wallet/receive.h>
#include <wallet/scriptpubkeyman.h>
-#include <wallet/spend.h>
#include <wallet/transaction.h>
#include <wallet/walletdb.h>
#include <wallet/walletutil.h>
@@ -331,8 +329,6 @@ private:
// ScriptPubKeyMan::GetID. In many cases it will be the hash of an internal structure
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
* block locator and m_last_block_processed, and registering for
@@ -353,17 +349,6 @@ public:
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.
*/
const std::string& GetName() const { return m_name; }
@@ -419,39 +404,40 @@ public:
interfaces::Chain& chain() const { assert(m_chain); return *m_chain; }
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);
-
- //! 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); }
- /**
- * 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);
+ // 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 list of available coins and locked coins grouped by non-change output address.
+ * 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
*/
- std::map<CTxDestination, std::vector<COutput>> ListCoins() 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.
+ int GetTxDepthInMainChain(const CWalletTx& wtx) const NO_THREAD_SAFETY_ANALYSIS;
+ bool IsTxInMainChain(const CWalletTx& wtx) const { return GetTxDepthInMainChain(wtx) > 0; }
/**
- * Find non-change parent output.
+ * @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
*/
- const CTxOut& FindNonChangeParentOutput(const CTransaction& tx, int output) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
+ int GetTxBlocksToMaturity(const CWalletTx& wtx) const;
+ bool IsTxImmatureCoinBase(const CWalletTx& wtx) const;
- /**
- * 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;
+ //! 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 IsSpent(const uint256& hash, unsigned int n) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
@@ -459,8 +445,6 @@ public:
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);
- 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 */
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 ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
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;
- /**
- * 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. */
bool SignTransaction(CMutableTransaction& tx) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/** Sign the tx given the input coins and sighash. */
@@ -591,12 +560,6 @@ public:
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.
* Should be called after CreateTransaction unless you want to abort
* broadcasting the transaction.
@@ -607,6 +570,9 @@ public:
*/
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
{
std::vector<CTxOut> v_txouts(txouts.size());
@@ -664,9 +630,6 @@ public:
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;
/**
@@ -680,25 +643,16 @@ public:
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 CTxIn& txin) const EXCLUSIVE_LOCKS_REQUIRED(cs_wallet);
/**
* Returns amount of debit if the input matches the
* filter, otherwise returns 0
*/
CAmount GetDebit(const CTxIn& txin, const isminefilter& filter) const;
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);
/** should probably be renamed to IsRelevantToMe */
bool IsFromMe(const CTransaction& tx) 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;
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.
bool AddWalletSetting(interfaces::Chain& chain, const std::string& wallet_name);
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 2fabe65a93..03464cd2c8 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -954,7 +954,7 @@ DBErrors WalletBatch::FindWalletTx(std::vector<uint256>& vTxHash, std::list<CWal
uint256 hash;
ssKey >> hash;
vTxHash.push_back(hash);
- vWtx.emplace_back(nullptr /* wallet */, nullptr /* tx */);
+ vWtx.emplace_back(nullptr /* tx */);
ssValue >> vWtx.back();
}
}
diff --git a/test/functional/wallet_basic.py b/test/functional/wallet_basic.py
index b5afc3785e..ab38ddb996 100755
--- a/test/functional/wallet_basic.py
+++ b/test/functional/wallet_basic.py
@@ -600,7 +600,7 @@ class WalletTest(BitcoinTestFramework):
total_txs = len(self.nodes[0].listtransactions("*", 99999))
# 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)
extra_args = ["-walletrejectlongchains", "-limitancestorcount=" + str(2 * chainlimit)]
self.start_node(0, extra_args=extra_args)
diff --git a/test/lint/lint-circular-dependencies.sh b/test/lint/lint-circular-dependencies.sh
index 233381f2d9..8e74f41bb6 100755
--- a/test/lint/lint-circular-dependencies.sh
+++ b/test/lint/lint-circular-dependencies.sh
@@ -24,10 +24,6 @@ EXPECTED_CIRCULAR_DEPENDENCIES=(
"wallet/fees -> wallet/wallet -> wallet/fees"
"wallet/wallet -> wallet/walletdb -> wallet/wallet"
"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