coin selection: knapsack, select closest UTXO above target if result exceeds max tx size

The simplest scenario where this is useful is on the 'check_max_weight' unit test
already:

We create 1515 UTXOs with 0.033 BTC each, and 1 UTXO with 50 BTC. Then perform
Coin Selection.

As the selection of the 1515 small UTXOs exceeds the max allowed tx size, the
expectation here is to receive a selection result that only contain the big
UTXO (which is not happening for the reasons stated below).

As knapsack returns a result that exceeds the max allowed transaction size, we
fallback to SRD, which selects coins randomly up until the target is met. So
we end up with a selection result with lot more coins than what is needed.
This commit is contained in:
furszy
2022-12-18 10:28:59 -03:00
parent 1284223691
commit 6107ec2229
5 changed files with 45 additions and 7 deletions

View File

@@ -87,6 +87,14 @@ static void add_coin(CoinsResult& available_coins, CWallet& wallet, const CAmoun
available_coins.Add(OutputType::BECH32, {COutPoint(wtx.GetHash(), nInput), txout, nAge, CalculateMaximumSignedInputSize(txout, &wallet, /*coin_control=*/nullptr), /*spendable=*/ true, /*solvable=*/ true, /*safe=*/ true, wtx.GetTxTime(), fIsFromMe, feerate});
}
// Helper
std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
CAmount change_target, FastRandomContext& rng)
{
auto res{KnapsackSolver(groups, nTargetValue, change_target, rng, MAX_STANDARD_TX_WEIGHT)};
return res ? std::optional<SelectionResult>(*res) : std::nullopt;
}
/** Check if SelectionResult a is equivalent to SelectionResult b.
* Equivalent means same input values, but maybe different inputs (i.e. same value, different prevout) */
static bool EquivalentResult(const SelectionResult& a, const SelectionResult& b)
@@ -987,7 +995,10 @@ BOOST_AUTO_TEST_CASE(check_max_weight)
chain);
BOOST_CHECK(result);
BOOST_CHECK(has_coin(result->GetInputSet(), CAmount(50 * COIN)));
// Verify that only the 50 BTC UTXO was selected
const auto& selection_res = result->GetInputSet();
BOOST_CHECK(selection_res.size() == 1);
BOOST_CHECK((*selection_res.begin())->GetEffectiveValue() == 50 * COIN);
}
{