Skip to content

Commit 295852f

Browse files
committed
wallet: encapsulate pre-selected-inputs lookup into its own function
First step towards decoupling the pre-selected-inputs fetching functionality from `SelectCoins`. Which, will let us not waste resources calculating the available coins if one of the pre-set inputs has an error. (right now, if one of the pre-set inputs is invalid, we first walk through the entire wallet txes map just to end up failing right after it finish)
1 parent 37e7887 commit 295852f

File tree

4 files changed

+82
-51
lines changed

4 files changed

+82
-51
lines changed

src/wallet/coinselection.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,12 @@ void SelectionResult::AddInput(const OutputGroup& group)
444444
m_use_effective = !group.m_subtract_fee_outputs;
445445
}
446446

447+
void SelectionResult::AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs)
448+
{
449+
util::insert(m_selected_inputs, inputs);
450+
m_use_effective = !subtract_fee_outputs;
451+
}
452+
447453
void SelectionResult::Merge(const SelectionResult& other)
448454
{
449455
m_target += other.m_target;

src/wallet/coinselection.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,7 @@ struct SelectionResult
308308
void Clear();
309309

310310
void AddInput(const OutputGroup& group);
311+
void AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs);
311312

312313
/** Calculates and stores the waste for this selection via GetSelectionWaste */
313314
void ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee);

src/wallet/spend.cpp

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,51 @@ static OutputType GetOutputType(TxoutType type, bool is_from_p2sh)
143143
}
144144
}
145145

146+
// Fetch and validate the coin control selected inputs.
147+
// Coins could be internal (from the wallet) or external.
148+
util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const CCoinControl& coin_control,
149+
const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
150+
{
151+
PreSelectedInputs result;
152+
std::vector<COutPoint> vPresetInputs;
153+
coin_control.ListSelected(vPresetInputs);
154+
for (const COutPoint& outpoint : vPresetInputs) {
155+
int input_bytes = -1;
156+
CTxOut txout;
157+
if (auto ptr_wtx = wallet.GetWalletTx(outpoint.hash)) {
158+
// Clearly invalid input, fail
159+
if (ptr_wtx->tx->vout.size() <= outpoint.n) {
160+
return util::Error{strprintf(_("Invalid pre-selected input %s"), outpoint.ToString())};
161+
}
162+
txout = ptr_wtx->tx->vout.at(outpoint.n);
163+
input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
164+
} else {
165+
// The input is external. We did not find the tx in mapWallet.
166+
if (!coin_control.GetExternalOutput(outpoint, txout)) {
167+
return util::Error{strprintf(_("Not found pre-selected input %s"), outpoint.ToString())};
168+
}
169+
}
170+
171+
if (input_bytes == -1) {
172+
input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
173+
}
174+
175+
// If available, override calculated size with coin control specified size
176+
if (coin_control.HasInputWeight(outpoint)) {
177+
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
178+
}
179+
180+
if (input_bytes == -1) {
181+
return util::Error{strprintf(_("Not solvable pre-selected input %s"), outpoint.ToString())}; // Not solvable, can't estimate size for fee
182+
}
183+
184+
/* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
185+
COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
186+
result.Insert(output, coin_selection_params.m_subtract_fee_outputs);
187+
}
188+
return result;
189+
}
190+
146191
CoinsResult AvailableCoins(const CWallet& wallet,
147192
const CCoinControl* coinControl,
148193
std::optional<CFeeRate> feerate,
@@ -527,58 +572,14 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
527572
{
528573
CAmount value_to_select = nTargetValue;
529574

530-
OutputGroup preset_inputs(coin_selection_params);
575+
util::Result<PreSelectedInputs> pre_selected_inputs = FetchSelectedInputs(wallet, coin_control, coin_selection_params);
576+
if (!pre_selected_inputs) return std::nullopt;
577+
PreSelectedInputs inputs = *pre_selected_inputs;
531578

532-
std::vector<COutPoint> vPresetInputs;
533-
coin_control.ListSelected(vPresetInputs);
534-
for (const COutPoint& outpoint : vPresetInputs) {
535-
int input_bytes = -1;
536-
CTxOut txout;
537-
auto ptr_wtx = wallet.GetWalletTx(outpoint.hash);
538-
if (ptr_wtx) {
539-
// Clearly invalid input, fail
540-
if (ptr_wtx->tx->vout.size() <= outpoint.n) {
541-
return std::nullopt;
542-
}
543-
txout = ptr_wtx->tx->vout.at(outpoint.n);
544-
input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
545-
} else {
546-
// The input is external. We did not find the tx in mapWallet.
547-
if (!coin_control.GetExternalOutput(outpoint, txout)) {
548-
return std::nullopt;
549-
}
550-
}
551-
552-
if (input_bytes == -1) {
553-
input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
554-
}
555-
556-
// If available, override calculated size with coin control specified size
557-
if (coin_control.HasInputWeight(outpoint)) {
558-
input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
559-
}
560-
561-
if (input_bytes == -1) {
562-
return std::nullopt; // Not solvable, can't estimate size for fee
563-
}
564-
565-
/* Set some defaults for depth, spendable, solvable, safe, time, and from_me as these don't matter for preset inputs since no selection is being done. */
566-
COutput output(outpoint, txout, /*depth=*/ 0, input_bytes, /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, /*time=*/ 0, /*from_me=*/ false, coin_selection_params.m_effective_feerate);
567-
if (coin_selection_params.m_subtract_fee_outputs) {
568-
value_to_select -= output.txout.nValue;
569-
} else {
570-
value_to_select -= output.GetEffectiveValue();
571-
}
572-
/* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done.
573-
* positive_only is set to false because we want to include all preset inputs, even if they are dust.
574-
*/
575-
preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
576-
}
577-
578-
// coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
579+
// If automatic coin selection was disabled, we just want to return the preset inputs result
579580
if (!coin_control.m_allow_other_inputs) {
580581
SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL);
581-
result.AddInput(preset_inputs);
582+
result.AddInputs(inputs.coins, coin_selection_params.m_subtract_fee_outputs);
582583

583584
if (!coin_selection_params.m_subtract_fee_outputs && result.GetSelectedEffectiveValue() < nTargetValue) {
584585
return std::nullopt;
@@ -590,6 +591,9 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
590591
return result;
591592
}
592593

594+
// Decrease the already selected amount
595+
value_to_select -= inputs.total_amount;
596+
593597
unsigned int limit_ancestor_count = 0;
594598
unsigned int limit_descendant_count = 0;
595599
wallet.chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
@@ -606,8 +610,8 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
606610
available_coins.Shuffle(coin_selection_params.rng_fast);
607611
}
608612

609-
SelectionResult preselected(preset_inputs.GetSelectionAmount(), SelectionAlgorithm::MANUAL);
610-
preselected.AddInput(preset_inputs);
613+
SelectionResult preselected(inputs.total_amount, SelectionAlgorithm::MANUAL);
614+
preselected.AddInputs(inputs.coins, coin_selection_params.m_subtract_fee_outputs);
611615

612616
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
613617
// transaction at a target feerate. If an attempt fails, more attempts may be made using a more

src/wallet/spend.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,26 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
121121
std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins,
122122
const CoinSelectionParams& coin_selection_params);
123123

124+
// User manually selected inputs that must be part of the transaction
125+
struct PreSelectedInputs
126+
{
127+
std::set<COutput> coins;
128+
// If subtract fee from outputs is disabled, the 'total_amount'
129+
// will be the sum of each output effective value
130+
// instead of the sum of the outputs amount
131+
CAmount total_amount{0};
132+
133+
void Insert(const COutput& output, bool subtract_fee_outputs)
134+
{
135+
if (subtract_fee_outputs) {
136+
total_amount += output.txout.nValue;
137+
} else {
138+
total_amount += output.GetEffectiveValue();
139+
}
140+
coins.insert(output);
141+
}
142+
};
143+
124144
/**
125145
* Select a set of coins such that nTargetValue is met and at least
126146
* all coins from coin_control are selected; never select unconfirmed coins if they are not ours

0 commit comments

Comments
 (0)