aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfurszy <matiasfurszyfer@protonmail.com>2022-07-22 16:16:44 -0300
committerfurszy <matiasfurszyfer@protonmail.com>2022-10-26 15:52:35 -0300
commit295852f61998a025b0b28a0671e6e1cf0dc08d0d (patch)
tree059002052b8d54834f629bd80f8f5fb558708f41
parent37e7887cb4bfd7db6eb462ed0741c45aea22a990 (diff)
downloadbitcoin-295852f61998a025b0b28a0671e6e1cf0dc08d0d.tar.xz
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)
-rw-r--r--src/wallet/coinselection.cpp6
-rw-r--r--src/wallet/coinselection.h1
-rw-r--r--src/wallet/spend.cpp106
-rw-r--r--src/wallet/spend.h20
4 files changed, 82 insertions, 51 deletions
diff --git a/src/wallet/coinselection.cpp b/src/wallet/coinselection.cpp
index b568e90998..a8be6cd83a 100644
--- a/src/wallet/coinselection.cpp
+++ b/src/wallet/coinselection.cpp
@@ -444,6 +444,12 @@ void SelectionResult::AddInput(const OutputGroup& group)
m_use_effective = !group.m_subtract_fee_outputs;
}
+void SelectionResult::AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs)
+{
+ util::insert(m_selected_inputs, inputs);
+ m_use_effective = !subtract_fee_outputs;
+}
+
void SelectionResult::Merge(const SelectionResult& other)
{
m_target += other.m_target;
diff --git a/src/wallet/coinselection.h b/src/wallet/coinselection.h
index 761c2be0b3..b23dd10867 100644
--- a/src/wallet/coinselection.h
+++ b/src/wallet/coinselection.h
@@ -308,6 +308,7 @@ public:
void Clear();
void AddInput(const OutputGroup& group);
+ void AddInputs(const std::set<COutput>& inputs, bool subtract_fee_outputs);
/** Calculates and stores the waste for this selection via GetSelectionWaste */
void ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee);
diff --git a/src/wallet/spend.cpp b/src/wallet/spend.cpp
index cd0c7c4a09..0b7cb7faa4 100644
--- a/src/wallet/spend.cpp
+++ b/src/wallet/spend.cpp
@@ -143,6 +143,51 @@ static OutputType GetOutputType(TxoutType type, bool is_from_p2sh)
}
}
+// Fetch and validate the coin control selected inputs.
+// Coins could be internal (from the wallet) or external.
+util::Result<PreSelectedInputs> FetchSelectedInputs(const CWallet& wallet, const CCoinControl& coin_control,
+ const CoinSelectionParams& coin_selection_params) EXCLUSIVE_LOCKS_REQUIRED(wallet.cs_wallet)
+{
+ PreSelectedInputs result;
+ std::vector<COutPoint> vPresetInputs;
+ coin_control.ListSelected(vPresetInputs);
+ for (const COutPoint& outpoint : vPresetInputs) {
+ int input_bytes = -1;
+ CTxOut txout;
+ if (auto ptr_wtx = wallet.GetWalletTx(outpoint.hash)) {
+ // Clearly invalid input, fail
+ if (ptr_wtx->tx->vout.size() <= outpoint.n) {
+ return util::Error{strprintf(_("Invalid pre-selected input %s"), outpoint.ToString())};
+ }
+ txout = ptr_wtx->tx->vout.at(outpoint.n);
+ input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
+ } else {
+ // The input is external. We did not find the tx in mapWallet.
+ if (!coin_control.GetExternalOutput(outpoint, txout)) {
+ return util::Error{strprintf(_("Not found pre-selected input %s"), outpoint.ToString())};
+ }
+ }
+
+ if (input_bytes == -1) {
+ input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
+ }
+
+ // If available, override calculated size with coin control specified size
+ if (coin_control.HasInputWeight(outpoint)) {
+ input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
+ }
+
+ if (input_bytes == -1) {
+ return util::Error{strprintf(_("Not solvable pre-selected input %s"), outpoint.ToString())}; // Not solvable, can't estimate size for fee
+ }
+
+ /* 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. */
+ 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);
+ result.Insert(output, coin_selection_params.m_subtract_fee_outputs);
+ }
+ return result;
+}
+
CoinsResult AvailableCoins(const CWallet& wallet,
const CCoinControl* coinControl,
std::optional<CFeeRate> feerate,
@@ -527,58 +572,14 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
{
CAmount value_to_select = nTargetValue;
- OutputGroup preset_inputs(coin_selection_params);
+ util::Result<PreSelectedInputs> pre_selected_inputs = FetchSelectedInputs(wallet, coin_control, coin_selection_params);
+ if (!pre_selected_inputs) return std::nullopt;
+ PreSelectedInputs inputs = *pre_selected_inputs;
- std::vector<COutPoint> vPresetInputs;
- coin_control.ListSelected(vPresetInputs);
- for (const COutPoint& outpoint : vPresetInputs) {
- int input_bytes = -1;
- CTxOut txout;
- auto ptr_wtx = wallet.GetWalletTx(outpoint.hash);
- if (ptr_wtx) {
- // Clearly invalid input, fail
- if (ptr_wtx->tx->vout.size() <= outpoint.n) {
- return std::nullopt;
- }
- txout = ptr_wtx->tx->vout.at(outpoint.n);
- input_bytes = CalculateMaximumSignedInputSize(txout, &wallet, &coin_control);
- } else {
- // The input is external. We did not find the tx in mapWallet.
- if (!coin_control.GetExternalOutput(outpoint, txout)) {
- return std::nullopt;
- }
- }
-
- if (input_bytes == -1) {
- input_bytes = CalculateMaximumSignedInputSize(txout, outpoint, &coin_control.m_external_provider, &coin_control);
- }
-
- // If available, override calculated size with coin control specified size
- if (coin_control.HasInputWeight(outpoint)) {
- input_bytes = GetVirtualTransactionSize(coin_control.GetInputWeight(outpoint), 0, 0);
- }
-
- if (input_bytes == -1) {
- return std::nullopt; // Not solvable, can't estimate size for fee
- }
-
- /* 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. */
- 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);
- if (coin_selection_params.m_subtract_fee_outputs) {
- value_to_select -= output.txout.nValue;
- } else {
- value_to_select -= output.GetEffectiveValue();
- }
- /* Set ancestors and descendants to 0 as they don't matter for preset inputs since no actual selection is being done.
- * positive_only is set to false because we want to include all preset inputs, even if they are dust.
- */
- preset_inputs.Insert(output, /*ancestors=*/ 0, /*descendants=*/ 0, /*positive_only=*/ false);
- }
-
- // coin control -> return all selected outputs (we want all selected to go into the transaction for sure)
+ // If automatic coin selection was disabled, we just want to return the preset inputs result
if (!coin_control.m_allow_other_inputs) {
SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL);
- result.AddInput(preset_inputs);
+ result.AddInputs(inputs.coins, coin_selection_params.m_subtract_fee_outputs);
if (!coin_selection_params.m_subtract_fee_outputs && result.GetSelectedEffectiveValue() < nTargetValue) {
return std::nullopt;
@@ -590,6 +591,9 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
return result;
}
+ // Decrease the already selected amount
+ value_to_select -= inputs.total_amount;
+
unsigned int limit_ancestor_count = 0;
unsigned int limit_descendant_count = 0;
wallet.chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
@@ -606,8 +610,8 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
available_coins.Shuffle(coin_selection_params.rng_fast);
}
- SelectionResult preselected(preset_inputs.GetSelectionAmount(), SelectionAlgorithm::MANUAL);
- preselected.AddInput(preset_inputs);
+ SelectionResult preselected(inputs.total_amount, SelectionAlgorithm::MANUAL);
+ preselected.AddInputs(inputs.coins, coin_selection_params.m_subtract_fee_outputs);
// Coin Selection attempts to select inputs from a pool of eligible UTXOs to fund the
// transaction at a target feerate. If an attempt fails, more attempts may be made using a more
diff --git a/src/wallet/spend.h b/src/wallet/spend.h
index c29e5be5c7..39832f363f 100644
--- a/src/wallet/spend.h
+++ b/src/wallet/spend.h
@@ -121,6 +121,26 @@ std::optional<SelectionResult> AttemptSelection(const CWallet& wallet, const CAm
std::optional<SelectionResult> ChooseSelectionResult(const CWallet& wallet, const CAmount& nTargetValue, const CoinEligibilityFilter& eligibility_filter, const std::vector<COutput>& available_coins,
const CoinSelectionParams& coin_selection_params);
+// User manually selected inputs that must be part of the transaction
+struct PreSelectedInputs
+{
+ std::set<COutput> coins;
+ // If subtract fee from outputs is disabled, the 'total_amount'
+ // will be the sum of each output effective value
+ // instead of the sum of the outputs amount
+ CAmount total_amount{0};
+
+ void Insert(const COutput& output, bool subtract_fee_outputs)
+ {
+ if (subtract_fee_outputs) {
+ total_amount += output.txout.nValue;
+ } else {
+ total_amount += output.GetEffectiveValue();
+ }
+ coins.insert(output);
+ }
+};
+
/**
* Select a set of coins such that nTargetValue is met and at least
* all coins from coin_control are selected; never select unconfirmed coins if they are not ours