Skip to content

Commit 2e67291

Browse files
committed
refactor: store by OutputType in CoinsResult
Store COutputs by OutputType in CoinsResult. The struct stores vectors of `COutput`s by `OutputType` for more convenient access
1 parent 948f5ba commit 2e67291

File tree

5 files changed

+108
-13
lines changed

5 files changed

+108
-13
lines changed

src/wallet/rpc/coins.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -638,7 +638,7 @@ RPCHelpMan listunspent()
638638
cctl.m_max_depth = nMaxDepth;
639639
cctl.m_include_unsafe_inputs = include_unsafe;
640640
LOCK(pwallet->cs_wallet);
641-
vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).coins;
641+
vecOutputs = AvailableCoinsListUnspent(*pwallet, &cctl, nMinimumAmount, nMaximumAmount, nMinimumSumAmount, nMaximumCount).all();
642642
}
643643

644644
LOCK(pwallet->cs_wallet);

src/wallet/rpc/spend.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1382,7 +1382,7 @@ RPCHelpMan sendall()
13821382
total_input_value += tx->tx->vout[input.prevout.n].nValue;
13831383
}
13841384
} else {
1385-
for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).coins) {
1385+
for (const COutput& output : AvailableCoins(*pwallet, &coin_control, fee_rate, /*nMinimumAmount=*/0).all()) {
13861386
CHECK_NONFATAL(output.input_bytes > 0);
13871387
if (send_max && fee_rate.GetFee(output.input_bytes) > output.txout.nValue) {
13881388
continue;

src/wallet/spend.cpp

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

82+
uint64_t CoinsResult::size() const
83+
{
84+
return bech32m.size() + bech32.size() + P2SH_segwit.size() + legacy.size() + other.size();
85+
}
86+
87+
std::vector<COutput> CoinsResult::all() const
88+
{
89+
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());
96+
return all;
97+
}
98+
99+
void CoinsResult::clear()
100+
{
101+
bech32m.clear();
102+
bech32.clear();
103+
P2SH_segwit.clear();
104+
legacy.clear();
105+
other.clear();
106+
}
107+
82108
CoinsResult AvailableCoins(const CWallet& wallet,
83109
const CCoinControl* coinControl,
84110
std::optional<CFeeRate> feerate,
@@ -193,10 +219,55 @@ CoinsResult AvailableCoins(const CWallet& wallet,
193219
// Filter by spendable outputs only
194220
if (!spendable && only_spendable) continue;
195221

222+
// When parsing a scriptPubKey, Solver returns the parsed pubkeys or hashes (depending on the script)
223+
// We don't need those here, so we are leaving them in return_values_unused
224+
std::vector<std::vector<uint8_t>> return_values_unused;
225+
TxoutType type;
226+
bool is_from_p2sh{false};
227+
228+
// If the Output is P2SH and spendable, we want to know if it is
229+
// a P2SH (legacy) or one of P2SH-P2WPKH, P2SH-P2WSH (P2SH-Segwit). We can determine
230+
// this from the redeemScript. If the Output is not spendable, it will be classified
231+
// as a P2SH (legacy), since we have no way of knowing otherwise without the redeemScript
232+
if (output.scriptPubKey.IsPayToScriptHash() && solvable) {
233+
CScript redeemScript;
234+
CTxDestination destination;
235+
if (!ExtractDestination(output.scriptPubKey, destination))
236+
continue;
237+
const CScriptID& hash = CScriptID(std::get<ScriptHash>(destination));
238+
if (!provider->GetCScript(hash, redeemScript))
239+
continue;
240+
type = Solver(redeemScript, return_values_unused);
241+
is_from_p2sh = true;
242+
} else {
243+
type = Solver(output.scriptPubKey, return_values_unused);
244+
}
245+
196246
int input_bytes = CalculateMaximumSignedInputSize(output, COutPoint(), provider.get(), coinControl);
197-
result.coins.emplace_back(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
247+
COutput coin(outpoint, output, nDepth, input_bytes, spendable, solvable, safeTx, wtx.GetTxTime(), tx_from_me, feerate);
248+
switch (type) {
249+
case TxoutType::WITNESS_UNKNOWN:
250+
case TxoutType::WITNESS_V1_TAPROOT:
251+
result.bech32m.push_back(coin);
252+
break;
253+
case TxoutType::WITNESS_V0_KEYHASH:
254+
case TxoutType::WITNESS_V0_SCRIPTHASH:
255+
if (is_from_p2sh) {
256+
result.P2SH_segwit.push_back(coin);
257+
break;
258+
}
259+
result.bech32.push_back(coin);
260+
break;
261+
case TxoutType::SCRIPTHASH:
262+
case TxoutType::PUBKEYHASH:
263+
result.legacy.push_back(coin);
264+
break;
265+
default:
266+
result.other.push_back(coin);
267+
};
268+
269+
// Cache total amount as we go
198270
result.total_amount += output.nValue;
199-
200271
// Checks the sum amount of all UTXO's.
201272
if (nMinimumSumAmount != MAX_MONEY) {
202273
if (result.total_amount >= nMinimumSumAmount) {
@@ -205,7 +276,7 @@ CoinsResult AvailableCoins(const CWallet& wallet,
205276
}
206277

207278
// Checks the maximum number of UTXO's.
208-
if (nMaximumCount > 0 && result.coins.size() >= nMaximumCount) {
279+
if (nMaximumCount > 0 && result.size() >= nMaximumCount) {
209280
return result;
210281
}
211282
}
@@ -261,7 +332,7 @@ std::map<CTxDestination, std::vector<COutput>> ListCoins(const CWallet& wallet)
261332

262333
std::map<CTxDestination, std::vector<COutput>> result;
263334

264-
for (const COutput& coin : AvailableCoinsListUnspent(wallet).coins) {
335+
for (const COutput& coin : AvailableCoinsListUnspent(wallet).all()) {
265336
CTxDestination address;
266337
if ((coin.spendable || (wallet.IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS) && coin.solvable)) &&
267338
ExtractDestination(FindNonChangeParentOutput(wallet, coin.outpoint).scriptPubKey, address)) {
@@ -787,7 +858,7 @@ static BResult<CreatedTransactionResult> CreateTransactionInternal(
787858
0); /*nMaximumCount*/
788859

789860
// Choose coins to use
790-
std::optional<SelectionResult> result = SelectCoins(wallet, res_available_coins.coins, /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
861+
std::optional<SelectionResult> result = SelectCoins(wallet, res_available_coins.all(), /*nTargetValue=*/selection_target, coin_control, coin_selection_params);
791862
if (!result) {
792863
return _("Insufficient funds");
793864
}

src/wallet/spend.h

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,38 @@ struct TxSize {
2929
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const std::vector<CTxOut>& txouts, const CCoinControl* coin_control = nullptr);
3030
TxSize CalculateMaximumSignedTxSize(const CTransaction& tx, const CWallet* wallet, const CCoinControl* coin_control = nullptr) EXCLUSIVE_LOCKS_REQUIRED(wallet->cs_wallet);
3131

32+
/**
33+
* COutputs available for spending, stored by OutputType.
34+
* This struct is really just a wrapper around OutputType vectors with a convenient
35+
* method for concatenating and returning all COutputs as one vector.
36+
*
37+
* clear(), size() methods are implemented so that one can interact with
38+
* the CoinsResult struct as if it were a vector
39+
*/
3240
struct CoinsResult {
33-
std::vector<COutput> coins;
34-
// Sum of all the coins amounts
41+
/** Vectors for each OutputType */
42+
std::vector<COutput> legacy;
43+
std::vector<COutput> P2SH_segwit;
44+
std::vector<COutput> bech32;
45+
std::vector<COutput> bech32m;
46+
47+
/** Other is a catch-all for anything that doesn't match the known OutputTypes */
48+
std::vector<COutput> other;
49+
50+
/** Concatenate and return all COutputs as one vector */
51+
std::vector<COutput> all() const;
52+
53+
/** The following methods are provided so that CoinsResult can mimic a vector,
54+
* i.e., methods can work with individual OutputType vectors or on the entire object */
55+
uint64_t size() const;
56+
void clear();
57+
58+
/** Sum of all available coins */
3559
CAmount total_amount{0};
3660
};
61+
3762
/**
38-
* Return vector of available COutputs.
39-
* By default, returns only the spendable coins.
63+
* Populate the CoinsResult struct with vectors of available COutputs, organized by OutputType.
4064
*/
4165
CoinsResult AvailableCoins(const CWallet& wallet,
4266
const CCoinControl* coinControl = nullptr,

src/wallet/test/wallet_tests.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -591,7 +591,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
591591
// Lock both coins. Confirm number of available coins drops to 0.
592592
{
593593
LOCK(wallet->cs_wallet);
594-
BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).coins.size(), 2U);
594+
BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).size(), 2U);
595595
}
596596
for (const auto& group : list) {
597597
for (const auto& coin : group.second) {
@@ -601,7 +601,7 @@ BOOST_FIXTURE_TEST_CASE(ListCoinsTest, ListCoinsTestingSetup)
601601
}
602602
{
603603
LOCK(wallet->cs_wallet);
604-
BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).coins.size(), 0U);
604+
BOOST_CHECK_EQUAL(AvailableCoinsListUnspent(*wallet).size(), 0U);
605605
}
606606
// Confirm ListCoins still returns same result as before, despite coins
607607
// being locked.

0 commit comments

Comments
 (0)