Skip to content

Commit db09aec

Browse files
josibakefurszy
andcommitted
wallet: switch to new shuffle, erase, push_back
switch to new methods, remove old code. this also updates the Size, All, and Clear methods to now use the coins map. this commit is not strictly a refactor because previously coin selection was never run over the UNKNOWN type until the last step when being run over all. now that we are iterating over each, it is run over UNKNOWN but this is expected to be empty most of the time. Co-authored-by: furszy <[email protected]>
1 parent b6b50b0 commit db09aec

File tree

5 files changed

+39
-84
lines changed

5 files changed

+39
-84
lines changed

src/bench/coin_selection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ static void CoinSelection(benchmark::Bench& bench)
5959
wallet::CoinsResult available_coins;
6060
for (const auto& wtx : wtxs) {
6161
const auto txout = wtx->tx->vout.at(0);
62-
available_coins.bech32.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);
62+
available_coins.coins[OutputType::BECH32].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);
6363
}
6464

6565
const CoinEligibilityFilter filter_standard(1, 6, 0);

src/wallet/spend.cpp

Lines changed: 29 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -79,30 +79,27 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction &tx, const CWallet *walle
7979
return CalculateMaximumSignedTxSize(tx, wallet, txouts, coin_control);
8080
}
8181

82-
uint64_t CoinsResult::Size() const
82+
size_t CoinsResult::Size() const
8383
{
84-
return bech32m.size() + bech32.size() + P2SH_segwit.size() + legacy.size() + other.size();
84+
size_t size{0};
85+
for (const auto& it : coins) {
86+
size += it.second.size();
87+
}
88+
return size;
8589
}
8690

8791
std::vector<COutput> CoinsResult::All() const
8892
{
8993
std::vector<COutput> all;
90-
all.reserve(this->Size());
91-
all.insert(all.end(), bech32m.begin(), bech32m.end());
92-
all.insert(all.end(), bech32.begin(), bech32.end());
93-
all.insert(all.end(), P2SH_segwit.begin(), P2SH_segwit.end());
94-
all.insert(all.end(), legacy.begin(), legacy.end());
95-
all.insert(all.end(), other.begin(), other.end());
94+
all.reserve(coins.size());
95+
for (const auto& it : coins) {
96+
all.insert(all.end(), it.second.begin(), it.second.end());
97+
}
9698
return all;
9799
}
98100

99-
void CoinsResult::Clear()
100-
{
101-
bech32m.clear();
102-
bech32.clear();
103-
P2SH_segwit.clear();
104-
legacy.clear();
105-
other.clear();
101+
void CoinsResult::Clear() {
102+
coins.clear();
106103
}
107104

108105
void CoinsResult::Erase(std::set<COutPoint>& preset_coins)
@@ -263,51 +260,32 @@ CoinsResult AvailableCoins(const CWallet& wallet,
263260
// Filter by spendable outputs only
264261
if (!spendable && only_spendable) continue;
265262

266-
// When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script)
267-
// We don't need those here, so we are leaving them in return_values_unused
268-
std::vector<std::vector<uint8_t>> return_values_unused;
269-
TxoutType type;
270-
bool is_from_p2sh{false};
271-
272263
// If the Output is P2SH and spendable, we want to know if it is
273264
// a P2SH (legacy) or one of P2SH-P2WPKH, P2SH-P2WSH (P2SH-Segwit). We can determine
274265
// this from the redeemScript. If the Output is not spendable, it will be classified
275266
// as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript
267+
CScript script;
268+
bool is_from_p2sh{false};
276269
if (output.scriptPubKey.IsPayToScriptHash() && solvable) {
277-
CScript redeemScript;
278270
CTxDestination destination;
279271
if (!ExtractDestination(output.scriptPubKey, destination))
280272
continue;
281273
const CScriptID& hash = CScriptID(std::get<ScriptHash>(destination));
282-
if (!provider->GetCScript(hash, redeemScript))
274+
if (!provider->GetCScript(hash, script))
283275
continue;
284-
type = Solver(redeemScript, return_values_unused);
285276
is_from_p2sh = true;
286277
} else {
287-
type = Solver(output.scriptPubKey, return_values_unused);
278+
script = output.scriptPubKey;
288279
}
289280

290281
COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
291-
switch (type) {
292-
case TxoutType::WITNESS_UNKNOWN:
293-
case TxoutType::WITNESS_V1_TAPROOT:
294-
result.bech32m.push_back(coin);
295-
break;
296-
case TxoutType::WITNESS_V0_KEYHASH:
297-
case TxoutType::WITNESS_V0_SCRIPTHASH:
298-
if (is_from_p2sh) {
299-
result.P2SH_segwit.push_back(coin);
300-
break;
301-
}
302-
result.bech32.push_back(coin);
303-
break;
304-
case TxoutType::SCRIPTHASH:
305-
case TxoutType::PUBKEYHASH:
306-
result.legacy.push_back(coin);
307-
break;
308-
default:
309-
result.other.push_back(coin);
310-
};
282+
283+
// When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script)
284+
// We don't need those here, so we are leaving them in return_values_unused
285+
std::vector<std::vector<uint8_t>> return_values_unused;
286+
TxoutType type;
287+
type = Solver(script, return_values_unused);
288+
result.Add(GetOutputType(type, is_from_p2sh), coin);
311289

312290
// Cache total amount as we go
313291
result.total_amount += output.nValue;
@@ -498,17 +476,10 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
498476
{
499477
// Run coin selection on each OutputType and compute the Waste Metric
500478
std::vector<SelectionResult> results;
501-
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.legacy, coin_selection_params)}) {
502-
results.push_back(*result);
503-
}
504-
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.P2SH_segwit, coin_selection_params)}) {
505-
results.push_back(*result);
506-
}
507-
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32, coin_selection_params)}) {
508-
results.push_back(*result);
509-
}
510-
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, available_coins.bech32m, coin_selection_params)}) {
511-
results.push_back(*result);
479+
for (const auto& it : available_coins.coins) {
480+
if (auto result{ChooseSelectionResult(wallet, nTargetValue, eligibility_filter, it.second, coin_selection_params)}) {
481+
results.push_back(*result);
482+
}
512483
}
513484

514485
// If we can't fund the transaction from any individual OutputType, run coin selection
@@ -633,11 +604,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
633604

634605
// remove preset inputs from coins so that Coin Selection doesn't pick them.
635606
if (coin_control.HasSelected()) {
636-
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());
637-
available_coins.P2SH_segwit.erase(remove_if(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.P2SH_segwit.end());
638-
available_coins.bech32.erase(remove_if(available_coins.bech32.begin(), available_coins.bech32.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32.end());
639-
available_coins.bech32m.erase(remove_if(available_coins.bech32m.begin(), available_coins.bech32m.end(), [&](const COutput& c) { return preset_coins.count(c.outpoint); }), available_coins.bech32m.end());
640-
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());
607+
available_coins.Erase(preset_coins);
641608
}
642609

643610
unsigned int limit_ancestor_count = 0;
@@ -653,11 +620,7 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
653620
// Cases where we have 101+ outputs all pointing to the same destination may result in
654621
// privacy leaks as they will potentially be deterministically sorted. We solve that by
655622
// explicitly shuffling the outputs before processing
656-
Shuffle(available_coins.legacy.begin(), available_coins.legacy.end(), coin_selection_params.rng_fast);
657-
Shuffle(available_coins.P2SH_segwit.begin(), available_coins.P2SH_segwit.end(), coin_selection_params.rng_fast);
658-
Shuffle(available_coins.bech32.begin(), available_coins.bech32.end(), coin_selection_params.rng_fast);
659-
Shuffle(available_coins.bech32m.begin(), available_coins.bech32m.end(), coin_selection_params.rng_fast);
660-
Shuffle(available_coins.other.begin(), available_coins.other.end(), coin_selection_params.rng_fast);
623+
available_coins.Shuffle(coin_selection_params.rng_fast);
661624
}
662625

663626
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the

src/wallet/spend.h

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,26 +34,18 @@ TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* walle
3434
* This struct is really just a wrapper around OutputType vectors with a convenient
3535
* method for concatenating and returning all COutputs as one vector.
3636
*
37-
* Clear(), Size() methods are implemented so that one can interact with
38-
* the CoinsResult struct as if it was a vector
37+
* Size(), Clear(), Erase(), Shuffle(), and Add() methods are implemented to
38+
* allow easy interaction with the struct.
3939
*/
4040
struct CoinsResult {
4141
std::map<OutputType, std::vector<COutput>> coins;
42-
/** Vectors for each OutputType */
43-
std::vector<COutput> legacy;
44-
std::vector<COutput> P2SH_segwit;
45-
std::vector<COutput> bech32;
46-
std::vector<COutput> bech32m;
47-
48-
/** Other is a catch-all for anything that doesn't match the known OutputTypes */
49-
std::vector<COutput> other;
5042

5143
/** Concatenate and return all COutputs as one vector */
5244
std::vector<COutput> All() const;
5345

5446
/** The following methods are provided so that CoinsResult can mimic a vector,
5547
* i.e., methods can work with individual OutputType vectors or on the entire object */
56-
uint64_t Size() const;
48+
size_t Size() const;
5749
void Clear();
5850
void Erase(std::set<COutPoint>& preset_coins);
5951
void Shuffle(FastRandomContext& rng_fast);

src/wallet/test/availablecoins_tests.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, AvailableCoinsTestingSetup)
6464
// This UTXO is a P2PK, so it should show up in the Other bucket
6565
available_coins = AvailableCoins(*wallet);
6666
BOOST_CHECK_EQUAL(available_coins.Size(), 1U);
67-
BOOST_CHECK_EQUAL(available_coins.other.size(), 1U);
67+
BOOST_CHECK_EQUAL(available_coins.coins[OutputType::UNKNOWN].size(), 1U);
6868

6969
// We will create a self transfer for each of the OutputTypes and
7070
// verify it is put in the correct bucket after running GetAvailablecoins
@@ -78,27 +78,27 @@ BOOST_FIXTURE_TEST_CASE(BasicOutputTypesTest, AvailableCoinsTestingSetup)
7878
BOOST_ASSERT(dest);
7979
AddTx(CRecipient{{GetScriptForDestination(*dest)}, 1 * COIN, /*fSubtractFeeFromAmount=*/true});
8080
available_coins = AvailableCoins(*wallet);
81-
BOOST_CHECK_EQUAL(available_coins.bech32m.size(), 2U);
81+
BOOST_CHECK_EQUAL(available_coins.coins[OutputType::BECH32M].size(), 2U);
8282

8383
// Bech32
8484
dest = wallet->GetNewDestination(OutputType::BECH32, "");
8585
BOOST_ASSERT(dest);
8686
AddTx(CRecipient{{GetScriptForDestination(*dest)}, 2 * COIN, /*fSubtractFeeFromAmount=*/true});
8787
available_coins = AvailableCoins(*wallet);
88-
BOOST_CHECK_EQUAL(available_coins.bech32.size(), 2U);
88+
BOOST_CHECK_EQUAL(available_coins.coins[OutputType::BECH32].size(), 2U);
8989

9090
// P2SH-SEGWIT
9191
dest = wallet->GetNewDestination(OutputType::P2SH_SEGWIT, "");
9292
AddTx(CRecipient{{GetScriptForDestination(*dest)}, 3 * COIN, /*fSubtractFeeFromAmount=*/true});
9393
available_coins = AvailableCoins(*wallet);
94-
BOOST_CHECK_EQUAL(available_coins.P2SH_segwit.size(), 2U);
94+
BOOST_CHECK_EQUAL(available_coins.coins[OutputType::P2SH_SEGWIT].size(), 2U);
9595

9696
// Legacy (P2PKH)
9797
dest = wallet->GetNewDestination(OutputType::LEGACY, "");
9898
BOOST_ASSERT(dest);
9999
AddTx(CRecipient{{GetScriptForDestination(*dest)}, 4 * COIN, /*fSubtractFeeFromAmount=*/true});
100100
available_coins = AvailableCoins(*wallet);
101-
BOOST_CHECK_EQUAL(available_coins.legacy.size(), 2U);
101+
BOOST_CHECK_EQUAL(available_coins.coins[OutputType::LEGACY].size(), 2U);
102102
}
103103

104104
BOOST_AUTO_TEST_SUITE_END()

src/wallet/test/coinselector_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmoun
8383
assert(ret.second);
8484
CWalletTx& wtx = (*ret.first).second;
8585
const auto& txout = wtx.tx->vout.at(nInput);
86-
available_coins.bech32.emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
86+
available_coins.coins[OutputType::BECH32].emplace_back(COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate);
8787
}
8888

8989
/** Check if SelectionResult a is equivalent to SelectionResult b.

0 commit comments

Comments
 (0)