mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-20 10:38:42 +01:00
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.
This commit is contained in:
parent
4566ab75f2
commit
fb716f7b25
@ -163,3 +163,138 @@ bool SelectCoinsBnB(std::vector<CInputCoin>& utxo_pool, const CAmount& target_va
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
|
||||
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
|
||||
{
|
||||
std::vector<char> vfIncluded;
|
||||
|
||||
vfBest.assign(vValue.size(), true);
|
||||
nBest = nTotalLower;
|
||||
|
||||
FastRandomContext insecure_rand;
|
||||
|
||||
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
|
||||
{
|
||||
vfIncluded.assign(vValue.size(), false);
|
||||
CAmount nTotal = 0;
|
||||
bool fReachedTarget = false;
|
||||
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
|
||||
{
|
||||
for (unsigned int i = 0; i < vValue.size(); i++)
|
||||
{
|
||||
//The solver here uses a randomized algorithm,
|
||||
//the randomness serves no real security purpose but is just
|
||||
//needed to prevent degenerate behavior and it is important
|
||||
//that the rng is fast. We do not use a constant random sequence,
|
||||
//because there may be some privacy improvement by making
|
||||
//the selection random.
|
||||
if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
|
||||
{
|
||||
nTotal += vValue[i].txout.nValue;
|
||||
vfIncluded[i] = true;
|
||||
if (nTotal >= nTargetValue)
|
||||
{
|
||||
fReachedTarget = true;
|
||||
if (nTotal < nBest)
|
||||
{
|
||||
nBest = nTotal;
|
||||
vfBest = vfIncluded;
|
||||
}
|
||||
nTotal -= vValue[i].txout.nValue;
|
||||
vfIncluded[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool KnapsackSolver(const CAmount& nTargetValue, std::vector<CInputCoin>& vCoins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet)
|
||||
{
|
||||
setCoinsRet.clear();
|
||||
nValueRet = 0;
|
||||
|
||||
// List of values less than target
|
||||
boost::optional<CInputCoin> coinLowestLarger;
|
||||
std::vector<CInputCoin> vValue;
|
||||
CAmount nTotalLower = 0;
|
||||
|
||||
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
|
||||
|
||||
for (const CInputCoin &coin : vCoins)
|
||||
{
|
||||
if (coin.txout.nValue == nTargetValue)
|
||||
{
|
||||
setCoinsRet.insert(coin);
|
||||
nValueRet += coin.txout.nValue;
|
||||
return true;
|
||||
}
|
||||
else if (coin.txout.nValue < nTargetValue + MIN_CHANGE)
|
||||
{
|
||||
vValue.push_back(coin);
|
||||
nTotalLower += coin.txout.nValue;
|
||||
}
|
||||
else if (!coinLowestLarger || coin.txout.nValue < coinLowestLarger->txout.nValue)
|
||||
{
|
||||
coinLowestLarger = coin;
|
||||
}
|
||||
}
|
||||
|
||||
if (nTotalLower == nTargetValue)
|
||||
{
|
||||
for (const auto& input : vValue)
|
||||
{
|
||||
setCoinsRet.insert(input);
|
||||
nValueRet += input.txout.nValue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nTotalLower < nTargetValue)
|
||||
{
|
||||
if (!coinLowestLarger)
|
||||
return false;
|
||||
setCoinsRet.insert(coinLowestLarger.get());
|
||||
nValueRet += coinLowestLarger->txout.nValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Solve subset sum by stochastic approximation
|
||||
std::sort(vValue.begin(), vValue.end(), descending);
|
||||
std::vector<char> vfBest;
|
||||
CAmount nBest;
|
||||
|
||||
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
|
||||
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE)
|
||||
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
|
||||
|
||||
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
|
||||
// or the next bigger coin is closer), return the bigger coin
|
||||
if (coinLowestLarger &&
|
||||
((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger->txout.nValue <= nBest))
|
||||
{
|
||||
setCoinsRet.insert(coinLowestLarger.get());
|
||||
nValueRet += coinLowestLarger->txout.nValue;
|
||||
}
|
||||
else {
|
||||
for (unsigned int i = 0; i < vValue.size(); i++)
|
||||
if (vfBest[i])
|
||||
{
|
||||
setCoinsRet.insert(vValue[i]);
|
||||
nValueRet += vValue[i].txout.nValue;
|
||||
}
|
||||
|
||||
if (LogAcceptCategory(BCLog::SELECTCOINS)) {
|
||||
LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: ");
|
||||
for (unsigned int i = 0; i < vValue.size(); i++) {
|
||||
if (vfBest[i]) {
|
||||
LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue));
|
||||
}
|
||||
}
|
||||
LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -49,4 +49,6 @@ public:
|
||||
|
||||
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);
|
||||
|
||||
// Original coin selection algorithm as a fallback
|
||||
bool KnapsackSolver(const CAmount& nTargetValue, std::vector<CInputCoin>& vCoins, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet);
|
||||
#endif // BITCOIN_COINSELECTION_H
|
||||
|
@ -2438,52 +2438,6 @@ const CTxOut& CWallet::FindNonChangeParentOutput(const CTransaction& tx, int out
|
||||
return ptx->vout[n];
|
||||
}
|
||||
|
||||
static void ApproximateBestSubset(const std::vector<CInputCoin>& vValue, const CAmount& nTotalLower, const CAmount& nTargetValue,
|
||||
std::vector<char>& vfBest, CAmount& nBest, int iterations = 1000)
|
||||
{
|
||||
std::vector<char> vfIncluded;
|
||||
|
||||
vfBest.assign(vValue.size(), true);
|
||||
nBest = nTotalLower;
|
||||
|
||||
FastRandomContext insecure_rand;
|
||||
|
||||
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++)
|
||||
{
|
||||
vfIncluded.assign(vValue.size(), false);
|
||||
CAmount nTotal = 0;
|
||||
bool fReachedTarget = false;
|
||||
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++)
|
||||
{
|
||||
for (unsigned int i = 0; i < vValue.size(); i++)
|
||||
{
|
||||
//The solver here uses a randomized algorithm,
|
||||
//the randomness serves no real security purpose but is just
|
||||
//needed to prevent degenerate behavior and it is important
|
||||
//that the rng is fast. We do not use a constant random sequence,
|
||||
//because there may be some privacy improvement by making
|
||||
//the selection random.
|
||||
if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i])
|
||||
{
|
||||
nTotal += vValue[i].txout.nValue;
|
||||
vfIncluded[i] = true;
|
||||
if (nTotal >= nTargetValue)
|
||||
{
|
||||
fReachedTarget = true;
|
||||
if (nTotal < nBest)
|
||||
{
|
||||
nBest = nTotal;
|
||||
vfBest = vfIncluded;
|
||||
}
|
||||
nTotal -= vValue[i].txout.nValue;
|
||||
vfIncluded[i] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool CWallet::OutputEligibleForSpending(const COutput& output, const CoinEligibilityFilter& eligibilty_filter) const
|
||||
{
|
||||
if (!output.fSpendable)
|
||||
@ -2504,94 +2458,16 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, const CoinEligibil
|
||||
setCoinsRet.clear();
|
||||
nValueRet = 0;
|
||||
|
||||
// List of values less than target
|
||||
boost::optional<CInputCoin> coinLowestLarger;
|
||||
std::vector<CInputCoin> vValue;
|
||||
CAmount nTotalLower = 0;
|
||||
|
||||
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
|
||||
|
||||
std::vector<CInputCoin> utxo_pool;
|
||||
for (const COutput &output : vCoins)
|
||||
{
|
||||
if (!OutputEligibleForSpending(output, eligibilty_filter))
|
||||
continue;
|
||||
|
||||
CInputCoin coin = CInputCoin(output.tx->tx, output.i);
|
||||
|
||||
if (coin.txout.nValue == nTargetValue)
|
||||
{
|
||||
setCoinsRet.insert(coin);
|
||||
nValueRet += coin.txout.nValue;
|
||||
return true;
|
||||
}
|
||||
else if (coin.txout.nValue < nTargetValue + MIN_CHANGE)
|
||||
{
|
||||
vValue.push_back(coin);
|
||||
nTotalLower += coin.txout.nValue;
|
||||
}
|
||||
else if (!coinLowestLarger || coin.txout.nValue < coinLowestLarger->txout.nValue)
|
||||
{
|
||||
coinLowestLarger = coin;
|
||||
}
|
||||
utxo_pool.push_back(coin);
|
||||
}
|
||||
|
||||
if (nTotalLower == nTargetValue)
|
||||
{
|
||||
for (const auto& input : vValue)
|
||||
{
|
||||
setCoinsRet.insert(input);
|
||||
nValueRet += input.txout.nValue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nTotalLower < nTargetValue)
|
||||
{
|
||||
if (!coinLowestLarger)
|
||||
return false;
|
||||
setCoinsRet.insert(coinLowestLarger.get());
|
||||
nValueRet += coinLowestLarger->txout.nValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Solve subset sum by stochastic approximation
|
||||
std::sort(vValue.begin(), vValue.end(), CompareValueOnly());
|
||||
std::reverse(vValue.begin(), vValue.end());
|
||||
std::vector<char> vfBest;
|
||||
CAmount nBest;
|
||||
|
||||
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
|
||||
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE)
|
||||
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
|
||||
|
||||
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
|
||||
// or the next bigger coin is closer), return the bigger coin
|
||||
if (coinLowestLarger &&
|
||||
((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger->txout.nValue <= nBest))
|
||||
{
|
||||
setCoinsRet.insert(coinLowestLarger.get());
|
||||
nValueRet += coinLowestLarger->txout.nValue;
|
||||
}
|
||||
else {
|
||||
for (unsigned int i = 0; i < vValue.size(); i++)
|
||||
if (vfBest[i])
|
||||
{
|
||||
setCoinsRet.insert(vValue[i]);
|
||||
nValueRet += vValue[i].txout.nValue;
|
||||
}
|
||||
|
||||
if (LogAcceptCategory(BCLog::SELECTCOINS)) {
|
||||
LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: ");
|
||||
for (unsigned int i = 0; i < vValue.size(); i++) {
|
||||
if (vfBest[i]) {
|
||||
LogPrint(BCLog::SELECTCOINS, "%s ", FormatMoney(vValue[i].txout.nValue));
|
||||
}
|
||||
}
|
||||
LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return KnapsackSolver(nTargetValue, utxo_pool, setCoinsRet, nValueRet);
|
||||
}
|
||||
|
||||
bool CWallet::SelectCoins(const std::vector<COutput>& vAvailableCoins, const CAmount& nTargetValue, std::set<CInputCoin>& setCoinsRet, CAmount& nValueRet, const CCoinControl* coinControl) const
|
||||
|
Loading…
Reference in New Issue
Block a user