mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-03 09:56:38 -05:00
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)
This commit is contained in:
parent
37e7887cb4
commit
295852f619
4 changed files with 82 additions and 51 deletions
|
@ -444,6 +444,12 @@ void SelectionResult::AddInput(const OutputGroup& group)
|
||||||
m_use_effective = !group.m_subtract_fee_outputs;
|
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)
|
void SelectionResult::Merge(const SelectionResult& other)
|
||||||
{
|
{
|
||||||
m_target += other.m_target;
|
m_target += other.m_target;
|
||||||
|
|
|
@ -308,6 +308,7 @@ public:
|
||||||
void Clear();
|
void Clear();
|
||||||
|
|
||||||
void AddInput(const OutputGroup& group);
|
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 */
|
/** 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);
|
void ComputeAndSetWaste(const CAmount min_viable_change, const CAmount change_cost, const CAmount change_fee);
|
||||||
|
|
|
@ -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,
|
CoinsResult AvailableCoins(const CWallet& wallet,
|
||||||
const CCoinControl* coinControl,
|
const CCoinControl* coinControl,
|
||||||
std::optional<CFeeRate> feerate,
|
std::optional<CFeeRate> feerate,
|
||||||
|
@ -527,58 +572,14 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
|
||||||
{
|
{
|
||||||
CAmount value_to_select = nTargetValue;
|
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;
|
// If automatic coin selection was disabled, we just want to return the preset inputs result
|
||||||
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 (!coin_control.m_allow_other_inputs) {
|
if (!coin_control.m_allow_other_inputs) {
|
||||||
SelectionResult result(nTargetValue, SelectionAlgorithm::MANUAL);
|
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) {
|
if (!coin_selection_params.m_subtract_fee_outputs && result.GetSelectedEffectiveValue() < nTargetValue) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -590,6 +591,9 @@ std::optional<SelectionResult> SelectCoins(const CWallet& wallet, CoinsResult& a
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Decrease the already selected amount
|
||||||
|
value_to_select -= inputs.total_amount;
|
||||||
|
|
||||||
unsigned int limit_ancestor_count = 0;
|
unsigned int limit_ancestor_count = 0;
|
||||||
unsigned int limit_descendant_count = 0;
|
unsigned int limit_descendant_count = 0;
|
||||||
wallet.chain().getPackageLimits(limit_ancestor_count, limit_descendant_count);
|
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);
|
available_coins.Shuffle(coin_selection_params.rng_fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
SelectionResult preselected(preset_inputs.GetSelectionAmount(), SelectionAlgorithm::MANUAL);
|
SelectionResult preselected(inputs.total_amount, SelectionAlgorithm::MANUAL);
|
||||||
preselected.AddInput(preset_inputs);
|
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
|
// 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
|
// transaction at a target feerate. If an attempt fails, more attempts may be made using a more
|
||||||
|
|
|
@ -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,
|
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);
|
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
|
* 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
|
* all coins from coin_control are selected; never select unconfirmed coins if they are not ours
|
||||||
|
|
Loading…
Add table
Reference in a new issue