@@ -187,11 +187,24 @@ std::optional<SelectionResult> SelectCoinsSRD(const std::vector<OutputGroup>& ut
187
187
return std::nullopt;
188
188
}
189
189
190
- static void ApproximateBestSubset (FastRandomContext& insecure_rand, const std::vector<OutputGroup>& groups, const CAmount& nTotalLower, const CAmount& nTargetValue,
190
+ /* * Find a subset of the OutputGroups that is at least as large as, but as close as possible to, the
191
+ * target amount; solve subset sum.
192
+ * param@[in] groups OutputGroups to choose from, sorted by value in descending order.
193
+ * param@[in] nTotalLower Total (effective) value of the UTXOs in groups.
194
+ * param@[in] nTargetValue Subset sum target, not including change.
195
+ * param@[out] vfBest Boolean vector representing the subset chosen that is closest to
196
+ * nTargetValue, with indices corresponding to groups. If the ith
197
+ * entry is true, that means the ith group in groups was selected.
198
+ * param@[out] nBest Total amount of subset chosen that is closest to nTargetValue.
199
+ * param@[in] iterations Maximum number of tries.
200
+ */
201
+ static void ApproximateBestSubset (FastRandomContext& insecure_rand, const std::vector<OutputGroup>& groups,
202
+ const CAmount& nTotalLower, const CAmount& nTargetValue,
191
203
std::vector<char >& vfBest, CAmount& nBest, int iterations = 1000 )
192
204
{
193
205
std::vector<char > vfIncluded;
194
206
207
+ // Worst case "best" approximation is just all of the groups.
195
208
vfBest.assign (groups.size (), true );
196
209
nBest = nTotalLower;
197
210
@@ -217,6 +230,8 @@ static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::v
217
230
if (nTotal >= nTargetValue)
218
231
{
219
232
fReachedTarget = true ;
233
+ // If the total is between nTargetValue and nBest, it's our new best
234
+ // approximation.
220
235
if (nTotal < nBest)
221
236
{
222
237
nBest = nTotal;
@@ -231,12 +246,15 @@ static void ApproximateBestSubset(FastRandomContext& insecure_rand, const std::v
231
246
}
232
247
}
233
248
234
- std::optional<SelectionResult> KnapsackSolver (std::vector<OutputGroup>& groups, const CAmount& nTargetValue, FastRandomContext& rng)
249
+ std::optional<SelectionResult> KnapsackSolver (std::vector<OutputGroup>& groups, const CAmount& nTargetValue,
250
+ CAmount change_target, FastRandomContext& rng)
235
251
{
236
252
SelectionResult result (nTargetValue);
237
253
238
254
// List of values less than target
239
255
std::optional<OutputGroup> lowest_larger;
256
+ // Groups with selection amount smaller than the target and any change we might produce.
257
+ // Don't include groups larger than this, because they will only cause us to overshoot.
240
258
std::vector<OutputGroup> applicable_groups;
241
259
CAmount nTotalLower = 0 ;
242
260
@@ -246,7 +264,7 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
246
264
if (group.GetSelectionAmount () == nTargetValue) {
247
265
result.AddInput (group);
248
266
return result;
249
- } else if (group.GetSelectionAmount () < nTargetValue + MIN_CHANGE ) {
267
+ } else if (group.GetSelectionAmount () < nTargetValue + change_target ) {
250
268
applicable_groups.push_back (group);
251
269
nTotalLower += group.GetSelectionAmount ();
252
270
} else if (!lowest_larger || group.GetSelectionAmount () < lowest_larger->GetSelectionAmount ()) {
@@ -273,14 +291,14 @@ std::optional<SelectionResult> KnapsackSolver(std::vector<OutputGroup>& groups,
273
291
CAmount nBest;
274
292
275
293
ApproximateBestSubset (rng, applicable_groups, nTotalLower, nTargetValue, vfBest, nBest);
276
- if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE ) {
277
- ApproximateBestSubset (rng, applicable_groups, nTotalLower, nTargetValue + MIN_CHANGE , vfBest, nBest);
294
+ if (nBest != nTargetValue && nTotalLower >= nTargetValue + change_target ) {
295
+ ApproximateBestSubset (rng, applicable_groups, nTotalLower, nTargetValue + change_target , vfBest, nBest);
278
296
}
279
297
280
298
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
281
299
// or the next bigger coin is closer), return the bigger coin
282
300
if (lowest_larger &&
283
- ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE ) || lowest_larger->GetSelectionAmount () <= nBest)) {
301
+ ((nBest != nTargetValue && nBest < nTargetValue + change_target ) || lowest_larger->GetSelectionAmount () <= nBest)) {
284
302
result.AddInput (*lowest_larger);
285
303
} else {
286
304
for (unsigned int i = 0 ; i < applicable_groups.size (); i++) {
@@ -380,6 +398,17 @@ CAmount GetSelectionWaste(const std::set<COutput>& inputs, CAmount change_cost,
380
398
return waste;
381
399
}
382
400
401
+ CAmount GenerateChangeTarget (CAmount payment_value, FastRandomContext& rng)
402
+ {
403
+ if (payment_value <= CHANGE_LOWER / 2 ) {
404
+ return CHANGE_LOWER;
405
+ } else {
406
+ // random value between 50ksat and min (payment_value * 2, 1milsat)
407
+ const auto upper_bound = std::min (payment_value * 2 , CHANGE_UPPER);
408
+ return rng.randrange (upper_bound - CHANGE_LOWER) + CHANGE_LOWER;
409
+ }
410
+ }
411
+
383
412
void SelectionResult::ComputeAndSetWaste (CAmount change_cost)
384
413
{
385
414
m_waste = GetSelectionWaste (m_selected_inputs, change_cost, m_target, m_use_effective);
0 commit comments