Skip to content

Commit e12871f

Browse files
DashCoreAutoGuixachow101
authored andcommitted
partial bitcoin#25734: wallet, refactor: bitcoin#24584 follow-ups
Co-authored-by: Andrew Chow <[email protected]>
1 parent 918053b commit e12871f

File tree

13 files changed

+169
-133
lines changed

13 files changed

+169
-133
lines changed

src/bench/coin_selection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ static void CoinSelection(benchmark::Bench& bench)
5757
wallet::CoinsResult available_coins;
5858
for (const auto& wtx : wtxs) {
5959
const auto txout = wtx->tx->vout.at(0);
60-
available_coins.legacy.emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
60+
available_coins.coins[OutputType::LEGACY].emplace_back(COutPoint(wtx->GetHash(), 0), txout, /*depth=*/6 * 24, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/true, /*solvable=*/true, /*safe=*/true, wtx->GetTxTime(), /*from_me=*/true, /*fees=*/ 0);
6161
}
6262
const CoinEligibilityFilter filter_standard(1, 6, 0);
6363
FastRandomContext rand{};

src/coinjoin/client.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1555,7 +1555,7 @@ bool CCoinJoinClientSession::CreateCollateralTransaction(CMutableTransaction& tx
15551555
AssertLockHeld(m_wallet->cs_wallet);
15561556

15571557
CCoinControl coin_control(CoinType::ONLY_COINJOIN_COLLATERAL);
1558-
std::vector<COutput> vCoins{AvailableCoinsListUnspent(*m_wallet, &coin_control).all()};
1558+
std::vector<COutput> vCoins{AvailableCoinsListUnspent(*m_wallet, &coin_control).All()};
15591559
if (vCoins.empty()) {
15601560
strReason = strprintf("%s requires a collateral transaction and could not locate an acceptable input!", gCoinJoinName);
15611561
return false;

src/outputtype.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,14 @@
1818
static const std::string OUTPUT_TYPE_STRING_LEGACY = "legacy";
1919
static const std::string OUTPUT_TYPE_STRING_UNKNOWN = "unknown";
2020

21-
const std::array<OutputType, 1> OUTPUT_TYPES = {OutputType::LEGACY};
22-
2321
bool ParseOutputType(const std::string& type, OutputType& output_type)
2422
{
2523
if (type == OUTPUT_TYPE_STRING_LEGACY) {
2624
output_type = OutputType::LEGACY;
2725
return true;
26+
} else if (type == OUTPUT_TYPE_STRING_UNKNOWN) {
27+
output_type = OutputType::UNKNOWN;
28+
return true;
2829
}
2930
return false;
3031
}

src/outputtype.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ enum class OutputType {
1818
UNKNOWN,
1919
};
2020

21-
extern const std::array<OutputType, 1> OUTPUT_TYPES;
21+
static constexpr auto OUTPUT_TYPES = std::array{
22+
OutputType::LEGACY,
23+
OutputType::UNKNOWN,
24+
};
2225

2326
[[nodiscard]] bool ParseOutputType(const std::string& str, OutputType& output_type);
2427
const std::string& FormatOutputType(OutputType type);

src/rpc/evo.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,7 @@ static void FundSpecialTx(CWallet& wallet, CMutableTransaction& tx, const Specia
292292
coinControl.destChange = fundDest;
293293
coinControl.fRequireAllInputs = false;
294294

295-
for (const auto& out : AvailableCoinsListUnspent(wallet).all()) {
295+
for (const auto& out : AvailableCoinsListUnspent(wallet).All()) {
296296
CTxDestination txDest;
297297
if (ExtractDestination(out.txout.scriptPubKey, txDest) && txDest == fundDest) {
298298
coinControl.Select(out.outpoint);

src/rpc/masternode.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ static RPCHelpMan masternode_outputs()
163163
CCoinControl coin_control(CoinType::ONLY_MASTERNODE_COLLATERAL);
164164

165165
UniValue outputsArr(UniValue::VARR);
166-
for (const auto& out : WITH_LOCK(wallet->cs_wallet, return AvailableCoinsListUnspent(*wallet, &coin_control).all())) {
166+
for (const auto& out : WITH_LOCK(wallet->cs_wallet, return AvailableCoinsListUnspent(*wallet, &coin_control).All())) {
167167
outputsArr.push_back(out.outpoint.ToStringShort());
168168
}
169169

src/wallet/coinjoin.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ bool CWallet::SelectTxDSInsByDenomination(int nDenom, CAmount nValueMax, std::ve
5858
CAmount nValueTotal{0};
5959
CCoinControl coin_control(CoinType::ONLY_READY_TO_MIX);
6060
std::set<uint256> setRecentTxIds;
61-
std::vector<COutput> vCoins{AvailableCoinsListUnspent(*this, &coin_control).all()};
61+
std::vector<COutput> vCoins{AvailableCoinsListUnspent(*this, &coin_control).All()};
6262

6363
WalletCJLogPrint(this, "CWallet::%s -- vCoins.size(): %d\n", __func__, vCoins.size());
6464

@@ -104,7 +104,7 @@ bool CWallet::SelectDenominatedAmounts(CAmount nValueMax, std::set<CAmount>& set
104104
CAmount nValueTotal{0};
105105
CCoinControl coin_control(CoinType::ONLY_READY_TO_MIX);
106106
// CompareByPriority() cares about effective value, which is only calculable when supplied a feerate
107-
std::vector<COutput> vCoins{AvailableCoins(*this, &coin_control, /*feerate=*/CFeeRate(0)).all()};
107+
std::vector<COutput> vCoins{AvailableCoins(*this, &coin_control, /*feerate=*/CFeeRate(0)).All()};
108108

109109
// larger denoms first
110110
std::sort(vCoins.rbegin(), vCoins.rend(), CompareByPriority());
@@ -235,7 +235,7 @@ bool CWallet::HasCollateralInputs(bool fOnlyConfirmed) const
235235
CCoinControl coin_control(CoinType::ONLY_COINJOIN_COLLATERAL);
236236
coin_control.m_include_unsafe_inputs = !fOnlyConfirmed;
237237

238-
return AvailableCoinsListUnspent(*this, &coin_control).size() > 0;
238+
return AvailableCoinsListUnspent(*this, &coin_control).Size() > 0;
239239
}
240240

241241
int CWallet::CountInputsWithAmount(CAmount nInputAmount) const

src/wallet/rpc/coins.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ RPCHelpMan listunspent()
658658
coinControl.m_include_unsafe_inputs = include_unsafe;
659659

660660
LOCK(pwallet->cs_wallet);
661-
vecOutputs = AvailableCoinsListUnspent(*pwallet, &coinControl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).all();
661+
vecOutputs = AvailableCoinsListUnspent(*pwallet, &coinControl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).All();
662662
}
663663

664664
LOCK(pwallet->cs_wallet);

src/wallet/spend.cpp

Lines changed: 80 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -77,24 +77,62 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall
7777
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
7878
}
7979

80-
uint64_t CoinsResult::size() const
80+
size_t CoinsResult::Size() const
8181
{
82-
return legacy.size() + other.size();
82+
size_t size{0};
83+
for (const auto& it : coins) {
84+
size += it.second.size();
85+
}
86+
return size;
8387
}
8488

85-
std::vector<COutput> CoinsResult::all() const
89+
std::vector<COutput> CoinsResult::All() const
8690
{
8791
std::vector<COutput> all;
88-
all.reserve(this->size());
89-
all.insert(all.end(), legacy.begin(), legacy.end());
90-
all.insert(all.end(), other.begin(), other.end());
92+
all.reserve(Size());
93+
for (const auto& it : coins) {
94+
all.insert(all.end(), it.second.begin(), it.second.end());
95+
}
9196
return all;
9297
}
9398

94-
void CoinsResult::clear()
99+
void CoinsResult::Clear() {
100+
coins.clear();
101+
}
102+
103+
void CoinsResult::Erase(std::set<COutPoint>& preset_coins)
95104
{
96-
legacy.clear();
97-
other.clear();
105+
for (auto& it : coins) {
106+
auto& vec = it.second;
107+
auto i = std::find_if(vec.begin(), vec.end(), [&](const COutput &c) { return preset_coins.count(c.outpoint);});
108+
if (i != vec.end()) {
109+
vec.erase(i);
110+
break;
111+
}
112+
}
113+
}
114+
115+
void CoinsResult::Shuffle(FastRandomContext& rng_fast)
116+
{
117+
for (auto& it : coins) {
118+
::Shuffle(it.second.begin(), it.second.end(), rng_fast);
119+
}
120+
}
121+
122+
void CoinsResult::Add(OutputType type, const COutput& out)
123+
{
124+
coins[type].emplace_back(out);
125+
}
126+
127+
static OutputType GetOutputType(TxoutType type)
128+
{
129+
switch (type) {
130+
case TxoutType::SCRIPTHASH:
131+
case TxoutType::PUBKEYHASH:
132+
return OutputType::LEGACY;
133+
default:
134+
return OutputType::UNKNOWN;
135+
}
98136
}
99137

100138
CoinsResult AvailableCoins(const CWallet& wallet,
@@ -215,37 +253,30 @@ CoinsResult AvailableCoins(const CWallet& wallet,
215253
// Filter by spendable outputs only
216254
if (!spendable && only_spendable) continue;
217255

218-
// When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script)
219-
// We don't need those here, so we are leaving them in return_values_unused
220-
std::vector<std::vector<uint8_t>> return_values_unused;
221-
TxoutType type;
222-
223256
// If the Output is P2SH and spendable, we want to know if it is
224257
// a P2SH (legacy). We can determine this from the redeemScript.
225258
// If the Output is not spendable, it will be classified as a P2SH (legacy),
226259
// since we have no way of knowing otherwise without the redeemScript
260+
CScript script;
227261
if (output.scriptPubKey.IsPayToScriptHash() && solvable) {
228-
CScript redeemScript;
229262
CTxDestination destination;
230263
if (!ExtractDestination(output.scriptPubKey, destination))
231264
continue;
232265
const CScriptID& hash = CScriptID(std::get<ScriptHash>(destination));
233-
if (!provider->GetCScript(hash, redeemScript))
266+
if (!provider->GetCScript(hash, script))
234267
continue;
235-
type = Solver(redeemScript, return_values_unused);
236268
} else {
237-
type = Solver(output.scriptPubKey, return_values_unused);
269+
script = output.scriptPubKey;
238270
}
239271

240272
COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
241-
switch (type) {
242-
case TxoutType::SCRIPTHASH:
243-
case TxoutType::PUBKEYHASH:
244-
result.legacy.push_back(coin);
245-
break;
246-
default:
247-
result.other.push_back(coin);
248-
};
273+
274+
// When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script)
275+
// We don't need those here, so we are leaving them in return_values_unused
276+
std::vector<std::vector<uint8_t>> return_values_unused;
277+
TxoutType type;
278+
type = Solver(script, return_values_unused);
279+
result.Add(GetOutputType(type), coin);
249280

250281
// Cache total amount as we go
251282
result.total_amount += output.nValue;
@@ -257,7 +288,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
257288
}
258289

259290
// Checks the maximum number of UTXO's.
260-
if (nMaximumCount > 0 && result.size() >= nMaximumCount) {
291+
if (nMaximumCount > 0 && result.Size() >= nMaximumCount) {
261292
return result;
262293
}
263294
}
@@ -313,7 +344,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
313344

314345
std::map<CTxDestination, std::vector<COutput>> result;
315346

316-
for (COutput& coin : AvailableCoinsListUnspent(wallet).all()) {
347+
for (const COutput& coin : AvailableCoinsListUnspent(wallet).All()) {
317348
CTxDestination address;
318349
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
319350
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
@@ -443,22 +474,25 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
443474
{
444475
// Run coin selection on each OutputType and compute the Waste Metric
445476
std::vector<SelectionResult> results;
446-
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.legacy, coin_selection_params, nCoinType)}) {
447-
results.push_back(*result);
477+
for (const auto& it : available_coins.coins) {
478+
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, it.second, coin_selection_params, nCoinType)}) {
479+
results.push_back(*result);
480+
}
448481
}
449-
450-
// If we can't fund the transaction from any individual OutputType, run coin selection
451-
// over all available coins, else pick the best solution from the results
452-
if (results.size() == 0) {
453-
if (allow_mixed_output_types) {
454-
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.all(), coin_selection_params, nCoinType)}) {
455-
return result;
456-
}
482+
// If we have at least one solution for funding the transaction without mixing, choose the minimum one according to waste metric
483+
// and return the result
484+
if (results.size() > 0) return *std::min_element(results.begin(), results.end());
485+
486+
// If we can't fund the transaction from any individual OutputType, run coin selection one last time
487+
// over all available coins, which would allow mixing
488+
if (allow_mixed_output_types) {
489+
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.All(), coin_selection_params, nCoinType)}) {
490+
return result;
457491
}
458-
return std::optional<SelectionResult>();
459-
};
460-
std::optional<SelectionResult> result{*std::min_element(results.begin(), results.end())};
461-
return result;
492+
}
493+
// Either mixing is not allowed and we couldn't find a solution from any single OutputType, or mixing was allowed and we still couldn't
494+
// find a solution using all available coins
495+
return std::nullopt;
462496
};
463497

464498
std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins,
@@ -576,7 +610,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
576610
// Calculate the smallest set of inputs required to meet nTargetValue from available_coins
577611
bool success{false};
578612
OutputGroup preset_candidates(coin_selection_params);
579-
for (const COutput& out : available_coins.all()) {
613+
for (const COutput& out : available_coins.All()) {
580614
if (!out.spendable) continue;
581615
if (preset_coins.count(out.outpoint)) {
582616
preset_candidates.Insert(out, /*ancestors=*/0, /*descendants=*/0, /*positive_only=*/false);
@@ -600,8 +634,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
600634

601635
// remove preset inputs from coins so that Coin Selection doesn't pick them.
602636
if (coin_control.HasSelected()) {
603-
available_coins.legacy.erase(remove_if(available_coins.legacy.begin(), available_coins.legacy.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.legacy.end());
604-
available_coins.other.erase(remove_if(available_coins.other.begin(), available_coins.other.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.other.end());
637+
available_coins.Erase(preset_coins);
605638
}
606639

607640
unsigned int limit_ancestor_count = 0;
@@ -613,12 +646,11 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
613646

614647
// form groups from remaining coins; note that preset coins will not
615648
// automatically have their associated (same address) coins included
616-
if (coin_control.m_avoid_partial_spends && available_coins.size() > OUTPUT_GROUP_MAX_ENTRIES) {
649+
if (coin_control.m_avoid_partial_spends && available_coins.Size() > OUTPUT_GROUP_MAX_ENTRIES) {
617650
// Cases where we have 101+ outputs all pointing to the same destination may result in
618651
// privacy leaks as they will potentially be deterministically sorted. We solve that by
619652
// explicitly shuffling the outputs before processing
620-
Shuffle(available_coins.legacy.begin(), available_coins.legacy.end(), coin_selection_params.rng_fast);
621-
Shuffle(available_coins.other.begin(), available_coins.other.end(), coin_selection_params.rng_fast);
653+
available_coins.Shuffle(coin_selection_params.rng_fast);
622654
}
623655
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
624656
// transaction at a target feerate. If an attempt fails, more attempts may be made using a more

src/wallet/spend.h

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,22 @@ int64_t CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *wall
3333
* This struct is really just a wrapper around OutputType vectors with a convenient
3434
* method for concatenating and returning all COutputs as one vector.
3535
*
36-
* clear(), size() methods are implemented so that one can interact with
37-
* the CoinsResult struct as if it was a vector
36+
* Size(), Clear(), Erase(), Shuffle(), and Add() methods are implemented to
37+
* allow easy interaction with the struct.
3838
*/
3939
struct CoinsResult {
40-
/** Vectors for each OutputType */
41-
std::vector<COutput> legacy;
42-
43-
/** Other is a catch-all for anything that doesn't match the known OutputTypes */
44-
std::vector<COutput> other;
40+
std::map<OutputType, std::vector<COutput>> coins;
4541

4642
/** Concatenate and return all COutputs as one vector */
47-
std::vector<COutput> all() const;
43+
std::vector<COutput> All() const;
4844

4945
/** The following methods are provided so that CoinsResult can mimic a vector,
5046
* i.e., methods can work with individual OutputType vectors or on the entire object */
51-
uint64_t size() const;
52-
void clear();
47+
size_t Size() const;
48+
void Clear();
49+
void Erase(std::set<COutPoint>& preset_coins);
50+
void Shuffle(FastRandomContext& rng_fast);
51+
void Add(OutputType type, const COutput& out);
5352

5453
/** Sum of all available coins */
5554
CAmount total_amount{0};

0 commit comments

Comments
 (0)