Skip to content

Commit fb716f7

Browse files
committed
Move current coin selection algorithm to coinselection.{cpp,h}
Moves the current coin selection algorithm out of SelectCoinsMinConf and puts it in coinselection.{cpp,h}. The new function, KnapsackSolver, instead of taking a vector of COutputs, will take a vector of CInputCoins that is prepared by SelectCoinsMinConf.
1 parent 4566ab7 commit fb716f7

File tree

3 files changed

+140
-127
lines changed

3 files changed

+140
-127
lines changed

src/wallet/coinselection.cpp

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,3 +163,138 @@ bool SelectCoinsBnB(std::vector<CInputCoin>& utxo_pool, const CAmount& target_va
163163

164164
return true;
165165
}
166+
167+
static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
168+
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
169+
{
170+
std::vector<char> vfIncluded;
171+
172+
vfBest.assign(vValue.size(), true);
173+
nBest = nTotalLower;
174+
175+
FastRandomContext insecure_rand;
176+
177+
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
178+
{
179+
vfIncluded.assign(vValue.size(), false);
180+
CAmount nTotal = 0;
181+
bool fReachedTarget = false;
182+
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
183+
{
184+
for (unsigned int i = 0; i < vValue.size(); i++)
185+
{
186+
//The solver here uses a randomized algorithm,
187+
//the randomness serves no real security purpose but is just
188+
//needed to prevent degenerate behavior and it is important
189+
//that the rng is fast. We do not use a constant random sequence,
190+
//because there may be some privacy improvement by making
191+
//the selection random.
192+
if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
193+
{
194+
nTotal += vValue[i].txout.nValue;
195+
vfIncluded[i] = true;
196+
if (nTotal >= nTargetValue)
197+
{
198+
fReachedTarget = true;
199+
if (nTotal < nBest)
200+
{
201+
nBest = nTotal;
202+
vfBest = vfIncluded;
203+
}
204+
nTotal -= vValue[i].txout.nValue;
205+
vfIncluded[i] = false;
206+
}
207+
}
208+
}
209+
}
210+
}
211+
}
212+
213+
bool KnapsackSolver(const CAmount& nTargetValue, std::vector<CInputCoin>& vCoins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet)
214+
{
215+
setCoinsRet.clear();
216+
nValueRet = 0;
217+
218+
// List of values less than target
219+
boost::optional<CInputCoin> coinLowestLarger;
220+
std::vector<CInputCoin> vValue;
221+
CAmount nTotalLower = 0;
222+
223+
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
224+
225+
for (const CInputCoin &coin : vCoins)
226+
{
227+
if (coin.txout.nValue == nTargetValue)
228+
{
229+
setCoinsRet.insert(coin);
230+
nValueRet += coin.txout.nValue;
231+
return true;
232+
}
233+
else if (coin.txout.nValue < nTargetValue + MIN_CHANGE)
234+
{
235+
vValue.push_back(coin);
236+
nTotalLower += coin.txout.nValue;
237+
}
238+
else if (!coinLowestLarger || coin.txout.nValue < coinLowestLarger->txout.nValue)
239+
{
240+
coinLowestLarger = coin;
241+
}
242+
}
243+
244+
if (nTotalLower == nTargetValue)
245+
{
246+
for (const auto& input : vValue)
247+
{
248+
setCoinsRet.insert(input);
249+
nValueRet += input.txout.nValue;
250+
}
251+
return true;
252+
}
253+
254+
if (nTotalLower < nTargetValue)
255+
{
256+
if (!coinLowestLarger)
257+
return false;
258+
setCoinsRet.insert(coinLowestLarger.get());
259+
nValueRet += coinLowestLarger->txout.nValue;
260+
return true;
261+
}
262+
263+
// Solve subset sum by stochastic approximation
264+
std::sort(vValue.begin(), vValue.end(), descending);
265+
std::vector<char> vfBest;
266+
CAmount nBest;
267+
268+
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
269+
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE)
270+
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
271+
272+
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
273+
// or the next bigger coin is closer), return the bigger coin
274+
if (coinLowestLarger &&
275+
((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger->txout.nValue <= nBest))
276+
{
277+
setCoinsRet.insert(coinLowestLarger.get());
278+
nValueRet += coinLowestLarger->txout.nValue;
279+
}
280+
else {
281+
for (unsigned int i = 0; i < vValue.size(); i++)
282+
if (vfBest[i])
283+
{
284+
setCoinsRet.insert(vValue[i]);
285+
nValueRet += vValue[i].txout.nValue;
286+
}
287+
288+
if (LogAcceptCategory(BCLog::SELECTCOINS)) {
289+
LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: ");
290+
for (unsigned int i = 0; i < vValue.size(); i++) {
291+
if (vfBest[i]) {
292+
LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue));
293+
}
294+
}
295+
LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
296+
}
297+
}
298+
299+
return true;
300+
}

src/wallet/coinselection.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,6 @@ class CInputCoin {
4949

5050
bool SelectCoinsBnB(std::vector<CInputCoin>& utxo_pool, const CAmount& target_value, const CAmount& cost_of_change, std::set<CInputCoin>& out_set, CAmount& value_ret, CAmount not_input_fees);
5151

52+
// Original coin selection algorithm as a fallback
53+
bool KnapsackSolver(const CAmount& nTargetValue, std::vector<CInputCoin>& vCoins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet);
5254
#endif // BITCOIN_COINSELECTION_H

src/wallet/wallet.cpp

Lines changed: 3 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -2438,52 +2438,6 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out
24382438
return ptx->vout[n];
24392439
}
24402440

2441-
static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
2442-
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
2443-
{
2444-
std::vector<char> vfIncluded;
2445-
2446-
vfBest.assign(vValue.size(), true);
2447-
nBest = nTotalLower;
2448-
2449-
FastRandomContext insecure_rand;
2450-
2451-
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
2452-
{
2453-
vfIncluded.assign(vValue.size(), false);
2454-
CAmount nTotal = 0;
2455-
bool fReachedTarget = false;
2456-
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
2457-
{
2458-
for (unsigned int i = 0; i < vValue.size(); i++)
2459-
{
2460-
//The solver here uses a randomized algorithm,
2461-
//the randomness serves no real security purpose but is just
2462-
//needed to prevent degenerate behavior and it is important
2463-
//that the rng is fast. We do not use a constant random sequence,
2464-
//because there may be some privacy improvement by making
2465-
//the selection random.
2466-
if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
2467-
{
2468-
nTotal += vValue[i].txout.nValue;
2469-
vfIncluded[i] = true;
2470-
if (nTotal >= nTargetValue)
2471-
{
2472-
fReachedTarget = true;
2473-
if (nTotal < nBest)
2474-
{
2475-
nBest = nTotal;
2476-
vfBest = vfIncluded;
2477-
}
2478-
nTotal -= vValue[i].txout.nValue;
2479-
vfIncluded[i] = false;
2480-
}
2481-
}
2482-
}
2483-
}
2484-
}
2485-
}
2486-
24872441
bool CWallet::OutputEligibleForSpending(const COutput& output, const CoinEligibilityFilter& eligibilty_filter) const
24882442
{
24892443
if (!output.fSpendable)
@@ -2504,94 +2458,16 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
25042458
setCoinsRet.clear();
25052459
nValueRet = 0;
25062460

2507-
// List of values less than target
2508-
boost::optional<CInputCoin> coinLowestLarger;
2509-
std::vector<CInputCoin> vValue;
2510-
CAmount nTotalLower = 0;
2511-
2512-
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
2513-
2461+
std::vector<CInputCoin> utxo_pool;
25142462
for (const COutput &output : vCoins)
25152463
{
25162464
if (!OutputEligibleForSpending(output, eligibilty_filter))
25172465
continue;
25182466

25192467
CInputCoin coin = CInputCoin(output.tx->tx, output.i);
2520-
2521-
if (coin.txout.nValue == nTargetValue)
2522-
{
2523-
setCoinsRet.insert(coin);
2524-
nValueRet += coin.txout.nValue;
2525-
return true;
2526-
}
2527-
else if (coin.txout.nValue < nTargetValue + MIN_CHANGE)
2528-
{
2529-
vValue.push_back(coin);
2530-
nTotalLower += coin.txout.nValue;
2531-
}
2532-
else if (!coinLowestLarger || coin.txout.nValue < coinLowestLarger->txout.nValue)
2533-
{
2534-
coinLowestLarger = coin;
2535-
}
2536-
}
2537-
2538-
if (nTotalLower == nTargetValue)
2539-
{
2540-
for (const auto& input : vValue)
2541-
{
2542-
setCoinsRet.insert(input);
2543-
nValueRet += input.txout.nValue;
2544-
}
2545-
return true;
2546-
}
2547-
2548-
if (nTotalLower < nTargetValue)
2549-
{
2550-
if (!coinLowestLarger)
2551-
return false;
2552-
setCoinsRet.insert(coinLowestLarger.get());
2553-
nValueRet += coinLowestLarger->txout.nValue;
2554-
return true;
2555-
}
2556-
2557-
// Solve subset sum by stochastic approximation
2558-
std::sort(vValue.begin(), vValue.end(), CompareValueOnly());
2559-
std::reverse(vValue.begin(), vValue.end());
2560-
std::vector<char> vfBest;
2561-
CAmount nBest;
2562-
2563-
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
2564-
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE)
2565-
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
2566-
2567-
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
2568-
// or the next bigger coin is closer), return the bigger coin
2569-
if (coinLowestLarger &&
2570-
((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger->txout.nValue <= nBest))
2571-
{
2572-
setCoinsRet.insert(coinLowestLarger.get());
2573-
nValueRet += coinLowestLarger->txout.nValue;
2468+
utxo_pool.push_back(coin);
25742469
}
2575-
else {
2576-
for (unsigned int i = 0; i < vValue.size(); i++)
2577-
if (vfBest[i])
2578-
{
2579-
setCoinsRet.insert(vValue[i]);
2580-
nValueRet += vValue[i].txout.nValue;
2581-
}
2582-
2583-
if (LogAcceptCategory(BCLog::SELECTCOINS)) {
2584-
LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: ");
2585-
for (unsigned int i = 0; i < vValue.size(); i++) {
2586-
if (vfBest[i]) {
2587-
LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue));
2588-
}
2589-
}
2590-
LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
2591-
}
2592-
}
2593-
2594-
return true;
2470+
return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet);
25952471
}
25962472

25972473
bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const

0 commit comments

Comments
 (0)